blob: b8abb1a7a0a0899b0718bbf6fb70724703e03ac6 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
Mike Frysingerebf04a42021-02-23 20:48:04 -050015import functools
Mike Frysingeracf63b22019-06-13 02:24:21 -040016import http.cookiejar as cookielib
Mike Frysinger7b586f22021-02-23 18:38:39 -050017import io
Anthony King85b24ac2014-05-06 15:57:48 +010018import json
Mike Frysingerebf04a42021-02-23 20:48:04 -050019import multiprocessing
David Pursehouse86d973d2012-08-24 10:21:02 +090020import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070021from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070022import os
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070023import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070024import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070025import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070026import time
Mike Frysingeracf63b22019-06-13 02:24:21 -040027import urllib.error
28import urllib.parse
29import urllib.request
30import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070031
Roy Lee18afd7f2010-05-09 04:32:08 +080032try:
33 import threading as _threading
34except ImportError:
35 import dummy_threading as _threading
36
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070037try:
38 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090039
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070040 def _rlimit_nofile():
41 return resource.getrlimit(resource.RLIMIT_NOFILE)
42except ImportError:
43 def _rlimit_nofile():
44 return (256, 256)
45
David Rileye0684ad2017-04-05 00:02:59 -070046import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040047from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090048from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090049from git_refs import R_HEADS, HEAD
Raman Tenneti6a872c92021-01-14 19:17:50 -080050import git_superproject
Simran Basibdb52712015-08-10 13:23:23 -070051import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070052from project import Project
53from project import RemoteSpec
Mike Frysingerebf04a42021-02-23 20:48:04 -050054from command import Command, MirrorSafeCommand, WORKER_BATCH_SIZE
Raman Tenneti1fd7bc22021-02-04 14:39:38 -080055from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070056import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070057from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070058from progress import Progress
Conley Owens094cdbe2014-01-30 15:09:59 -080059from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070060from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070061
Dave Borowitz67700e92012-10-23 15:00:54 -070062_ONE_DAY_S = 24 * 60 * 60
63
David Pursehouse819827a2020-02-12 15:20:19 +090064
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080065class Sync(Command, MirrorSafeCommand):
Roy Lee18afd7f2010-05-09 04:32:08 +080066 jobs = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070067 common = True
68 helpSummary = "Update working tree to the latest revision"
69 helpUsage = """
70%prog [<project>...]
71"""
72 helpDescription = """
73The '%prog' command synchronizes local project directories
74with the remote repositories specified in the manifest. If a local
75project does not yet exist, it will clone a new local directory from
76the remote repository and set up tracking branches as specified in
77the manifest. If the local project already exists, '%prog'
78will update the remote branches and rebase any new local changes
79on top of the new remote changes.
80
81'%prog' will synchronize all projects listed at the command
82line. Projects can be specified either by name, or by a relative
83or absolute path to the project's local directory. If no projects
84are specified, '%prog' will synchronize all projects listed in
85the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070086
87The -d/--detach option can be used to switch specified projects
88back to the manifest revision. This option is especially helpful
89if the project is currently on a topic branch, but the manifest
90revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -070091
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070092The -s/--smart-sync option can be used to sync to a known good
93build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +020094manifest. The -t/--smart-tag option is similar and allows you to
95specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070096
David Pursehousecf76b1b2012-09-14 10:31:42 +090097The -u/--manifest-server-username and -p/--manifest-server-password
98options can be used to specify a username and password to authenticate
99with the manifest server when using the -s or -t option.
100
101If -u and -p are not specified when using the -s or -t option, '%prog'
102will attempt to read authentication credentials for the manifest server
103from the user's .netrc file.
104
105'%prog' will not use authentication credentials from -u/-p or .netrc
106if the manifest server specified in the manifest file already includes
107credentials.
108
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400109By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400110to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500111
Kevin Degiabaa7f32014-11-12 11:27:45 -0700112The --force-sync option can be used to overwrite existing git
113directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900114object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700115refs may be removed when overwriting.
116
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500117The --force-remove-dirty option can be used to remove previously used
118projects with uncommitted changes. WARNING: This may cause data to be
119lost since uncommitted changes may be removed with projects that no longer
120exist in the manifest.
121
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700122The --no-clone-bundle option disables any attempt to use
123$URL/clone.bundle to bootstrap a new Git repository from a
124resumeable bundle file on a content delivery network. This
125may be necessary if there are problems with the local Python
126HTTP client or proxy configuration, but the Git binary works.
127
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800128The --fetch-submodules option enables fetching Git submodules
129of a project from server.
130
David Pursehousef2fad612015-01-29 14:36:28 +0900131The -c/--current-branch option can be used to only fetch objects that
132are on the branch specified by a project's revision.
133
David Pursehouseb1553542014-09-04 21:28:09 +0900134The --optimized-fetch option can be used to only fetch projects that
135are fixed to a sha1 revision if the sha1 revision does not already
136exist locally.
137
David Pursehouse74cfd272015-10-14 10:50:15 +0900138The --prune option can be used to remove any refs that no longer
139exist on the remote.
140
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400141# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700142
143If at least one project remote URL uses an SSH connection (ssh://,
144git+ssh://, or user@host:path syntax) repo will automatically
145enable the SSH ControlMaster option when connecting to that host.
146This feature permits other projects in the same '%prog' session to
147reuse the same SSH tunnel, saving connection setup overheads.
148
149To disable this behavior on UNIX platforms, set the GIT_SSH
150environment variable to 'ssh'. For example:
151
152 export GIT_SSH=ssh
153 %prog
154
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400155# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700156
157This feature is automatically disabled on Windows, due to the lack
158of UNIX domain socket support.
159
160This feature is not compatible with url.insteadof rewrites in the
161user's ~/.gitconfig. '%prog' is currently not able to perform the
162rewrite early enough to establish the ControlMaster tunnel.
163
164If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
165later is required to fix a server side protocol bug.
166
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167"""
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500168 PARALLEL_JOBS = 1
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700169
Nico Sallembien6623b212010-05-11 12:57:01 -0700170 def _Options(self, p, show_smart=True):
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000171 try:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500172 self.PARALLEL_JOBS = self.manifest.default.sync_j
Torne (Richard Coles)7bdbde72012-12-05 10:58:06 +0000173 except ManifestParseError:
Mike Frysinger6a2400a2021-02-16 01:43:31 -0500174 pass
175 super()._Options(p)
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700176
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400177 p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
178 help='number of network jobs to run in parallel (defaults to --jobs)')
179 p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
180 help='number of local checkout jobs to run in parallel (defaults to --jobs)')
181
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500182 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200183 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400184 help='obsolete option (to be deleted in the future)')
185 p.add_option('--fail-fast',
186 dest='fail_fast', action='store_true',
187 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700188 p.add_option('--force-sync',
189 dest='force_sync', action='store_true',
190 help="overwrite an existing git directory if it needs to "
191 "point to a different object directory. WARNING: this "
192 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500193 p.add_option('--force-remove-dirty',
194 dest='force_remove_dirty', action='store_true',
195 help="force remove projects with uncommitted modifications if "
196 "projects no longer exist in the manifest. "
197 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900198 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700199 dest='local_only', action='store_true',
200 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900201 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100202 dest='mp_update', action='store_false', default='true',
203 help='use the existing manifest checkout as-is. '
204 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900205 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700206 dest='network_only', action='store_true',
207 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900208 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700209 dest='detach_head', action='store_true',
210 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900211 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700212 dest='current_branch_only', action='store_true',
213 help='fetch only current branch from server')
Mike Frysinger521d01b2020-02-17 01:51:49 -0500214 p.add_option('-v', '--verbose',
215 dest='output_mode', action='store_true',
216 help='show all sync output')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900217 p.add_option('-q', '--quiet',
Mike Frysinger521d01b2020-02-17 01:51:49 -0500218 dest='output_mode', action='store_false',
219 help='only show errors')
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500220 p.add_option('-m', '--manifest-name',
221 dest='manifest_name',
222 help='temporary manifest to use for this sync', metavar='NAME.xml')
Xin Lid79a4bc2020-05-20 16:03:45 -0700223 p.add_option('--clone-bundle', action='store_true',
224 help='enable use of /clone.bundle on HTTP/HTTPS')
225 p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700226 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800227 p.add_option('-u', '--manifest-server-username', action='store',
228 dest='manifest_server_username',
229 help='username to authenticate with the manifest server')
230 p.add_option('-p', '--manifest-server-password', action='store',
231 dest='manifest_server_password',
232 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800233 p.add_option('--fetch-submodules',
234 dest='fetch_submodules', action='store_true',
235 help='fetch submodules from server')
Raman Tenneti6a872c92021-01-14 19:17:50 -0800236 p.add_option('--use-superproject', action='store_true',
237 help='use the manifest superproject to sync projects')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700238 p.add_option('--no-tags',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500239 dest='tags', default=True, action='store_false',
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700240 help="don't fetch tags")
David Pursehouseb1553542014-09-04 21:28:09 +0900241 p.add_option('--optimized-fetch',
242 dest='optimized_fetch', action='store_true',
243 help='only fetch projects fixed to sha1 if revision does not exist locally')
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600244 p.add_option('--retry-fetches',
245 default=0, action='store', type='int',
246 help='number of times to retry fetches on transient errors')
David Pursehouse74cfd272015-10-14 10:50:15 +0900247 p.add_option('--prune', dest='prune', action='store_true',
248 help='delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700249 if show_smart:
250 p.add_option('-s', '--smart-sync',
251 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900252 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200253 p.add_option('-t', '--smart-tag',
254 dest='smart_tag', action='store',
255 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700256
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700257 g = p.add_option_group('repo Version options')
258 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500259 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700260 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700261 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800262 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700263 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700264
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800265 def _GetBranch(self):
266 """Returns the branch name for getting the approved manifest."""
267 p = self.manifest.manifestProject
268 b = p.GetBranch(p.CurrentBranch)
269 branch = b.merge
270 if branch.startswith(R_HEADS):
271 branch = branch[len(R_HEADS):]
272 return branch
273
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700274 def _UseSuperproject(self, opt):
275 """Returns True if use-superproject option is enabled"""
276 return (opt.use_superproject or
277 self.manifest.manifestProject.config.GetBoolean(
278 'repo.superproject'))
279
280 def _GetCurrentBranchOnly(self, opt):
281 """Returns True if current-branch or use-superproject options are enabled."""
282 return opt.current_branch_only or self._UseSuperproject(opt)
283
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800284 def _UpdateProjectsRevisionId(self, opt, args):
285 """Update revisionId of every project with the SHA from superproject.
286
287 This function updates each project's revisionId with SHA from superproject.
288 It writes the updated manifest into a file and reloads the manifest from it.
289
290 Args:
291 opt: Program options returned from optparse. See _Options().
292 args: Arguments to pass to GetProjects. See the GetProjects
293 docstring for details.
294
295 Returns:
296 Returns path to the overriding manifest file.
297 """
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800298 superproject = git_superproject.Superproject(self.manifest,
Raman Tennetief99ec02021-03-04 10:29:40 -0800299 self.repodir,
300 quiet=opt.quiet)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800301 all_projects = self.GetProjects(args,
302 missing_ok=True,
303 submodules_ok=opt.fetch_submodules)
Raman Tenneti21dce3d2021-02-09 00:26:31 -0800304 manifest_path = superproject.UpdateProjectsRevisionId(all_projects)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800305 if not manifest_path:
306 print('error: Update of revsionId from superproject has failed',
307 file=sys.stderr)
308 sys.exit(1)
309 self._ReloadManifest(manifest_path)
310 return manifest_path
311
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500312 def _FetchProjectList(self, opt, projects):
313 """Main function of the fetch worker.
314
315 The projects we're given share the same underlying git object store, so we
316 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800317
David James8d201162013-10-11 17:03:19 -0700318 Delegates most of the work to _FetchHelper.
319
320 Args:
321 opt: Program options returned from optparse. See _Options().
322 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700323 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500324 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700325
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500326 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700327 """Fetch git objects for a single project.
328
David Pursehousec1b86a22012-11-14 11:36:51 +0900329 Args:
330 opt: Program options returned from optparse. See _Options().
331 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700332
333 Returns:
334 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900335 """
David Rileye0684ad2017-04-05 00:02:59 -0700336 start = time.time()
337 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500338 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900339 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500340 success = project.Sync_NetworkHalf(
341 quiet=opt.quiet,
342 verbose=opt.verbose,
343 output_redir=buf,
344 current_branch_only=self._GetCurrentBranchOnly(opt),
345 force_sync=opt.force_sync,
346 clone_bundle=opt.clone_bundle,
347 tags=opt.tags, archive=self.manifest.IsArchive,
348 optimized_fetch=opt.optimized_fetch,
349 retry_fetches=opt.retry_fetches,
350 prune=opt.prune,
Raman Tennetif32f2432021-04-12 20:57:25 -0700351 clone_filter=self.manifest.CloneFilter,
352 partial_clone_exclude=self.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700353
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500354 output = buf.getvalue()
355 if opt.verbose and output:
356 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700357
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500358 if not success:
359 print('error: Cannot fetch %s from %s'
360 % (project.name, project.remote.url),
361 file=sys.stderr)
362 except Exception as e:
363 print('error: Cannot fetch %s (%s: %s)'
364 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
365 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500366
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500367 finish = time.time()
368 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700369
Mike Frysinger5a033082019-09-23 19:21:20 -0400370 def _Fetch(self, projects, opt, err_event):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500371 ret = True
372
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400373 jobs = opt.jobs_network if opt.jobs_network else self.jobs
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700374 fetched = set()
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500375 pm = Progress('Fetching', len(projects), delay=False)
Roy Lee18afd7f2010-05-09 04:32:08 +0800376
David James89ece422014-01-09 18:51:58 -0800377 objdir_project_map = dict()
378 for project in projects:
379 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500380 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700381
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500382 def _ProcessResults(results_sets):
383 ret = True
384 for results in results_sets:
385 for (success, project, start, finish) in results:
386 self._fetch_times.Set(project, finish - start)
387 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
388 start, finish, success)
389 # Check for any errors before running any more tasks.
390 # ...we'll let existing jobs finish, though.
391 if not success:
392 ret = False
393 else:
394 fetched.add(project.gitdir)
395 pm.update(msg=project.name)
396 if not ret and opt.fail_fast:
397 break
398 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700399
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500400 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400401 if len(projects_list) == 1 or jobs == 1:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500402 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
403 ret = False
404 else:
405 # Favor throughput over responsiveness when quiet. It seems that imap()
406 # will yield results in batches relative to chunksize, so even as the
407 # children finish a sync, we won't see the result until one child finishes
408 # ~chunksize jobs. When using a large --jobs with large chunksize, this
409 # can be jarring as there will be a large initial delay where repo looks
410 # like it isn't doing anything and sits at 0%, but then suddenly completes
411 # a lot of jobs all at once. Since this code is more network bound, we
412 # can accept a bit more CPU overhead with a smaller chunksize so that the
413 # user sees more immediate & continuous feedback.
414 if opt.quiet:
415 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800416 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500417 pm.update(inc=0, msg='warming up')
418 chunksize = 4
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400419 with multiprocessing.Pool(jobs) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500420 results = pool.imap_unordered(
421 functools.partial(self._FetchProjectList, opt),
422 projects_list,
423 chunksize=chunksize)
424 if not _ProcessResults(results):
425 ret = False
426 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800427
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700428 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700429 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700430
Julien Campergue335f5ef2013-10-16 11:02:35 +0200431 if not self.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400432 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200433
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500434 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700435
Mike Frysingerebf04a42021-02-23 20:48:04 -0500436 def _CheckoutOne(self, opt, project):
Xin Li745be2e2019-06-03 11:24:30 -0700437 """Checkout work tree for one project
438
439 Args:
440 opt: Program options returned from optparse. See _Options().
441 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700442
443 Returns:
444 Whether the fetch was successful.
445 """
Xin Li745be2e2019-06-03 11:24:30 -0700446 start = time.time()
447 syncbuf = SyncBuffer(self.manifest.manifestProject.config,
448 detach_head=opt.detach_head)
449 success = False
450 try:
Mike Frysingerebf04a42021-02-23 20:48:04 -0500451 project.Sync_LocalHalf(syncbuf, force_sync=opt.force_sync)
452 success = syncbuf.Finish()
453 except Exception as e:
454 print('error: Cannot checkout %s: %s: %s' %
455 (project.name, type(e).__name__, str(e)),
456 file=sys.stderr)
457 raise
Xin Li745be2e2019-06-03 11:24:30 -0700458
Mike Frysingerebf04a42021-02-23 20:48:04 -0500459 if not success:
460 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
461 finish = time.time()
462 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700463
Mike Frysingerebf04a42021-02-23 20:48:04 -0500464 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700465 """Checkout projects listed in all_projects
466
467 Args:
468 all_projects: List of all projects that should be checked out.
469 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500470 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700471 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500472 ret = True
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400473 jobs = opt.jobs_checkout if opt.jobs_checkout else self.jobs
Xin Li745be2e2019-06-03 11:24:30 -0700474
Mike Frysingerebf04a42021-02-23 20:48:04 -0500475 # Only checkout projects with worktrees.
476 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700477
Mike Frysingerfbb95a42021-02-23 17:34:35 -0500478 pm = Progress('Checking out', len(all_projects))
Xin Li745be2e2019-06-03 11:24:30 -0700479
Mike Frysingerebf04a42021-02-23 20:48:04 -0500480 def _ProcessResults(results):
481 for (success, project, start, finish) in results:
482 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
483 start, finish, success)
484 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500485 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500486 if not success:
487 err_results.append(project.relpath)
488 if opt.fail_fast:
489 return False
490 pm.update(msg=project.name)
491 return True
Xin Li745be2e2019-06-03 11:24:30 -0700492
Mike Frysingerebf04a42021-02-23 20:48:04 -0500493 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400494 if len(all_projects) == 1 or jobs == 1:
Mike Frysingerebf04a42021-02-23 20:48:04 -0500495 if not _ProcessResults(self._CheckoutOne(opt, x) for x in all_projects):
496 ret = False
497 else:
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400498 with multiprocessing.Pool(jobs) as pool:
Mike Frysingerebf04a42021-02-23 20:48:04 -0500499 results = pool.imap_unordered(
500 functools.partial(self._CheckoutOne, opt),
501 all_projects,
502 chunksize=WORKER_BATCH_SIZE)
503 if not _ProcessResults(results):
504 ret = False
505 pool.close()
Xin Li745be2e2019-06-03 11:24:30 -0700506
507 pm.end()
Xin Li745be2e2019-06-03 11:24:30 -0700508
Mike Frysinger511a0e52021-03-12 20:03:51 -0500509 return ret and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500510
Mike Frysinger5a033082019-09-23 19:21:20 -0400511 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger65af2602021-04-08 22:47:44 -0400512 pm = Progress('Garbage collecting', len(projects), delay=False)
513 pm.update(inc=0, msg='prescan')
514
Gabe Black2ff30292014-10-09 17:54:35 -0700515 gc_gitdirs = {}
David James8d201162013-10-11 17:03:19 -0700516 for project in projects:
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500517 # Make sure pruning never kicks in with shared projects.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500518 if (not project.use_git_worktrees and
David Pursehouseaa611a22020-02-20 10:47:26 +0900519 len(project.manifest.GetProjectsWithName(project.name)) > 1):
Anders Björklund2a2da802021-01-18 10:32:36 +0100520 if not opt.quiet:
Mike Frysinger65af2602021-04-08 22:47:44 -0400521 print('\r%s: Shared project %s found, disabling pruning.' %
Anders Björklund2a2da802021-01-18 10:32:36 +0100522 (project.relpath, project.name))
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500523 if git_require((2, 7, 0)):
Mike Frysingerf81c72e2020-02-19 15:50:00 -0500524 project.EnableRepositoryExtension('preciousObjects')
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500525 else:
526 # This isn't perfect, but it's the best we can do with old git.
Mike Frysinger65af2602021-04-08 22:47:44 -0400527 print('\r%s: WARNING: shared projects are unreliable when using old '
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500528 'versions of git; please upgrade to git-2.7.0+.'
529 % (project.relpath,),
530 file=sys.stderr)
531 project.config.SetString('gc.pruneExpire', 'never')
Gabe Black2ff30292014-10-09 17:54:35 -0700532 gc_gitdirs[project.gitdir] = project.bare_git
David James8d201162013-10-11 17:03:19 -0700533
Mike Frysinger65af2602021-04-08 22:47:44 -0400534 pm.update(inc=len(projects) - len(gc_gitdirs), msg='warming up')
535
536 cpu_count = os.cpu_count()
Dave Borowitz18857212012-10-23 17:02:59 -0700537 jobs = min(self.jobs, cpu_count)
538
539 if jobs < 2:
Gabe Black2ff30292014-10-09 17:54:35 -0700540 for bare_git in gc_gitdirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400541 pm.update(msg=bare_git._project.name)
David James8d201162013-10-11 17:03:19 -0700542 bare_git.gc('--auto')
Mike Frysinger65af2602021-04-08 22:47:44 -0400543 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700544 return
545
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400546 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700547
548 threads = set()
549 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700550
David James8d201162013-10-11 17:03:19 -0700551 def GC(bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400552 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700553 try:
554 try:
David James8d201162013-10-11 17:03:19 -0700555 bare_git.gc('--auto', config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700556 except GitError:
557 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900558 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700559 err_event.set()
560 raise
561 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400562 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700563 sem.release()
564
Gabe Black2ff30292014-10-09 17:54:35 -0700565 for bare_git in gc_gitdirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500566 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700567 break
568 sem.acquire()
David James8d201162013-10-11 17:03:19 -0700569 t = _threading.Thread(target=GC, args=(bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700570 t.daemon = True
571 threads.add(t)
572 t.start()
573
574 for t in threads:
575 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400576 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700577
Tim Kilbourn07669002013-03-08 15:02:49 -0800578 def _ReloadManifest(self, manifest_name=None):
579 if manifest_name:
580 # Override calls _Unload already
581 self.manifest.Override(manifest_name)
582 else:
583 self.manifest._Unload()
584
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500585 def UpdateProjectList(self, opt):
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700586 new_project_paths = []
Colin Cross5acde752012-03-28 20:15:45 -0700587 for project in self.GetProjects(None, missing_ok=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700588 if project.relpath:
589 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700590 file_name = 'project.list'
Mike Frysingere3315bb2021-02-09 23:45:28 -0500591 file_path = os.path.join(self.repodir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700592 old_project_paths = []
593
594 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500595 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700596 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800597 # In reversed order, so subfolders are deleted before parent folder.
598 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700599 if not path:
600 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700601 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900602 # If the path has already been deleted, we don't need to do it
Dan Willemsen43507912016-09-01 16:26:02 -0700603 gitdir = os.path.join(self.manifest.topdir, path, '.git')
604 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900605 project = Project(
David Pursehouseabdf7502020-02-12 14:58:39 +0900606 manifest=self.manifest,
607 name=path,
608 remote=RemoteSpec('origin'),
609 gitdir=gitdir,
610 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500611 use_git_worktrees=os.path.isfile(gitdir),
David Pursehouseabdf7502020-02-12 14:58:39 +0900612 worktree=os.path.join(self.manifest.topdir, path),
613 relpath=path,
614 revisionExpr='HEAD',
615 revisionId=None,
616 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500617 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900618 quiet=opt.quiet,
619 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400620 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700621
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700622 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500623 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700624 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700625 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700626 return 0
627
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400628 def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
629 if not self.manifest.manifest_server:
630 print('error: cannot smart sync: no manifest server defined in '
631 'manifest', file=sys.stderr)
632 sys.exit(1)
633
634 manifest_server = self.manifest.manifest_server
635 if not opt.quiet:
636 print('Using manifest server %s' % manifest_server)
637
David Pursehouseeeff3532020-02-12 11:24:10 +0900638 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400639 username = None
640 password = None
641 if opt.manifest_server_username and opt.manifest_server_password:
642 username = opt.manifest_server_username
643 password = opt.manifest_server_password
644 else:
645 try:
646 info = netrc.netrc()
647 except IOError:
648 # .netrc file does not exist or could not be opened
649 pass
650 else:
651 try:
652 parse_result = urllib.parse.urlparse(manifest_server)
653 if parse_result.hostname:
654 auth = info.authenticators(parse_result.hostname)
655 if auth:
656 username, _account, password = auth
657 else:
658 print('No credentials found for %s in .netrc'
659 % parse_result.hostname, file=sys.stderr)
660 except netrc.NetrcParseError as e:
661 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
662
663 if (username and password):
664 manifest_server = manifest_server.replace('://', '://%s:%s@' %
665 (username, password),
666 1)
667
668 transport = PersistentTransport(manifest_server)
669 if manifest_server.startswith('persistent-'):
670 manifest_server = manifest_server[len('persistent-'):]
671
672 try:
673 server = xmlrpc.client.Server(manifest_server, transport=transport)
674 if opt.smart_sync:
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800675 branch = self._GetBranch()
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400676
Mike Frysinger56ce3462019-12-04 19:30:48 -0500677 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500678 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400679 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500680 elif ('TARGET_PRODUCT' in os.environ and
681 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500682 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
683 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400684 [success, manifest_str] = server.GetApprovedManifest(branch, target)
685 else:
686 [success, manifest_str] = server.GetApprovedManifest(branch)
687 else:
688 assert(opt.smart_tag)
689 [success, manifest_str] = server.GetManifest(opt.smart_tag)
690
691 if success:
692 manifest_name = os.path.basename(smart_sync_manifest_path)
693 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500694 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400695 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400696 except IOError as e:
697 print('error: cannot write manifest to %s:\n%s'
698 % (smart_sync_manifest_path, e),
699 file=sys.stderr)
700 sys.exit(1)
701 self._ReloadManifest(manifest_name)
702 else:
703 print('error: manifest server RPC call failed: %s' %
704 manifest_str, file=sys.stderr)
705 sys.exit(1)
706 except (socket.error, IOError, xmlrpc.client.Fault) as e:
707 print('error: cannot connect to manifest server %s:\n%s'
708 % (self.manifest.manifest_server, e), file=sys.stderr)
709 sys.exit(1)
710 except xmlrpc.client.ProtocolError as e:
711 print('error: cannot connect to manifest server %s:\n%d %s'
712 % (self.manifest.manifest_server, e.errcode, e.errmsg),
713 file=sys.stderr)
714 sys.exit(1)
715
716 return manifest_name
717
Mike Frysingerfb527e32019-08-27 02:34:32 -0400718 def _UpdateManifestProject(self, opt, mp, manifest_name):
719 """Fetch & update the local manifest project."""
720 if not opt.local_only:
721 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -0500722 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700723 current_branch_only=self._GetCurrentBranchOnly(opt),
Erwan Yvindc5c4d12019-06-18 13:49:12 +0200724 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500725 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400726 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600727 retry_fetches=opt.retry_fetches,
Mike Frysingerfb527e32019-08-27 02:34:32 -0400728 submodules=self.manifest.HasSubmodules,
Raman Tennetif32f2432021-04-12 20:57:25 -0700729 clone_filter=self.manifest.CloneFilter,
730 partial_clone_exclude=self.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -0400731 finish = time.time()
732 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
733 start, finish, success)
734
735 if mp.HasChanges:
736 syncbuf = SyncBuffer(mp.config)
737 start = time.time()
738 mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
739 clean = syncbuf.Finish()
740 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
741 start, time.time(), clean)
742 if not clean:
743 sys.exit(1)
744 self._ReloadManifest(opt.manifest_name)
745 if opt.jobs is None:
746 self.jobs = self.manifest.default.sync_j
747
Mike Frysingerae6cb082019-08-27 01:10:59 -0400748 def ValidateOptions(self, opt, args):
749 if opt.force_broken:
750 print('warning: -f/--force-broken is now the default behavior, and the '
751 'options are deprecated', file=sys.stderr)
752 if opt.network_only and opt.detach_head:
753 self.OptionParser.error('cannot combine -n and -d')
754 if opt.network_only and opt.local_only:
755 self.OptionParser.error('cannot combine -n and -l')
756 if opt.manifest_name and opt.smart_sync:
757 self.OptionParser.error('cannot combine -m and -s')
758 if opt.manifest_name and opt.smart_tag:
759 self.OptionParser.error('cannot combine -m and -t')
760 if opt.manifest_server_username or opt.manifest_server_password:
761 if not (opt.smart_sync or opt.smart_tag):
762 self.OptionParser.error('-u and -p may only be combined with -s or -t')
763 if None in [opt.manifest_server_username, opt.manifest_server_password]:
764 self.OptionParser.error('both -u and -p must be given')
765
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700766 def Execute(self, opt, args):
Roy Lee18afd7f2010-05-09 04:32:08 +0800767 if opt.jobs:
768 self.jobs = opt.jobs
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700769 if self.jobs > 1:
770 soft_limit, _ = _rlimit_nofile()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400771 self.jobs = min(self.jobs, (soft_limit - 5) // 3)
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -0700772
Mike Frysinger521d01b2020-02-17 01:51:49 -0500773 opt.quiet = opt.output_mode is False
774 opt.verbose = opt.output_mode is True
775
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500776 if opt.manifest_name:
777 self.manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700778
Chirayu Desaia892b102013-06-11 14:18:46 +0530779 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +0900780 smart_sync_manifest_path = os.path.join(
David Pursehouseabdf7502020-02-12 14:58:39 +0900781 self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +0530782
Xin Lid79a4bc2020-05-20 16:03:45 -0700783 if opt.clone_bundle is None:
784 opt.clone_bundle = self.manifest.CloneBundle
785
Victor Boivie08c880d2011-04-19 10:32:52 +0200786 if opt.smart_sync or opt.smart_tag:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400787 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
788 else:
David Pursehouse59b41742015-05-07 14:36:09 +0900789 if os.path.isfile(smart_sync_manifest_path):
790 try:
Renaud Paquay010fed72016-11-11 14:25:29 -0800791 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +0900792 except OSError as e:
793 print('error: failed to remove existing smart sync override manifest: %s' %
794 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700795
Mike Frysinger5a033082019-09-23 19:21:20 -0400796 err_event = _threading.Event()
797
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700798 rp = self.manifest.repoProject
799 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -0500800 cb = rp.CurrentBranch
801 if cb:
802 base = rp.GetBranch(cb).merge
803 if not base or not base.startswith('refs/heads/'):
804 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -0400805 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -0500806 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700807
808 mp = self.manifest.manifestProject
809 mp.PreSync()
810
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800811 if opt.repo_upgraded:
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700812 _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800813
Fredrik de Grootcc960972019-11-22 09:04:31 +0100814 if not opt.mp_update:
815 print('Skipping update of local manifest project.')
816 else:
817 self._UpdateManifestProject(opt, mp, manifest_name)
Simran Basib9a1b732015-08-20 12:19:28 -0700818
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700819 if self._UseSuperproject(opt):
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800820 manifest_name = self._UpdateProjectsRevisionId(opt, args)
821
Simran Basib9a1b732015-08-20 12:19:28 -0700822 if self.gitc_manifest:
823 gitc_manifest_projects = self.GetProjects(args,
Simran Basib9a1b732015-08-20 12:19:28 -0700824 missing_ok=True)
825 gitc_projects = []
826 opened_projects = []
827 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700828 if project.relpath in self.gitc_manifest.paths and \
829 self.gitc_manifest.paths[project.relpath].old_revision:
830 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700831 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700832 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -0700833
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700834 if not args:
835 gitc_projects = None
836
837 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -0700838 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700839 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
840 if manifest_name:
841 manifest.Override(manifest_name)
842 else:
843 manifest.Override(self.manifest.manifestFile)
844 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
845 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -0700846 gitc_projects)
847 print('GITC client successfully synced.')
848
849 # The opened projects need to be synced as normal, therefore we
850 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -0700851 # TODO: make this more reliable -- if there's a project name/path overlap,
852 # this may choose the wrong project.
David Pursehouse3bcd3052017-07-10 22:42:22 +0900853 args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
854 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -0700855 if not args:
856 return
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800857 all_projects = self.GetProjects(args,
858 missing_ok=True,
859 submodules_ok=opt.fetch_submodules)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700860
Mike Frysinger5a033082019-09-23 19:21:20 -0400861 err_network_sync = False
862 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -0400863
Dave Borowitz67700e92012-10-23 15:00:54 -0700864 self._fetch_times = _FetchTimes(self.manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700865 if not opt.local_only:
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700866 to_fetch = []
867 now = time.time()
Dave Borowitz67700e92012-10-23 15:00:54 -0700868 if _ONE_DAY_S <= (now - rp.LastFetch):
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700869 to_fetch.append(rp)
David Pursehouse8a68ff92012-09-24 12:15:13 +0900870 to_fetch.extend(all_projects)
Dave Borowitz67700e92012-10-23 15:00:54 -0700871 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Shawn O. Pearcef6906872009-04-18 10:49:00 -0700872
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500873 success, fetched = self._Fetch(to_fetch, opt, err_event)
874 if not success:
875 err_event.set()
Mike Frysinger5a033082019-09-23 19:21:20 -0400876
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500877 _PostRepoFetch(rp, opt.repo_verify)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700878 if opt.network_only:
879 # bail out now; the rest touches the working tree
Mike Frysingerbe24a542021-02-23 03:24:12 -0500880 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400881 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
882 sys.exit(1)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700883 return
884
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800885 # Iteratively fetch missing and/or nested unregistered submodules
886 previously_missing_set = set()
887 while True:
Victor Boivie53a6c5d2013-03-19 12:20:52 +0100888 self._ReloadManifest(manifest_name)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800889 all_projects = self.GetProjects(args,
890 missing_ok=True,
891 submodules_ok=opt.fetch_submodules)
892 missing = []
893 for project in all_projects:
894 if project.gitdir not in fetched:
895 missing.append(project)
896 if not missing:
897 break
898 # Stop us from non-stopped fetching actually-missing repos: If set of
899 # missing repos has not been changed from last fetch, we break.
900 missing_set = set(p.name for p in missing)
901 if previously_missing_set == missing_set:
902 break
903 previously_missing_set = missing_set
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500904 success, new_fetched = self._Fetch(to_fetch, opt, err_event)
905 if not success:
906 err_event.set()
907 fetched.update(new_fetched)
Mike Frysinger5a033082019-09-23 19:21:20 -0400908
909 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500910 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400911 err_network_sync = True
912 if opt.fail_fast:
913 print('\nerror: Exited sync due to fetch errors.\n'
914 'Local checkouts *not* updated. Resolve network issues & '
915 'retry.\n'
916 '`repo sync -l` will update some local checkouts.',
917 file=sys.stderr)
918 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800919
Julien Campergue335f5ef2013-10-16 11:02:35 +0200920 if self.manifest.IsMirror or self.manifest.IsArchive:
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -0700921 # bail out now, we have no working tree
922 return
923
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500924 if self.UpdateProjectList(opt):
Mike Frysinger5a033082019-09-23 19:21:20 -0400925 err_event.set()
926 err_update_projects = True
927 if opt.fail_fast:
928 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
929 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700930
Mike Frysinger5a033082019-09-23 19:21:20 -0400931 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -0500932 # NB: We don't exit here because this is the last step.
933 err_checkout = not self._Checkout(all_projects, opt, err_results)
934 if err_checkout:
935 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700936
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700937 # If there's a notice that's supposed to print at the end of the sync, print
938 # it now...
939 if self.manifest.notice:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700940 print(self.manifest.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -0700941
Mike Frysinger5a033082019-09-23 19:21:20 -0400942 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -0500943 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -0400944 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
945 if err_network_sync:
946 print('error: Downloading network changes failed.', file=sys.stderr)
947 if err_update_projects:
948 print('error: Updating local project lists failed.', file=sys.stderr)
949 if err_checkout:
950 print('error: Checking out local projects failed.', file=sys.stderr)
951 if err_results:
952 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
953 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
954 file=sys.stderr)
955 sys.exit(1)
956
Mike Frysingere19d9e12020-02-12 11:23:32 -0500957 if not opt.quiet:
958 print('repo sync has finished successfully.')
959
David Pursehouse819827a2020-02-12 15:20:19 +0900960
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700961def _PostRepoUpgrade(manifest, quiet=False):
Conley Owens094cdbe2014-01-30 15:09:59 -0800962 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -0700963 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -0700964 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -0800965 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700966 if project.Exists:
967 project.PostRepoUpgrade()
968
David Pursehouse819827a2020-02-12 15:20:19 +0900969
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500970def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700971 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700972 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -0400973 wrapper = Wrapper()
974 try:
975 rev = rp.bare_git.describe(rp.GetRevisionId())
976 except GitError:
977 rev = None
978 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
979 # See if we're held back due to missing signed tag.
980 current_revid = rp.bare_git.rev_parse('HEAD')
981 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
982 if current_revid != new_revid:
983 # We want to switch to the new rev, but also not trash any uncommitted
984 # changes. This helps with local testing/hacking.
985 # If a local change has been made, we will throw that away.
986 # We also have to make sure this will switch to an older commit if that's
987 # the latest tag in order to support release rollback.
988 try:
989 rp.work_git.reset('--keep', new_rev)
990 except GitError as e:
991 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -0700992 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700993 raise RepoChangedException(['--repo-upgraded'])
994 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700995 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -0700996 else:
997 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -0700998 print('repo version %s is current' % rp.work_git.describe(HEAD),
999 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001000
David Pursehouse819827a2020-02-12 15:20:19 +09001001
Dave Borowitz67700e92012-10-23 15:00:54 -07001002class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001003 _ALPHA = 0.5
1004
Dave Borowitz67700e92012-10-23 15:00:54 -07001005 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001006 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001007 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001008 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001009
1010 def Get(self, project):
1011 self._Load()
1012 return self._times.get(project.name, _ONE_DAY_S)
1013
1014 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001015 self._Load()
1016 name = project.name
1017 old = self._times.get(name, t)
1018 self._seen.add(name)
1019 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001020 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001021
1022 def _Load(self):
1023 if self._times is None:
1024 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001025 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001026 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001027 except (IOError, ValueError):
1028 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001029 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001030 except OSError:
1031 pass
1032 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001033
1034 def Save(self):
1035 if self._times is None:
1036 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001037
1038 to_delete = []
1039 for name in self._times:
1040 if name not in self._seen:
1041 to_delete.append(name)
1042 for name in to_delete:
1043 del self._times[name]
1044
Dave Borowitz67700e92012-10-23 15:00:54 -07001045 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001046 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001047 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001048 except (IOError, TypeError):
1049 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001050 platform_utils.remove(self._path)
Anthony King85b24ac2014-05-06 15:57:48 +01001051 except OSError:
1052 pass
Dan Willemsen0745bb22015-08-17 13:41:45 -07001053
1054# This is a replacement for xmlrpc.client.Transport using urllib2
1055# and supporting persistent-http[s]. It cannot change hosts from
1056# request to request like the normal transport, the real url
1057# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001058
1059
Dan Willemsen0745bb22015-08-17 13:41:45 -07001060class PersistentTransport(xmlrpc.client.Transport):
1061 def __init__(self, orig_host):
1062 self.orig_host = orig_host
1063
1064 def request(self, host, handler, request_body, verbose=False):
1065 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1066 # Python doesn't understand cookies with the #HttpOnly_ prefix
1067 # Since we're only using them for HTTP, copy the file temporarily,
1068 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001069 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001070 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001071 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001072 try:
1073 with open(cookiefile) as f:
1074 for line in f:
1075 if line.startswith("#HttpOnly_"):
1076 line = line[len("#HttpOnly_"):]
1077 tmpcookiefile.write(line)
1078 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001079
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001080 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001081 try:
1082 cookiejar.load()
1083 except cookielib.LoadError:
1084 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001085 finally:
1086 tmpcookiefile.close()
1087 else:
1088 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001089
1090 proxyhandler = urllib.request.ProxyHandler
1091 if proxy:
1092 proxyhandler = urllib.request.ProxyHandler({
1093 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001094 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001095
1096 opener = urllib.request.build_opener(
1097 urllib.request.HTTPCookieProcessor(cookiejar),
1098 proxyhandler)
1099
1100 url = urllib.parse.urljoin(self.orig_host, handler)
1101 parse_results = urllib.parse.urlparse(url)
1102
1103 scheme = parse_results.scheme
1104 if scheme == 'persistent-http':
1105 scheme = 'http'
1106 if scheme == 'persistent-https':
1107 # If we're proxying through persistent-https, use http. The
1108 # proxy itself will do the https.
1109 if proxy:
1110 scheme = 'http'
1111 else:
1112 scheme = 'https'
1113
1114 # Parse out any authentication information using the base class
1115 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1116
1117 url = urllib.parse.urlunparse((
1118 scheme,
1119 host,
1120 parse_results.path,
1121 parse_results.params,
1122 parse_results.query,
1123 parse_results.fragment))
1124
1125 request = urllib.request.Request(url, request_body)
1126 if extra_headers is not None:
1127 for (name, header) in extra_headers:
1128 request.add_header(name, header)
1129 request.add_header('Content-Type', 'text/xml')
1130 try:
1131 response = opener.open(request)
1132 except urllib.error.HTTPError as e:
1133 if e.code == 501:
1134 # We may have been redirected through a login process
1135 # but our POST turned into a GET. Retry.
1136 response = opener.open(request)
1137 else:
1138 raise
1139
1140 p, u = xmlrpc.client.getparser()
1141 while 1:
1142 data = response.read(1024)
1143 if not data:
1144 break
1145 p.feed(data)
1146 p.close()
1147 return u.close()
1148
1149 def close(self):
1150 pass