blob: aafec1d26aa2a9850327d59c76bd3b9c72daa0a5 [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 Frysingerd41eed02021-04-20 23:21:29 -040054from 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
Mike Frysinger9180a072021-04-13 14:57:40 -0400170 def _CommonOptions(self, p):
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
Mike Frysinger9180a072021-04-13 14:57:40 -0400175 super()._CommonOptions(p)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700176
Mike Frysinger9180a072021-04-13 14:57:40 -0400177 def _Options(self, p, show_smart=True):
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400178 p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
179 help='number of network jobs to run in parallel (defaults to --jobs)')
180 p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
181 help='number of local checkout jobs to run in parallel (defaults to --jobs)')
182
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500183 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200184 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400185 help='obsolete option (to be deleted in the future)')
186 p.add_option('--fail-fast',
187 dest='fail_fast', action='store_true',
188 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700189 p.add_option('--force-sync',
190 dest='force_sync', action='store_true',
191 help="overwrite an existing git directory if it needs to "
192 "point to a different object directory. WARNING: this "
193 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500194 p.add_option('--force-remove-dirty',
195 dest='force_remove_dirty', action='store_true',
196 help="force remove projects with uncommitted modifications if "
197 "projects no longer exist in the manifest. "
198 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900199 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700200 dest='local_only', action='store_true',
201 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900202 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100203 dest='mp_update', action='store_false', default='true',
204 help='use the existing manifest checkout as-is. '
205 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900206 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700207 dest='network_only', action='store_true',
208 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900209 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700210 dest='detach_head', action='store_true',
211 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900212 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700213 dest='current_branch_only', action='store_true',
214 help='fetch only current branch from server')
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,
Raman Tennetif32f2432021-04-12 20:57:25 -0700346 clone_filter=self.manifest.CloneFilter,
347 partial_clone_exclude=self.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700348
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500349 output = buf.getvalue()
350 if opt.verbose and output:
351 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700352
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500353 if not success:
354 print('error: Cannot fetch %s from %s'
355 % (project.name, project.remote.url),
356 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700357 except GitError as e:
358 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500359 except Exception as e:
360 print('error: Cannot fetch %s (%s: %s)'
361 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
362 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500363
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500364 finish = time.time()
365 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700366
Mike Frysinger5a033082019-09-23 19:21:20 -0400367 def _Fetch(self, projects, opt, err_event):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500368 ret = True
369
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400370 jobs = opt.jobs_network if opt.jobs_network else self.jobs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700371 fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400372 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800373
David James89ece422014-01-09 18:51:58 -0800374 objdir_project_map = dict()
375 for project in projects:
376 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500377 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700378
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500379 def _ProcessResults(results_sets):
380 ret = True
381 for results in results_sets:
382 for (success, project, start, finish) in results:
383 self._fetch_times.Set(project, finish - start)
384 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
385 start, finish, success)
386 # Check for any errors before running any more tasks.
387 # ...we'll let existing jobs finish, though.
388 if not success:
389 ret = False
390 else:
391 fetched.add(project.gitdir)
392 pm.update(msg=project.name)
393 if not ret and opt.fail_fast:
394 break
395 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700396
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500397 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400398 if len(projects_list) == 1 or jobs == 1:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500399 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
400 ret = False
401 else:
402 # Favor throughput over responsiveness when quiet. It seems that imap()
403 # will yield results in batches relative to chunksize, so even as the
404 # children finish a sync, we won't see the result until one child finishes
405 # ~chunksize jobs. When using a large --jobs with large chunksize, this
406 # can be jarring as there will be a large initial delay where repo looks
407 # like it isn't doing anything and sits at 0%, but then suddenly completes
408 # a lot of jobs all at once. Since this code is more network bound, we
409 # can accept a bit more CPU overhead with a smaller chunksize so that the
410 # user sees more immediate & continuous feedback.
411 if opt.quiet:
412 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800413 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500414 pm.update(inc=0, msg='warming up')
415 chunksize = 4
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400416 with multiprocessing.Pool(jobs) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500417 results = pool.imap_unordered(
418 functools.partial(self._FetchProjectList, opt),
419 projects_list,
420 chunksize=chunksize)
421 if not _ProcessResults(results):
422 ret = False
423 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800424
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700425 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700426 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700427
Julien Campergue335f5ef2013-10-16 11:02:35 +0200428 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400429 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200430
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500431 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700432
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500433 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700434 """Checkout work tree for one project
435
436 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500437 detach_head: Whether to leave a detached HEAD.
438 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700439 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700440
441 Returns:
442 Whether the fetch was successful.
443 """
Xin Li745be2e2019-06-03 11:24:30 -0700444 start = time.time()
445 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500446 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700447 success = False
448 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500449 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500450 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700451 except GitError as e:
452 print('error.GitError: Cannot checkout %s: %s' %
453 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500454 except Exception as e:
455 print('error: Cannot checkout %s: %s: %s' %
456 (project.name, type(e).__name__, str(e)),
457 file=sys.stderr)
458 raise
Xin Li745be2e2019-06-03 11:24:30 -0700459
Mike Frysingerebf04a42021-02-23 20:48:04 -0500460 if not success:
461 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
462 finish = time.time()
463 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700464
Mike Frysingerebf04a42021-02-23 20:48:04 -0500465 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700466 """Checkout projects listed in all_projects
467
468 Args:
469 all_projects: List of all projects that should be checked out.
470 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500471 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700472 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500473 # Only checkout projects with worktrees.
474 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700475
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500476 def _ProcessResults(pool, pm, results):
477 ret = True
Mike Frysingerebf04a42021-02-23 20:48:04 -0500478 for (success, project, start, finish) in results:
479 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
480 start, finish, success)
481 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500482 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500483 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500484 ret = False
Mike Frysingerebf04a42021-02-23 20:48:04 -0500485 err_results.append(project.relpath)
486 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500487 if pool:
488 pool.close()
489 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500490 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500491 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700492
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500493 return self.ExecuteInParallel(
494 opt.jobs_checkout if opt.jobs_checkout else self.jobs,
495 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
496 all_projects,
497 callback=_ProcessResults,
498 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500499
Mike Frysinger5a033082019-09-23 19:21:20 -0400500 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger151701e2021-04-13 15:07:21 -0400501 pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400502 pm.update(inc=0, msg='prescan')
503
Gabe Black2ff30292014-10-09 17:54:35 -0700504 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700505 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500506 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500507 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900508 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100509 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400510 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100511 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500512 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500513 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500514 else:
515 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400516 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500517 'versions of git; please upgrade to git-2.7.0+.'
518 % (project.relpath,),
519 file=sys.stderr)
520 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700521 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700522
Mike Frysinger65af2602021-04-08 22:47:44 -0400523 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
524
525 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700526 jobs = min(self.jobs, cpu_count)
527
528 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700529 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400530 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700531 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400532 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700533 return
534
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400535 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700536
537 threads = set()
538 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700539
David James8d201162013-10-11 17:03:19 -0700540 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400541 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700542 try:
543 try:
David James8d201162013-10-11 17:03:19 -0700544 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700545 except GitError:
546 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900547 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700548 err_event.set()
549 raise
550 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400551 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700552 sem.release()
553
Gabe Black2ff30292014-10-09 17:54:35 -0700554 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500555 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700556 break
557 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700558 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700559 t.daemon = True
560 threads.add(t)
561 t.start()
562
563 for t in threads:
564 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400565 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700566
Tim Kilbourn07669002013-03-08 15:02:49 -0800567 def _ReloadManifest(self, manifest_name=None):
568 if manifest_name:
569 # Override calls _Unload already
570 self.manifest.Override(manifest_name)
571 else:
572 self.manifest._Unload()
573
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500574 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700575 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700576 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700577 if project.relpath:
578 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700579 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500580 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700581 old_project_paths = []
582
583 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500584 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700585 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800586 # In reversed order, so subfolders are deleted before parent folder.
587 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700588 if not path:
589 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700590 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900591 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700592 gitdir = os.path.join(self.manifest.topdir, path, '.git')
593 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900594 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900595 manifest=self.manifest,
596 name=path,
597 remote=RemoteSpec('origin'),
598 gitdir=gitdir,
599 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500600 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900601 worktree=os.path.join(self.manifest.topdir, path),
602 relpath=path,
603 revisionExpr='HEAD',
604 revisionId=None,
605 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500606 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900607 quiet=opt.quiet,
608 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400609 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700610
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700611 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500612 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700613 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700614 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700615 return 0
616
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400617 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
618 if not self.manifest.manifest_server:
619 print('error: cannot smart sync: no manifest server defined in '
620 'manifest', file=sys.stderr)
621 sys.exit(1)
622
623 manifest_server = self.manifest.manifest_server
624 if not opt.quiet:
625 print('Using manifest server %s' % manifest_server)
626
David Pursehouseeeff3532020-02-12 11:24:10 +0900627 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400628 username = None
629 password = None
630 if opt.manifest_server_username and opt.manifest_server_password:
631 username = opt.manifest_server_username
632 password = opt.manifest_server_password
633 else:
634 try:
635 info = netrc.netrc()
636 except IOError:
637 # .netrc file does not exist or could not be opened
638 pass
639 else:
640 try:
641 parse_result = urllib.parse.urlparse(manifest_server)
642 if parse_result.hostname:
643 auth = info.authenticators(parse_result.hostname)
644 if auth:
645 username, _account, password = auth
646 else:
647 print('No credentials found for %s in .netrc'
648 % parse_result.hostname, file=sys.stderr)
649 except netrc.NetrcParseError as e:
650 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
651
652 if (username and password):
653 manifest_server = manifest_server.replace('://', '://%s:%s@' %
654 (username, password),
655 1)
656
657 transport = PersistentTransport(manifest_server)
658 if manifest_server.startswith('persistent-'):
659 manifest_server = manifest_server[len('persistent-'):]
660
661 try:
662 server = xmlrpc.client.Server(manifest_server, transport=transport)
663 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800664 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400665
Mike Frysinger56ce3462019-12-04 19:30:48 -0500666 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500667 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400668 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500669 elif ('TARGET_PRODUCT' in os.environ and
670 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500671 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
672 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400673 [success, manifest_str] = server.GetApprovedManifest(branch, target)
674 else:
675 [success, manifest_str] = server.GetApprovedManifest(branch)
676 else:
677 assert(opt.smart_tag)
678 [success, manifest_str] = server.GetManifest(opt.smart_tag)
679
680 if success:
681 manifest_name = os.path.basename(smart_sync_manifest_path)
682 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500683 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400684 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400685 except IOError as e:
686 print('error: cannot write manifest to %s:\n%s'
687 % (smart_sync_manifest_path, e),
688 file=sys.stderr)
689 sys.exit(1)
690 self._ReloadManifest(manifest_name)
691 else:
692 print('error: manifest server RPC call failed: %s' %
693 manifest_str, file=sys.stderr)
694 sys.exit(1)
695 except (socket.error, IOError, xmlrpc.client.Fault) as e:
696 print('error: cannot connect to manifest server %s:\n%s'
697 % (self.manifest.manifest_server, e), file=sys.stderr)
698 sys.exit(1)
699 except xmlrpc.client.ProtocolError as e:
700 print('error: cannot connect to manifest server %s:\n%d %s'
701 % (self.manifest.manifest_server, e.errcode, e.errmsg),
702 file=sys.stderr)
703 sys.exit(1)
704
705 return manifest_name
706
Mike Frysingerfb527e32019-08-27 02:34:32 -0400707 def _UpdateManifestProject(self, opt, mp, manifest_name):
708 """Fetch & update the local manifest project."""
709 if not opt.local_only:
710 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500711 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700712 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200713 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500714 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400715 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600716 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400717 submodules=self.manifest.HasSubmodules,
Raman Tennetif32f2432021-04-12 20:57:25 -0700718 clone_filter=self.manifest.CloneFilter,
719 partial_clone_exclude=self.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400720 finish = time.time()
721 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
722 start, finish, success)
723
724 if mp.HasChanges:
725 syncbuf = SyncBuffer(mp.config)
726 start = time.time()
727 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
728 clean = syncbuf.Finish()
729 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
730 start, time.time(), clean)
731 if not clean:
732 sys.exit(1)
733 self._ReloadManifest(opt.manifest_name)
734 if opt.jobs is None:
735 self.jobs = self.manifest.default.sync_j
736
Mike Frysingerae6cb082019-08-27 01:10:59 -0400737 def ValidateOptions(self, opt, args):
738 if opt.force_broken:
739 print('warning: -f/--force-broken is now the default behavior, and the '
740 'options are deprecated', file=sys.stderr)
741 if opt.network_only and opt.detach_head:
742 self.OptionParser.error('cannot combine -n and -d')
743 if opt.network_only and opt.local_only:
744 self.OptionParser.error('cannot combine -n and -l')
745 if opt.manifest_name and opt.smart_sync:
746 self.OptionParser.error('cannot combine -m and -s')
747 if opt.manifest_name and opt.smart_tag:
748 self.OptionParser.error('cannot combine -m and -t')
749 if opt.manifest_server_username or opt.manifest_server_password:
750 if not (opt.smart_sync or opt.smart_tag):
751 self.OptionParser.error('-u and -p may only be combined with -s or -t')
752 if None in [opt.manifest_server_username, opt.manifest_server_password]:
753 self.OptionParser.error('both -u and -p must be given')
754
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700755 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800756 if opt.jobs:
757 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700758 if self.jobs > 1:
759 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400760 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700761
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500762 if opt.manifest_name:
763 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700764
Chirayu Desaia892b102013-06-11 14:18:46 +0530765 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900766 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900767 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530768
Xin Lid79a4bc2020-05-20 16:03:45 -0700769 if opt.clone_bundle is None:
770 opt.clone_bundle = self.manifest.CloneBundle
771
Victor Boivie08c880d2011-04-19 10:32:52 +0200772 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400773 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
774 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900775 if os.path.isfile(smart_sync_manifest_path):
776 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800777 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900778 except OSError as e:
779 print('error: failed to remove existing smart sync override manifest: %s' %
780 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700781
Mike Frysinger5a033082019-09-23 19:21:20 -0400782 err_event = _threading.Event()
783
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700784 rp = self.manifest.repoProject
785 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500786 cb = rp.CurrentBranch
787 if cb:
788 base = rp.GetBranch(cb).merge
789 if not base or not base.startswith('refs/heads/'):
790 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400791 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500792 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700793
794 mp = self.manifest.manifestProject
795 mp.PreSync()
796
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800797 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700798 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800799
Fredrik de Grootcc960972019-11-22 09:04:31 +0100800 if not opt.mp_update:
801 print('Skipping update of local manifest project.')
802 else:
803 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700804
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700805 if self._UseSuperproject(opt):
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800806 manifest_name = self._UpdateProjectsRevisionId(opt, args)
807
Simran Basib9a1b732015-08-20 12:19:28 -0700808 if self.gitc_manifest:
809 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700810 missing_ok=True)
811 gitc_projects = []
812 opened_projects = []
813 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700814 if project.relpath in self.gitc_manifest.paths and \
815 self.gitc_manifest.paths[project.relpath].old_revision:
816 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700817 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700818 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700819
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700820 if not args:
821 gitc_projects = None
822
823 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700824 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700825 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
826 if manifest_name:
827 manifest.Override(manifest_name)
828 else:
829 manifest.Override(self.manifest.manifestFile)
830 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
831 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700832 gitc_projects)
833 print('GITC client successfully synced.')
834
835 # The opened projects need to be synced as normal, therefore we
836 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700837 # TODO: make this more reliable -- if there's a project name/path overlap,
838 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900839 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
840 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700841 if not args:
842 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800843 all_projects = self.GetProjects(args,
844 missing_ok=True,
845 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700846
Mike Frysinger5a033082019-09-23 19:21:20 -0400847 err_network_sync = False
848 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -0400849
Dave Borowitz67700e92012-10-23 15:00:54 -0700850 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700851 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700852 to_fetch = []
853 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700854 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700855 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900856 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700857 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700858
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500859 success, fetched = self._Fetch(to_fetch, opt, err_event)
860 if not success:
861 err_event.set()
Mike Frysinger5a033082019-09-23 19:21:20 -0400862
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500863 _PostRepoFetch(rp, opt.repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700864 if opt.network_only:
865 # bail out now; the rest touches the working tree
Mike Frysingerbe24a542021-02-23 03:24:12 -0500866 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400867 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
868 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700869 return
870
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800871 # Iteratively fetch missing and/or nested unregistered submodules
872 previously_missing_set = set()
873 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100874 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800875 all_projects = self.GetProjects(args,
876 missing_ok=True,
877 submodules_ok=opt.fetch_submodules)
878 missing = []
879 for project in all_projects:
880 if project.gitdir not in fetched:
881 missing.append(project)
882 if not missing:
883 break
884 # Stop us from non-stopped fetching actually-missing repos: If set of
885 # missing repos has not been changed from last fetch, we break.
886 missing_set = set(p.name for p in missing)
887 if previously_missing_set == missing_set:
888 break
889 previously_missing_set = missing_set
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500890 success, new_fetched = self._Fetch(to_fetch, opt, err_event)
891 if not success:
892 err_event.set()
893 fetched.update(new_fetched)
Mike Frysinger5a033082019-09-23 19:21:20 -0400894
895 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500896 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400897 err_network_sync = True
898 if opt.fail_fast:
899 print('\nerror: Exited sync due to fetch errors.\n'
900 'Local checkouts *not* updated. Resolve network issues & '
901 'retry.\n'
902 '`repo sync -l` will update some local checkouts.',
903 file=sys.stderr)
904 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800905
Julien Campergue335f5ef2013-10-16 11:02:35 +0200906 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700907 # bail out now, we have no working tree
908 return
909
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500910 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -0400911 err_event.set()
912 err_update_projects = True
913 if opt.fail_fast:
914 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
915 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700916
Mike Frysinger5a033082019-09-23 19:21:20 -0400917 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -0500918 # NB: We don't exit here because this is the last step.
919 err_checkout = not self._Checkout(all_projects, opt, err_results)
920 if err_checkout:
921 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700922
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700923 # If there's a notice that's supposed to print at the end of the sync, print
924 # it now...
925 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700926 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700927
Mike Frysinger5a033082019-09-23 19:21:20 -0400928 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500929 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400930 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
931 if err_network_sync:
932 print('error: Downloading network changes failed.', file=sys.stderr)
933 if err_update_projects:
934 print('error: Updating local project lists failed.', file=sys.stderr)
935 if err_checkout:
936 print('error: Checking out local projects failed.', file=sys.stderr)
937 if err_results:
938 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
939 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
940 file=sys.stderr)
941 sys.exit(1)
942
Mike Frysingere19d9e12020-02-12 11:23:32 -0500943 if not opt.quiet:
944 print('repo sync has finished successfully.')
945
David Pursehouse819827a2020-02-12 15:20:19 +0900946
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700947def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -0800948 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -0700949 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700950 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -0800951 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700952 if project.Exists:
953 project.PostRepoUpgrade()
954
David Pursehouse819827a2020-02-12 15:20:19 +0900955
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500956def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700957 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700958 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -0400959 wrapper = Wrapper()
960 try:
961 rev = rp.bare_git.describe(rp.GetRevisionId())
962 except GitError:
963 rev = None
964 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
965 # See if we're held back due to missing signed tag.
966 current_revid = rp.bare_git.rev_parse('HEAD')
967 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
968 if current_revid != new_revid:
969 # We want to switch to the new rev, but also not trash any uncommitted
970 # changes. This helps with local testing/hacking.
971 # If a local change has been made, we will throw that away.
972 # We also have to make sure this will switch to an older commit if that's
973 # the latest tag in order to support release rollback.
974 try:
975 rp.work_git.reset('--keep', new_rev)
976 except GitError as e:
977 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -0700978 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700979 raise RepoChangedException(['--repo-upgraded'])
980 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700981 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700982 else:
983 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700984 print('repo version %s is current' % rp.work_git.describe(HEAD),
985 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700986
David Pursehouse819827a2020-02-12 15:20:19 +0900987
Dave Borowitz67700e92012-10-23 15:00:54 -0700988class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -0700989 _ALPHA = 0.5
990
Dave Borowitz67700e92012-10-23 15:00:54 -0700991 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +0100992 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -0700993 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -0700994 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -0700995
996 def Get(self, project):
997 self._Load()
998 return self._times.get(project.name, _ONE_DAY_S)
999
1000 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001001 self._Load()
1002 name = project.name
1003 old = self._times.get(name, t)
1004 self._seen.add(name)
1005 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001006 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001007
1008 def _Load(self):
1009 if self._times is None:
1010 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001011 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001012 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001013 except (IOError, ValueError):
1014 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001015 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001016 except OSError:
1017 pass
1018 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001019
1020 def Save(self):
1021 if self._times is None:
1022 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001023
1024 to_delete = []
1025 for name in self._times:
1026 if name not in self._seen:
1027 to_delete.append(name)
1028 for name in to_delete:
1029 del self._times[name]
1030
Dave Borowitz67700e92012-10-23 15:00:54 -07001031 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001032 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001033 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001034 except (IOError, TypeError):
1035 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001036 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001037 except OSError:
1038 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001039
1040# This is a replacement for xmlrpc.client.Transport using urllib2
1041# and supporting persistent-http[s]. It cannot change hosts from
1042# request to request like the normal transport, the real url
1043# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001044
1045
Dan Willemsen0745bb22015-08-17 13:41:45 -07001046class PersistentTransport(xmlrpc.client.Transport):
1047 def __init__(self, orig_host):
1048 self.orig_host = orig_host
1049
1050 def request(self, host, handler, request_body, verbose=False):
1051 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1052 # Python doesn't understand cookies with the #HttpOnly_ prefix
1053 # Since we're only using them for HTTP, copy the file temporarily,
1054 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001055 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001056 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001057 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001058 try:
1059 with open(cookiefile) as f:
1060 for line in f:
1061 if line.startswith("#HttpOnly_"):
1062 line = line[len("#HttpOnly_"):]
1063 tmpcookiefile.write(line)
1064 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001065
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001066 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001067 try:
1068 cookiejar.load()
1069 except cookielib.LoadError:
1070 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001071 finally:
1072 tmpcookiefile.close()
1073 else:
1074 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001075
1076 proxyhandler = urllib.request.ProxyHandler
1077 if proxy:
1078 proxyhandler = urllib.request.ProxyHandler({
1079 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001080 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001081
1082 opener = urllib.request.build_opener(
1083 urllib.request.HTTPCookieProcessor(cookiejar),
1084 proxyhandler)
1085
1086 url = urllib.parse.urljoin(self.orig_host, handler)
1087 parse_results = urllib.parse.urlparse(url)
1088
1089 scheme = parse_results.scheme
1090 if scheme == 'persistent-http':
1091 scheme = 'http'
1092 if scheme == 'persistent-https':
1093 # If we're proxying through persistent-https, use http. The
1094 # proxy itself will do the https.
1095 if proxy:
1096 scheme = 'http'
1097 else:
1098 scheme = 'https'
1099
1100 # Parse out any authentication information using the base class
1101 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1102
1103 url = urllib.parse.urlunparse((
1104 scheme,
1105 host,
1106 parse_results.path,
1107 parse_results.params,
1108 parse_results.query,
1109 parse_results.fragment))
1110
1111 request = urllib.request.Request(url, request_body)
1112 if extra_headers is not None:
1113 for (name, header) in extra_headers:
1114 request.add_header(name, header)
1115 request.add_header('Content-Type', 'text/xml')
1116 try:
1117 response = opener.open(request)
1118 except urllib.error.HTTPError as e:
1119 if e.code == 501:
1120 # We may have been redirected through a login process
1121 # but our POST turned into a GET. Retry.
1122 response = opener.open(request)
1123 else:
1124 raise
1125
1126 p, u = xmlrpc.client.getparser()
1127 while 1:
1128 data = response.read(1024)
1129 if not data:
1130 break
1131 p.feed(data)
1132 p.close()
1133 return u.close()
1134
1135 def close(self):
1136 pass