blob: 0c386add402bf0eddddf09422f9b0005d9c2eac6 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
jiajia tanga590e642021-04-25 20:02:02 +080015import errno
Mike Frysingerebf04a42021-02-23 20:48:04 -050016import functools
Mike Frysingeracf63b22019-06-13 02:24:21 -040017import http.cookiejar as cookielib
Mike Frysinger7b586f22021-02-23 18:38:39 -050018import io
Anthony King85b24ac2014-05-06 15:57:48 +010019import json
Mike Frysingerebf04a42021-02-23 20:48:04 -050020import multiprocessing
David Pursehouse86d973d2012-08-24 10:21:02 +090021import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070022from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import os
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070024import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040028import urllib.error
29import urllib.parse
30import urllib.request
31import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070032
Roy Lee18afd7f2010-05-09 04:32:08 +080033try:
34 import threading as _threading
35except ImportError:
36 import dummy_threading as _threading
37
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070038try:
39 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090040
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070041 def _rlimit_nofile():
42 return resource.getrlimit(resource.RLIMIT_NOFILE)
43except ImportError:
44 def _rlimit_nofile():
45 return (256, 256)
46
David Rileye0684ad2017-04-05 00:02:59 -070047import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040048from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090049from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090050from git_refs import R_HEADS, HEAD
Raman Tenneti6a872c92021-01-14 19:17:50 -080051import git_superproject
Simran Basibdb52712015-08-10 13:23:23 -070052import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070053from project import Project
54from project import RemoteSpec
Mike Frysingerd41eed02021-04-20 23:21:29 -040055from command import Command, MirrorSafeCommand, WORKER_BATCH_SIZE
Raman Tenneti1fd7bc22021-02-04 14:39:38 -080056from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070057import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070058from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070059from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080060from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070061from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070062
Dave Borowitz67700e92012-10-23 15:00:54 -070063_ONE_DAY_S = 24 * 60 * 60
64
David Pursehouse819827a2020-02-12 15:20:19 +090065
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080066class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080067 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070068 common = True
69 helpSummary = "Update working tree to the latest revision"
70 helpUsage = """
71%prog [<project>...]
72"""
73 helpDescription = """
74The '%prog' command synchronizes local project directories
75with the remote repositories specified in the manifest. If a local
76project does not yet exist, it will clone a new local directory from
77the remote repository and set up tracking branches as specified in
78the manifest. If the local project already exists, '%prog'
79will update the remote branches and rebase any new local changes
80on top of the new remote changes.
81
82'%prog' will synchronize all projects listed at the command
83line. Projects can be specified either by name, or by a relative
84or absolute path to the project's local directory. If no projects
85are specified, '%prog' will synchronize all projects listed in
86the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070087
88The -d/--detach option can be used to switch specified projects
89back to the manifest revision. This option is especially helpful
90if the project is currently on a topic branch, but the manifest
91revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070092
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070093The -s/--smart-sync option can be used to sync to a known good
94build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +020095manifest. The -t/--smart-tag option is similar and allows you to
96specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070097
David Pursehousecf76b1b2012-09-14 10:31:42 +090098The -u/--manifest-server-username and -p/--manifest-server-password
99options can be used to specify a username and password to authenticate
100with the manifest server when using the -s or -t option.
101
102If -u and -p are not specified when using the -s or -t option, '%prog'
103will attempt to read authentication credentials for the manifest server
104from the user's .netrc file.
105
106'%prog' will not use authentication credentials from -u/-p or .netrc
107if the manifest server specified in the manifest file already includes
108credentials.
109
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400110By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400111to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500112
Kevin Degiabaa7f32014-11-12 11:27:45 -0700113The --force-sync option can be used to overwrite existing git
114directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900115object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700116refs may be removed when overwriting.
117
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500118The --force-remove-dirty option can be used to remove previously used
119projects with uncommitted changes. WARNING: This may cause data to be
120lost since uncommitted changes may be removed with projects that no longer
121exist in the manifest.
122
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700123The --no-clone-bundle option disables any attempt to use
124$URL/clone.bundle to bootstrap a new Git repository from a
125resumeable bundle file on a content delivery network. This
126may be necessary if there are problems with the local Python
127HTTP client or proxy configuration, but the Git binary works.
128
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800129The --fetch-submodules option enables fetching Git submodules
130of a project from server.
131
David Pursehousef2fad612015-01-29 14:36:28 +0900132The -c/--current-branch option can be used to only fetch objects that
133are on the branch specified by a project's revision.
134
David Pursehouseb1553542014-09-04 21:28:09 +0900135The --optimized-fetch option can be used to only fetch projects that
136are fixed to a sha1 revision if the sha1 revision does not already
137exist locally.
138
David Pursehouse74cfd272015-10-14 10:50:15 +0900139The --prune option can be used to remove any refs that no longer
140exist on the remote.
141
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400142# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700143
144If at least one project remote URL uses an SSH connection (ssh://,
145git+ssh://, or user@host:path syntax) repo will automatically
146enable the SSH ControlMaster option when connecting to that host.
147This feature permits other projects in the same '%prog' session to
148reuse the same SSH tunnel, saving connection setup overheads.
149
150To disable this behavior on UNIX platforms, set the GIT_SSH
151environment variable to 'ssh'. For example:
152
153 export GIT_SSH=ssh
154 %prog
155
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400156# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700157
158This feature is automatically disabled on Windows, due to the lack
159of UNIX domain socket support.
160
161This feature is not compatible with url.insteadof rewrites in the
162user's ~/.gitconfig. '%prog' is currently not able to perform the
163rewrite early enough to establish the ControlMaster tunnel.
164
165If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
166later is required to fix a server side protocol bug.
167
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700168"""
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500169 PARALLEL_JOBS = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170
Mike Frysinger9180a072021-04-13 14:57:40 -0400171 def _CommonOptions(self, p):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000172 try:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500173 self.PARALLEL_JOBS = self.manifest.default.sync_j
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000174 except ManifestParseError:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500175 pass
Mike Frysinger9180a072021-04-13 14:57:40 -0400176 super()._CommonOptions(p)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700177
Mike Frysinger9180a072021-04-13 14:57:40 -0400178 def _Options(self, p, show_smart=True):
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400179 p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
180 help='number of network jobs to run in parallel (defaults to --jobs)')
181 p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
182 help='number of local checkout jobs to run in parallel (defaults to --jobs)')
183
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500184 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200185 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400186 help='obsolete option (to be deleted in the future)')
187 p.add_option('--fail-fast',
188 dest='fail_fast', action='store_true',
189 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700190 p.add_option('--force-sync',
191 dest='force_sync', action='store_true',
192 help="overwrite an existing git directory if it needs to "
193 "point to a different object directory. WARNING: this "
194 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500195 p.add_option('--force-remove-dirty',
196 dest='force_remove_dirty', action='store_true',
197 help="force remove projects with uncommitted modifications if "
198 "projects no longer exist in the manifest. "
199 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900200 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700201 dest='local_only', action='store_true',
202 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900203 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100204 dest='mp_update', action='store_false', default='true',
205 help='use the existing manifest checkout as-is. '
206 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900207 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700208 dest='network_only', action='store_true',
209 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900210 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700211 dest='detach_head', action='store_true',
212 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900213 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700214 dest='current_branch_only', action='store_true',
215 help='fetch only current branch from server')
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500216 p.add_option('-m', '--manifest-name',
217 dest='manifest_name',
218 help='temporary manifest to use for this sync', metavar='NAME.xml')
Xin Lid79a4bc2020-05-20 16:03:45 -0700219 p.add_option('--clone-bundle', action='store_true',
220 help='enable use of /clone.bundle on HTTP/HTTPS')
221 p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700222 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800223 p.add_option('-u', '--manifest-server-username', action='store',
224 dest='manifest_server_username',
225 help='username to authenticate with the manifest server')
226 p.add_option('-p', '--manifest-server-password', action='store',
227 dest='manifest_server_password',
228 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800229 p.add_option('--fetch-submodules',
230 dest='fetch_submodules', action='store_true',
231 help='fetch submodules from server')
Raman Tenneti6a872c92021-01-14 19:17:50 -0800232 p.add_option('--use-superproject', action='store_true',
233 help='use the manifest superproject to sync projects')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700234 p.add_option('--no-tags',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500235 dest='tags', default=True, action='store_false',
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700236 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900237 p.add_option('--optimized-fetch',
238 dest='optimized_fetch', action='store_true',
239 help='only fetch projects fixed to sha1 if revision does not exist locally')
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600240 p.add_option('--retry-fetches',
241 default=0, action='store', type='int',
242 help='number of times to retry fetches on transient errors')
David Pursehouse74cfd272015-10-14 10:50:15 +0900243 p.add_option('--prune', dest='prune', action='store_true',
244 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700245 if show_smart:
246 p.add_option('-s', '--smart-sync',
247 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900248 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200249 p.add_option('-t', '--smart-tag',
250 dest='smart_tag', action='store',
251 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700252
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700253 g = p.add_option_group('repo Version options')
254 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500255 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700256 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700257 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800258 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700259 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800261 def _GetBranch(self):
262 """Returns the branch name for getting the approved manifest."""
263 p = self.manifest.manifestProject
264 b = p.GetBranch(p.CurrentBranch)
265 branch = b.merge
266 if branch.startswith(R_HEADS):
267 branch = branch[len(R_HEADS):]
268 return branch
269
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700270 def _UseSuperproject(self, opt):
271 """Returns True if use-superproject option is enabled"""
272 return (opt.use_superproject or
273 self.manifest.manifestProject.config.GetBoolean(
274 'repo.superproject'))
275
276 def _GetCurrentBranchOnly(self, opt):
277 """Returns True if current-branch or use-superproject options are enabled."""
278 return opt.current_branch_only or self._UseSuperproject(opt)
279
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800280 def _UpdateProjectsRevisionId(self, opt, args):
281 """Update revisionId of every project with the SHA from superproject.
282
283 This function updates each project's revisionId with SHA from superproject.
284 It writes the updated manifest into a file and reloads the manifest from it.
285
286 Args:
287 opt: Program options returned from optparse. See _Options().
288 args: Arguments to pass to GetProjects. See the GetProjects
289 docstring for details.
290
291 Returns:
292 Returns path to the overriding manifest file.
293 """
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800294 superproject = git_superproject.Superproject(self.manifest,
Raman Tennetief99ec02021-03-04 10:29:40 -0800295 self.repodir,
296 quiet=opt.quiet)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800297 all_projects = self.GetProjects(args,
298 missing_ok=True,
299 submodules_ok=opt.fetch_submodules)
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800300 manifest_path = superproject.UpdateProjectsRevisionId(all_projects)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800301 if not manifest_path:
302 print('error: Update of revsionId from superproject has failed',
303 file=sys.stderr)
304 sys.exit(1)
305 self._ReloadManifest(manifest_path)
306 return manifest_path
307
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500308 def _FetchProjectList(self, opt, projects):
309 """Main function of the fetch worker.
310
311 The projects we're given share the same underlying git object store, so we
312 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800313
David James8d201162013-10-11 17:03:19 -0700314 Delegates most of the work to _FetchHelper.
315
316 Args:
317 opt: Program options returned from optparse. See _Options().
318 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700319 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500320 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700321
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500322 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700323 """Fetch git objects for a single project.
324
David Pursehousec1b86a22012-11-14 11:36:51 +0900325 Args:
326 opt: Program options returned from optparse. See _Options().
327 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700328
329 Returns:
330 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900331 """
David Rileye0684ad2017-04-05 00:02:59 -0700332 start = time.time()
333 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500334 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900335 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500336 success = project.Sync_NetworkHalf(
337 quiet=opt.quiet,
338 verbose=opt.verbose,
339 output_redir=buf,
340 current_branch_only=self._GetCurrentBranchOnly(opt),
341 force_sync=opt.force_sync,
342 clone_bundle=opt.clone_bundle,
343 tags=opt.tags, archive=self.manifest.IsArchive,
344 optimized_fetch=opt.optimized_fetch,
345 retry_fetches=opt.retry_fetches,
346 prune=opt.prune,
Raman Tennetif32f2432021-04-12 20:57:25 -0700347 clone_filter=self.manifest.CloneFilter,
348 partial_clone_exclude=self.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700349
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500350 output = buf.getvalue()
351 if opt.verbose and output:
352 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700353
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500354 if not success:
355 print('error: Cannot fetch %s from %s'
356 % (project.name, project.remote.url),
357 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700358 except GitError as e:
359 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500360 except Exception as e:
361 print('error: Cannot fetch %s (%s: %s)'
362 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
363 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500364
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500365 finish = time.time()
366 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700367
Mike Frysinger5a033082019-09-23 19:21:20 -0400368 def _Fetch(self, projects, opt, err_event):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500369 ret = True
370
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400371 jobs = opt.jobs_network if opt.jobs_network else self.jobs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700372 fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400373 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800374
David James89ece422014-01-09 18:51:58 -0800375 objdir_project_map = dict()
376 for project in projects:
377 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500378 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700379
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500380 def _ProcessResults(results_sets):
381 ret = True
382 for results in results_sets:
383 for (success, project, start, finish) in results:
384 self._fetch_times.Set(project, finish - start)
385 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
386 start, finish, success)
387 # Check for any errors before running any more tasks.
388 # ...we'll let existing jobs finish, though.
389 if not success:
390 ret = False
391 else:
392 fetched.add(project.gitdir)
393 pm.update(msg=project.name)
394 if not ret and opt.fail_fast:
395 break
396 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700397
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500398 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400399 if len(projects_list) == 1 or jobs == 1:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500400 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
401 ret = False
402 else:
403 # Favor throughput over responsiveness when quiet. It seems that imap()
404 # will yield results in batches relative to chunksize, so even as the
405 # children finish a sync, we won't see the result until one child finishes
406 # ~chunksize jobs. When using a large --jobs with large chunksize, this
407 # can be jarring as there will be a large initial delay where repo looks
408 # like it isn't doing anything and sits at 0%, but then suddenly completes
409 # a lot of jobs all at once. Since this code is more network bound, we
410 # can accept a bit more CPU overhead with a smaller chunksize so that the
411 # user sees more immediate & continuous feedback.
412 if opt.quiet:
413 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800414 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500415 pm.update(inc=0, msg='warming up')
416 chunksize = 4
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400417 with multiprocessing.Pool(jobs) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500418 results = pool.imap_unordered(
419 functools.partial(self._FetchProjectList, opt),
420 projects_list,
421 chunksize=chunksize)
422 if not _ProcessResults(results):
423 ret = False
424 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800425
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700426 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700427 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700428
Julien Campergue335f5ef2013-10-16 11:02:35 +0200429 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400430 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200431
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500432 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700433
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500434 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700435 """Checkout work tree for one project
436
437 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500438 detach_head: Whether to leave a detached HEAD.
439 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700440 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700441
442 Returns:
443 Whether the fetch was successful.
444 """
Xin Li745be2e2019-06-03 11:24:30 -0700445 start = time.time()
446 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500447 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700448 success = False
449 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500450 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500451 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700452 except GitError as e:
453 print('error.GitError: Cannot checkout %s: %s' %
454 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500455 except Exception as e:
456 print('error: Cannot checkout %s: %s: %s' %
457 (project.name, type(e).__name__, str(e)),
458 file=sys.stderr)
459 raise
Xin Li745be2e2019-06-03 11:24:30 -0700460
Mike Frysingerebf04a42021-02-23 20:48:04 -0500461 if not success:
462 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
463 finish = time.time()
464 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700465
Mike Frysingerebf04a42021-02-23 20:48:04 -0500466 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700467 """Checkout projects listed in all_projects
468
469 Args:
470 all_projects: List of all projects that should be checked out.
471 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500472 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700473 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500474 # Only checkout projects with worktrees.
475 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700476
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500477 def _ProcessResults(pool, pm, results):
478 ret = True
Mike Frysingerebf04a42021-02-23 20:48:04 -0500479 for (success, project, start, finish) in results:
480 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
481 start, finish, success)
482 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500483 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500484 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500485 ret = False
Mike Frysingerebf04a42021-02-23 20:48:04 -0500486 err_results.append(project.relpath)
487 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500488 if pool:
489 pool.close()
490 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500491 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500492 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700493
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500494 return self.ExecuteInParallel(
495 opt.jobs_checkout if opt.jobs_checkout else self.jobs,
496 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
497 all_projects,
498 callback=_ProcessResults,
499 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500500
Mike Frysinger5a033082019-09-23 19:21:20 -0400501 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger151701e2021-04-13 15:07:21 -0400502 pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400503 pm.update(inc=0, msg='prescan')
504
Gabe Black2ff30292014-10-09 17:54:35 -0700505 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700506 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500507 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500508 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900509 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100510 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400511 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100512 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500513 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500514 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500515 else:
516 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400517 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500518 'versions of git; please upgrade to git-2.7.0+.'
519 % (project.relpath,),
520 file=sys.stderr)
521 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700522 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700523
Mike Frysinger65af2602021-04-08 22:47:44 -0400524 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
525
526 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700527 jobs = min(self.jobs, cpu_count)
528
529 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700530 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400531 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700532 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400533 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700534 return
535
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400536 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700537
538 threads = set()
539 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700540
David James8d201162013-10-11 17:03:19 -0700541 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400542 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700543 try:
544 try:
David James8d201162013-10-11 17:03:19 -0700545 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700546 except GitError:
547 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900548 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700549 err_event.set()
550 raise
551 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400552 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700553 sem.release()
554
Gabe Black2ff30292014-10-09 17:54:35 -0700555 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500556 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700557 break
558 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700559 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700560 t.daemon = True
561 threads.add(t)
562 t.start()
563
564 for t in threads:
565 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400566 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700567
Tim Kilbourn07669002013-03-08 15:02:49 -0800568 def _ReloadManifest(self, manifest_name=None):
569 if manifest_name:
570 # Override calls _Unload already
571 self.manifest.Override(manifest_name)
572 else:
573 self.manifest._Unload()
574
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500575 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700576 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700577 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700578 if project.relpath:
579 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700580 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500581 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700582 old_project_paths = []
583
584 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500585 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700586 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800587 # In reversed order, so subfolders are deleted before parent folder.
588 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700589 if not path:
590 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700591 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900592 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700593 gitdir = os.path.join(self.manifest.topdir, path, '.git')
594 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900595 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900596 manifest=self.manifest,
597 name=path,
598 remote=RemoteSpec('origin'),
599 gitdir=gitdir,
600 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500601 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900602 worktree=os.path.join(self.manifest.topdir, path),
603 relpath=path,
604 revisionExpr='HEAD',
605 revisionId=None,
606 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500607 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900608 quiet=opt.quiet,
609 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400610 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700611
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700612 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500613 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700614 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700615 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700616 return 0
617
jiajia tanga590e642021-04-25 20:02:02 +0800618 def UpdateCopyLinkfileList(self):
619 """Save all dests of copyfile and linkfile, and update them if needed.
620
621 Returns:
622 Whether update was successful.
623 """
624 new_paths = {}
625 new_linkfile_paths = []
626 new_copyfile_paths = []
627 for project in self.GetProjects(None, missing_ok=True):
628 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
629 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
630
631 new_paths = {
632 'linkfile': new_linkfile_paths,
633 'copyfile': new_copyfile_paths,
634 }
635
636 copylinkfile_name = 'copy-link-files.json'
637 copylinkfile_path = os.path.join(self.manifest.repodir, copylinkfile_name)
638 old_copylinkfile_paths = {}
639
640 if os.path.exists(copylinkfile_path):
641 with open(copylinkfile_path, 'rb') as fp:
642 try:
643 old_copylinkfile_paths = json.load(fp)
644 except:
645 print('error: %s is not a json formatted file.' %
646 copylinkfile_path, file=sys.stderr)
647 platform_utils.remove(copylinkfile_path)
648 return False
649
650 need_remove_files = []
651 need_remove_files.extend(
652 set(old_copylinkfile_paths.get('linkfile', [])) -
653 set(new_linkfile_paths))
654 need_remove_files.extend(
655 set(old_copylinkfile_paths.get('copyfile', [])) -
656 set(new_copyfile_paths))
657
658 for need_remove_file in need_remove_files:
659 try:
660 platform_utils.remove(need_remove_file)
661 except OSError as e:
662 if e.errno == errno.ENOENT:
663 # Try to remove the updated copyfile or linkfile.
664 # So, if the file is not exist, nothing need to do.
665 pass
666
667 # Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
668 with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
669 json.dump(new_paths, fp)
670 return True
671
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400672 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
673 if not self.manifest.manifest_server:
674 print('error: cannot smart sync: no manifest server defined in '
675 'manifest', file=sys.stderr)
676 sys.exit(1)
677
678 manifest_server = self.manifest.manifest_server
679 if not opt.quiet:
680 print('Using manifest server %s' % manifest_server)
681
David Pursehouseeeff3532020-02-12 11:24:10 +0900682 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400683 username = None
684 password = None
685 if opt.manifest_server_username and opt.manifest_server_password:
686 username = opt.manifest_server_username
687 password = opt.manifest_server_password
688 else:
689 try:
690 info = netrc.netrc()
691 except IOError:
692 # .netrc file does not exist or could not be opened
693 pass
694 else:
695 try:
696 parse_result = urllib.parse.urlparse(manifest_server)
697 if parse_result.hostname:
698 auth = info.authenticators(parse_result.hostname)
699 if auth:
700 username, _account, password = auth
701 else:
702 print('No credentials found for %s in .netrc'
703 % parse_result.hostname, file=sys.stderr)
704 except netrc.NetrcParseError as e:
705 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
706
707 if (username and password):
708 manifest_server = manifest_server.replace('://', '://%s:%s@' %
709 (username, password),
710 1)
711
712 transport = PersistentTransport(manifest_server)
713 if manifest_server.startswith('persistent-'):
714 manifest_server = manifest_server[len('persistent-'):]
715
716 try:
717 server = xmlrpc.client.Server(manifest_server, transport=transport)
718 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800719 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400720
Mike Frysinger56ce3462019-12-04 19:30:48 -0500721 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500722 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400723 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500724 elif ('TARGET_PRODUCT' in os.environ and
725 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500726 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
727 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400728 [success, manifest_str] = server.GetApprovedManifest(branch, target)
729 else:
730 [success, manifest_str] = server.GetApprovedManifest(branch)
731 else:
732 assert(opt.smart_tag)
733 [success, manifest_str] = server.GetManifest(opt.smart_tag)
734
735 if success:
736 manifest_name = os.path.basename(smart_sync_manifest_path)
737 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500738 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400739 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400740 except IOError as e:
741 print('error: cannot write manifest to %s:\n%s'
742 % (smart_sync_manifest_path, e),
743 file=sys.stderr)
744 sys.exit(1)
745 self._ReloadManifest(manifest_name)
746 else:
747 print('error: manifest server RPC call failed: %s' %
748 manifest_str, file=sys.stderr)
749 sys.exit(1)
750 except (socket.error, IOError, xmlrpc.client.Fault) as e:
751 print('error: cannot connect to manifest server %s:\n%s'
752 % (self.manifest.manifest_server, e), file=sys.stderr)
753 sys.exit(1)
754 except xmlrpc.client.ProtocolError as e:
755 print('error: cannot connect to manifest server %s:\n%d %s'
756 % (self.manifest.manifest_server, e.errcode, e.errmsg),
757 file=sys.stderr)
758 sys.exit(1)
759
760 return manifest_name
761
Mike Frysingerfb527e32019-08-27 02:34:32 -0400762 def _UpdateManifestProject(self, opt, mp, manifest_name):
763 """Fetch & update the local manifest project."""
764 if not opt.local_only:
765 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500766 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700767 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200768 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500769 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400770 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600771 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400772 submodules=self.manifest.HasSubmodules,
Raman Tennetif32f2432021-04-12 20:57:25 -0700773 clone_filter=self.manifest.CloneFilter,
774 partial_clone_exclude=self.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400775 finish = time.time()
776 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
777 start, finish, success)
778
779 if mp.HasChanges:
780 syncbuf = SyncBuffer(mp.config)
781 start = time.time()
782 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
783 clean = syncbuf.Finish()
784 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
785 start, time.time(), clean)
786 if not clean:
787 sys.exit(1)
788 self._ReloadManifest(opt.manifest_name)
789 if opt.jobs is None:
790 self.jobs = self.manifest.default.sync_j
791
Mike Frysingerae6cb082019-08-27 01:10:59 -0400792 def ValidateOptions(self, opt, args):
793 if opt.force_broken:
794 print('warning: -f/--force-broken is now the default behavior, and the '
795 'options are deprecated', file=sys.stderr)
796 if opt.network_only and opt.detach_head:
797 self.OptionParser.error('cannot combine -n and -d')
798 if opt.network_only and opt.local_only:
799 self.OptionParser.error('cannot combine -n and -l')
800 if opt.manifest_name and opt.smart_sync:
801 self.OptionParser.error('cannot combine -m and -s')
802 if opt.manifest_name and opt.smart_tag:
803 self.OptionParser.error('cannot combine -m and -t')
804 if opt.manifest_server_username or opt.manifest_server_password:
805 if not (opt.smart_sync or opt.smart_tag):
806 self.OptionParser.error('-u and -p may only be combined with -s or -t')
807 if None in [opt.manifest_server_username, opt.manifest_server_password]:
808 self.OptionParser.error('both -u and -p must be given')
809
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700810 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800811 if opt.jobs:
812 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700813 if self.jobs > 1:
814 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400815 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700816
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500817 if opt.manifest_name:
818 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700819
Chirayu Desaia892b102013-06-11 14:18:46 +0530820 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900821 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900822 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530823
Xin Lid79a4bc2020-05-20 16:03:45 -0700824 if opt.clone_bundle is None:
825 opt.clone_bundle = self.manifest.CloneBundle
826
Victor Boivie08c880d2011-04-19 10:32:52 +0200827 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400828 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
829 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900830 if os.path.isfile(smart_sync_manifest_path):
831 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800832 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900833 except OSError as e:
834 print('error: failed to remove existing smart sync override manifest: %s' %
835 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700836
Mike Frysinger5a033082019-09-23 19:21:20 -0400837 err_event = _threading.Event()
838
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700839 rp = self.manifest.repoProject
840 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500841 cb = rp.CurrentBranch
842 if cb:
843 base = rp.GetBranch(cb).merge
844 if not base or not base.startswith('refs/heads/'):
845 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400846 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500847 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700848
849 mp = self.manifest.manifestProject
850 mp.PreSync()
851
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800852 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700853 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800854
Fredrik de Grootcc960972019-11-22 09:04:31 +0100855 if not opt.mp_update:
856 print('Skipping update of local manifest project.')
857 else:
858 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700859
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700860 if self._UseSuperproject(opt):
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800861 manifest_name = self._UpdateProjectsRevisionId(opt, args)
862
Simran Basib9a1b732015-08-20 12:19:28 -0700863 if self.gitc_manifest:
864 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700865 missing_ok=True)
866 gitc_projects = []
867 opened_projects = []
868 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700869 if project.relpath in self.gitc_manifest.paths and \
870 self.gitc_manifest.paths[project.relpath].old_revision:
871 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700872 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700873 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700874
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700875 if not args:
876 gitc_projects = None
877
878 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700879 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700880 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
881 if manifest_name:
882 manifest.Override(manifest_name)
883 else:
884 manifest.Override(self.manifest.manifestFile)
885 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
886 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700887 gitc_projects)
888 print('GITC client successfully synced.')
889
890 # The opened projects need to be synced as normal, therefore we
891 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700892 # TODO: make this more reliable -- if there's a project name/path overlap,
893 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900894 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
895 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700896 if not args:
897 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800898 all_projects = self.GetProjects(args,
899 missing_ok=True,
900 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700901
Mike Frysinger5a033082019-09-23 19:21:20 -0400902 err_network_sync = False
903 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -0400904
Dave Borowitz67700e92012-10-23 15:00:54 -0700905 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700906 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700907 to_fetch = []
908 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700909 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700910 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900911 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700912 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700913
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500914 success, fetched = self._Fetch(to_fetch, opt, err_event)
915 if not success:
916 err_event.set()
Mike Frysinger5a033082019-09-23 19:21:20 -0400917
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500918 _PostRepoFetch(rp, opt.repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700919 if opt.network_only:
920 # bail out now; the rest touches the working tree
Mike Frysingerbe24a542021-02-23 03:24:12 -0500921 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400922 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
923 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700924 return
925
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800926 # Iteratively fetch missing and/or nested unregistered submodules
927 previously_missing_set = set()
928 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100929 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800930 all_projects = self.GetProjects(args,
931 missing_ok=True,
932 submodules_ok=opt.fetch_submodules)
933 missing = []
934 for project in all_projects:
935 if project.gitdir not in fetched:
936 missing.append(project)
937 if not missing:
938 break
939 # Stop us from non-stopped fetching actually-missing repos: If set of
940 # missing repos has not been changed from last fetch, we break.
941 missing_set = set(p.name for p in missing)
942 if previously_missing_set == missing_set:
943 break
944 previously_missing_set = missing_set
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500945 success, new_fetched = self._Fetch(to_fetch, opt, err_event)
946 if not success:
947 err_event.set()
948 fetched.update(new_fetched)
Mike Frysinger5a033082019-09-23 19:21:20 -0400949
950 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500951 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400952 err_network_sync = True
953 if opt.fail_fast:
954 print('\nerror: Exited sync due to fetch errors.\n'
955 'Local checkouts *not* updated. Resolve network issues & '
956 'retry.\n'
957 '`repo sync -l` will update some local checkouts.',
958 file=sys.stderr)
959 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800960
Julien Campergue335f5ef2013-10-16 11:02:35 +0200961 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700962 # bail out now, we have no working tree
963 return
964
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500965 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -0400966 err_event.set()
967 err_update_projects = True
968 if opt.fail_fast:
969 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
970 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700971
jiajia tanga590e642021-04-25 20:02:02 +0800972 if not self.UpdateCopyLinkfileList():
973 err_event.set()
974 err_update_linkfiles = True
975 if opt.fail_fast:
976 print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
977 sys.exit(1)
978
Mike Frysinger5a033082019-09-23 19:21:20 -0400979 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -0500980 # NB: We don't exit here because this is the last step.
981 err_checkout = not self._Checkout(all_projects, opt, err_results)
982 if err_checkout:
983 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700984
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700985 # If there's a notice that's supposed to print at the end of the sync, print
986 # it now...
987 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700988 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700989
Mike Frysinger5a033082019-09-23 19:21:20 -0400990 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500991 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400992 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
993 if err_network_sync:
994 print('error: Downloading network changes failed.', file=sys.stderr)
995 if err_update_projects:
996 print('error: Updating local project lists failed.', file=sys.stderr)
jiajia tanga590e642021-04-25 20:02:02 +0800997 if err_update_linkfiles:
998 print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr)
Mike Frysinger5a033082019-09-23 19:21:20 -0400999 if err_checkout:
1000 print('error: Checking out local projects failed.', file=sys.stderr)
1001 if err_results:
1002 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1003 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1004 file=sys.stderr)
1005 sys.exit(1)
1006
Mike Frysingere19d9e12020-02-12 11:23:32 -05001007 if not opt.quiet:
1008 print('repo sync has finished successfully.')
1009
David Pursehouse819827a2020-02-12 15:20:19 +09001010
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001011def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -08001012 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001013 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001014 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001015 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001016 if project.Exists:
1017 project.PostRepoUpgrade()
1018
David Pursehouse819827a2020-02-12 15:20:19 +09001019
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001020def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001021 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001022 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -04001023 wrapper = Wrapper()
1024 try:
1025 rev = rp.bare_git.describe(rp.GetRevisionId())
1026 except GitError:
1027 rev = None
1028 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
1029 # See if we're held back due to missing signed tag.
1030 current_revid = rp.bare_git.rev_parse('HEAD')
1031 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
1032 if current_revid != new_revid:
1033 # We want to switch to the new rev, but also not trash any uncommitted
1034 # changes. This helps with local testing/hacking.
1035 # If a local change has been made, we will throw that away.
1036 # We also have to make sure this will switch to an older commit if that's
1037 # the latest tag in order to support release rollback.
1038 try:
1039 rp.work_git.reset('--keep', new_rev)
1040 except GitError as e:
1041 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -07001042 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001043 raise RepoChangedException(['--repo-upgraded'])
1044 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001045 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001046 else:
1047 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001048 print('repo version %s is current' % rp.work_git.describe(HEAD),
1049 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001050
David Pursehouse819827a2020-02-12 15:20:19 +09001051
Dave Borowitz67700e92012-10-23 15:00:54 -07001052class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001053 _ALPHA = 0.5
1054
Dave Borowitz67700e92012-10-23 15:00:54 -07001055 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001056 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001057 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001058 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001059
1060 def Get(self, project):
1061 self._Load()
1062 return self._times.get(project.name, _ONE_DAY_S)
1063
1064 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001065 self._Load()
1066 name = project.name
1067 old = self._times.get(name, t)
1068 self._seen.add(name)
1069 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001070 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001071
1072 def _Load(self):
1073 if self._times is None:
1074 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001075 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001076 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001077 except (IOError, ValueError):
1078 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001079 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001080 except OSError:
1081 pass
1082 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001083
1084 def Save(self):
1085 if self._times is None:
1086 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001087
1088 to_delete = []
1089 for name in self._times:
1090 if name not in self._seen:
1091 to_delete.append(name)
1092 for name in to_delete:
1093 del self._times[name]
1094
Dave Borowitz67700e92012-10-23 15:00:54 -07001095 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001096 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001097 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001098 except (IOError, TypeError):
1099 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001100 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001101 except OSError:
1102 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001103
1104# This is a replacement for xmlrpc.client.Transport using urllib2
1105# and supporting persistent-http[s]. It cannot change hosts from
1106# request to request like the normal transport, the real url
1107# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001108
1109
Dan Willemsen0745bb22015-08-17 13:41:45 -07001110class PersistentTransport(xmlrpc.client.Transport):
1111 def __init__(self, orig_host):
1112 self.orig_host = orig_host
1113
1114 def request(self, host, handler, request_body, verbose=False):
1115 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1116 # Python doesn't understand cookies with the #HttpOnly_ prefix
1117 # Since we're only using them for HTTP, copy the file temporarily,
1118 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001119 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001120 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001121 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001122 try:
1123 with open(cookiefile) as f:
1124 for line in f:
1125 if line.startswith("#HttpOnly_"):
1126 line = line[len("#HttpOnly_"):]
1127 tmpcookiefile.write(line)
1128 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001129
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001130 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001131 try:
1132 cookiejar.load()
1133 except cookielib.LoadError:
1134 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001135 finally:
1136 tmpcookiefile.close()
1137 else:
1138 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001139
1140 proxyhandler = urllib.request.ProxyHandler
1141 if proxy:
1142 proxyhandler = urllib.request.ProxyHandler({
1143 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001144 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001145
1146 opener = urllib.request.build_opener(
1147 urllib.request.HTTPCookieProcessor(cookiejar),
1148 proxyhandler)
1149
1150 url = urllib.parse.urljoin(self.orig_host, handler)
1151 parse_results = urllib.parse.urlparse(url)
1152
1153 scheme = parse_results.scheme
1154 if scheme == 'persistent-http':
1155 scheme = 'http'
1156 if scheme == 'persistent-https':
1157 # If we're proxying through persistent-https, use http. The
1158 # proxy itself will do the https.
1159 if proxy:
1160 scheme = 'http'
1161 else:
1162 scheme = 'https'
1163
1164 # Parse out any authentication information using the base class
1165 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1166
1167 url = urllib.parse.urlunparse((
1168 scheme,
1169 host,
1170 parse_results.path,
1171 parse_results.params,
1172 parse_results.query,
1173 parse_results.fragment))
1174
1175 request = urllib.request.Request(url, request_body)
1176 if extra_headers is not None:
1177 for (name, header) in extra_headers:
1178 request.add_header(name, header)
1179 request.add_header('Content-Type', 'text/xml')
1180 try:
1181 response = opener.open(request)
1182 except urllib.error.HTTPError as e:
1183 if e.code == 501:
1184 # We may have been redirected through a login process
1185 # but our POST turned into a GET. Retry.
1186 response = opener.open(request)
1187 else:
1188 raise
1189
1190 p, u = xmlrpc.client.getparser()
1191 while 1:
1192 data = response.read(1024)
1193 if not data:
1194 break
1195 p.feed(data)
1196 p.close()
1197 return u.close()
1198
1199 def close(self):
1200 pass