blob: bf1369c0b1d25e4310ef052ee00f0aa3ba9372c6 [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
23import re
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070024import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import subprocess
26import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070027import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070028import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040029import urllib.error
30import urllib.parse
31import urllib.request
32import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070033
Roy Lee18afd7f2010-05-09 04:32:08 +080034try:
35 import threading as _threading
36except ImportError:
37 import dummy_threading as _threading
38
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070039try:
40 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090041
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070042 def _rlimit_nofile():
43 return resource.getrlimit(resource.RLIMIT_NOFILE)
44except ImportError:
45 def _rlimit_nofile():
46 return (256, 256)
47
David Rileye0684ad2017-04-05 00:02:59 -070048import event_log
Dave Borowitze2152672012-10-31 12:24:38 -070049from git_command import GIT, git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090050from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090051from git_refs import R_HEADS, HEAD
Raman Tenneti6a872c92021-01-14 19:17:50 -080052import git_superproject
Simran Basibdb52712015-08-10 13:23:23 -070053import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070054from project import Project
55from project import RemoteSpec
Mike Frysingerebf04a42021-02-23 20:48:04 -050056from command import Command, MirrorSafeCommand, WORKER_BATCH_SIZE
Raman Tenneti1fd7bc22021-02-04 14:39:38 -080057from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070058import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070059from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070060from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080061from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070062from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070063
Dave Borowitz67700e92012-10-23 15:00:54 -070064_ONE_DAY_S = 24 * 60 * 60
65
David Pursehouse819827a2020-02-12 15:20:19 +090066
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080067class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080068 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070069 common = True
70 helpSummary = "Update working tree to the latest revision"
71 helpUsage = """
72%prog [<project>...]
73"""
74 helpDescription = """
75The '%prog' command synchronizes local project directories
76with the remote repositories specified in the manifest. If a local
77project does not yet exist, it will clone a new local directory from
78the remote repository and set up tracking branches as specified in
79the manifest. If the local project already exists, '%prog'
80will update the remote branches and rebase any new local changes
81on top of the new remote changes.
82
83'%prog' will synchronize all projects listed at the command
84line. Projects can be specified either by name, or by a relative
85or absolute path to the project's local directory. If no projects
86are specified, '%prog' will synchronize all projects listed in
87the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070088
89The -d/--detach option can be used to switch specified projects
90back to the manifest revision. This option is especially helpful
91if the project is currently on a topic branch, but the manifest
92revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070093
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070094The -s/--smart-sync option can be used to sync to a known good
95build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +020096manifest. The -t/--smart-tag option is similar and allows you to
97specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070098
David Pursehousecf76b1b2012-09-14 10:31:42 +090099The -u/--manifest-server-username and -p/--manifest-server-password
100options can be used to specify a username and password to authenticate
101with the manifest server when using the -s or -t option.
102
103If -u and -p are not specified when using the -s or -t option, '%prog'
104will attempt to read authentication credentials for the manifest server
105from the user's .netrc file.
106
107'%prog' will not use authentication credentials from -u/-p or .netrc
108if the manifest server specified in the manifest file already includes
109credentials.
110
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400111By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400112to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500113
Kevin Degiabaa7f32014-11-12 11:27:45 -0700114The --force-sync option can be used to overwrite existing git
115directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900116object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700117refs may be removed when overwriting.
118
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500119The --force-remove-dirty option can be used to remove previously used
120projects with uncommitted changes. WARNING: This may cause data to be
121lost since uncommitted changes may be removed with projects that no longer
122exist in the manifest.
123
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700124The --no-clone-bundle option disables any attempt to use
125$URL/clone.bundle to bootstrap a new Git repository from a
126resumeable bundle file on a content delivery network. This
127may be necessary if there are problems with the local Python
128HTTP client or proxy configuration, but the Git binary works.
129
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800130The --fetch-submodules option enables fetching Git submodules
131of a project from server.
132
David Pursehousef2fad612015-01-29 14:36:28 +0900133The -c/--current-branch option can be used to only fetch objects that
134are on the branch specified by a project's revision.
135
David Pursehouseb1553542014-09-04 21:28:09 +0900136The --optimized-fetch option can be used to only fetch projects that
137are fixed to a sha1 revision if the sha1 revision does not already
138exist locally.
139
David Pursehouse74cfd272015-10-14 10:50:15 +0900140The --prune option can be used to remove any refs that no longer
141exist on the remote.
142
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400143# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700144
145If at least one project remote URL uses an SSH connection (ssh://,
146git+ssh://, or user@host:path syntax) repo will automatically
147enable the SSH ControlMaster option when connecting to that host.
148This feature permits other projects in the same '%prog' session to
149reuse the same SSH tunnel, saving connection setup overheads.
150
151To disable this behavior on UNIX platforms, set the GIT_SSH
152environment variable to 'ssh'. For example:
153
154 export GIT_SSH=ssh
155 %prog
156
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400157# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700158
159This feature is automatically disabled on Windows, due to the lack
160of UNIX domain socket support.
161
162This feature is not compatible with url.insteadof rewrites in the
163user's ~/.gitconfig. '%prog' is currently not able to perform the
164rewrite early enough to establish the ControlMaster tunnel.
165
166If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
167later is required to fix a server side protocol bug.
168
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169"""
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500170 PARALLEL_JOBS = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171
Nico Sallembien6623b212010-05-11 12:57:01 -0700172 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000173 try:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500174 self.PARALLEL_JOBS = self.manifest.default.sync_j
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000175 except ManifestParseError:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500176 pass
177 super()._Options(p)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700178
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500179 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200180 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400181 help='obsolete option (to be deleted in the future)')
182 p.add_option('--fail-fast',
183 dest='fail_fast', action='store_true',
184 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700185 p.add_option('--force-sync',
186 dest='force_sync', action='store_true',
187 help="overwrite an existing git directory if it needs to "
188 "point to a different object directory. WARNING: this "
189 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500190 p.add_option('--force-remove-dirty',
191 dest='force_remove_dirty', action='store_true',
192 help="force remove projects with uncommitted modifications if "
193 "projects no longer exist in the manifest. "
194 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900195 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700196 dest='local_only', action='store_true',
197 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900198 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100199 dest='mp_update', action='store_false', default='true',
200 help='use the existing manifest checkout as-is. '
201 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900202 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700203 dest='network_only', action='store_true',
204 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900205 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700206 dest='detach_head', action='store_true',
207 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900208 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700209 dest='current_branch_only', action='store_true',
210 help='fetch only current branch from server')
Mike Frysinger521d01b2020-02-17 01:51:49 -0500211 p.add_option('-v', '--verbose',
212 dest='output_mode', action='store_true',
213 help='show all sync output')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900214 p.add_option('-q', '--quiet',
Mike Frysinger521d01b2020-02-17 01:51:49 -0500215 dest='output_mode', action='store_false',
216 help='only show errors')
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500217 p.add_option('-m', '--manifest-name',
218 dest='manifest_name',
219 help='temporary manifest to use for this sync', metavar='NAME.xml')
Xin Lid79a4bc2020-05-20 16:03:45 -0700220 p.add_option('--clone-bundle', action='store_true',
221 help='enable use of /clone.bundle on HTTP/HTTPS')
222 p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700223 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800224 p.add_option('-u', '--manifest-server-username', action='store',
225 dest='manifest_server_username',
226 help='username to authenticate with the manifest server')
227 p.add_option('-p', '--manifest-server-password', action='store',
228 dest='manifest_server_password',
229 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800230 p.add_option('--fetch-submodules',
231 dest='fetch_submodules', action='store_true',
232 help='fetch submodules from server')
Raman Tenneti6a872c92021-01-14 19:17:50 -0800233 p.add_option('--use-superproject', action='store_true',
234 help='use the manifest superproject to sync projects')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700235 p.add_option('--no-tags',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500236 dest='tags', default=True, action='store_false',
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700237 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900238 p.add_option('--optimized-fetch',
239 dest='optimized_fetch', action='store_true',
240 help='only fetch projects fixed to sha1 if revision does not exist locally')
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600241 p.add_option('--retry-fetches',
242 default=0, action='store', type='int',
243 help='number of times to retry fetches on transient errors')
David Pursehouse74cfd272015-10-14 10:50:15 +0900244 p.add_option('--prune', dest='prune', action='store_true',
245 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700246 if show_smart:
247 p.add_option('-s', '--smart-sync',
248 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900249 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200250 p.add_option('-t', '--smart-tag',
251 dest='smart_tag', action='store',
252 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700253
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700254 g = p.add_option_group('repo Version options')
255 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500256 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700257 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700258 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800259 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700260 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700261
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800262 def _GetBranch(self):
263 """Returns the branch name for getting the approved manifest."""
264 p = self.manifest.manifestProject
265 b = p.GetBranch(p.CurrentBranch)
266 branch = b.merge
267 if branch.startswith(R_HEADS):
268 branch = branch[len(R_HEADS):]
269 return branch
270
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700271 def _UseSuperproject(self, opt):
272 """Returns True if use-superproject option is enabled"""
273 return (opt.use_superproject or
274 self.manifest.manifestProject.config.GetBoolean(
275 'repo.superproject'))
276
277 def _GetCurrentBranchOnly(self, opt):
278 """Returns True if current-branch or use-superproject options are enabled."""
279 return opt.current_branch_only or self._UseSuperproject(opt)
280
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800281 def _UpdateProjectsRevisionId(self, opt, args):
282 """Update revisionId of every project with the SHA from superproject.
283
284 This function updates each project's revisionId with SHA from superproject.
285 It writes the updated manifest into a file and reloads the manifest from it.
286
287 Args:
288 opt: Program options returned from optparse. See _Options().
289 args: Arguments to pass to GetProjects. See the GetProjects
290 docstring for details.
291
292 Returns:
293 Returns path to the overriding manifest file.
294 """
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800295 superproject = git_superproject.Superproject(self.manifest,
Raman Tennetief99ec02021-03-04 10:29:40 -0800296 self.repodir,
297 quiet=opt.quiet)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800298 all_projects = self.GetProjects(args,
299 missing_ok=True,
300 submodules_ok=opt.fetch_submodules)
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800301 manifest_path = superproject.UpdateProjectsRevisionId(all_projects)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800302 if not manifest_path:
303 print('error: Update of revsionId from superproject has failed',
304 file=sys.stderr)
305 sys.exit(1)
306 self._ReloadManifest(manifest_path)
307 return manifest_path
308
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500309 def _FetchProjectList(self, opt, projects):
310 """Main function of the fetch worker.
311
312 The projects we're given share the same underlying git object store, so we
313 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800314
David James8d201162013-10-11 17:03:19 -0700315 Delegates most of the work to _FetchHelper.
316
317 Args:
318 opt: Program options returned from optparse. See _Options().
319 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700320 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500321 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700322
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500323 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700324 """Fetch git objects for a single project.
325
David Pursehousec1b86a22012-11-14 11:36:51 +0900326 Args:
327 opt: Program options returned from optparse. See _Options().
328 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700329
330 Returns:
331 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900332 """
David Rileye0684ad2017-04-05 00:02:59 -0700333 start = time.time()
334 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500335 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900336 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500337 success = project.Sync_NetworkHalf(
338 quiet=opt.quiet,
339 verbose=opt.verbose,
340 output_redir=buf,
341 current_branch_only=self._GetCurrentBranchOnly(opt),
342 force_sync=opt.force_sync,
343 clone_bundle=opt.clone_bundle,
344 tags=opt.tags, archive=self.manifest.IsArchive,
345 optimized_fetch=opt.optimized_fetch,
346 retry_fetches=opt.retry_fetches,
347 prune=opt.prune,
348 clone_filter=self.manifest.CloneFilter)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700349
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500350 output = buf.getvalue()
351 if opt.verbose and output:
352 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700353
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500354 if not success:
355 print('error: Cannot fetch %s from %s'
356 % (project.name, project.remote.url),
357 file=sys.stderr)
358 except Exception as e:
359 print('error: Cannot fetch %s (%s: %s)'
360 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
361 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500362
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500363 finish = time.time()
364 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700365
Mike Frysinger5a033082019-09-23 19:21:20 -0400366 def _Fetch(self, projects, opt, err_event):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500367 ret = True
368
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700369 fetched = set()
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500370 pm = Progress('Fetching', len(projects), delay=False)
Roy Lee18afd7f2010-05-09 04:32:08 +0800371
David James89ece422014-01-09 18:51:58 -0800372 objdir_project_map = dict()
373 for project in projects:
374 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500375 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700376
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500377 def _ProcessResults(results_sets):
378 ret = True
379 for results in results_sets:
380 for (success, project, start, finish) in results:
381 self._fetch_times.Set(project, finish - start)
382 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
383 start, finish, success)
384 # Check for any errors before running any more tasks.
385 # ...we'll let existing jobs finish, though.
386 if not success:
387 ret = False
388 else:
389 fetched.add(project.gitdir)
390 pm.update(msg=project.name)
391 if not ret and opt.fail_fast:
392 break
393 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700394
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500395 # NB: Multiprocessing is heavy, so don't spin it up for one job.
396 if len(projects_list) == 1 or opt.jobs == 1:
397 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
398 ret = False
399 else:
400 # Favor throughput over responsiveness when quiet. It seems that imap()
401 # will yield results in batches relative to chunksize, so even as the
402 # children finish a sync, we won't see the result until one child finishes
403 # ~chunksize jobs. When using a large --jobs with large chunksize, this
404 # can be jarring as there will be a large initial delay where repo looks
405 # like it isn't doing anything and sits at 0%, but then suddenly completes
406 # a lot of jobs all at once. Since this code is more network bound, we
407 # can accept a bit more CPU overhead with a smaller chunksize so that the
408 # user sees more immediate & continuous feedback.
409 if opt.quiet:
410 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800411 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500412 pm.update(inc=0, msg='warming up')
413 chunksize = 4
414 with multiprocessing.Pool(opt.jobs) as pool:
415 results = pool.imap_unordered(
416 functools.partial(self._FetchProjectList, opt),
417 projects_list,
418 chunksize=chunksize)
419 if not _ProcessResults(results):
420 ret = False
421 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800422
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700423 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700424 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700425
Julien Campergue335f5ef2013-10-16 11:02:35 +0200426 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400427 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200428
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500429 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700430
Mike Frysingerebf04a42021-02-23 20:48:04 -0500431 def _CheckoutOne(self, opt, project):
Xin Li745be2e2019-06-03 11:24:30 -0700432 """Checkout work tree for one project
433
434 Args:
435 opt: Program options returned from optparse. See _Options().
436 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700437
438 Returns:
439 Whether the fetch was successful.
440 """
Xin Li745be2e2019-06-03 11:24:30 -0700441 start = time.time()
442 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
443 detach_head=opt.detach_head)
444 success = False
445 try:
Mike Frysingerebf04a42021-02-23 20:48:04 -0500446 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
447 success = syncbuf.Finish()
448 except Exception as e:
449 print('error: Cannot checkout %s: %s: %s' %
450 (project.name, type(e).__name__, str(e)),
451 file=sys.stderr)
452 raise
Xin Li745be2e2019-06-03 11:24:30 -0700453
Mike Frysingerebf04a42021-02-23 20:48:04 -0500454 if not success:
455 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
456 finish = time.time()
457 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700458
Mike Frysingerebf04a42021-02-23 20:48:04 -0500459 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700460 """Checkout projects listed in all_projects
461
462 Args:
463 all_projects: List of all projects that should be checked out.
464 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500465 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700466 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500467 ret = True
Xin Li745be2e2019-06-03 11:24:30 -0700468
Mike Frysingerebf04a42021-02-23 20:48:04 -0500469 # Only checkout projects with worktrees.
470 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700471
Mike Frysingerfbb95a42021-02-23 17:34:35 -0500472 pm = Progress('Checking out', len(all_projects))
Xin Li745be2e2019-06-03 11:24:30 -0700473
Mike Frysingerebf04a42021-02-23 20:48:04 -0500474 def _ProcessResults(results):
475 for (success, project, start, finish) in results:
476 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
477 start, finish, success)
478 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500479 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500480 if not success:
481 err_results.append(project.relpath)
482 if opt.fail_fast:
483 return False
484 pm.update(msg=project.name)
485 return True
Xin Li745be2e2019-06-03 11:24:30 -0700486
Mike Frysingerebf04a42021-02-23 20:48:04 -0500487 # NB: Multiprocessing is heavy, so don't spin it up for one job.
488 if len(all_projects) == 1 or opt.jobs == 1:
489 if not _ProcessResults(self._CheckoutOne(opt, x) for x in all_projects):
490 ret = False
491 else:
492 with multiprocessing.Pool(opt.jobs) as pool:
493 results = pool.imap_unordered(
494 functools.partial(self._CheckoutOne, opt),
495 all_projects,
496 chunksize=WORKER_BATCH_SIZE)
497 if not _ProcessResults(results):
498 ret = False
499 pool.close()
Xin Li745be2e2019-06-03 11:24:30 -0700500
501 pm.end()
Xin Li745be2e2019-06-03 11:24:30 -0700502
Mike Frysinger511a0e52021-03-12 20:03:51 -0500503 return ret and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500504
Mike Frysinger5a033082019-09-23 19:21:20 -0400505 def _GCProjects(self, projects, opt, err_event):
Gabe Black2ff30292014-10-09 17:54:35 -0700506 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700507 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500508 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500509 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900510 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100511 if not opt.quiet:
512 print('%s: Shared project %s found, disabling pruning.' %
513 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500514 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500515 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500516 else:
517 # This isn't perfect, but it's the best we can do with old git.
518 print('%s: WARNING: shared projects are unreliable when using old '
519 'versions of git; please upgrade to git-2.7.0+.'
520 % (project.relpath,),
521 file=sys.stderr)
522 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700523 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700524
Mike Frysinger6f1c6262020-02-04 00:09:23 -0500525 if multiprocessing:
Dave Borowitz18857212012-10-23 17:02:59 -0700526 cpu_count = multiprocessing.cpu_count()
527 else:
528 cpu_count = 1
529 jobs = min(self.jobs, cpu_count)
530
531 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700532 for bare_git in gc_gitdirs.values():
David James8d201162013-10-11 17:03:19 -0700533 bare_git.gc('--auto')
Dave Borowitz18857212012-10-23 17:02:59 -0700534 return
535
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400536 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700537
538 threads = set()
539 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700540
David James8d201162013-10-11 17:03:19 -0700541 def GC(bare_git):
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:
551 sem.release()
552
Gabe Black2ff30292014-10-09 17:54:35 -0700553 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500554 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700555 break
556 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700557 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700558 t.daemon = True
559 threads.add(t)
560 t.start()
561
562 for t in threads:
563 t.join()
564
Tim Kilbourn07669002013-03-08 15:02:49 -0800565 def _ReloadManifest(self, manifest_name=None):
566 if manifest_name:
567 # Override calls _Unload already
568 self.manifest.Override(manifest_name)
569 else:
570 self.manifest._Unload()
571
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500572 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700573 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700574 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700575 if project.relpath:
576 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700577 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500578 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700579 old_project_paths = []
580
581 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500582 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700583 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800584 # In reversed order, so subfolders are deleted before parent folder.
585 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700586 if not path:
587 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700588 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900589 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700590 gitdir = os.path.join(self.manifest.topdir, path, '.git')
591 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900592 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900593 manifest=self.manifest,
594 name=path,
595 remote=RemoteSpec('origin'),
596 gitdir=gitdir,
597 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500598 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900599 worktree=os.path.join(self.manifest.topdir, path),
600 relpath=path,
601 revisionExpr='HEAD',
602 revisionId=None,
603 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500604 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900605 quiet=opt.quiet,
606 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400607 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700608
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700609 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500610 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700611 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700612 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700613 return 0
614
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400615 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
616 if not self.manifest.manifest_server:
617 print('error: cannot smart sync: no manifest server defined in '
618 'manifest', file=sys.stderr)
619 sys.exit(1)
620
621 manifest_server = self.manifest.manifest_server
622 if not opt.quiet:
623 print('Using manifest server %s' % manifest_server)
624
David Pursehouseeeff3532020-02-12 11:24:10 +0900625 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400626 username = None
627 password = None
628 if opt.manifest_server_username and opt.manifest_server_password:
629 username = opt.manifest_server_username
630 password = opt.manifest_server_password
631 else:
632 try:
633 info = netrc.netrc()
634 except IOError:
635 # .netrc file does not exist or could not be opened
636 pass
637 else:
638 try:
639 parse_result = urllib.parse.urlparse(manifest_server)
640 if parse_result.hostname:
641 auth = info.authenticators(parse_result.hostname)
642 if auth:
643 username, _account, password = auth
644 else:
645 print('No credentials found for %s in .netrc'
646 % parse_result.hostname, file=sys.stderr)
647 except netrc.NetrcParseError as e:
648 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
649
650 if (username and password):
651 manifest_server = manifest_server.replace('://', '://%s:%s@' %
652 (username, password),
653 1)
654
655 transport = PersistentTransport(manifest_server)
656 if manifest_server.startswith('persistent-'):
657 manifest_server = manifest_server[len('persistent-'):]
658
659 try:
660 server = xmlrpc.client.Server(manifest_server, transport=transport)
661 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800662 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400663
Mike Frysinger56ce3462019-12-04 19:30:48 -0500664 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500665 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400666 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500667 elif ('TARGET_PRODUCT' in os.environ and
668 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500669 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
670 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400671 [success, manifest_str] = server.GetApprovedManifest(branch, target)
672 else:
673 [success, manifest_str] = server.GetApprovedManifest(branch)
674 else:
675 assert(opt.smart_tag)
676 [success, manifest_str] = server.GetManifest(opt.smart_tag)
677
678 if success:
679 manifest_name = os.path.basename(smart_sync_manifest_path)
680 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500681 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400682 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400683 except IOError as e:
684 print('error: cannot write manifest to %s:\n%s'
685 % (smart_sync_manifest_path, e),
686 file=sys.stderr)
687 sys.exit(1)
688 self._ReloadManifest(manifest_name)
689 else:
690 print('error: manifest server RPC call failed: %s' %
691 manifest_str, file=sys.stderr)
692 sys.exit(1)
693 except (socket.error, IOError, xmlrpc.client.Fault) as e:
694 print('error: cannot connect to manifest server %s:\n%s'
695 % (self.manifest.manifest_server, e), file=sys.stderr)
696 sys.exit(1)
697 except xmlrpc.client.ProtocolError as e:
698 print('error: cannot connect to manifest server %s:\n%d %s'
699 % (self.manifest.manifest_server, e.errcode, e.errmsg),
700 file=sys.stderr)
701 sys.exit(1)
702
703 return manifest_name
704
Mike Frysingerfb527e32019-08-27 02:34:32 -0400705 def _UpdateManifestProject(self, opt, mp, manifest_name):
706 """Fetch & update the local manifest project."""
707 if not opt.local_only:
708 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500709 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700710 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200711 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500712 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400713 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600714 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400715 submodules=self.manifest.HasSubmodules,
716 clone_filter=self.manifest.CloneFilter)
717 finish = time.time()
718 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
719 start, finish, success)
720
721 if mp.HasChanges:
722 syncbuf = SyncBuffer(mp.config)
723 start = time.time()
724 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
725 clean = syncbuf.Finish()
726 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
727 start, time.time(), clean)
728 if not clean:
729 sys.exit(1)
730 self._ReloadManifest(opt.manifest_name)
731 if opt.jobs is None:
732 self.jobs = self.manifest.default.sync_j
733
Mike Frysingerae6cb082019-08-27 01:10:59 -0400734 def ValidateOptions(self, opt, args):
735 if opt.force_broken:
736 print('warning: -f/--force-broken is now the default behavior, and the '
737 'options are deprecated', file=sys.stderr)
738 if opt.network_only and opt.detach_head:
739 self.OptionParser.error('cannot combine -n and -d')
740 if opt.network_only and opt.local_only:
741 self.OptionParser.error('cannot combine -n and -l')
742 if opt.manifest_name and opt.smart_sync:
743 self.OptionParser.error('cannot combine -m and -s')
744 if opt.manifest_name and opt.smart_tag:
745 self.OptionParser.error('cannot combine -m and -t')
746 if opt.manifest_server_username or opt.manifest_server_password:
747 if not (opt.smart_sync or opt.smart_tag):
748 self.OptionParser.error('-u and -p may only be combined with -s or -t')
749 if None in [opt.manifest_server_username, opt.manifest_server_password]:
750 self.OptionParser.error('both -u and -p must be given')
751
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700752 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800753 if opt.jobs:
754 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700755 if self.jobs > 1:
756 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400757 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700758
Mike Frysinger521d01b2020-02-17 01:51:49 -0500759 opt.quiet = opt.output_mode is False
760 opt.verbose = opt.output_mode is True
761
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)
959 print(file=sys.stderr)
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500960 if not repo_verify or _VerifyTag(rp):
Shawn O. Pearce350cde42009-04-16 11:21:18 -0700961 syncbuf = SyncBuffer(rp.config)
962 rp.Sync_LocalHalf(syncbuf)
963 if not syncbuf.Finish():
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700964 sys.exit(1)
Sarah Owenscecd1d82012-11-01 22:59:27 -0700965 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700966 raise RepoChangedException(['--repo-upgraded'])
967 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700968 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700969 else:
970 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700971 print('repo version %s is current' % rp.work_git.describe(HEAD),
972 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700973
David Pursehouse819827a2020-02-12 15:20:19 +0900974
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700975def _VerifyTag(project):
976 gpg_dir = os.path.expanduser('~/.repoconfig/gnupg')
977 if not os.path.exists(gpg_dir):
Sarah Owenscecd1d82012-11-01 22:59:27 -0700978 print('warning: GnuPG was not available during last "repo init"\n'
979 'warning: Cannot automatically authenticate repo."""',
980 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700981 return True
982
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700983 try:
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700984 cur = project.bare_git.describe(project.GetRevisionId())
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985 except GitError:
986 cur = None
987
988 if not cur \
989 or re.compile(r'^.*-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur):
Shawn O. Pearce3c8dea12009-05-29 18:38:17 -0700990 rev = project.revisionExpr
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700991 if rev.startswith(R_HEADS):
992 rev = rev[len(R_HEADS):]
993
Sarah Owenscecd1d82012-11-01 22:59:27 -0700994 print(file=sys.stderr)
995 print("warning: project '%s' branch '%s' is not signed"
996 % (project.name, rev), file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700997 return False
998
Shawn O. Pearcef18cb762010-12-07 11:41:05 -0800999 env = os.environ.copy()
Mike Frysinger56ce3462019-12-04 19:30:48 -05001000 env['GIT_DIR'] = project.gitdir
1001 env['GNUPGHOME'] = gpg_dir
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002
1003 cmd = [GIT, 'tag', '-v', cur]
Mike Frysingerfb21d6a2021-02-16 02:37:55 -05001004 result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
1005 env=env, check=False)
1006 if result.returncode:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001007 print(file=sys.stderr)
Mike Frysingerfb21d6a2021-02-16 02:37:55 -05001008 print(result.stdout, file=sys.stderr)
Sarah Owenscecd1d82012-11-01 22:59:27 -07001009 print(file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010 return False
1011 return True
Dave Borowitz67700e92012-10-23 15:00:54 -07001012
David Rileye0684ad2017-04-05 00:02:59 -07001013
Dave Borowitz67700e92012-10-23 15:00:54 -07001014class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001015 _ALPHA = 0.5
1016
Dave Borowitz67700e92012-10-23 15:00:54 -07001017 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001018 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001019 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001020 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001021
1022 def Get(self, project):
1023 self._Load()
1024 return self._times.get(project.name, _ONE_DAY_S)
1025
1026 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001027 self._Load()
1028 name = project.name
1029 old = self._times.get(name, t)
1030 self._seen.add(name)
1031 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001032 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001033
1034 def _Load(self):
1035 if self._times is None:
1036 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001037 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001038 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001039 except (IOError, ValueError):
1040 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001041 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001042 except OSError:
1043 pass
1044 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001045
1046 def Save(self):
1047 if self._times is None:
1048 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001049
1050 to_delete = []
1051 for name in self._times:
1052 if name not in self._seen:
1053 to_delete.append(name)
1054 for name in to_delete:
1055 del self._times[name]
1056
Dave Borowitz67700e92012-10-23 15:00:54 -07001057 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001058 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001059 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001060 except (IOError, TypeError):
1061 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001062 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001063 except OSError:
1064 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001065
1066# This is a replacement for xmlrpc.client.Transport using urllib2
1067# and supporting persistent-http[s]. It cannot change hosts from
1068# request to request like the normal transport, the real url
1069# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001070
1071
Dan Willemsen0745bb22015-08-17 13:41:45 -07001072class PersistentTransport(xmlrpc.client.Transport):
1073 def __init__(self, orig_host):
1074 self.orig_host = orig_host
1075
1076 def request(self, host, handler, request_body, verbose=False):
1077 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1078 # Python doesn't understand cookies with the #HttpOnly_ prefix
1079 # Since we're only using them for HTTP, copy the file temporarily,
1080 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001081 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001082 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001083 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001084 try:
1085 with open(cookiefile) as f:
1086 for line in f:
1087 if line.startswith("#HttpOnly_"):
1088 line = line[len("#HttpOnly_"):]
1089 tmpcookiefile.write(line)
1090 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001091
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001092 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001093 try:
1094 cookiejar.load()
1095 except cookielib.LoadError:
1096 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001097 finally:
1098 tmpcookiefile.close()
1099 else:
1100 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001101
1102 proxyhandler = urllib.request.ProxyHandler
1103 if proxy:
1104 proxyhandler = urllib.request.ProxyHandler({
1105 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001106 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001107
1108 opener = urllib.request.build_opener(
1109 urllib.request.HTTPCookieProcessor(cookiejar),
1110 proxyhandler)
1111
1112 url = urllib.parse.urljoin(self.orig_host, handler)
1113 parse_results = urllib.parse.urlparse(url)
1114
1115 scheme = parse_results.scheme
1116 if scheme == 'persistent-http':
1117 scheme = 'http'
1118 if scheme == 'persistent-https':
1119 # If we're proxying through persistent-https, use http. The
1120 # proxy itself will do the https.
1121 if proxy:
1122 scheme = 'http'
1123 else:
1124 scheme = 'https'
1125
1126 # Parse out any authentication information using the base class
1127 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1128
1129 url = urllib.parse.urlunparse((
1130 scheme,
1131 host,
1132 parse_results.path,
1133 parse_results.params,
1134 parse_results.query,
1135 parse_results.fragment))
1136
1137 request = urllib.request.Request(url, request_body)
1138 if extra_headers is not None:
1139 for (name, header) in extra_headers:
1140 request.add_header(name, header)
1141 request.add_header('Content-Type', 'text/xml')
1142 try:
1143 response = opener.open(request)
1144 except urllib.error.HTTPError as e:
1145 if e.code == 501:
1146 # We may have been redirected through a login process
1147 # but our POST turned into a GET. Retry.
1148 response = opener.open(request)
1149 else:
1150 raise
1151
1152 p, u = xmlrpc.client.getparser()
1153 while 1:
1154 data = response.read(1024)
1155 if not data:
1156 break
1157 p.feed(data)
1158 p.close()
1159 return u.close()
1160
1161 def close(self):
1162 pass