blob: b41b20b70f96982496c065465acda5444111ce25 [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
Mike Frysingerebf04a42021-02-23 20:48:04 -050015import functools
Mike Frysingeracf63b22019-06-13 02:24:21 -040016import http.cookiejar as cookielib
Mike Frysinger7b586f22021-02-23 18:38:39 -050017import io
Anthony King85b24ac2014-05-06 15:57:48 +010018import json
Mike Frysingerebf04a42021-02-23 20:48:04 -050019import multiprocessing
David Pursehouse86d973d2012-08-24 10:21:02 +090020import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070021from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070023import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070025import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070026import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040027import urllib.error
28import urllib.parse
29import urllib.request
30import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031
Roy Lee18afd7f2010-05-09 04:32:08 +080032try:
33 import threading as _threading
34except ImportError:
35 import dummy_threading as _threading
36
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070037try:
38 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090039
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070040 def _rlimit_nofile():
41 return resource.getrlimit(resource.RLIMIT_NOFILE)
42except ImportError:
43 def _rlimit_nofile():
44 return (256, 256)
45
David Rileye0684ad2017-04-05 00:02:59 -070046import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040047from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090048from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090049from git_refs import R_HEADS, HEAD
Raman Tenneti6a872c92021-01-14 19:17:50 -080050import git_superproject
Simran Basibdb52712015-08-10 13:23:23 -070051import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070052from project import Project
53from project import RemoteSpec
Mike Frysingerebf04a42021-02-23 20:48:04 -050054from command import Command, MirrorSafeCommand, WORKER_BATCH_SIZE
Raman Tenneti1fd7bc22021-02-04 14:39:38 -080055from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070056import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070057from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070058from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080059from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070060from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070061
Dave Borowitz67700e92012-10-23 15:00:54 -070062_ONE_DAY_S = 24 * 60 * 60
63
David Pursehouse819827a2020-02-12 15:20:19 +090064
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080065class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080066 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067 common = True
68 helpSummary = "Update working tree to the latest revision"
69 helpUsage = """
70%prog [<project>...]
71"""
72 helpDescription = """
73The '%prog' command synchronizes local project directories
74with the remote repositories specified in the manifest. If a local
75project does not yet exist, it will clone a new local directory from
76the remote repository and set up tracking branches as specified in
77the manifest. If the local project already exists, '%prog'
78will update the remote branches and rebase any new local changes
79on top of the new remote changes.
80
81'%prog' will synchronize all projects listed at the command
82line. Projects can be specified either by name, or by a relative
83or absolute path to the project's local directory. If no projects
84are specified, '%prog' will synchronize all projects listed in
85the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070086
87The -d/--detach option can be used to switch specified projects
88back to the manifest revision. This option is especially helpful
89if the project is currently on a topic branch, but the manifest
90revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070091
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070092The -s/--smart-sync option can be used to sync to a known good
93build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +020094manifest. The -t/--smart-tag option is similar and allows you to
95specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070096
David Pursehousecf76b1b2012-09-14 10:31:42 +090097The -u/--manifest-server-username and -p/--manifest-server-password
98options can be used to specify a username and password to authenticate
99with the manifest server when using the -s or -t option.
100
101If -u and -p are not specified when using the -s or -t option, '%prog'
102will attempt to read authentication credentials for the manifest server
103from the user's .netrc file.
104
105'%prog' will not use authentication credentials from -u/-p or .netrc
106if the manifest server specified in the manifest file already includes
107credentials.
108
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400109By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400110to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500111
Kevin Degiabaa7f32014-11-12 11:27:45 -0700112The --force-sync option can be used to overwrite existing git
113directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900114object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700115refs may be removed when overwriting.
116
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500117The --force-remove-dirty option can be used to remove previously used
118projects with uncommitted changes. WARNING: This may cause data to be
119lost since uncommitted changes may be removed with projects that no longer
120exist in the manifest.
121
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700122The --no-clone-bundle option disables any attempt to use
123$URL/clone.bundle to bootstrap a new Git repository from a
124resumeable bundle file on a content delivery network. This
125may be necessary if there are problems with the local Python
126HTTP client or proxy configuration, but the Git binary works.
127
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800128The --fetch-submodules option enables fetching Git submodules
129of a project from server.
130
David Pursehousef2fad612015-01-29 14:36:28 +0900131The -c/--current-branch option can be used to only fetch objects that
132are on the branch specified by a project's revision.
133
David Pursehouseb1553542014-09-04 21:28:09 +0900134The --optimized-fetch option can be used to only fetch projects that
135are fixed to a sha1 revision if the sha1 revision does not already
136exist locally.
137
David Pursehouse74cfd272015-10-14 10:50:15 +0900138The --prune option can be used to remove any refs that no longer
139exist on the remote.
140
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400141# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700142
143If at least one project remote URL uses an SSH connection (ssh://,
144git+ssh://, or user@host:path syntax) repo will automatically
145enable the SSH ControlMaster option when connecting to that host.
146This feature permits other projects in the same '%prog' session to
147reuse the same SSH tunnel, saving connection setup overheads.
148
149To disable this behavior on UNIX platforms, set the GIT_SSH
150environment variable to 'ssh'. For example:
151
152 export GIT_SSH=ssh
153 %prog
154
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400155# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700156
157This feature is automatically disabled on Windows, due to the lack
158of UNIX domain socket support.
159
160This feature is not compatible with url.insteadof rewrites in the
161user's ~/.gitconfig. '%prog' is currently not able to perform the
162rewrite early enough to establish the ControlMaster tunnel.
163
164If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
165later is required to fix a server side protocol bug.
166
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167"""
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500168 PARALLEL_JOBS = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169
Nico Sallembien6623b212010-05-11 12:57:01 -0700170 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000171 try:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500172 self.PARALLEL_JOBS = self.manifest.default.sync_j
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000173 except ManifestParseError:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500174 pass
175 super()._Options(p)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700176
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500177 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200178 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400179 help='obsolete option (to be deleted in the future)')
180 p.add_option('--fail-fast',
181 dest='fail_fast', action='store_true',
182 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700183 p.add_option('--force-sync',
184 dest='force_sync', action='store_true',
185 help="overwrite an existing git directory if it needs to "
186 "point to a different object directory. WARNING: this "
187 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500188 p.add_option('--force-remove-dirty',
189 dest='force_remove_dirty', action='store_true',
190 help="force remove projects with uncommitted modifications if "
191 "projects no longer exist in the manifest. "
192 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900193 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700194 dest='local_only', action='store_true',
195 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900196 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100197 dest='mp_update', action='store_false', default='true',
198 help='use the existing manifest checkout as-is. '
199 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900200 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700201 dest='network_only', action='store_true',
202 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900203 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700204 dest='detach_head', action='store_true',
205 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900206 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700207 dest='current_branch_only', action='store_true',
208 help='fetch only current branch from server')
Mike Frysinger521d01b2020-02-17 01:51:49 -0500209 p.add_option('-v', '--verbose',
210 dest='output_mode', action='store_true',
211 help='show all sync output')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900212 p.add_option('-q', '--quiet',
Mike Frysinger521d01b2020-02-17 01:51:49 -0500213 dest='output_mode', action='store_false',
214 help='only show errors')
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500215 p.add_option('-m', '--manifest-name',
216 dest='manifest_name',
217 help='temporary manifest to use for this sync', metavar='NAME.xml')
Xin Lid79a4bc2020-05-20 16:03:45 -0700218 p.add_option('--clone-bundle', action='store_true',
219 help='enable use of /clone.bundle on HTTP/HTTPS')
220 p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700221 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800222 p.add_option('-u', '--manifest-server-username', action='store',
223 dest='manifest_server_username',
224 help='username to authenticate with the manifest server')
225 p.add_option('-p', '--manifest-server-password', action='store',
226 dest='manifest_server_password',
227 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800228 p.add_option('--fetch-submodules',
229 dest='fetch_submodules', action='store_true',
230 help='fetch submodules from server')
Raman Tenneti6a872c92021-01-14 19:17:50 -0800231 p.add_option('--use-superproject', action='store_true',
232 help='use the manifest superproject to sync projects')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700233 p.add_option('--no-tags',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500234 dest='tags', default=True, action='store_false',
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700235 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900236 p.add_option('--optimized-fetch',
237 dest='optimized_fetch', action='store_true',
238 help='only fetch projects fixed to sha1 if revision does not exist locally')
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600239 p.add_option('--retry-fetches',
240 default=0, action='store', type='int',
241 help='number of times to retry fetches on transient errors')
David Pursehouse74cfd272015-10-14 10:50:15 +0900242 p.add_option('--prune', dest='prune', action='store_true',
243 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700244 if show_smart:
245 p.add_option('-s', '--smart-sync',
246 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900247 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200248 p.add_option('-t', '--smart-tag',
249 dest='smart_tag', action='store',
250 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700251
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700252 g = p.add_option_group('repo Version options')
253 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500254 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700255 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700256 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800257 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700258 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700259
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800260 def _GetBranch(self):
261 """Returns the branch name for getting the approved manifest."""
262 p = self.manifest.manifestProject
263 b = p.GetBranch(p.CurrentBranch)
264 branch = b.merge
265 if branch.startswith(R_HEADS):
266 branch = branch[len(R_HEADS):]
267 return branch
268
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700269 def _UseSuperproject(self, opt):
270 """Returns True if use-superproject option is enabled"""
271 return (opt.use_superproject or
272 self.manifest.manifestProject.config.GetBoolean(
273 'repo.superproject'))
274
275 def _GetCurrentBranchOnly(self, opt):
276 """Returns True if current-branch or use-superproject options are enabled."""
277 return opt.current_branch_only or self._UseSuperproject(opt)
278
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800279 def _UpdateProjectsRevisionId(self, opt, args):
280 """Update revisionId of every project with the SHA from superproject.
281
282 This function updates each project's revisionId with SHA from superproject.
283 It writes the updated manifest into a file and reloads the manifest from it.
284
285 Args:
286 opt: Program options returned from optparse. See _Options().
287 args: Arguments to pass to GetProjects. See the GetProjects
288 docstring for details.
289
290 Returns:
291 Returns path to the overriding manifest file.
292 """
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800293 superproject = git_superproject.Superproject(self.manifest,
Raman Tennetief99ec02021-03-04 10:29:40 -0800294 self.repodir,
295 quiet=opt.quiet)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800296 all_projects = self.GetProjects(args,
297 missing_ok=True,
298 submodules_ok=opt.fetch_submodules)
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800299 manifest_path = superproject.UpdateProjectsRevisionId(all_projects)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800300 if not manifest_path:
301 print('error: Update of revsionId from superproject has failed',
302 file=sys.stderr)
303 sys.exit(1)
304 self._ReloadManifest(manifest_path)
305 return manifest_path
306
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500307 def _FetchProjectList(self, opt, projects):
308 """Main function of the fetch worker.
309
310 The projects we're given share the same underlying git object store, so we
311 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800312
David James8d201162013-10-11 17:03:19 -0700313 Delegates most of the work to _FetchHelper.
314
315 Args:
316 opt: Program options returned from optparse. See _Options().
317 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700318 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500319 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700320
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500321 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700322 """Fetch git objects for a single project.
323
David Pursehousec1b86a22012-11-14 11:36:51 +0900324 Args:
325 opt: Program options returned from optparse. See _Options().
326 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700327
328 Returns:
329 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900330 """
David Rileye0684ad2017-04-05 00:02:59 -0700331 start = time.time()
332 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500333 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900334 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500335 success = project.Sync_NetworkHalf(
336 quiet=opt.quiet,
337 verbose=opt.verbose,
338 output_redir=buf,
339 current_branch_only=self._GetCurrentBranchOnly(opt),
340 force_sync=opt.force_sync,
341 clone_bundle=opt.clone_bundle,
342 tags=opt.tags, archive=self.manifest.IsArchive,
343 optimized_fetch=opt.optimized_fetch,
344 retry_fetches=opt.retry_fetches,
345 prune=opt.prune,
346 clone_filter=self.manifest.CloneFilter)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700347
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500348 output = buf.getvalue()
349 if opt.verbose and output:
350 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700351
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500352 if not success:
353 print('error: Cannot fetch %s from %s'
354 % (project.name, project.remote.url),
355 file=sys.stderr)
356 except Exception as e:
357 print('error: Cannot fetch %s (%s: %s)'
358 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
359 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500360
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500361 finish = time.time()
362 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700363
Mike Frysinger5a033082019-09-23 19:21:20 -0400364 def _Fetch(self, projects, opt, err_event):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500365 ret = True
366
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700367 fetched = set()
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500368 pm = Progress('Fetching', len(projects), delay=False)
Roy Lee18afd7f2010-05-09 04:32:08 +0800369
David James89ece422014-01-09 18:51:58 -0800370 objdir_project_map = dict()
371 for project in projects:
372 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500373 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700374
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500375 def _ProcessResults(results_sets):
376 ret = True
377 for results in results_sets:
378 for (success, project, start, finish) in results:
379 self._fetch_times.Set(project, finish - start)
380 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
381 start, finish, success)
382 # Check for any errors before running any more tasks.
383 # ...we'll let existing jobs finish, though.
384 if not success:
385 ret = False
386 else:
387 fetched.add(project.gitdir)
388 pm.update(msg=project.name)
389 if not ret and opt.fail_fast:
390 break
391 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700392
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500393 # NB: Multiprocessing is heavy, so don't spin it up for one job.
394 if len(projects_list) == 1 or opt.jobs == 1:
395 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
396 ret = False
397 else:
398 # Favor throughput over responsiveness when quiet. It seems that imap()
399 # will yield results in batches relative to chunksize, so even as the
400 # children finish a sync, we won't see the result until one child finishes
401 # ~chunksize jobs. When using a large --jobs with large chunksize, this
402 # can be jarring as there will be a large initial delay where repo looks
403 # like it isn't doing anything and sits at 0%, but then suddenly completes
404 # a lot of jobs all at once. Since this code is more network bound, we
405 # can accept a bit more CPU overhead with a smaller chunksize so that the
406 # user sees more immediate & continuous feedback.
407 if opt.quiet:
408 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800409 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500410 pm.update(inc=0, msg='warming up')
411 chunksize = 4
412 with multiprocessing.Pool(opt.jobs) as pool:
413 results = pool.imap_unordered(
414 functools.partial(self._FetchProjectList, opt),
415 projects_list,
416 chunksize=chunksize)
417 if not _ProcessResults(results):
418 ret = False
419 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800420
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700421 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700422 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700423
Julien Campergue335f5ef2013-10-16 11:02:35 +0200424 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400425 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200426
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500427 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700428
Mike Frysingerebf04a42021-02-23 20:48:04 -0500429 def _CheckoutOne(self, opt, project):
Xin Li745be2e2019-06-03 11:24:30 -0700430 """Checkout work tree for one project
431
432 Args:
433 opt: Program options returned from optparse. See _Options().
434 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700435
436 Returns:
437 Whether the fetch was successful.
438 """
Xin Li745be2e2019-06-03 11:24:30 -0700439 start = time.time()
440 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
441 detach_head=opt.detach_head)
442 success = False
443 try:
Mike Frysingerebf04a42021-02-23 20:48:04 -0500444 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
445 success = syncbuf.Finish()
446 except Exception as e:
447 print('error: Cannot checkout %s: %s: %s' %
448 (project.name, type(e).__name__, str(e)),
449 file=sys.stderr)
450 raise
Xin Li745be2e2019-06-03 11:24:30 -0700451
Mike Frysingerebf04a42021-02-23 20:48:04 -0500452 if not success:
453 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
454 finish = time.time()
455 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700456
Mike Frysingerebf04a42021-02-23 20:48:04 -0500457 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700458 """Checkout projects listed in all_projects
459
460 Args:
461 all_projects: List of all projects that should be checked out.
462 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500463 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700464 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500465 ret = True
Xin Li745be2e2019-06-03 11:24:30 -0700466
Mike Frysingerebf04a42021-02-23 20:48:04 -0500467 # Only checkout projects with worktrees.
468 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700469
Mike Frysingerfbb95a42021-02-23 17:34:35 -0500470 pm = Progress('Checking out', len(all_projects))
Xin Li745be2e2019-06-03 11:24:30 -0700471
Mike Frysingerebf04a42021-02-23 20:48:04 -0500472 def _ProcessResults(results):
473 for (success, project, start, finish) in results:
474 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
475 start, finish, success)
476 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500477 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500478 if not success:
479 err_results.append(project.relpath)
480 if opt.fail_fast:
481 return False
482 pm.update(msg=project.name)
483 return True
Xin Li745be2e2019-06-03 11:24:30 -0700484
Mike Frysingerebf04a42021-02-23 20:48:04 -0500485 # NB: Multiprocessing is heavy, so don't spin it up for one job.
486 if len(all_projects) == 1 or opt.jobs == 1:
487 if not _ProcessResults(self._CheckoutOne(opt, x) for x in all_projects):
488 ret = False
489 else:
490 with multiprocessing.Pool(opt.jobs) as pool:
491 results = pool.imap_unordered(
492 functools.partial(self._CheckoutOne, opt),
493 all_projects,
494 chunksize=WORKER_BATCH_SIZE)
495 if not _ProcessResults(results):
496 ret = False
497 pool.close()
Xin Li745be2e2019-06-03 11:24:30 -0700498
499 pm.end()
Xin Li745be2e2019-06-03 11:24:30 -0700500
Mike Frysinger511a0e52021-03-12 20:03:51 -0500501 return ret and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500502
Mike Frysinger5a033082019-09-23 19:21:20 -0400503 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger65af2602021-04-08 22:47:44 -0400504 pm = Progress('Garbage collecting', len(projects), delay=False)
505 pm.update(inc=0, msg='prescan')
506
Gabe Black2ff30292014-10-09 17:54:35 -0700507 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700508 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500509 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500510 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900511 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100512 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400513 #pm.update(inc=0, msg='Shared project found')
514 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100515 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500516 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500517 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500518 else:
519 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400520 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500521 'versions of git; please upgrade to git-2.7.0+.'
522 % (project.relpath,),
523 file=sys.stderr)
524 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700525 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700526
Mike Frysinger65af2602021-04-08 22:47:44 -0400527 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
528
529 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700530 jobs = min(self.jobs, cpu_count)
531
532 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700533 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400534 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700535 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400536 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700537 return
538
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400539 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700540
541 threads = set()
542 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700543
David James8d201162013-10-11 17:03:19 -0700544 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400545 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700546 try:
547 try:
David James8d201162013-10-11 17:03:19 -0700548 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700549 except GitError:
550 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900551 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700552 err_event.set()
553 raise
554 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400555 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700556 sem.release()
557
Gabe Black2ff30292014-10-09 17:54:35 -0700558 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500559 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700560 break
561 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700562 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700563 t.daemon = True
564 threads.add(t)
565 t.start()
566
567 for t in threads:
568 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400569 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700570
Tim Kilbourn07669002013-03-08 15:02:49 -0800571 def _ReloadManifest(self, manifest_name=None):
572 if manifest_name:
573 # Override calls _Unload already
574 self.manifest.Override(manifest_name)
575 else:
576 self.manifest._Unload()
577
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500578 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700579 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700580 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700581 if project.relpath:
582 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700583 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500584 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700585 old_project_paths = []
586
587 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500588 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700589 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800590 # In reversed order, so subfolders are deleted before parent folder.
591 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700592 if not path:
593 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700594 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900595 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700596 gitdir = os.path.join(self.manifest.topdir, path, '.git')
597 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900598 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900599 manifest=self.manifest,
600 name=path,
601 remote=RemoteSpec('origin'),
602 gitdir=gitdir,
603 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500604 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900605 worktree=os.path.join(self.manifest.topdir, path),
606 relpath=path,
607 revisionExpr='HEAD',
608 revisionId=None,
609 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500610 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900611 quiet=opt.quiet,
612 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400613 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700614
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700615 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500616 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700617 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700618 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700619 return 0
620
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400621 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
622 if not self.manifest.manifest_server:
623 print('error: cannot smart sync: no manifest server defined in '
624 'manifest', file=sys.stderr)
625 sys.exit(1)
626
627 manifest_server = self.manifest.manifest_server
628 if not opt.quiet:
629 print('Using manifest server %s' % manifest_server)
630
David Pursehouseeeff3532020-02-12 11:24:10 +0900631 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400632 username = None
633 password = None
634 if opt.manifest_server_username and opt.manifest_server_password:
635 username = opt.manifest_server_username
636 password = opt.manifest_server_password
637 else:
638 try:
639 info = netrc.netrc()
640 except IOError:
641 # .netrc file does not exist or could not be opened
642 pass
643 else:
644 try:
645 parse_result = urllib.parse.urlparse(manifest_server)
646 if parse_result.hostname:
647 auth = info.authenticators(parse_result.hostname)
648 if auth:
649 username, _account, password = auth
650 else:
651 print('No credentials found for %s in .netrc'
652 % parse_result.hostname, file=sys.stderr)
653 except netrc.NetrcParseError as e:
654 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
655
656 if (username and password):
657 manifest_server = manifest_server.replace('://', '://%s:%s@' %
658 (username, password),
659 1)
660
661 transport = PersistentTransport(manifest_server)
662 if manifest_server.startswith('persistent-'):
663 manifest_server = manifest_server[len('persistent-'):]
664
665 try:
666 server = xmlrpc.client.Server(manifest_server, transport=transport)
667 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800668 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400669
Mike Frysinger56ce3462019-12-04 19:30:48 -0500670 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500671 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400672 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500673 elif ('TARGET_PRODUCT' in os.environ and
674 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500675 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
676 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400677 [success, manifest_str] = server.GetApprovedManifest(branch, target)
678 else:
679 [success, manifest_str] = server.GetApprovedManifest(branch)
680 else:
681 assert(opt.smart_tag)
682 [success, manifest_str] = server.GetManifest(opt.smart_tag)
683
684 if success:
685 manifest_name = os.path.basename(smart_sync_manifest_path)
686 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500687 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400688 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400689 except IOError as e:
690 print('error: cannot write manifest to %s:\n%s'
691 % (smart_sync_manifest_path, e),
692 file=sys.stderr)
693 sys.exit(1)
694 self._ReloadManifest(manifest_name)
695 else:
696 print('error: manifest server RPC call failed: %s' %
697 manifest_str, file=sys.stderr)
698 sys.exit(1)
699 except (socket.error, IOError, xmlrpc.client.Fault) as e:
700 print('error: cannot connect to manifest server %s:\n%s'
701 % (self.manifest.manifest_server, e), file=sys.stderr)
702 sys.exit(1)
703 except xmlrpc.client.ProtocolError as e:
704 print('error: cannot connect to manifest server %s:\n%d %s'
705 % (self.manifest.manifest_server, e.errcode, e.errmsg),
706 file=sys.stderr)
707 sys.exit(1)
708
709 return manifest_name
710
Mike Frysingerfb527e32019-08-27 02:34:32 -0400711 def _UpdateManifestProject(self, opt, mp, manifest_name):
712 """Fetch & update the local manifest project."""
713 if not opt.local_only:
714 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500715 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700716 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200717 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500718 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400719 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600720 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400721 submodules=self.manifest.HasSubmodules,
722 clone_filter=self.manifest.CloneFilter)
723 finish = time.time()
724 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
725 start, finish, success)
726
727 if mp.HasChanges:
728 syncbuf = SyncBuffer(mp.config)
729 start = time.time()
730 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
731 clean = syncbuf.Finish()
732 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
733 start, time.time(), clean)
734 if not clean:
735 sys.exit(1)
736 self._ReloadManifest(opt.manifest_name)
737 if opt.jobs is None:
738 self.jobs = self.manifest.default.sync_j
739
Mike Frysingerae6cb082019-08-27 01:10:59 -0400740 def ValidateOptions(self, opt, args):
741 if opt.force_broken:
742 print('warning: -f/--force-broken is now the default behavior, and the '
743 'options are deprecated', file=sys.stderr)
744 if opt.network_only and opt.detach_head:
745 self.OptionParser.error('cannot combine -n and -d')
746 if opt.network_only and opt.local_only:
747 self.OptionParser.error('cannot combine -n and -l')
748 if opt.manifest_name and opt.smart_sync:
749 self.OptionParser.error('cannot combine -m and -s')
750 if opt.manifest_name and opt.smart_tag:
751 self.OptionParser.error('cannot combine -m and -t')
752 if opt.manifest_server_username or opt.manifest_server_password:
753 if not (opt.smart_sync or opt.smart_tag):
754 self.OptionParser.error('-u and -p may only be combined with -s or -t')
755 if None in [opt.manifest_server_username, opt.manifest_server_password]:
756 self.OptionParser.error('both -u and -p must be given')
757
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700758 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800759 if opt.jobs:
760 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700761 if self.jobs > 1:
762 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400763 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700764
Mike Frysinger521d01b2020-02-17 01:51:49 -0500765 opt.quiet = opt.output_mode is False
766 opt.verbose = opt.output_mode is True
767
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500768 if opt.manifest_name:
769 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700770
Chirayu Desaia892b102013-06-11 14:18:46 +0530771 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900772 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900773 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530774
Xin Lid79a4bc2020-05-20 16:03:45 -0700775 if opt.clone_bundle is None:
776 opt.clone_bundle = self.manifest.CloneBundle
777
Victor Boivie08c880d2011-04-19 10:32:52 +0200778 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400779 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
780 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900781 if os.path.isfile(smart_sync_manifest_path):
782 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800783 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900784 except OSError as e:
785 print('error: failed to remove existing smart sync override manifest: %s' %
786 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700787
Mike Frysinger5a033082019-09-23 19:21:20 -0400788 err_event = _threading.Event()
789
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700790 rp = self.manifest.repoProject
791 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500792 cb = rp.CurrentBranch
793 if cb:
794 base = rp.GetBranch(cb).merge
795 if not base or not base.startswith('refs/heads/'):
796 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400797 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500798 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700799
800 mp = self.manifest.manifestProject
801 mp.PreSync()
802
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800803 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700804 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800805
Fredrik de Grootcc960972019-11-22 09:04:31 +0100806 if not opt.mp_update:
807 print('Skipping update of local manifest project.')
808 else:
809 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700810
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700811 if self._UseSuperproject(opt):
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800812 manifest_name = self._UpdateProjectsRevisionId(opt, args)
813
Simran Basib9a1b732015-08-20 12:19:28 -0700814 if self.gitc_manifest:
815 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700816 missing_ok=True)
817 gitc_projects = []
818 opened_projects = []
819 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700820 if project.relpath in self.gitc_manifest.paths and \
821 self.gitc_manifest.paths[project.relpath].old_revision:
822 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700823 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700824 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700825
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700826 if not args:
827 gitc_projects = None
828
829 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700830 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700831 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
832 if manifest_name:
833 manifest.Override(manifest_name)
834 else:
835 manifest.Override(self.manifest.manifestFile)
836 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
837 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700838 gitc_projects)
839 print('GITC client successfully synced.')
840
841 # The opened projects need to be synced as normal, therefore we
842 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700843 # TODO: make this more reliable -- if there's a project name/path overlap,
844 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900845 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
846 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700847 if not args:
848 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800849 all_projects = self.GetProjects(args,
850 missing_ok=True,
851 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700852
Mike Frysinger5a033082019-09-23 19:21:20 -0400853 err_network_sync = False
854 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -0400855
Dave Borowitz67700e92012-10-23 15:00:54 -0700856 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700857 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700858 to_fetch = []
859 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700860 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700861 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900862 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700863 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700864
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500865 success, fetched = self._Fetch(to_fetch, opt, err_event)
866 if not success:
867 err_event.set()
Mike Frysinger5a033082019-09-23 19:21:20 -0400868
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500869 _PostRepoFetch(rp, opt.repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700870 if opt.network_only:
871 # bail out now; the rest touches the working tree
Mike Frysingerbe24a542021-02-23 03:24:12 -0500872 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400873 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
874 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700875 return
876
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800877 # Iteratively fetch missing and/or nested unregistered submodules
878 previously_missing_set = set()
879 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100880 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800881 all_projects = self.GetProjects(args,
882 missing_ok=True,
883 submodules_ok=opt.fetch_submodules)
884 missing = []
885 for project in all_projects:
886 if project.gitdir not in fetched:
887 missing.append(project)
888 if not missing:
889 break
890 # Stop us from non-stopped fetching actually-missing repos: If set of
891 # missing repos has not been changed from last fetch, we break.
892 missing_set = set(p.name for p in missing)
893 if previously_missing_set == missing_set:
894 break
895 previously_missing_set = missing_set
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500896 success, new_fetched = self._Fetch(to_fetch, opt, err_event)
897 if not success:
898 err_event.set()
899 fetched.update(new_fetched)
Mike Frysinger5a033082019-09-23 19:21:20 -0400900
901 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500902 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400903 err_network_sync = True
904 if opt.fail_fast:
905 print('\nerror: Exited sync due to fetch errors.\n'
906 'Local checkouts *not* updated. Resolve network issues & '
907 'retry.\n'
908 '`repo sync -l` will update some local checkouts.',
909 file=sys.stderr)
910 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800911
Julien Campergue335f5ef2013-10-16 11:02:35 +0200912 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700913 # bail out now, we have no working tree
914 return
915
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500916 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -0400917 err_event.set()
918 err_update_projects = True
919 if opt.fail_fast:
920 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
921 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700922
Mike Frysinger5a033082019-09-23 19:21:20 -0400923 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -0500924 # NB: We don't exit here because this is the last step.
925 err_checkout = not self._Checkout(all_projects, opt, err_results)
926 if err_checkout:
927 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700928
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700929 # If there's a notice that's supposed to print at the end of the sync, print
930 # it now...
931 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700932 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700933
Mike Frysinger5a033082019-09-23 19:21:20 -0400934 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500935 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400936 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
937 if err_network_sync:
938 print('error: Downloading network changes failed.', file=sys.stderr)
939 if err_update_projects:
940 print('error: Updating local project lists failed.', file=sys.stderr)
941 if err_checkout:
942 print('error: Checking out local projects failed.', file=sys.stderr)
943 if err_results:
944 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
945 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
946 file=sys.stderr)
947 sys.exit(1)
948
Mike Frysingere19d9e12020-02-12 11:23:32 -0500949 if not opt.quiet:
950 print('repo sync has finished successfully.')
951
David Pursehouse819827a2020-02-12 15:20:19 +0900952
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700953def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -0800954 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -0700955 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700956 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -0800957 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700958 if project.Exists:
959 project.PostRepoUpgrade()
960
David Pursehouse819827a2020-02-12 15:20:19 +0900961
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500962def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700963 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700964 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -0400965 wrapper = Wrapper()
966 try:
967 rev = rp.bare_git.describe(rp.GetRevisionId())
968 except GitError:
969 rev = None
970 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
971 # See if we're held back due to missing signed tag.
972 current_revid = rp.bare_git.rev_parse('HEAD')
973 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
974 if current_revid != new_revid:
975 # We want to switch to the new rev, but also not trash any uncommitted
976 # changes. This helps with local testing/hacking.
977 # If a local change has been made, we will throw that away.
978 # We also have to make sure this will switch to an older commit if that's
979 # the latest tag in order to support release rollback.
980 try:
981 rp.work_git.reset('--keep', new_rev)
982 except GitError as e:
983 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -0700984 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700985 raise RepoChangedException(['--repo-upgraded'])
986 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700987 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700988 else:
989 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700990 print('repo version %s is current' % rp.work_git.describe(HEAD),
991 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700992
David Pursehouse819827a2020-02-12 15:20:19 +0900993
Dave Borowitz67700e92012-10-23 15:00:54 -0700994class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -0700995 _ALPHA = 0.5
996
Dave Borowitz67700e92012-10-23 15:00:54 -0700997 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +0100998 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -0700999 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001000 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001001
1002 def Get(self, project):
1003 self._Load()
1004 return self._times.get(project.name, _ONE_DAY_S)
1005
1006 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001007 self._Load()
1008 name = project.name
1009 old = self._times.get(name, t)
1010 self._seen.add(name)
1011 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001012 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001013
1014 def _Load(self):
1015 if self._times is None:
1016 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001017 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001018 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001019 except (IOError, ValueError):
1020 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001021 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001022 except OSError:
1023 pass
1024 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001025
1026 def Save(self):
1027 if self._times is None:
1028 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001029
1030 to_delete = []
1031 for name in self._times:
1032 if name not in self._seen:
1033 to_delete.append(name)
1034 for name in to_delete:
1035 del self._times[name]
1036
Dave Borowitz67700e92012-10-23 15:00:54 -07001037 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001038 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001039 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001040 except (IOError, TypeError):
1041 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001042 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001043 except OSError:
1044 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001045
1046# This is a replacement for xmlrpc.client.Transport using urllib2
1047# and supporting persistent-http[s]. It cannot change hosts from
1048# request to request like the normal transport, the real url
1049# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001050
1051
Dan Willemsen0745bb22015-08-17 13:41:45 -07001052class PersistentTransport(xmlrpc.client.Transport):
1053 def __init__(self, orig_host):
1054 self.orig_host = orig_host
1055
1056 def request(self, host, handler, request_body, verbose=False):
1057 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1058 # Python doesn't understand cookies with the #HttpOnly_ prefix
1059 # Since we're only using them for HTTP, copy the file temporarily,
1060 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001061 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001062 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001063 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001064 try:
1065 with open(cookiefile) as f:
1066 for line in f:
1067 if line.startswith("#HttpOnly_"):
1068 line = line[len("#HttpOnly_"):]
1069 tmpcookiefile.write(line)
1070 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001071
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001072 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001073 try:
1074 cookiejar.load()
1075 except cookielib.LoadError:
1076 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001077 finally:
1078 tmpcookiefile.close()
1079 else:
1080 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001081
1082 proxyhandler = urllib.request.ProxyHandler
1083 if proxy:
1084 proxyhandler = urllib.request.ProxyHandler({
1085 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001086 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001087
1088 opener = urllib.request.build_opener(
1089 urllib.request.HTTPCookieProcessor(cookiejar),
1090 proxyhandler)
1091
1092 url = urllib.parse.urljoin(self.orig_host, handler)
1093 parse_results = urllib.parse.urlparse(url)
1094
1095 scheme = parse_results.scheme
1096 if scheme == 'persistent-http':
1097 scheme = 'http'
1098 if scheme == 'persistent-https':
1099 # If we're proxying through persistent-https, use http. The
1100 # proxy itself will do the https.
1101 if proxy:
1102 scheme = 'http'
1103 else:
1104 scheme = 'https'
1105
1106 # Parse out any authentication information using the base class
1107 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1108
1109 url = urllib.parse.urlunparse((
1110 scheme,
1111 host,
1112 parse_results.path,
1113 parse_results.params,
1114 parse_results.query,
1115 parse_results.fragment))
1116
1117 request = urllib.request.Request(url, request_body)
1118 if extra_headers is not None:
1119 for (name, header) in extra_headers:
1120 request.add_header(name, header)
1121 request.add_header('Content-Type', 'text/xml')
1122 try:
1123 response = opener.open(request)
1124 except urllib.error.HTTPError as e:
1125 if e.code == 501:
1126 # We may have been redirected through a login process
1127 # but our POST turned into a GET. Retry.
1128 response = opener.open(request)
1129 else:
1130 raise
1131
1132 p, u = xmlrpc.client.getparser()
1133 while 1:
1134 data = response.read(1024)
1135 if not data:
1136 break
1137 p.feed(data)
1138 p.close()
1139 return u.close()
1140
1141 def close(self):
1142 pass