blob: f41de8dfe0f8df5cf4db0fd7ab3aa47c1fecc4a8 [file] [log] [blame]
Mike Frysingerf6013762019-06-13 02:30:51 -04001# -*- coding:utf-8 -*-
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07002#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Sarah Owenscecd1d82012-11-01 22:59:27 -070017from __future__ import print_function
Anthony King85b24ac2014-05-06 15:57:48 +010018import json
David Pursehouse86d973d2012-08-24 10:21:02 +090019import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070020from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070021import os
22import re
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070023import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import subprocess
25import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
David Pursehouse59bbb582013-05-17 10:49:33 +090028
29from pyversion import is_python3
30if is_python3():
Dan Willemsen0745bb22015-08-17 13:41:45 -070031 import http.cookiejar as cookielib
32 import urllib.error
Chirayu Desai217ea7d2013-03-01 19:14:38 +053033 import urllib.parse
Dan Willemsen0745bb22015-08-17 13:41:45 -070034 import urllib.request
David Pursehouse59bbb582013-05-17 10:49:33 +090035 import xmlrpc.client
36else:
Dan Willemsen0745bb22015-08-17 13:41:45 -070037 import cookielib
Chirayu Desai217ea7d2013-03-01 19:14:38 +053038 import imp
Dan Willemsen0745bb22015-08-17 13:41:45 -070039 import urllib2
Chirayu Desai217ea7d2013-03-01 19:14:38 +053040 import urlparse
David Pursehouse59bbb582013-05-17 10:49:33 +090041 import xmlrpclib
Chirayu Desai217ea7d2013-03-01 19:14:38 +053042 urllib = imp.new_module('urllib')
Dan Willemsen0745bb22015-08-17 13:41:45 -070043 urllib.error = urllib2
Chirayu Desaidb2ad9d2013-06-11 13:42:25 +053044 urllib.parse = urlparse
Dan Willemsen0745bb22015-08-17 13:41:45 -070045 urllib.request = urllib2
Chirayu Desai217ea7d2013-03-01 19:14:38 +053046 xmlrpc = imp.new_module('xmlrpc')
47 xmlrpc.client = xmlrpclib
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070048
Roy Lee18afd7f2010-05-09 04:32:08 +080049try:
50 import threading as _threading
51except ImportError:
52 import dummy_threading as _threading
53
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070054try:
55 import resource
56 def _rlimit_nofile():
57 return resource.getrlimit(resource.RLIMIT_NOFILE)
58except ImportError:
59 def _rlimit_nofile():
60 return (256, 256)
61
Dave Borowitz18857212012-10-23 17:02:59 -070062try:
63 import multiprocessing
64except ImportError:
65 multiprocessing = None
66
David Rileye0684ad2017-04-05 00:02:59 -070067import event_log
Dave Borowitze2152672012-10-31 12:24:38 -070068from git_command import GIT, git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090069from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090070from git_refs import R_HEADS, HEAD
Simran Basibdb52712015-08-10 13:23:23 -070071import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070072from project import Project
73from project import RemoteSpec
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080074from command import Command, MirrorSafeCommand
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +000075from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070076import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070077from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070078from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080079from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070080from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081
Dave Borowitz67700e92012-10-23 15:00:54 -070082_ONE_DAY_S = 24 * 60 * 60
83
Doug Andersonfc06ced2011-03-16 15:49:18 -070084class _FetchError(Exception):
85 """Internal error thrown in _FetchHelper() when we don't want stack trace."""
86 pass
87
Xin Li745be2e2019-06-03 11:24:30 -070088class _CheckoutError(Exception):
89 """Internal error thrown in _CheckoutOne() when we don't want stack trace."""
90
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080091class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080092 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070093 common = True
94 helpSummary = "Update working tree to the latest revision"
95 helpUsage = """
96%prog [<project>...]
97"""
98 helpDescription = """
99The '%prog' command synchronizes local project directories
100with the remote repositories specified in the manifest. If a local
101project does not yet exist, it will clone a new local directory from
102the remote repository and set up tracking branches as specified in
103the manifest. If the local project already exists, '%prog'
104will update the remote branches and rebase any new local changes
105on top of the new remote changes.
106
107'%prog' will synchronize all projects listed at the command
108line. Projects can be specified either by name, or by a relative
109or absolute path to the project's local directory. If no projects
110are specified, '%prog' will synchronize all projects listed in
111the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700112
113The -d/--detach option can be used to switch specified projects
114back to the manifest revision. This option is especially helpful
115if the project is currently on a topic branch, but the manifest
116revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700117
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700118The -s/--smart-sync option can be used to sync to a known good
119build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200120manifest. The -t/--smart-tag option is similar and allows you to
121specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700122
David Pursehousecf76b1b2012-09-14 10:31:42 +0900123The -u/--manifest-server-username and -p/--manifest-server-password
124options can be used to specify a username and password to authenticate
125with the manifest server when using the -s or -t option.
126
127If -u and -p are not specified when using the -s or -t option, '%prog'
128will attempt to read authentication credentials for the manifest server
129from the user's .netrc file.
130
131'%prog' will not use authentication credentials from -u/-p or .netrc
132if the manifest server specified in the manifest file already includes
133credentials.
134
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400135By default, all projects will be synced. The --fail-fast option can be used
136to halt syncing as soon as possible when the the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500137
Kevin Degiabaa7f32014-11-12 11:27:45 -0700138The --force-sync option can be used to overwrite existing git
139directories if they have previously been linked to a different
140object direcotry. WARNING: This may cause data to be lost since
141refs may be removed when overwriting.
142
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500143The --force-remove-dirty option can be used to remove previously used
144projects with uncommitted changes. WARNING: This may cause data to be
145lost since uncommitted changes may be removed with projects that no longer
146exist in the manifest.
147
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700148The --no-clone-bundle option disables any attempt to use
149$URL/clone.bundle to bootstrap a new Git repository from a
150resumeable bundle file on a content delivery network. This
151may be necessary if there are problems with the local Python
152HTTP client or proxy configuration, but the Git binary works.
153
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800154The --fetch-submodules option enables fetching Git submodules
155of a project from server.
156
David Pursehousef2fad612015-01-29 14:36:28 +0900157The -c/--current-branch option can be used to only fetch objects that
158are on the branch specified by a project's revision.
159
David Pursehouseb1553542014-09-04 21:28:09 +0900160The --optimized-fetch option can be used to only fetch projects that
161are fixed to a sha1 revision if the sha1 revision does not already
162exist locally.
163
David Pursehouse74cfd272015-10-14 10:50:15 +0900164The --prune option can be used to remove any refs that no longer
165exist on the remote.
166
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400167# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700168
169If at least one project remote URL uses an SSH connection (ssh://,
170git+ssh://, or user@host:path syntax) repo will automatically
171enable the SSH ControlMaster option when connecting to that host.
172This feature permits other projects in the same '%prog' session to
173reuse the same SSH tunnel, saving connection setup overheads.
174
175To disable this behavior on UNIX platforms, set the GIT_SSH
176environment variable to 'ssh'. For example:
177
178 export GIT_SSH=ssh
179 %prog
180
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400181# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700182
183This feature is automatically disabled on Windows, due to the lack
184of UNIX domain socket support.
185
186This feature is not compatible with url.insteadof rewrites in the
187user's ~/.gitconfig. '%prog' is currently not able to perform the
188rewrite early enough to establish the ControlMaster tunnel.
189
190If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
191later is required to fix a server side protocol bug.
192
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700193"""
194
Nico Sallembien6623b212010-05-11 12:57:01 -0700195 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000196 try:
197 self.jobs = self.manifest.default.sync_j
198 except ManifestParseError:
199 self.jobs = 1
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700200
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500201 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200202 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400203 help='obsolete option (to be deleted in the future)')
204 p.add_option('--fail-fast',
205 dest='fail_fast', action='store_true',
206 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700207 p.add_option('--force-sync',
208 dest='force_sync', action='store_true',
209 help="overwrite an existing git directory if it needs to "
210 "point to a different object directory. WARNING: this "
211 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500212 p.add_option('--force-remove-dirty',
213 dest='force_remove_dirty', action='store_true',
214 help="force remove projects with uncommitted modifications if "
215 "projects no longer exist in the manifest. "
216 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900217 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700218 dest='local_only', action='store_true',
219 help="only update working tree, don't fetch")
Fredrik de Grootcc960972019-11-22 09:04:31 +0100220 p.add_option('--no-manifest-update','--nmu',
221 dest='mp_update', action='store_false', default='true',
222 help='use the existing manifest checkout as-is. '
223 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900224 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700225 dest='network_only', action='store_true',
226 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900227 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700228 dest='detach_head', action='store_true',
229 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900230 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700231 dest='current_branch_only', action='store_true',
232 help='fetch only current branch from server')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900233 p.add_option('-q', '--quiet',
Shawn O. Pearce16614f82010-10-29 12:05:43 -0700234 dest='quiet', action='store_true',
235 help='be more quiet')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900236 p.add_option('-j', '--jobs',
Roy Lee18afd7f2010-05-09 04:32:08 +0800237 dest='jobs', action='store', type='int',
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700238 help="projects to fetch simultaneously (default %d)" % self.jobs)
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500239 p.add_option('-m', '--manifest-name',
240 dest='manifest_name',
241 help='temporary manifest to use for this sync', metavar='NAME.xml')
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700242 p.add_option('--no-clone-bundle',
243 dest='no_clone_bundle', action='store_true',
244 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800245 p.add_option('-u', '--manifest-server-username', action='store',
246 dest='manifest_server_username',
247 help='username to authenticate with the manifest server')
248 p.add_option('-p', '--manifest-server-password', action='store',
249 dest='manifest_server_password',
250 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800251 p.add_option('--fetch-submodules',
252 dest='fetch_submodules', action='store_true',
253 help='fetch submodules from server')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700254 p.add_option('--no-tags',
255 dest='no_tags', action='store_true',
256 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900257 p.add_option('--optimized-fetch',
258 dest='optimized_fetch', action='store_true',
259 help='only fetch projects fixed to sha1 if revision does not exist locally')
David Pursehouse74cfd272015-10-14 10:50:15 +0900260 p.add_option('--prune', dest='prune', action='store_true',
261 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700262 if show_smart:
263 p.add_option('-s', '--smart-sync',
264 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900265 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200266 p.add_option('-t', '--smart-tag',
267 dest='smart_tag', action='store',
268 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700269
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700270 g = p.add_option_group('repo Version options')
271 g.add_option('--no-repo-verify',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700272 dest='no_repo_verify', action='store_true',
273 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700274 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800275 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700276 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700277
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500278 def _FetchProjectList(self, opt, projects, sem, *args, **kwargs):
Xin Li745be2e2019-06-03 11:24:30 -0700279 """Main function of the fetch threads.
Roy Lee18afd7f2010-05-09 04:32:08 +0800280
David James8d201162013-10-11 17:03:19 -0700281 Delegates most of the work to _FetchHelper.
282
283 Args:
284 opt: Program options returned from optparse. See _Options().
285 projects: Projects to fetch.
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500286 sem: We'll release() this semaphore when we exit so that another thread
287 can be started up.
David James89ece422014-01-09 18:51:58 -0800288 *args, **kwargs: Remaining arguments to pass to _FetchHelper. See the
David James8d201162013-10-11 17:03:19 -0700289 _FetchHelper docstring for details.
290 """
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500291 try:
292 for project in projects:
293 success = self._FetchHelper(opt, project, *args, **kwargs)
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400294 if not success and opt.fail_fast:
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500295 break
296 finally:
297 sem.release()
David James8d201162013-10-11 17:03:19 -0700298
Xin Li745be2e2019-06-03 11:24:30 -0700299 def _FetchHelper(self, opt, project, lock, fetched, pm, err_event,
300 clone_filter):
David James8d201162013-10-11 17:03:19 -0700301 """Fetch git objects for a single project.
302
David Pursehousec1b86a22012-11-14 11:36:51 +0900303 Args:
304 opt: Program options returned from optparse. See _Options().
305 project: Project object for the project to fetch.
306 lock: Lock for accessing objects that are shared amongst multiple
307 _FetchHelper() threads.
308 fetched: set object that we will add project.gitdir to when we're done
309 (with our lock held).
310 pm: Instance of a Project object. We will call pm.update() (with our
311 lock held).
David Pursehousec1b86a22012-11-14 11:36:51 +0900312 err_event: We'll set this event in the case of an error (after printing
313 out info about the error).
Xin Li745be2e2019-06-03 11:24:30 -0700314 clone_filter: Filter for use in a partial clone.
David James8d201162013-10-11 17:03:19 -0700315
316 Returns:
317 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900318 """
319 # We'll set to true once we've locked the lock.
320 did_lock = False
Doug Andersonfc06ced2011-03-16 15:49:18 -0700321
David Pursehousec1b86a22012-11-14 11:36:51 +0900322 # Encapsulate everything in a try/except/finally so that:
323 # - We always set err_event in the case of an exception.
David Pursehousec1b86a22012-11-14 11:36:51 +0900324 # - We always make sure we unlock the lock if we locked it.
David Rileye0684ad2017-04-05 00:02:59 -0700325 start = time.time()
326 success = False
David Pursehousec1b86a22012-11-14 11:36:51 +0900327 try:
Doug Andersonfc06ced2011-03-16 15:49:18 -0700328 try:
David Pursehousec1b86a22012-11-14 11:36:51 +0900329 success = project.Sync_NetworkHalf(
330 quiet=opt.quiet,
331 current_branch_only=opt.current_branch_only,
Kevin Degiabaa7f32014-11-12 11:27:45 -0700332 force_sync=opt.force_sync,
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700333 clone_bundle=not opt.no_clone_bundle,
David Pursehouseb1553542014-09-04 21:28:09 +0900334 no_tags=opt.no_tags, archive=self.manifest.IsArchive,
David Pursehouse74cfd272015-10-14 10:50:15 +0900335 optimized_fetch=opt.optimized_fetch,
Xin Li745be2e2019-06-03 11:24:30 -0700336 prune=opt.prune,
337 clone_filter=clone_filter)
David Pursehousec1b86a22012-11-14 11:36:51 +0900338 self._fetch_times.Set(project, time.time() - start)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700339
David Pursehousec1b86a22012-11-14 11:36:51 +0900340 # Lock around all the rest of the code, since printing, updating a set
341 # and Progress.update() are not thread safe.
342 lock.acquire()
343 did_lock = True
Doug Andersonfc06ced2011-03-16 15:49:18 -0700344
David Pursehousec1b86a22012-11-14 11:36:51 +0900345 if not success:
Hu Xiuyune9becc02015-11-25 15:52:26 +0800346 err_event.set()
Marc Herbertffb4b892017-04-04 22:03:53 -0700347 print('error: Cannot fetch %s from %s'
348 % (project.name, project.remote.url),
349 file=sys.stderr)
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400350 if opt.fail_fast:
David Pursehousec1b86a22012-11-14 11:36:51 +0900351 raise _FetchError()
Doug Andersonfc06ced2011-03-16 15:49:18 -0700352
David Pursehousec1b86a22012-11-14 11:36:51 +0900353 fetched.add(project.gitdir)
Mike Frysinger3538dd22019-08-26 15:32:06 -0400354 pm.update(msg=project.name)
David Pursehousec1b86a22012-11-14 11:36:51 +0900355 except _FetchError:
Hu Xiuyune9becc02015-11-25 15:52:26 +0800356 pass
Dan Sandlerc5cd4332015-07-31 09:37:53 -0400357 except Exception as e:
358 print('error: Cannot fetch %s (%s: %s)' \
359 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
David Pursehousec1b86a22012-11-14 11:36:51 +0900360 err_event.set()
361 raise
362 finally:
363 if did_lock:
364 lock.release()
David Rileye0684ad2017-04-05 00:02:59 -0700365 finish = time.time()
366 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
367 start, finish, success)
Roy Lee18afd7f2010-05-09 04:32:08 +0800368
David James8d201162013-10-11 17:03:19 -0700369 return success
370
Mike Frysinger5a033082019-09-23 19:21:20 -0400371 def _Fetch(self, projects, opt, err_event):
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700372 fetched = set()
David James89ece422014-01-09 18:51:58 -0800373 lock = _threading.Lock()
Tim Schumacher913327f2017-06-05 15:01:41 +0200374 pm = Progress('Fetching projects', len(projects),
Tim Schumacher7be072e2017-06-28 18:29:23 +0200375 always_print_percentage=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800376
David James89ece422014-01-09 18:51:58 -0800377 objdir_project_map = dict()
378 for project in projects:
379 objdir_project_map.setdefault(project.objdir, []).append(project)
David James8d201162013-10-11 17:03:19 -0700380
David James89ece422014-01-09 18:51:58 -0800381 threads = set()
382 sem = _threading.Semaphore(self.jobs)
David James89ece422014-01-09 18:51:58 -0800383 for project_list in objdir_project_map.values():
384 # Check for any errors before running any more tasks.
385 # ...we'll let existing threads finish, though.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400386 if err_event.isSet() and opt.fail_fast:
David James89ece422014-01-09 18:51:58 -0800387 break
Doug Andersonfc06ced2011-03-16 15:49:18 -0700388
David James89ece422014-01-09 18:51:58 -0800389 sem.acquire()
390 kwargs = dict(opt=opt,
391 projects=project_list,
Andrew Wheeler7f1ccfb2016-06-17 16:51:07 -0500392 sem=sem,
David James89ece422014-01-09 18:51:58 -0800393 lock=lock,
394 fetched=fetched,
395 pm=pm,
Xin Li745be2e2019-06-03 11:24:30 -0700396 err_event=err_event,
397 clone_filter=self.manifest.CloneFilter)
David James89ece422014-01-09 18:51:58 -0800398 if self.jobs > 1:
David James8d201162013-10-11 17:03:19 -0700399 t = _threading.Thread(target = self._FetchProjectList,
David James89ece422014-01-09 18:51:58 -0800400 kwargs = kwargs)
David 'Digit' Turnere2126652012-09-05 10:35:06 +0200401 # Ensure that Ctrl-C will not freeze the repo process.
402 t.daemon = True
Roy Lee18afd7f2010-05-09 04:32:08 +0800403 threads.add(t)
404 t.start()
David James89ece422014-01-09 18:51:58 -0800405 else:
406 self._FetchProjectList(**kwargs)
Roy Lee18afd7f2010-05-09 04:32:08 +0800407
David James89ece422014-01-09 18:51:58 -0800408 for t in threads:
409 t.join()
Roy Lee18afd7f2010-05-09 04:32:08 +0800410
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700411 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700412 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700413
Julien Campergue335f5ef2013-10-16 11:02:35 +0200414 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400415 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200416
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700417 return fetched
418
Xin Li745be2e2019-06-03 11:24:30 -0700419 def _CheckoutWorker(self, opt, sem, project, *args, **kwargs):
420 """Main function of the fetch threads.
421
422 Delegates most of the work to _CheckoutOne.
423
424 Args:
425 opt: Program options returned from optparse. See _Options().
426 projects: Projects to fetch.
427 sem: We'll release() this semaphore when we exit so that another thread
428 can be started up.
429 *args, **kwargs: Remaining arguments to pass to _CheckoutOne. See the
430 _CheckoutOne docstring for details.
431 """
432 try:
Mike Frysingera34186e2019-08-07 18:07:31 -0400433 return self._CheckoutOne(opt, project, *args, **kwargs)
Xin Li745be2e2019-06-03 11:24:30 -0700434 finally:
435 sem.release()
436
Vadim Bendeburydff91942019-11-06 11:05:00 -0800437 def _CheckoutOne(self, opt, project, lock, pm, err_event, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700438 """Checkout work tree for one project
439
440 Args:
441 opt: Program options returned from optparse. See _Options().
442 project: Project object for the project to checkout.
443 lock: Lock for accessing objects that are shared amongst multiple
444 _CheckoutWorker() threads.
445 pm: Instance of a Project object. We will call pm.update() (with our
446 lock held).
447 err_event: We'll set this event in the case of an error (after printing
448 out info about the error).
Vadim Bendeburydff91942019-11-06 11:05:00 -0800449 err_results: A list of strings, paths to git repos where checkout
450 failed.
Xin Li745be2e2019-06-03 11:24:30 -0700451
452 Returns:
453 Whether the fetch was successful.
454 """
455 # We'll set to true once we've locked the lock.
456 did_lock = False
457
Xin Li745be2e2019-06-03 11:24:30 -0700458 # Encapsulate everything in a try/except/finally so that:
459 # - We always set err_event in the case of an exception.
460 # - We always make sure we unlock the lock if we locked it.
461 start = time.time()
462 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
463 detach_head=opt.detach_head)
464 success = False
465 try:
466 try:
467 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
Xin Li745be2e2019-06-03 11:24:30 -0700468
469 # Lock around all the rest of the code, since printing, updating a set
470 # and Progress.update() are not thread safe.
471 lock.acquire()
Mike Frysinger3538dd22019-08-26 15:32:06 -0400472 success = syncbuf.Finish()
Xin Li745be2e2019-06-03 11:24:30 -0700473 did_lock = True
474
475 if not success:
476 err_event.set()
477 print('error: Cannot checkout %s' % (project.name),
478 file=sys.stderr)
479 raise _CheckoutError()
480
Mike Frysinger3538dd22019-08-26 15:32:06 -0400481 pm.update(msg=project.name)
Xin Li745be2e2019-06-03 11:24:30 -0700482 except _CheckoutError:
483 pass
484 except Exception as e:
485 print('error: Cannot checkout %s: %s: %s' %
486 (project.name, type(e).__name__, str(e)),
487 file=sys.stderr)
488 err_event.set()
489 raise
490 finally:
491 if did_lock:
Vadim Bendeburydff91942019-11-06 11:05:00 -0800492 if not success:
493 err_results.append(project.relpath)
Xin Li745be2e2019-06-03 11:24:30 -0700494 lock.release()
495 finish = time.time()
496 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
497 start, finish, success)
498
499 return success
500
Mike Frysinger5a033082019-09-23 19:21:20 -0400501 def _Checkout(self, all_projects, opt, err_event, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700502 """Checkout projects listed in all_projects
503
504 Args:
505 all_projects: List of all projects that should be checked out.
506 opt: Program options returned from optparse. See _Options().
Mike Frysinger5a033082019-09-23 19:21:20 -0400507 err_event: We'll set this event in the case of an error (after printing
508 out info about the error).
509 err_results: A list of strings, paths to git repos where checkout
510 failed.
Xin Li745be2e2019-06-03 11:24:30 -0700511 """
512
513 # Perform checkouts in multiple threads when we are using partial clone.
514 # Without partial clone, all needed git objects are already downloaded,
515 # in this situation it's better to use only one process because the checkout
516 # would be mostly disk I/O; with partial clone, the objects are only
517 # downloaded when demanded (at checkout time), which is similar to the
518 # Sync_NetworkHalf case and parallelism would be helpful.
519 if self.manifest.CloneFilter:
520 syncjobs = self.jobs
521 else:
522 syncjobs = 1
523
524 lock = _threading.Lock()
Mike Frysinger3538dd22019-08-26 15:32:06 -0400525 pm = Progress('Checking out projects', len(all_projects))
Xin Li745be2e2019-06-03 11:24:30 -0700526
527 threads = set()
528 sem = _threading.Semaphore(syncjobs)
Xin Li745be2e2019-06-03 11:24:30 -0700529
530 for project in all_projects:
531 # Check for any errors before running any more tasks.
532 # ...we'll let existing threads finish, though.
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400533 if err_event.isSet() and opt.fail_fast:
Xin Li745be2e2019-06-03 11:24:30 -0700534 break
535
536 sem.acquire()
537 if project.worktree:
538 kwargs = dict(opt=opt,
539 sem=sem,
540 project=project,
541 lock=lock,
542 pm=pm,
Vadim Bendeburydff91942019-11-06 11:05:00 -0800543 err_event=err_event,
544 err_results=err_results)
Xin Li745be2e2019-06-03 11:24:30 -0700545 if syncjobs > 1:
546 t = _threading.Thread(target=self._CheckoutWorker,
547 kwargs=kwargs)
548 # Ensure that Ctrl-C will not freeze the repo process.
549 t.daemon = True
550 threads.add(t)
551 t.start()
552 else:
553 self._CheckoutWorker(**kwargs)
554
555 for t in threads:
556 t.join()
557
558 pm.end()
Xin Li745be2e2019-06-03 11:24:30 -0700559
Mike Frysinger5a033082019-09-23 19:21:20 -0400560 def _GCProjects(self, projects, opt, err_event):
Gabe Black2ff30292014-10-09 17:54:35 -0700561 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700562 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500563 # Make sure pruning never kicks in with shared projects.
Gabe Black2ff30292014-10-09 17:54:35 -0700564 if len(project.manifest.GetProjectsWithName(project.name)) > 1:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500565 print('%s: Shared project %s found, disabling pruning.' %
566 (project.relpath, project.name))
567 if git_require((2, 7, 0)):
568 project.config.SetString('core.repositoryFormatVersion', '1')
569 project.config.SetString('extensions.preciousObjects', 'true')
570 else:
571 # This isn't perfect, but it's the best we can do with old git.
572 print('%s: WARNING: shared projects are unreliable when using old '
573 'versions of git; please upgrade to git-2.7.0+.'
574 % (project.relpath,),
575 file=sys.stderr)
576 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700577 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700578
Dave Borowitze2152672012-10-31 12:24:38 -0700579 has_dash_c = git_require((1, 7, 2))
580 if multiprocessing and has_dash_c:
Dave Borowitz18857212012-10-23 17:02:59 -0700581 cpu_count = multiprocessing.cpu_count()
582 else:
583 cpu_count = 1
584 jobs = min(self.jobs, cpu_count)
585
586 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700587 for bare_git in gc_gitdirs.values():
David James8d201162013-10-11 17:03:19 -0700588 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700589 return
590
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400591 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700592
593 threads = set()
594 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700595
David James8d201162013-10-11 17:03:19 -0700596 def GC(bare_git):
Dave Borowitz18857212012-10-23 17:02:59 -0700597 try:
598 try:
David James8d201162013-10-11 17:03:19 -0700599 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700600 except GitError:
601 err_event.set()
602 except:
603 err_event.set()
604 raise
605 finally:
606 sem.release()
607
Gabe Black2ff30292014-10-09 17:54:35 -0700608 for bare_git in gc_gitdirs.values():
Mike Frysinger5a033082019-09-23 19:21:20 -0400609 if err_event.isSet() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700610 break
611 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700612 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700613 t.daemon = True
614 threads.add(t)
615 t.start()
616
617 for t in threads:
618 t.join()
619
Tim Kilbourn07669002013-03-08 15:02:49 -0800620 def _ReloadManifest(self, manifest_name=None):
621 if manifest_name:
622 # Override calls _Unload already
623 self.manifest.Override(manifest_name)
624 else:
625 self.manifest._Unload()
626
Dan Willemsen43507912016-09-01 16:26:02 -0700627 def _DeleteProject(self, path):
628 print('Deleting obsolete path %s' % path, file=sys.stderr)
629
630 # Delete the .git directory first, so we're less likely to have a partially
631 # working git repository around. There shouldn't be any git projects here,
632 # so rmtree works.
633 try:
Renaud Paquaya65adf72016-11-03 10:37:53 -0700634 platform_utils.rmtree(os.path.join(path, '.git'))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700635 except OSError as e:
636 print('Failed to remove %s (%s)' % (os.path.join(path, '.git'), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700637 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
638 print(' remove manually, then run sync again', file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400639 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700640
641 # Delete everything under the worktree, except for directories that contain
642 # another git project
643 dirs_to_remove = []
644 failed = False
Renaud Paquaybed8b622018-09-27 10:46:58 -0700645 for root, dirs, files in platform_utils.walk(path):
Dan Willemsen43507912016-09-01 16:26:02 -0700646 for f in files:
647 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800648 platform_utils.remove(os.path.join(root, f))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700649 except OSError as e:
650 print('Failed to remove %s (%s)' % (os.path.join(root, f), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700651 failed = True
652 dirs[:] = [d for d in dirs
653 if not os.path.lexists(os.path.join(root, d, '.git'))]
654 dirs_to_remove += [os.path.join(root, d) for d in dirs
655 if os.path.join(root, d) not in dirs_to_remove]
656 for d in reversed(dirs_to_remove):
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700657 if platform_utils.islink(d):
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700658 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800659 platform_utils.remove(d)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700660 except OSError as e:
661 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemseneceeb1b2016-09-25 18:24:27 -0700662 failed = True
Renaud Paquaybed8b622018-09-27 10:46:58 -0700663 elif len(platform_utils.listdir(d)) == 0:
Dan Willemsen43507912016-09-01 16:26:02 -0700664 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700665 platform_utils.rmdir(d)
666 except OSError as e:
667 print('Failed to remove %s (%s)' % (os.path.join(root, d), str(e)), file=sys.stderr)
Dan Willemsen43507912016-09-01 16:26:02 -0700668 failed = True
669 continue
670 if failed:
671 print('error: Failed to delete obsolete path %s' % path, file=sys.stderr)
672 print(' remove manually, then run sync again', file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400673 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700674
675 # Try deleting parent dirs if they are empty
676 project_dir = path
677 while project_dir != self.manifest.topdir:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700678 if len(platform_utils.listdir(project_dir)) == 0:
679 platform_utils.rmdir(project_dir)
Dan Willemsen43507912016-09-01 16:26:02 -0700680 else:
681 break
682 project_dir = os.path.dirname(project_dir)
683
684 return 0
685
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500686 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700687 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700688 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700689 if project.relpath:
690 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700691 file_name = 'project.list'
692 file_path = os.path.join(self.manifest.repodir, file_name)
693 old_project_paths = []
694
695 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500696 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700697 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800698 # In reversed order, so subfolders are deleted before parent folder.
699 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700700 if not path:
701 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700702 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900703 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700704 gitdir = os.path.join(self.manifest.topdir, path, '.git')
705 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900706 project = Project(
707 manifest = self.manifest,
708 name = path,
709 remote = RemoteSpec('origin'),
David James8d201162013-10-11 17:03:19 -0700710 gitdir = gitdir,
711 objdir = gitdir,
David Pursehousec1b86a22012-11-14 11:36:51 +0900712 worktree = os.path.join(self.manifest.topdir, path),
713 relpath = path,
714 revisionExpr = 'HEAD',
715 revisionId = None,
716 groups = None)
Anthonyf3fdf822009-09-26 13:38:52 -0400717
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500718 if project.IsDirty() and opt.force_remove_dirty:
719 print('WARNING: Removing dirty project "%s": uncommitted changes '
720 'erased' % project.relpath, file=sys.stderr)
721 self._DeleteProject(project.worktree)
722 elif project.IsDirty():
David Pursehouse2f9e7e42013-03-05 17:26:46 +0900723 print('error: Cannot remove project "%s": uncommitted changes '
David Pursehousec1b86a22012-11-14 11:36:51 +0900724 'are present' % project.relpath, file=sys.stderr)
725 print(' commit changes, then run sync again',
726 file=sys.stderr)
Mike Frysingera850ca22019-08-07 17:19:24 -0400727 return 1
Dan Willemsen43507912016-09-01 16:26:02 -0700728 elif self._DeleteProject(project.worktree):
Mike Frysingera850ca22019-08-07 17:19:24 -0400729 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700730
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700731 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500732 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700733 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700734 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700735 return 0
736
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400737 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
738 if not self.manifest.manifest_server:
739 print('error: cannot smart sync: no manifest server defined in '
740 'manifest', file=sys.stderr)
741 sys.exit(1)
742
743 manifest_server = self.manifest.manifest_server
744 if not opt.quiet:
745 print('Using manifest server %s' % manifest_server)
746
747 if not '@' in manifest_server:
748 username = None
749 password = None
750 if opt.manifest_server_username and opt.manifest_server_password:
751 username = opt.manifest_server_username
752 password = opt.manifest_server_password
753 else:
754 try:
755 info = netrc.netrc()
756 except IOError:
757 # .netrc file does not exist or could not be opened
758 pass
759 else:
760 try:
761 parse_result = urllib.parse.urlparse(manifest_server)
762 if parse_result.hostname:
763 auth = info.authenticators(parse_result.hostname)
764 if auth:
765 username, _account, password = auth
766 else:
767 print('No credentials found for %s in .netrc'
768 % parse_result.hostname, file=sys.stderr)
769 except netrc.NetrcParseError as e:
770 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
771
772 if (username and password):
773 manifest_server = manifest_server.replace('://', '://%s:%s@' %
774 (username, password),
775 1)
776
777 transport = PersistentTransport(manifest_server)
778 if manifest_server.startswith('persistent-'):
779 manifest_server = manifest_server[len('persistent-'):]
780
781 try:
782 server = xmlrpc.client.Server(manifest_server, transport=transport)
783 if opt.smart_sync:
784 p = self.manifest.manifestProject
785 b = p.GetBranch(p.CurrentBranch)
786 branch = b.merge
787 if branch.startswith(R_HEADS):
788 branch = branch[len(R_HEADS):]
789
790 env = os.environ.copy()
791 if 'SYNC_TARGET' in env:
792 target = env['SYNC_TARGET']
793 [success, manifest_str] = server.GetApprovedManifest(branch, target)
794 elif 'TARGET_PRODUCT' in env and 'TARGET_BUILD_VARIANT' in env:
795 target = '%s-%s' % (env['TARGET_PRODUCT'],
796 env['TARGET_BUILD_VARIANT'])
797 [success, manifest_str] = server.GetApprovedManifest(branch, target)
798 else:
799 [success, manifest_str] = server.GetApprovedManifest(branch)
800 else:
801 assert(opt.smart_tag)
802 [success, manifest_str] = server.GetManifest(opt.smart_tag)
803
804 if success:
805 manifest_name = os.path.basename(smart_sync_manifest_path)
806 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500807 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400808 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400809 except IOError as e:
810 print('error: cannot write manifest to %s:\n%s'
811 % (smart_sync_manifest_path, e),
812 file=sys.stderr)
813 sys.exit(1)
814 self._ReloadManifest(manifest_name)
815 else:
816 print('error: manifest server RPC call failed: %s' %
817 manifest_str, file=sys.stderr)
818 sys.exit(1)
819 except (socket.error, IOError, xmlrpc.client.Fault) as e:
820 print('error: cannot connect to manifest server %s:\n%s'
821 % (self.manifest.manifest_server, e), file=sys.stderr)
822 sys.exit(1)
823 except xmlrpc.client.ProtocolError as e:
824 print('error: cannot connect to manifest server %s:\n%d %s'
825 % (self.manifest.manifest_server, e.errcode, e.errmsg),
826 file=sys.stderr)
827 sys.exit(1)
828
829 return manifest_name
830
Mike Frysingerfb527e32019-08-27 02:34:32 -0400831 def _UpdateManifestProject(self, opt, mp, manifest_name):
832 """Fetch & update the local manifest project."""
833 if not opt.local_only:
834 start = time.time()
835 success = mp.Sync_NetworkHalf(quiet=opt.quiet,
836 current_branch_only=opt.current_branch_only,
837 no_tags=opt.no_tags,
838 optimized_fetch=opt.optimized_fetch,
839 submodules=self.manifest.HasSubmodules,
840 clone_filter=self.manifest.CloneFilter)
841 finish = time.time()
842 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
843 start, finish, success)
844
845 if mp.HasChanges:
846 syncbuf = SyncBuffer(mp.config)
847 start = time.time()
848 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
849 clean = syncbuf.Finish()
850 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
851 start, time.time(), clean)
852 if not clean:
853 sys.exit(1)
854 self._ReloadManifest(opt.manifest_name)
855 if opt.jobs is None:
856 self.jobs = self.manifest.default.sync_j
857
Mike Frysingerae6cb082019-08-27 01:10:59 -0400858 def ValidateOptions(self, opt, args):
859 if opt.force_broken:
860 print('warning: -f/--force-broken is now the default behavior, and the '
861 'options are deprecated', file=sys.stderr)
862 if opt.network_only and opt.detach_head:
863 self.OptionParser.error('cannot combine -n and -d')
864 if opt.network_only and opt.local_only:
865 self.OptionParser.error('cannot combine -n and -l')
866 if opt.manifest_name and opt.smart_sync:
867 self.OptionParser.error('cannot combine -m and -s')
868 if opt.manifest_name and opt.smart_tag:
869 self.OptionParser.error('cannot combine -m and -t')
870 if opt.manifest_server_username or opt.manifest_server_password:
871 if not (opt.smart_sync or opt.smart_tag):
872 self.OptionParser.error('-u and -p may only be combined with -s or -t')
873 if None in [opt.manifest_server_username, opt.manifest_server_password]:
874 self.OptionParser.error('both -u and -p must be given')
875
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700876 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800877 if opt.jobs:
878 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700879 if self.jobs > 1:
880 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400881 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700882
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500883 if opt.manifest_name:
884 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700885
Chirayu Desaia892b102013-06-11 14:18:46 +0530886 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900887 smart_sync_manifest_path = os.path.join(
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400888 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530889
Victor Boivie08c880d2011-04-19 10:32:52 +0200890 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400891 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
892 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900893 if os.path.isfile(smart_sync_manifest_path):
894 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800895 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900896 except OSError as e:
897 print('error: failed to remove existing smart sync override manifest: %s' %
898 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700899
Mike Frysinger5a033082019-09-23 19:21:20 -0400900 err_event = _threading.Event()
901
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700902 rp = self.manifest.repoProject
903 rp.PreSync()
904
905 mp = self.manifest.manifestProject
906 mp.PreSync()
907
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800908 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700909 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800910
Fredrik de Grootcc960972019-11-22 09:04:31 +0100911 if not opt.mp_update:
912 print('Skipping update of local manifest project.')
913 else:
914 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700915
Simran Basib9a1b732015-08-20 12:19:28 -0700916 if self.gitc_manifest:
917 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700918 missing_ok=True)
919 gitc_projects = []
920 opened_projects = []
921 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700922 if project.relpath in self.gitc_manifest.paths and \
923 self.gitc_manifest.paths[project.relpath].old_revision:
924 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700925 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700926 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700927
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700928 if not args:
929 gitc_projects = None
930
931 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700932 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700933 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
934 if manifest_name:
935 manifest.Override(manifest_name)
936 else:
937 manifest.Override(self.manifest.manifestFile)
938 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
939 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700940 gitc_projects)
941 print('GITC client successfully synced.')
942
943 # The opened projects need to be synced as normal, therefore we
944 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700945 # TODO: make this more reliable -- if there's a project name/path overlap,
946 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900947 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
948 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700949 if not args:
950 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800951 all_projects = self.GetProjects(args,
952 missing_ok=True,
953 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700954
Mike Frysinger5a033082019-09-23 19:21:20 -0400955 err_network_sync = False
956 err_update_projects = False
957 err_checkout = False
958
Dave Borowitz67700e92012-10-23 15:00:54 -0700959 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700960 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700961 to_fetch = []
962 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700963 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700964 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900965 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700966 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700967
Mike Frysinger5a033082019-09-23 19:21:20 -0400968 fetched = self._Fetch(to_fetch, opt, err_event)
969
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700970 _PostRepoFetch(rp, opt.no_repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700971 if opt.network_only:
972 # bail out now; the rest touches the working tree
Mike Frysinger5a033082019-09-23 19:21:20 -0400973 if err_event.isSet():
974 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
975 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700976 return
977
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800978 # Iteratively fetch missing and/or nested unregistered submodules
979 previously_missing_set = set()
980 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100981 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800982 all_projects = self.GetProjects(args,
983 missing_ok=True,
984 submodules_ok=opt.fetch_submodules)
985 missing = []
986 for project in all_projects:
987 if project.gitdir not in fetched:
988 missing.append(project)
989 if not missing:
990 break
991 # Stop us from non-stopped fetching actually-missing repos: If set of
992 # missing repos has not been changed from last fetch, we break.
993 missing_set = set(p.name for p in missing)
994 if previously_missing_set == missing_set:
995 break
996 previously_missing_set = missing_set
Mike Frysinger5a033082019-09-23 19:21:20 -0400997 fetched.update(self._Fetch(missing, opt, err_event))
998
999 # If we saw an error, exit with code 1 so that other scripts can check.
1000 if err_event.isSet():
1001 err_network_sync = True
1002 if opt.fail_fast:
1003 print('\nerror: Exited sync due to fetch errors.\n'
1004 'Local checkouts *not* updated. Resolve network issues & '
1005 'retry.\n'
1006 '`repo sync -l` will update some local checkouts.',
1007 file=sys.stderr)
1008 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001009
Julien Campergue335f5ef2013-10-16 11:02:35 +02001010 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001011 # bail out now, we have no working tree
1012 return
1013
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -05001014 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -04001015 err_event.set()
1016 err_update_projects = True
1017 if opt.fail_fast:
1018 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1019 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001020
Mike Frysinger5a033082019-09-23 19:21:20 -04001021 err_results = []
1022 self._Checkout(all_projects, opt, err_event, err_results)
1023 if err_event.isSet():
1024 err_checkout = True
1025 # NB: We don't exit here because this is the last step.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001026
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001027 # If there's a notice that's supposed to print at the end of the sync, print
1028 # it now...
1029 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001030 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001031
Mike Frysinger5a033082019-09-23 19:21:20 -04001032 # If we saw an error, exit with code 1 so that other scripts can check.
1033 if err_event.isSet():
1034 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1035 if err_network_sync:
1036 print('error: Downloading network changes failed.', file=sys.stderr)
1037 if err_update_projects:
1038 print('error: Updating local project lists failed.', file=sys.stderr)
1039 if err_checkout:
1040 print('error: Checking out local projects failed.', file=sys.stderr)
1041 if err_results:
1042 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1043 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1044 file=sys.stderr)
1045 sys.exit(1)
1046
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001047def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001048 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001049 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001050 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001051 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001052 if project.Exists:
1053 project.PostRepoUpgrade()
1054
1055def _PostRepoFetch(rp, no_repo_verify=False, verbose=False):
1056 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001057 print('info: A new version of repo is available', file=sys.stderr)
1058 print(file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001059 if no_repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -07001060 syncbuf = SyncBuffer(rp.config)
1061 rp.Sync_LocalHalf(syncbuf)
1062 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001063 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -07001064 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001065 raise RepoChangedException(['--repo-upgraded'])
1066 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001067 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001068 else:
1069 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001070 print('repo version %s is current' % rp.work_git.describe(HEAD),
1071 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001072
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001073def _VerifyTag(project):
1074 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
1075 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -07001076 print('warning: GnuPG was not available during last "repo init"\n'
1077 'warning: Cannot automatically authenticate repo."""',
1078 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079 return True
1080
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001081 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001082 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001083 except GitError:
1084 cur = None
1085
1086 if not cur \
1087 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -07001088 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001089 if rev.startswith(R_HEADS):
1090 rev = rev[len(R_HEADS):]
1091
Sarah Owenscecd1d82012-11-01 22:59:27 -07001092 print(file=sys.stderr)
1093 print("warning: project '%s' branch '%s' is not signed"
1094 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001095 return False
1096
Shawn O. Pearcef18cb762010-12-07 11:41:05 -08001097 env = os.environ.copy()
1098 env['GIT_DIR'] = project.gitdir.encode()
1099 env['GNUPGHOME'] = gpg_dir.encode()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001100
1101 cmd = [GIT, 'tag', '-v', cur]
1102 proc = subprocess.Popen(cmd,
1103 stdout = subprocess.PIPE,
1104 stderr = subprocess.PIPE,
1105 env = env)
1106 out = proc.stdout.read()
1107 proc.stdout.close()
1108
1109 err = proc.stderr.read()
1110 proc.stderr.close()
1111
1112 if proc.wait() != 0:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001113 print(file=sys.stderr)
1114 print(out, file=sys.stderr)
1115 print(err, file=sys.stderr)
1116 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001117 return False
1118 return True
Dave Borowitz67700e92012-10-23 15:00:54 -07001119
David Rileye0684ad2017-04-05 00:02:59 -07001120
Dave Borowitz67700e92012-10-23 15:00:54 -07001121class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001122 _ALPHA = 0.5
1123
Dave Borowitz67700e92012-10-23 15:00:54 -07001124 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001125 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001126 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001127 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001128
1129 def Get(self, project):
1130 self._Load()
1131 return self._times.get(project.name, _ONE_DAY_S)
1132
1133 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001134 self._Load()
1135 name = project.name
1136 old = self._times.get(name, t)
1137 self._seen.add(name)
1138 a = self._ALPHA
1139 self._times[name] = (a*t) + ((1-a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001140
1141 def _Load(self):
1142 if self._times is None:
1143 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001144 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001145 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001146 except (IOError, ValueError):
1147 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001148 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001149 except OSError:
1150 pass
1151 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001152
1153 def Save(self):
1154 if self._times is None:
1155 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001156
1157 to_delete = []
1158 for name in self._times:
1159 if name not in self._seen:
1160 to_delete.append(name)
1161 for name in to_delete:
1162 del self._times[name]
1163
Dave Borowitz67700e92012-10-23 15:00:54 -07001164 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001165 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001166 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001167 except (IOError, TypeError):
1168 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001169 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001170 except OSError:
1171 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001172
1173# This is a replacement for xmlrpc.client.Transport using urllib2
1174# and supporting persistent-http[s]. It cannot change hosts from
1175# request to request like the normal transport, the real url
1176# is passed during initialization.
1177class PersistentTransport(xmlrpc.client.Transport):
1178 def __init__(self, orig_host):
1179 self.orig_host = orig_host
1180
1181 def request(self, host, handler, request_body, verbose=False):
1182 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1183 # Python doesn't understand cookies with the #HttpOnly_ prefix
1184 # Since we're only using them for HTTP, copy the file temporarily,
1185 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001186 if cookiefile:
1187 tmpcookiefile = tempfile.NamedTemporaryFile()
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001188 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001189 try:
1190 with open(cookiefile) as f:
1191 for line in f:
1192 if line.startswith("#HttpOnly_"):
1193 line = line[len("#HttpOnly_"):]
1194 tmpcookiefile.write(line)
1195 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001196
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001197 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001198 try:
1199 cookiejar.load()
1200 except cookielib.LoadError:
1201 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001202 finally:
1203 tmpcookiefile.close()
1204 else:
1205 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001206
1207 proxyhandler = urllib.request.ProxyHandler
1208 if proxy:
1209 proxyhandler = urllib.request.ProxyHandler({
1210 "http": proxy,
1211 "https": proxy })
1212
1213 opener = urllib.request.build_opener(
1214 urllib.request.HTTPCookieProcessor(cookiejar),
1215 proxyhandler)
1216
1217 url = urllib.parse.urljoin(self.orig_host, handler)
1218 parse_results = urllib.parse.urlparse(url)
1219
1220 scheme = parse_results.scheme
1221 if scheme == 'persistent-http':
1222 scheme = 'http'
1223 if scheme == 'persistent-https':
1224 # If we're proxying through persistent-https, use http. The
1225 # proxy itself will do the https.
1226 if proxy:
1227 scheme = 'http'
1228 else:
1229 scheme = 'https'
1230
1231 # Parse out any authentication information using the base class
1232 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1233
1234 url = urllib.parse.urlunparse((
1235 scheme,
1236 host,
1237 parse_results.path,
1238 parse_results.params,
1239 parse_results.query,
1240 parse_results.fragment))
1241
1242 request = urllib.request.Request(url, request_body)
1243 if extra_headers is not None:
1244 for (name, header) in extra_headers:
1245 request.add_header(name, header)
1246 request.add_header('Content-Type', 'text/xml')
1247 try:
1248 response = opener.open(request)
1249 except urllib.error.HTTPError as e:
1250 if e.code == 501:
1251 # We may have been redirected through a login process
1252 # but our POST turned into a GET. Retry.
1253 response = opener.open(request)
1254 else:
1255 raise
1256
1257 p, u = xmlrpc.client.getparser()
1258 while 1:
1259 data = response.read(1024)
1260 if not data:
1261 break
1262 p.feed(data)
1263 p.close()
1264 return u.close()
1265
1266 def close(self):
1267 pass
1268