blob: caa5588004cbb6cf147f57b1a8f4e8346c83a8cc [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
LaMont Jonesbdcba7d2022-04-11 22:50:11 +000015import collections
Mike Frysingerebf04a42021-02-23 20:48:04 -050016import functools
Mike Frysingeracf63b22019-06-13 02:24:21 -040017import http.cookiejar as cookielib
Mike Frysinger7b586f22021-02-23 18:38:39 -050018import io
Anthony King85b24ac2014-05-06 15:57:48 +010019import json
Mike Frysingerebf04a42021-02-23 20:48:04 -050020import multiprocessing
David Pursehouse86d973d2012-08-24 10:21:02 +090021import netrc
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070022from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import os
LaMont Jones891e8f72022-09-08 20:17:58 +000024import shutil
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070025import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070026import 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
Mike Frysinger5951e302022-05-20 23:34:44 -040032import xml.parsers.expat
Mike Frysingeracf63b22019-06-13 02:24:21 -040033import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Roy Lee18afd7f2010-05-09 04:32:08 +080035try:
36 import threading as _threading
37except ImportError:
38 import dummy_threading as _threading
39
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070040try:
41 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090042
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070043 def _rlimit_nofile():
44 return resource.getrlimit(resource.RLIMIT_NOFILE)
45except ImportError:
46 def _rlimit_nofile():
47 return (256, 256)
48
David Rileye0684ad2017-04-05 00:02:59 -070049import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040050from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090051from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090052from git_refs import R_HEADS, HEAD
Raman Tenneti6a872c92021-01-14 19:17:50 -080053import git_superproject
Simran Basibdb52712015-08-10 13:23:23 -070054import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070055from project import Project
56from project import RemoteSpec
Mike Frysinger355f4392022-07-20 17:15:29 -040057from command import Command, DEFAULT_LOCAL_JOBS, MirrorSafeCommand, WORKER_BATCH_SIZE
Raman Tenneti1fd7bc22021-02-04 14:39:38 -080058from error import RepoChangedException, GitError, ManifestParseError
Renaud Paquaya65adf72016-11-03 10:37:53 -070059import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070060from project import SyncBuffer
Shawn O. Pearce68194f42009-04-10 16:48:52 -070061from progress import Progress
LaMont Jones891e8f72022-09-08 20:17:58 +000062from repo_trace import IsTrace, Trace
Mike Frysinger19e409c2021-05-05 19:44:35 -040063import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080064from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070065from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070066
Dave Borowitz67700e92012-10-23 15:00:54 -070067_ONE_DAY_S = 24 * 60 * 60
LaMont Jones891e8f72022-09-08 20:17:58 +000068# Env var to implicitly turn off object backups.
69REPO_BACKUP_OBJECTS = 'REPO_BACKUP_OBJECTS'
70
71_BACKUP_OBJECTS = os.environ.get(REPO_BACKUP_OBJECTS) != '0'
Dave Borowitz67700e92012-10-23 15:00:54 -070072
David Pursehouse819827a2020-02-12 15:20:19 +090073
Shawn O. Pearcec95583b2009-03-03 17:47:06 -080074class Sync(Command, MirrorSafeCommand):
Mike Frysinger4f210542021-06-14 16:05:19 -040075 COMMON = True
LaMont Jonesbdcba7d2022-04-11 22:50:11 +000076 MULTI_MANIFEST_SUPPORT = True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070077 helpSummary = "Update working tree to the latest revision"
78 helpUsage = """
79%prog [<project>...]
80"""
81 helpDescription = """
82The '%prog' command synchronizes local project directories
83with the remote repositories specified in the manifest. If a local
84project does not yet exist, it will clone a new local directory from
85the remote repository and set up tracking branches as specified in
86the manifest. If the local project already exists, '%prog'
87will update the remote branches and rebase any new local changes
88on top of the new remote changes.
89
90'%prog' will synchronize all projects listed at the command
91line. Projects can be specified either by name, or by a relative
92or absolute path to the project's local directory. If no projects
93are specified, '%prog' will synchronize all projects listed in
94the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -070095
96The -d/--detach option can be used to switch specified projects
97back to the manifest revision. This option is especially helpful
98if the project is currently on a topic branch, but the manifest
99revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700100
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700101The -s/--smart-sync option can be used to sync to a known good
102build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200103manifest. The -t/--smart-tag option is similar and allows you to
104specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700105
David Pursehousecf76b1b2012-09-14 10:31:42 +0900106The -u/--manifest-server-username and -p/--manifest-server-password
107options can be used to specify a username and password to authenticate
108with the manifest server when using the -s or -t option.
109
110If -u and -p are not specified when using the -s or -t option, '%prog'
111will attempt to read authentication credentials for the manifest server
112from the user's .netrc file.
113
114'%prog' will not use authentication credentials from -u/-p or .netrc
115if the manifest server specified in the manifest file already includes
116credentials.
117
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400118By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400119to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500120
Kevin Degiabaa7f32014-11-12 11:27:45 -0700121The --force-sync option can be used to overwrite existing git
122directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900123object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700124refs may be removed when overwriting.
125
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500126The --force-remove-dirty option can be used to remove previously used
127projects with uncommitted changes. WARNING: This may cause data to be
128lost since uncommitted changes may be removed with projects that no longer
129exist in the manifest.
130
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700131The --no-clone-bundle option disables any attempt to use
132$URL/clone.bundle to bootstrap a new Git repository from a
133resumeable bundle file on a content delivery network. This
134may be necessary if there are problems with the local Python
135HTTP client or proxy configuration, but the Git binary works.
136
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800137The --fetch-submodules option enables fetching Git submodules
138of a project from server.
139
David Pursehousef2fad612015-01-29 14:36:28 +0900140The -c/--current-branch option can be used to only fetch objects that
141are on the branch specified by a project's revision.
142
David Pursehouseb1553542014-09-04 21:28:09 +0900143The --optimized-fetch option can be used to only fetch projects that
144are fixed to a sha1 revision if the sha1 revision does not already
145exist locally.
146
David Pursehouse74cfd272015-10-14 10:50:15 +0900147The --prune option can be used to remove any refs that no longer
148exist on the remote.
149
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400150# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700151
152If at least one project remote URL uses an SSH connection (ssh://,
153git+ssh://, or user@host:path syntax) repo will automatically
154enable the SSH ControlMaster option when connecting to that host.
155This feature permits other projects in the same '%prog' session to
156reuse the same SSH tunnel, saving connection setup overheads.
157
158To disable this behavior on UNIX platforms, set the GIT_SSH
159environment variable to 'ssh'. For example:
160
161 export GIT_SSH=ssh
162 %prog
163
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400164# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700165
166This feature is automatically disabled on Windows, due to the lack
167of UNIX domain socket support.
168
169This feature is not compatible with url.insteadof rewrites in the
170user's ~/.gitconfig. '%prog' is currently not able to perform the
171rewrite early enough to establish the ControlMaster tunnel.
172
173If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
174later is required to fix a server side protocol bug.
175
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700176"""
Mike Frysinger355f4392022-07-20 17:15:29 -0400177 # A value of 0 means we want parallel jobs, but we'll determine the default
178 # value later on.
179 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700180
Mike Frysinger9180a072021-04-13 14:57:40 -0400181 def _Options(self, p, show_smart=True):
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400182 p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
Mike Frysinger355f4392022-07-20 17:15:29 -0400183 help='number of network jobs to run in parallel (defaults to --jobs or 1)')
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400184 p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
Mike Frysinger355f4392022-07-20 17:15:29 -0400185 help='number of local checkout jobs to run in parallel (defaults to --jobs or '
186 f'{DEFAULT_LOCAL_JOBS})')
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400187
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500188 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200189 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400190 help='obsolete option (to be deleted in the future)')
191 p.add_option('--fail-fast',
192 dest='fail_fast', action='store_true',
193 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700194 p.add_option('--force-sync',
195 dest='force_sync', action='store_true',
196 help="overwrite an existing git directory if it needs to "
197 "point to a different object directory. WARNING: this "
198 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500199 p.add_option('--force-remove-dirty',
200 dest='force_remove_dirty', action='store_true',
201 help="force remove projects with uncommitted modifications if "
202 "projects no longer exist in the manifest. "
203 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900204 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700205 dest='local_only', action='store_true',
206 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900207 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100208 dest='mp_update', action='store_false', default='true',
209 help='use the existing manifest checkout as-is. '
210 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900211 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700212 dest='network_only', action='store_true',
213 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900214 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700215 dest='detach_head', action='store_true',
216 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900217 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700218 dest='current_branch_only', action='store_true',
219 help='fetch only current branch from server')
Mike Frysinger73561142021-05-03 01:10:09 -0400220 p.add_option('--no-current-branch',
221 dest='current_branch_only', action='store_false',
222 help='fetch all branches from server')
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500223 p.add_option('-m', '--manifest-name',
224 dest='manifest_name',
225 help='temporary manifest to use for this sync', metavar='NAME.xml')
Xin Lid79a4bc2020-05-20 16:03:45 -0700226 p.add_option('--clone-bundle', action='store_true',
227 help='enable use of /clone.bundle on HTTP/HTTPS')
228 p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700229 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800230 p.add_option('-u', '--manifest-server-username', action='store',
231 dest='manifest_server_username',
232 help='username to authenticate with the manifest server')
233 p.add_option('-p', '--manifest-server-password', action='store',
234 dest='manifest_server_password',
235 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800236 p.add_option('--fetch-submodules',
237 dest='fetch_submodules', action='store_true',
238 help='fetch submodules from server')
Raman Tenneti6a872c92021-01-14 19:17:50 -0800239 p.add_option('--use-superproject', action='store_true',
Raman Tenneti62517292021-11-01 14:49:16 -0700240 help='use the manifest superproject to sync projects; implies -c')
Raman Tenneti23ea7542021-05-07 14:01:54 -0700241 p.add_option('--no-use-superproject', action='store_false',
242 dest='use_superproject',
243 help='disable use of manifest superprojects')
Mike Frysinger2273f462021-11-05 15:10:33 -0400244 p.add_option('--tags', action='store_true',
Mike Frysingerd68ed632021-05-03 01:21:35 -0400245 help='fetch tags')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700246 p.add_option('--no-tags',
Mike Frysingerd68ed632021-05-03 01:21:35 -0400247 dest='tags', action='store_false',
Mike Frysinger2273f462021-11-05 15:10:33 -0400248 help="don't fetch tags (default)")
David Pursehouseb1553542014-09-04 21:28:09 +0900249 p.add_option('--optimized-fetch',
250 dest='optimized_fetch', action='store_true',
251 help='only fetch projects fixed to sha1 if revision does not exist locally')
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600252 p.add_option('--retry-fetches',
253 default=0, action='store', type='int',
254 help='number of times to retry fetches on transient errors')
Mike Frysinger0531a622021-11-05 15:22:01 -0400255 p.add_option('--prune', action='store_true',
256 help='delete refs that no longer exist on the remote (default)')
257 p.add_option('--no-prune', dest='prune', action='store_false',
258 help='do not delete refs that no longer exist on the remote')
Nico Sallembien6623b212010-05-11 12:57:01 -0700259 if show_smart:
260 p.add_option('-s', '--smart-sync',
261 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900262 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200263 p.add_option('-t', '--smart-tag',
264 dest='smart_tag', action='store',
265 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700266
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700267 g = p.add_option_group('repo Version options')
268 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500269 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700270 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700271 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800272 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700273 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700274
LaMont Jonesa46047a2022-04-07 21:57:06 +0000275 def _GetBranch(self, manifest_project):
276 """Returns the branch name for getting the approved smartsync manifest.
277
278 Args:
279 manifest_project: the manifestProject to query.
280 """
281 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800282 branch = b.merge
283 if branch.startswith(R_HEADS):
284 branch = branch[len(R_HEADS):]
285 return branch
286
LaMont Jonesa46047a2022-04-07 21:57:06 +0000287 def _GetCurrentBranchOnly(self, opt, manifest):
Daniel Anderssond52ca422022-04-01 12:55:38 +0200288 """Returns whether current-branch or use-superproject options are enabled.
289
LaMont Jonesa46047a2022-04-07 21:57:06 +0000290 Args:
291 opt: Program options returned from optparse. See _Options().
292 manifest: The manifest to use.
293
Daniel Anderssond52ca422022-04-01 12:55:38 +0200294 Returns:
295 True if a superproject is requested, otherwise the value of the
296 current_branch option (True, False or None).
297 """
LaMont Jonesa46047a2022-04-07 21:57:06 +0000298 return git_superproject.UseSuperproject(opt.use_superproject, manifest) or opt.current_branch_only
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700299
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000300 def _UpdateProjectsRevisionId(self, opt, args, superproject_logging_data,
301 manifest):
302 """Update revisionId of projects with the commit hash from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800303
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000304 This function updates each project's revisionId with the commit hash from
305 the superproject. It writes the updated manifest into a file and reloads
306 the manifest from it. When appropriate, sub manifests are also processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800307
308 Args:
309 opt: Program options returned from optparse. See _Options().
310 args: Arguments to pass to GetProjects. See the GetProjects
311 docstring for details.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000312 superproject_logging_data: A dictionary of superproject data to log.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000313 manifest: The manifest to use.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800314 """
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000315 have_superproject = manifest.superproject or any(
316 m.superproject for m in manifest.all_children)
317 if not have_superproject:
318 return
319
LaMont Jonesff6b1da2022-06-01 21:03:34 +0000320 if opt.local_only and manifest.superproject:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000321 manifest_path = manifest.superproject.manifest_path
Raman Tennetiae86a462021-07-27 08:54:59 -0700322 if manifest_path:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000323 self._ReloadManifest(manifest_path, manifest)
324 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700325
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800326 all_projects = self.GetProjects(args,
327 missing_ok=True,
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000328 submodules_ok=opt.fetch_submodules,
329 manifest=manifest,
330 all_manifests=not opt.this_manifest_only)
331
332 per_manifest = collections.defaultdict(list)
333 manifest_paths = {}
334 if opt.this_manifest_only:
335 per_manifest[manifest.path_prefix] = all_projects
Raman Tenneti784e16f2021-06-11 17:29:45 -0700336 else:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000337 for p in all_projects:
338 per_manifest[p.manifest.path_prefix].append(p)
339
340 superproject_logging_data = {}
341 need_unload = False
342 for m in self.ManifestList(opt):
343 if not m.path_prefix in per_manifest:
344 continue
345 use_super = git_superproject.UseSuperproject(opt.use_superproject, m)
346 if superproject_logging_data:
347 superproject_logging_data['multimanifest'] = True
348 superproject_logging_data.update(
349 superproject=use_super,
350 haslocalmanifests=bool(m.HasLocalManifests),
351 hassuperprojecttag=bool(m.superproject),
352 )
353 if use_super and (m.IsMirror or m.IsArchive):
354 # Don't use superproject, because we have no working tree.
355 use_super = False
356 superproject_logging_data['superproject'] = False
357 superproject_logging_data['noworktree'] = True
358 if opt.use_superproject is not False:
359 print(f'{m.path_prefix}: not using superproject because there is no '
360 'working tree.')
361
362 if not use_super:
363 continue
364 m.superproject.SetQuiet(opt.quiet)
365 print_messages = git_superproject.PrintMessages(opt.use_superproject, m)
366 m.superproject.SetPrintMessages(print_messages)
367 update_result = m.superproject.UpdateProjectsRevisionId(
368 per_manifest[m.path_prefix], git_event_log=self.git_event_log)
369 manifest_path = update_result.manifest_path
370 superproject_logging_data['updatedrevisionid'] = bool(manifest_path)
371 if manifest_path:
372 m.SetManifestOverride(manifest_path)
373 need_unload = True
374 else:
375 if print_messages:
376 print(f'{m.path_prefix}: warning: Update of revisionId from '
377 'superproject has failed, repo sync will not use superproject '
378 'to fetch the source. ',
379 'Please resync with the --no-use-superproject option to avoid '
380 'this repo warning.',
381 file=sys.stderr)
382 if update_result.fatal and opt.use_superproject is not None:
383 sys.exit(1)
384 if need_unload:
385 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800386
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500387 def _FetchProjectList(self, opt, projects):
388 """Main function of the fetch worker.
389
390 The projects we're given share the same underlying git object store, so we
391 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800392
David James8d201162013-10-11 17:03:19 -0700393 Delegates most of the work to _FetchHelper.
394
395 Args:
396 opt: Program options returned from optparse. See _Options().
397 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700398 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500399 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700400
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500401 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700402 """Fetch git objects for a single project.
403
David Pursehousec1b86a22012-11-14 11:36:51 +0900404 Args:
405 opt: Program options returned from optparse. See _Options().
406 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700407
408 Returns:
409 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900410 """
David Rileye0684ad2017-04-05 00:02:59 -0700411 start = time.time()
412 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500413 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900414 try:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500415 success = project.Sync_NetworkHalf(
416 quiet=opt.quiet,
417 verbose=opt.verbose,
418 output_redir=buf,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000419 current_branch_only=self._GetCurrentBranchOnly(opt, project.manifest),
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500420 force_sync=opt.force_sync,
421 clone_bundle=opt.clone_bundle,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000422 tags=opt.tags, archive=project.manifest.IsArchive,
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500423 optimized_fetch=opt.optimized_fetch,
424 retry_fetches=opt.retry_fetches,
425 prune=opt.prune,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400426 ssh_proxy=self.ssh_proxy,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000427 clone_filter=project.manifest.CloneFilter,
428 partial_clone_exclude=project.manifest.PartialCloneExclude)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700429
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500430 output = buf.getvalue()
Mike Frysinger58929732021-07-02 00:29:35 -0400431 if (opt.verbose or not success) and output:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500432 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700433
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500434 if not success:
435 print('error: Cannot fetch %s from %s'
436 % (project.name, project.remote.url),
437 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700438 except GitError as e:
439 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500440 except Exception as e:
441 print('error: Cannot fetch %s (%s: %s)'
442 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
443 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500444
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500445 finish = time.time()
446 return (success, project, start, finish)
David James8d201162013-10-11 17:03:19 -0700447
Mike Frysinger339f2df2021-05-06 00:44:42 -0400448 @classmethod
449 def _FetchInitChild(cls, ssh_proxy):
450 cls.ssh_proxy = ssh_proxy
451
452 def _Fetch(self, projects, opt, err_event, ssh_proxy):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500453 ret = True
454
Mike Frysinger355f4392022-07-20 17:15:29 -0400455 jobs = opt.jobs_network
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700456 fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400457 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800458
David James89ece422014-01-09 18:51:58 -0800459 objdir_project_map = dict()
460 for project in projects:
461 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500462 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700463
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500464 def _ProcessResults(results_sets):
465 ret = True
466 for results in results_sets:
467 for (success, project, start, finish) in results:
468 self._fetch_times.Set(project, finish - start)
469 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
470 start, finish, success)
471 # Check for any errors before running any more tasks.
472 # ...we'll let existing jobs finish, though.
473 if not success:
474 ret = False
475 else:
476 fetched.add(project.gitdir)
477 pm.update(msg=project.name)
478 if not ret and opt.fail_fast:
479 break
480 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700481
Mike Frysinger339f2df2021-05-06 00:44:42 -0400482 # We pass the ssh proxy settings via the class. This allows multiprocessing
483 # to pickle it up when spawning children. We can't pass it as an argument
484 # to _FetchProjectList below as multiprocessing is unable to pickle those.
485 Sync.ssh_proxy = None
486
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500487 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400488 if len(projects_list) == 1 or jobs == 1:
Mike Frysinger339f2df2021-05-06 00:44:42 -0400489 self._FetchInitChild(ssh_proxy)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500490 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
491 ret = False
492 else:
493 # Favor throughput over responsiveness when quiet. It seems that imap()
494 # will yield results in batches relative to chunksize, so even as the
495 # children finish a sync, we won't see the result until one child finishes
496 # ~chunksize jobs. When using a large --jobs with large chunksize, this
497 # can be jarring as there will be a large initial delay where repo looks
498 # like it isn't doing anything and sits at 0%, but then suddenly completes
499 # a lot of jobs all at once. Since this code is more network bound, we
500 # can accept a bit more CPU overhead with a smaller chunksize so that the
501 # user sees more immediate & continuous feedback.
502 if opt.quiet:
503 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800504 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500505 pm.update(inc=0, msg='warming up')
506 chunksize = 4
Raman Tenneti4a478ed2021-11-17 18:38:24 -0800507 with multiprocessing.Pool(jobs, initializer=self._FetchInitChild,
508 initargs=(ssh_proxy,)) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500509 results = pool.imap_unordered(
510 functools.partial(self._FetchProjectList, opt),
511 projects_list,
512 chunksize=chunksize)
513 if not _ProcessResults(results):
514 ret = False
515 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800516
Mike Frysinger339f2df2021-05-06 00:44:42 -0400517 # Cleanup the reference now that we're done with it, and we're going to
518 # release any resources it points to. If we don't, later multiprocessing
519 # usage (e.g. checkouts) will try to pickle and then crash.
520 del Sync.ssh_proxy
521
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700522 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700523 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700524
LaMont Jonesa46047a2022-04-07 21:57:06 +0000525 if not self.outer_client.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400526 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200527
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500528 return (ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700529
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000530 def _FetchMain(self, opt, args, all_projects, err_event,
531 ssh_proxy, manifest):
Mike Frysingerb4429432021-05-05 20:03:26 -0400532 """The main network fetch loop.
533
534 Args:
535 opt: Program options returned from optparse. See _Options().
536 args: Command line args used to filter out projects.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200537 all_projects: List of all projects that should be fetched.
Mike Frysingerb4429432021-05-05 20:03:26 -0400538 err_event: Whether an error was hit while processing.
Mike Frysinger339f2df2021-05-06 00:44:42 -0400539 ssh_proxy: SSH manager for clients & masters.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000540 manifest: The manifest to use.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200541
542 Returns:
543 List of all projects that should be checked out.
Mike Frysingerb4429432021-05-05 20:03:26 -0400544 """
LaMont Jonesa46047a2022-04-07 21:57:06 +0000545 rp = manifest.repoProject
Mike Frysingerb4429432021-05-05 20:03:26 -0400546
547 to_fetch = []
548 now = time.time()
549 if _ONE_DAY_S <= (now - rp.LastFetch):
550 to_fetch.append(rp)
551 to_fetch.extend(all_projects)
552 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
553
Mike Frysinger339f2df2021-05-06 00:44:42 -0400554 success, fetched = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400555 if not success:
556 err_event.set()
557
558 _PostRepoFetch(rp, opt.repo_verify)
559 if opt.network_only:
560 # bail out now; the rest touches the working tree
561 if err_event.is_set():
562 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
563 sys.exit(1)
564 return
565
566 # Iteratively fetch missing and/or nested unregistered submodules
567 previously_missing_set = set()
568 while True:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000569 self._ReloadManifest(None, manifest)
Mike Frysingerb4429432021-05-05 20:03:26 -0400570 all_projects = self.GetProjects(args,
571 missing_ok=True,
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000572 submodules_ok=opt.fetch_submodules,
573 manifest=manifest,
574 all_manifests=not opt.this_manifest_only)
Mike Frysingerb4429432021-05-05 20:03:26 -0400575 missing = []
576 for project in all_projects:
577 if project.gitdir not in fetched:
578 missing.append(project)
579 if not missing:
580 break
581 # Stop us from non-stopped fetching actually-missing repos: If set of
582 # missing repos has not been changed from last fetch, we break.
583 missing_set = set(p.name for p in missing)
584 if previously_missing_set == missing_set:
585 break
586 previously_missing_set = missing_set
Mike Frysinger339f2df2021-05-06 00:44:42 -0400587 success, new_fetched = self._Fetch(missing, opt, err_event, ssh_proxy)
Mike Frysingerb4429432021-05-05 20:03:26 -0400588 if not success:
589 err_event.set()
590 fetched.update(new_fetched)
591
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200592 return all_projects
593
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500594 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700595 """Checkout work tree for one project
596
597 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500598 detach_head: Whether to leave a detached HEAD.
599 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700600 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700601
602 Returns:
603 Whether the fetch was successful.
604 """
Xin Li745be2e2019-06-03 11:24:30 -0700605 start = time.time()
LaMont Jonesa46047a2022-04-07 21:57:06 +0000606 syncbuf = SyncBuffer(project.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500607 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700608 success = False
609 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500610 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500611 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700612 except GitError as e:
613 print('error.GitError: Cannot checkout %s: %s' %
614 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500615 except Exception as e:
616 print('error: Cannot checkout %s: %s: %s' %
617 (project.name, type(e).__name__, str(e)),
618 file=sys.stderr)
619 raise
Xin Li745be2e2019-06-03 11:24:30 -0700620
Mike Frysingerebf04a42021-02-23 20:48:04 -0500621 if not success:
622 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
623 finish = time.time()
624 return (success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700625
Mike Frysingerebf04a42021-02-23 20:48:04 -0500626 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700627 """Checkout projects listed in all_projects
628
629 Args:
630 all_projects: List of all projects that should be checked out.
631 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500632 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700633 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500634 # Only checkout projects with worktrees.
635 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700636
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500637 def _ProcessResults(pool, pm, results):
638 ret = True
Mike Frysingerebf04a42021-02-23 20:48:04 -0500639 for (success, project, start, finish) in results:
640 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
641 start, finish, success)
642 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500643 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500644 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500645 ret = False
Mike Frysingerebf04a42021-02-23 20:48:04 -0500646 err_results.append(project.relpath)
647 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500648 if pool:
649 pool.close()
650 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500651 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500652 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700653
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500654 return self.ExecuteInParallel(
Mike Frysinger355f4392022-07-20 17:15:29 -0400655 opt.jobs_checkout,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500656 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
657 all_projects,
658 callback=_ProcessResults,
659 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500660
Mike Frysinger5a033082019-09-23 19:21:20 -0400661 def _GCProjects(self, projects, opt, err_event):
Mike Frysinger151701e2021-04-13 15:07:21 -0400662 pm = Progress('Garbage collecting', len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400663 pm.update(inc=0, msg='prescan')
664
Allen Webb4ee4a452021-10-07 10:42:38 -0500665 tidy_dirs = {}
David James8d201162013-10-11 17:03:19 -0700666 for project in projects:
LaMont Jones48ea25c2022-05-20 10:35:04 +0000667 # Make sure pruning never kicks in with shared projects that do not use
668 # alternates to avoid corruption.
Mike Frysinger979d5bd2020-02-09 02:28:34 -0500669 if (not project.use_git_worktrees and
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000670 len(project.manifest.GetProjectsWithName(project.name, all_manifests=True)) > 1):
LaMont Jones48ea25c2022-05-20 10:35:04 +0000671 if project.UseAlternates:
672 # Undo logic set by previous versions of repo.
673 project.config.SetString('extensions.preciousObjects', None)
674 project.config.SetString('gc.pruneExpire', None)
Mike Frysinger5f2b0452020-02-11 03:23:24 -0500675 else:
LaMont Jones48ea25c2022-05-20 10:35:04 +0000676 if not opt.quiet:
677 print('\r%s: Shared project %s found, disabling pruning.' %
678 (project.relpath, project.name))
679 if git_require((2, 7, 0)):
680 project.EnableRepositoryExtension('preciousObjects')
681 else:
682 # This isn't perfect, but it's the best we can do with old git.
683 print('\r%s: WARNING: shared projects are unreliable when using old '
684 'versions of git; please upgrade to git-2.7.0+.'
685 % (project.relpath,),
686 file=sys.stderr)
687 project.config.SetString('gc.pruneExpire', 'never')
Allen Webb669efd02021-10-01 15:25:31 -0500688 project.config.SetString('gc.autoDetach', 'false')
Allen Webb4ee4a452021-10-07 10:42:38 -0500689 # Only call git gc once per objdir, but call pack-refs for the remainder.
690 if project.objdir not in tidy_dirs:
691 tidy_dirs[project.objdir] = (
692 True, # Run a full gc.
693 project.bare_git,
694 )
695 elif project.gitdir not in tidy_dirs:
696 tidy_dirs[project.gitdir] = (
697 False, # Do not run a full gc; just run pack-refs.
698 project.bare_git,
699 )
Mike Frysinger65af2602021-04-08 22:47:44 -0400700
Mike Frysinger355f4392022-07-20 17:15:29 -0400701 jobs = opt.jobs
Dave Borowitz18857212012-10-23 17:02:59 -0700702
LaMont Jones891e8f72022-09-08 20:17:58 +0000703 def _backup_cruft(bare_git):
704 # Find any cruft packs in the current gitdir, and save them.
705 # b/221065125 (repo sync complains that objects are missing). This does
706 # not prevent that state, but makes it so that the missing objects are
707 # available.
708 if not _BACKUP_OBJECTS:
709 return
710 saved = []
711 objdir = bare_git.GetDotgitPath('objects')
712 pack_dir = os.path.join(objdir, 'pack')
713 bak_dir = os.path.join(objdir, '.repo','pack.bak')
714 files = set(platform_utils.listdir(pack_dir))
715 to_backup = []
716 for f in files:
717 base, ext = os.path.splitext(f)
718 if base + ".mtimes" in files:
719 to_backup.append(f)
720 if to_backup and not platform_utils.isdir(bak_dir):
721 os.makedirs(bak_dir)
722 for fname in to_backup:
723 bak_fname = os.path.join(bak_dir, fname)
724 if not os.path.exists(bak_fname):
725 saved.append(fname)
726 # Use a tmp file so that we are sure of a complete copy.
727 shutil.copy(os.path.join(pack_dir, fname), bak_fname + '.tmp')
728 shutil.move(bak_fname + '.tmp', bak_fname)
729 if saved and IsTrace():
730 Trace('%s saved %s', bare_git._project.name, ' '.join(saved))
731
732 gc_args = ('--auto', '--cruft')
733 pack_refs_args = ()
Dave Borowitz18857212012-10-23 17:02:59 -0700734 if jobs < 2:
Allen Webb4ee4a452021-10-07 10:42:38 -0500735 for (run_gc, bare_git) in tidy_dirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400736 pm.update(msg=bare_git._project.name)
LaMont Jones891e8f72022-09-08 20:17:58 +0000737
Allen Webb4ee4a452021-10-07 10:42:38 -0500738 if run_gc:
LaMont Jones891e8f72022-09-08 20:17:58 +0000739 bare_git.gc(*gc_args)
Allen Webb4ee4a452021-10-07 10:42:38 -0500740 else:
LaMont Jones891e8f72022-09-08 20:17:58 +0000741 bare_git.pack_refs(*pack_refs_args)
742 _backup_cruft(bare_git)
Mike Frysinger65af2602021-04-08 22:47:44 -0400743 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700744 return
745
Mike Frysinger355f4392022-07-20 17:15:29 -0400746 cpu_count = os.cpu_count()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400747 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700748
749 threads = set()
750 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700751
Allen Webb4ee4a452021-10-07 10:42:38 -0500752 def tidy_up(run_gc, bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400753 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700754 try:
755 try:
Allen Webb4ee4a452021-10-07 10:42:38 -0500756 if run_gc:
LaMont Jones891e8f72022-09-08 20:17:58 +0000757 bare_git.gc(*gc_args, config=config)
Allen Webb4ee4a452021-10-07 10:42:38 -0500758 else:
LaMont Jones891e8f72022-09-08 20:17:58 +0000759 bare_git.pack_refs(*pack_refs_args, config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700760 except GitError:
761 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900762 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700763 err_event.set()
764 raise
765 finally:
LaMont Jones891e8f72022-09-08 20:17:58 +0000766 _backup_cruft(bare_git)
Mike Frysinger65af2602021-04-08 22:47:44 -0400767 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700768 sem.release()
769
Allen Webb4ee4a452021-10-07 10:42:38 -0500770 for (run_gc, bare_git) in tidy_dirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500771 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700772 break
773 sem.acquire()
Allen Webb4ee4a452021-10-07 10:42:38 -0500774 t = _threading.Thread(target=tidy_up, args=(run_gc, bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700775 t.daemon = True
776 threads.add(t)
777 t.start()
778
779 for t in threads:
780 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400781 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700782
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000783 def _ReloadManifest(self, manifest_name, manifest):
Raman Tennetifeb28912021-05-02 19:47:29 -0700784 """Reload the manfiest from the file specified by the |manifest_name|.
785
786 It unloads the manifest if |manifest_name| is None.
787
788 Args:
789 manifest_name: Manifest file to be reloaded.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000790 manifest: The manifest to use.
Raman Tennetifeb28912021-05-02 19:47:29 -0700791 """
Tim Kilbourn07669002013-03-08 15:02:49 -0800792 if manifest_name:
LaMont Jonesa2ff20d2022-04-07 16:49:06 +0000793 # Override calls Unload already
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000794 manifest.Override(manifest_name)
Tim Kilbourn07669002013-03-08 15:02:49 -0800795 else:
LaMont Jonesa46047a2022-04-07 21:57:06 +0000796 manifest.Unload()
Tim Kilbourn07669002013-03-08 15:02:49 -0800797
LaMont Jonesa46047a2022-04-07 21:57:06 +0000798 def UpdateProjectList(self, opt, manifest):
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000799 """Update the cached projects list for |manifest|
800
801 In a multi-manifest checkout, each manifest has its own project.list.
802
803 Args:
804 opt: Program options returned from optparse. See _Options().
805 manifest: The manifest to use.
806
807 Returns:
808 0: success
809 1: failure
810 """
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700811 new_project_paths = []
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000812 for project in self.GetProjects(None, missing_ok=True, manifest=manifest,
813 all_manifests=False):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700814 if project.relpath:
815 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700816 file_name = 'project.list'
LaMont Jonesa46047a2022-04-07 21:57:06 +0000817 file_path = os.path.join(manifest.subdir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700818 old_project_paths = []
819
820 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500821 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700822 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800823 # In reversed order, so subfolders are deleted before parent folder.
824 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700825 if not path:
826 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700827 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900828 # If the path has already been deleted, we don't need to do it
LaMont Jonesa46047a2022-04-07 21:57:06 +0000829 gitdir = os.path.join(manifest.topdir, path, '.git')
Dan Willemsen43507912016-09-01 16:26:02 -0700830 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900831 project = Project(
LaMont Jonesa46047a2022-04-07 21:57:06 +0000832 manifest=manifest,
David Pursehouseabdf7502020-02-12 14:58:39 +0900833 name=path,
834 remote=RemoteSpec('origin'),
835 gitdir=gitdir,
836 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500837 use_git_worktrees=os.path.isfile(gitdir),
LaMont Jonesa46047a2022-04-07 21:57:06 +0000838 worktree=os.path.join(manifest.topdir, path),
David Pursehouseabdf7502020-02-12 14:58:39 +0900839 relpath=path,
840 revisionExpr='HEAD',
841 revisionId=None,
842 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500843 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900844 quiet=opt.quiet,
845 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400846 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700847
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700848 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500849 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700850 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700851 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700852 return 0
853
LaMont Jonesa46047a2022-04-07 21:57:06 +0000854 def UpdateCopyLinkfileList(self, manifest):
jiajia tanga590e642021-04-25 20:02:02 +0800855 """Save all dests of copyfile and linkfile, and update them if needed.
856
857 Returns:
858 Whether update was successful.
859 """
860 new_paths = {}
861 new_linkfile_paths = []
862 new_copyfile_paths = []
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000863 for project in self.GetProjects(None, missing_ok=True,
864 manifest=manifest, all_manifests=False):
jiajia tanga590e642021-04-25 20:02:02 +0800865 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
866 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
867
868 new_paths = {
869 'linkfile': new_linkfile_paths,
870 'copyfile': new_copyfile_paths,
871 }
872
873 copylinkfile_name = 'copy-link-files.json'
LaMont Jonesa46047a2022-04-07 21:57:06 +0000874 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
jiajia tanga590e642021-04-25 20:02:02 +0800875 old_copylinkfile_paths = {}
876
877 if os.path.exists(copylinkfile_path):
878 with open(copylinkfile_path, 'rb') as fp:
879 try:
880 old_copylinkfile_paths = json.load(fp)
Raman Tenneti4a478ed2021-11-17 18:38:24 -0800881 except Exception:
jiajia tanga590e642021-04-25 20:02:02 +0800882 print('error: %s is not a json formatted file.' %
883 copylinkfile_path, file=sys.stderr)
884 platform_utils.remove(copylinkfile_path)
885 return False
886
887 need_remove_files = []
888 need_remove_files.extend(
889 set(old_copylinkfile_paths.get('linkfile', [])) -
890 set(new_linkfile_paths))
891 need_remove_files.extend(
892 set(old_copylinkfile_paths.get('copyfile', [])) -
893 set(new_copyfile_paths))
894
895 for need_remove_file in need_remove_files:
Mike Frysinger9d96f582021-09-28 11:27:24 -0400896 # Try to remove the updated copyfile or linkfile.
897 # So, if the file is not exist, nothing need to do.
898 platform_utils.remove(need_remove_file, missing_ok=True)
jiajia tanga590e642021-04-25 20:02:02 +0800899
900 # Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
901 with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
902 json.dump(new_paths, fp)
903 return True
904
LaMont Jonesa46047a2022-04-07 21:57:06 +0000905 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
906 if not manifest.manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400907 print('error: cannot smart sync: no manifest server defined in '
908 'manifest', file=sys.stderr)
909 sys.exit(1)
910
LaMont Jonesa46047a2022-04-07 21:57:06 +0000911 manifest_server = manifest.manifest_server
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400912 if not opt.quiet:
913 print('Using manifest server %s' % manifest_server)
914
David Pursehouseeeff3532020-02-12 11:24:10 +0900915 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400916 username = None
917 password = None
918 if opt.manifest_server_username and opt.manifest_server_password:
919 username = opt.manifest_server_username
920 password = opt.manifest_server_password
921 else:
922 try:
923 info = netrc.netrc()
924 except IOError:
925 # .netrc file does not exist or could not be opened
926 pass
927 else:
928 try:
929 parse_result = urllib.parse.urlparse(manifest_server)
930 if parse_result.hostname:
931 auth = info.authenticators(parse_result.hostname)
932 if auth:
933 username, _account, password = auth
934 else:
935 print('No credentials found for %s in .netrc'
936 % parse_result.hostname, file=sys.stderr)
937 except netrc.NetrcParseError as e:
938 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
939
940 if (username and password):
941 manifest_server = manifest_server.replace('://', '://%s:%s@' %
942 (username, password),
943 1)
944
945 transport = PersistentTransport(manifest_server)
946 if manifest_server.startswith('persistent-'):
947 manifest_server = manifest_server[len('persistent-'):]
948
949 try:
950 server = xmlrpc.client.Server(manifest_server, transport=transport)
951 if opt.smart_sync:
LaMont Jonesa46047a2022-04-07 21:57:06 +0000952 branch = self._GetBranch(manifest.manifestProject)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400953
Mike Frysinger56ce3462019-12-04 19:30:48 -0500954 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -0500955 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400956 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -0500957 elif ('TARGET_PRODUCT' in os.environ and
958 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -0500959 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
960 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400961 [success, manifest_str] = server.GetApprovedManifest(branch, target)
962 else:
963 [success, manifest_str] = server.GetApprovedManifest(branch)
964 else:
965 assert(opt.smart_tag)
966 [success, manifest_str] = server.GetManifest(opt.smart_tag)
967
968 if success:
969 manifest_name = os.path.basename(smart_sync_manifest_path)
970 try:
Mike Frysinger3164d402019-11-11 05:40:22 -0500971 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400972 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400973 except IOError as e:
974 print('error: cannot write manifest to %s:\n%s'
975 % (smart_sync_manifest_path, e),
976 file=sys.stderr)
977 sys.exit(1)
LaMont Jonesa46047a2022-04-07 21:57:06 +0000978 self._ReloadManifest(manifest_name, manifest)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400979 else:
980 print('error: manifest server RPC call failed: %s' %
981 manifest_str, file=sys.stderr)
982 sys.exit(1)
983 except (socket.error, IOError, xmlrpc.client.Fault) as e:
984 print('error: cannot connect to manifest server %s:\n%s'
LaMont Jonesa46047a2022-04-07 21:57:06 +0000985 % (manifest.manifest_server, e), file=sys.stderr)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400986 sys.exit(1)
987 except xmlrpc.client.ProtocolError as e:
988 print('error: cannot connect to manifest server %s:\n%d %s'
LaMont Jonesa46047a2022-04-07 21:57:06 +0000989 % (manifest.manifest_server, e.errcode, e.errmsg),
Mike Frysinger01d6c3c2019-08-27 01:56:43 -0400990 file=sys.stderr)
991 sys.exit(1)
992
993 return manifest_name
994
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000995 def _UpdateAllManifestProjects(self, opt, mp, manifest_name):
996 """Fetch & update the local manifest project.
997
998 After syncing the manifest project, if the manifest has any sub manifests,
999 those are recursively processed.
1000
1001 Args:
1002 opt: Program options returned from optparse. See _Options().
1003 mp: the manifestProject to query.
1004 manifest_name: Manifest file to be reloaded.
1005 """
1006 if not mp.standalone_manifest_url:
1007 self._UpdateManifestProject(opt, mp, manifest_name)
1008
1009 if mp.manifest.submanifests:
1010 for submanifest in mp.manifest.submanifests.values():
1011 child = submanifest.repo_client.manifest
1012 child.manifestProject.SyncWithPossibleInit(
1013 submanifest,
1014 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1015 verbose=opt.verbose,
1016 tags=opt.tags,
1017 git_event_log=self.git_event_log,
1018 )
1019 self._UpdateAllManifestProjects(opt, child.manifestProject, None)
1020
Mike Frysingerfb527e32019-08-27 02:34:32 -04001021 def _UpdateManifestProject(self, opt, mp, manifest_name):
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001022 """Fetch & update the local manifest project.
1023
1024 Args:
1025 opt: Program options returned from optparse. See _Options().
1026 mp: the manifestProject to query.
1027 manifest_name: Manifest file to be reloaded.
1028 """
Mike Frysingerfb527e32019-08-27 02:34:32 -04001029 if not opt.local_only:
1030 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -05001031 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
LaMont Jonesa46047a2022-04-07 21:57:06 +00001032 current_branch_only=self._GetCurrentBranchOnly(opt, mp.manifest),
Erwan Yvindc5c4d12019-06-18 13:49:12 +02001033 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001034 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -04001035 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001036 retry_fetches=opt.retry_fetches,
LaMont Jonesa46047a2022-04-07 21:57:06 +00001037 submodules=mp.manifest.HasSubmodules,
1038 clone_filter=mp.manifest.CloneFilter,
1039 partial_clone_exclude=mp.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -04001040 finish = time.time()
1041 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
1042 start, finish, success)
1043
1044 if mp.HasChanges:
1045 syncbuf = SyncBuffer(mp.config)
1046 start = time.time()
LaMont Jonesa46047a2022-04-07 21:57:06 +00001047 mp.Sync_LocalHalf(syncbuf, submodules=mp.manifest.HasSubmodules)
Mike Frysingerfb527e32019-08-27 02:34:32 -04001048 clean = syncbuf.Finish()
1049 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
1050 start, time.time(), clean)
1051 if not clean:
1052 sys.exit(1)
LaMont Jonesa46047a2022-04-07 21:57:06 +00001053 self._ReloadManifest(manifest_name, mp.manifest)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001054
Mike Frysingerae6cb082019-08-27 01:10:59 -04001055 def ValidateOptions(self, opt, args):
1056 if opt.force_broken:
1057 print('warning: -f/--force-broken is now the default behavior, and the '
1058 'options are deprecated', file=sys.stderr)
1059 if opt.network_only and opt.detach_head:
1060 self.OptionParser.error('cannot combine -n and -d')
1061 if opt.network_only and opt.local_only:
1062 self.OptionParser.error('cannot combine -n and -l')
1063 if opt.manifest_name and opt.smart_sync:
1064 self.OptionParser.error('cannot combine -m and -s')
1065 if opt.manifest_name and opt.smart_tag:
1066 self.OptionParser.error('cannot combine -m and -t')
1067 if opt.manifest_server_username or opt.manifest_server_password:
1068 if not (opt.smart_sync or opt.smart_tag):
1069 self.OptionParser.error('-u and -p may only be combined with -s or -t')
1070 if None in [opt.manifest_server_username, opt.manifest_server_password]:
1071 self.OptionParser.error('both -u and -p must be given')
1072
Mike Frysinger0531a622021-11-05 15:22:01 -04001073 if opt.prune is None:
1074 opt.prune = True
1075
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001076 def Execute(self, opt, args):
LaMont Jonesa46047a2022-04-07 21:57:06 +00001077 manifest = self.outer_manifest
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001078 if not opt.outer_manifest:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001079 manifest = self.manifest
1080
Chris Wolfee9dc3b32012-01-26 11:36:18 -05001081 if opt.manifest_name:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001082 manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -07001083
Chirayu Desaia892b102013-06-11 14:18:46 +05301084 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +09001085 smart_sync_manifest_path = os.path.join(
LaMont Jonesa46047a2022-04-07 21:57:06 +00001086 manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +05301087
Xin Lid79a4bc2020-05-20 16:03:45 -07001088 if opt.clone_bundle is None:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001089 opt.clone_bundle = manifest.CloneBundle
Xin Lid79a4bc2020-05-20 16:03:45 -07001090
Victor Boivie08c880d2011-04-19 10:32:52 +02001091 if opt.smart_sync or opt.smart_tag:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001092 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path, manifest)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001093 else:
David Pursehouse59b41742015-05-07 14:36:09 +09001094 if os.path.isfile(smart_sync_manifest_path):
1095 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001096 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +09001097 except OSError as e:
1098 print('error: failed to remove existing smart sync override manifest: %s' %
1099 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -07001100
Mike Frysingerc99322a2021-05-04 15:32:43 -04001101 err_event = multiprocessing.Event()
Mike Frysinger5a033082019-09-23 19:21:20 -04001102
LaMont Jonesa46047a2022-04-07 21:57:06 +00001103 rp = manifest.repoProject
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001104 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -05001105 cb = rp.CurrentBranch
1106 if cb:
1107 base = rp.GetBranch(cb).merge
1108 if not base or not base.startswith('refs/heads/'):
1109 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -04001110 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -05001111 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001112
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001113 for m in self.ManifestList(opt):
LaMont Jones4112c072022-08-24 17:32:25 +00001114 if not m.manifestProject.standalone_manifest_url:
1115 m.manifestProject.PreSync()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001116
LaMont Jones4112c072022-08-24 17:32:25 +00001117 if opt.repo_upgraded:
1118 _PostRepoUpgrade(manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001119
LaMont Jones4112c072022-08-24 17:32:25 +00001120 mp = manifest.manifestProject
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001121 if opt.mp_update:
1122 self._UpdateAllManifestProjects(opt, mp, manifest_name)
1123 else:
Fredrik de Grootcc960972019-11-22 09:04:31 +01001124 print('Skipping update of local manifest project.')
Simran Basib9a1b732015-08-20 12:19:28 -07001125
Mike Frysinger355f4392022-07-20 17:15:29 -04001126 # Now that the manifests are up-to-date, setup the jobs value.
1127 if opt.jobs is None:
1128 # User has not made a choice, so use the manifest settings.
1129 opt.jobs = mp.default.sync_j
1130 if opt.jobs is not None:
1131 # Neither user nor manifest have made a choice.
1132 if opt.jobs_network is None:
1133 opt.jobs_network = opt.jobs
1134 if opt.jobs_checkout is None:
1135 opt.jobs_checkout = opt.jobs
1136 # Setup defaults if jobs==0.
1137 if not opt.jobs:
1138 if not opt.jobs_network:
1139 opt.jobs_network = 1
1140 if not opt.jobs_checkout:
1141 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1142 opt.jobs = os.cpu_count()
1143
1144 # Try to stay under user rlimit settings.
1145 #
1146 # Since each worker requires at 3 file descriptors to run `git fetch`, use
1147 # that to scale down the number of jobs. Unfortunately there isn't an easy
1148 # way to determine this reliably as systems change, but it was last measured
1149 # by hand in 2011.
1150 soft_limit, _ = _rlimit_nofile()
1151 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1152 opt.jobs = min(opt.jobs, jobs_soft_limit)
1153 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1154 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1155
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001156 superproject_logging_data = {}
1157 self._UpdateProjectsRevisionId(opt, args, superproject_logging_data,
1158 manifest)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001159
Simran Basib9a1b732015-08-20 12:19:28 -07001160 if self.gitc_manifest:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001161 gitc_manifest_projects = self.GetProjects(args, missing_ok=True)
Simran Basib9a1b732015-08-20 12:19:28 -07001162 gitc_projects = []
1163 opened_projects = []
1164 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001165 if project.relpath in self.gitc_manifest.paths and \
1166 self.gitc_manifest.paths[project.relpath].old_revision:
1167 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -07001168 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001169 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -07001170
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001171 if not args:
1172 gitc_projects = None
1173
1174 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -07001175 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001176 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
1177 if manifest_name:
1178 manifest.Override(manifest_name)
1179 else:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001180 manifest.Override(manifest.manifestFile)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001181 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
1182 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -07001183 gitc_projects)
1184 print('GITC client successfully synced.')
1185
1186 # The opened projects need to be synced as normal, therefore we
1187 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001188 # TODO: make this more reliable -- if there's a project name/path overlap,
1189 # this may choose the wrong project.
LaMont Jonesa46047a2022-04-07 21:57:06 +00001190 args = [os.path.relpath(manifest.paths[path].worktree, os.getcwd())
David Pursehouse3bcd3052017-07-10 22:42:22 +09001191 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -07001192 if not args:
1193 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001194
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001195 all_projects = self.GetProjects(args,
1196 missing_ok=True,
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001197 submodules_ok=opt.fetch_submodules,
1198 manifest=manifest,
1199 all_manifests=not opt.this_manifest_only)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001200
Mike Frysinger5a033082019-09-23 19:21:20 -04001201 err_network_sync = False
1202 err_update_projects = False
Mike Frysinger5a033082019-09-23 19:21:20 -04001203
LaMont Jonesa46047a2022-04-07 21:57:06 +00001204 self._fetch_times = _FetchTimes(manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -07001205 if not opt.local_only:
Mike Frysinger339f2df2021-05-06 00:44:42 -04001206 with multiprocessing.Manager() as manager:
1207 with ssh.ProxyManager(manager) as ssh_proxy:
1208 # Initialize the socket dir once in the parent.
1209 ssh_proxy.sock()
Peter Kjellerstedtd1776092021-05-19 19:37:23 +02001210 all_projects = self._FetchMain(opt, args, all_projects, err_event,
LaMont Jonesa46047a2022-04-07 21:57:06 +00001211 ssh_proxy, manifest)
Mike Frysinger339f2df2021-05-06 00:44:42 -04001212
1213 if opt.network_only:
1214 return
Mike Frysinger5a033082019-09-23 19:21:20 -04001215
1216 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001217 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001218 err_network_sync = True
1219 if opt.fail_fast:
1220 print('\nerror: Exited sync due to fetch errors.\n'
1221 'Local checkouts *not* updated. Resolve network issues & '
1222 'retry.\n'
1223 '`repo sync -l` will update some local checkouts.',
1224 file=sys.stderr)
1225 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001226
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001227 for m in self.ManifestList(opt):
1228 if m.IsMirror or m.IsArchive:
1229 # bail out now, we have no working tree
1230 continue
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001231
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001232 if self.UpdateProjectList(opt, m):
1233 err_event.set()
1234 err_update_projects = True
1235 if opt.fail_fast:
1236 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1237 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001238
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001239 err_update_linkfiles = not self.UpdateCopyLinkfileList(m)
1240 if err_update_linkfiles:
1241 err_event.set()
1242 if opt.fail_fast:
1243 print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
1244 sys.exit(1)
jiajia tanga590e642021-04-25 20:02:02 +08001245
Mike Frysinger5a033082019-09-23 19:21:20 -04001246 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -05001247 # NB: We don't exit here because this is the last step.
1248 err_checkout = not self._Checkout(all_projects, opt, err_results)
1249 if err_checkout:
1250 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001251
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001252 printed_notices = set()
1253 # If there's a notice that's supposed to print at the end of the sync,
1254 # print it now... But avoid printing duplicate messages, and preserve
1255 # order.
1256 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1257 if m.notice and m.notice not in printed_notices:
1258 print(m.notice)
1259 printed_notices.add(m.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001260
Mike Frysinger5a033082019-09-23 19:21:20 -04001261 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001262 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001263 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1264 if err_network_sync:
1265 print('error: Downloading network changes failed.', file=sys.stderr)
1266 if err_update_projects:
1267 print('error: Updating local project lists failed.', file=sys.stderr)
jiajia tanga590e642021-04-25 20:02:02 +08001268 if err_update_linkfiles:
1269 print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr)
Mike Frysinger5a033082019-09-23 19:21:20 -04001270 if err_checkout:
1271 print('error: Checking out local projects failed.', file=sys.stderr)
1272 if err_results:
1273 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1274 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1275 file=sys.stderr)
1276 sys.exit(1)
1277
Raman Tenneti7954de12021-07-28 14:36:49 -07001278 # Log the previous sync analysis state from the config.
Raman Tenneti6448a4f2021-09-13 17:40:07 -07001279 self.git_event_log.LogDataConfigEvents(mp.config.GetSyncAnalysisStateData(),
1280 'previous_sync_state')
Raman Tenneti7954de12021-07-28 14:36:49 -07001281
1282 # Update and log with the new sync analysis state.
1283 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
Raman Tenneti6448a4f2021-09-13 17:40:07 -07001284 self.git_event_log.LogDataConfigEvents(mp.config.GetSyncAnalysisStateData(),
1285 'current_sync_state')
Raman Tenneti7954de12021-07-28 14:36:49 -07001286
Mike Frysingere19d9e12020-02-12 11:23:32 -05001287 if not opt.quiet:
1288 print('repo sync has finished successfully.')
1289
David Pursehouse819827a2020-02-12 15:20:19 +09001290
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001291def _PostRepoUpgrade(manifest, quiet=False):
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001292 # Link the docs for the internal .repo/ layout for people
1293 link = os.path.join(manifest.repodir, 'internal-fs-layout.md')
1294 if not platform_utils.islink(link):
1295 target = os.path.join('repo', 'docs', 'internal-fs-layout.md')
1296 try:
1297 platform_utils.symlink(target, link)
Raman Tenneti4a478ed2021-11-17 18:38:24 -08001298 except Exception:
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001299 pass
1300
Conley Owens094cdbe2014-01-30 15:09:59 -08001301 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001302 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001303 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001304 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001305 if project.Exists:
1306 project.PostRepoUpgrade()
1307
David Pursehouse819827a2020-02-12 15:20:19 +09001308
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001309def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001310 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001311 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -04001312 wrapper = Wrapper()
1313 try:
1314 rev = rp.bare_git.describe(rp.GetRevisionId())
1315 except GitError:
1316 rev = None
1317 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
1318 # See if we're held back due to missing signed tag.
1319 current_revid = rp.bare_git.rev_parse('HEAD')
1320 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
1321 if current_revid != new_revid:
1322 # We want to switch to the new rev, but also not trash any uncommitted
1323 # changes. This helps with local testing/hacking.
1324 # If a local change has been made, we will throw that away.
1325 # We also have to make sure this will switch to an older commit if that's
1326 # the latest tag in order to support release rollback.
1327 try:
1328 rp.work_git.reset('--keep', new_rev)
1329 except GitError as e:
1330 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -07001331 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001332 raise RepoChangedException(['--repo-upgraded'])
1333 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001334 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001335 else:
1336 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001337 print('repo version %s is current' % rp.work_git.describe(HEAD),
1338 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001339
David Pursehouse819827a2020-02-12 15:20:19 +09001340
Dave Borowitz67700e92012-10-23 15:00:54 -07001341class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001342 _ALPHA = 0.5
1343
Dave Borowitz67700e92012-10-23 15:00:54 -07001344 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001345 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001346 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001347 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001348
1349 def Get(self, project):
1350 self._Load()
1351 return self._times.get(project.name, _ONE_DAY_S)
1352
1353 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001354 self._Load()
1355 name = project.name
1356 old = self._times.get(name, t)
1357 self._seen.add(name)
1358 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001359 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001360
1361 def _Load(self):
1362 if self._times is None:
1363 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001364 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001365 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001366 except (IOError, ValueError):
Mike Frysinger9d96f582021-09-28 11:27:24 -04001367 platform_utils.remove(self._path, missing_ok=True)
Anthony King85b24ac2014-05-06 15:57:48 +01001368 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001369
1370 def Save(self):
1371 if self._times is None:
1372 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001373
1374 to_delete = []
1375 for name in self._times:
1376 if name not in self._seen:
1377 to_delete.append(name)
1378 for name in to_delete:
1379 del self._times[name]
1380
Dave Borowitz67700e92012-10-23 15:00:54 -07001381 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001382 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001383 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001384 except (IOError, TypeError):
Mike Frysinger9d96f582021-09-28 11:27:24 -04001385 platform_utils.remove(self._path, missing_ok=True)
Dan Willemsen0745bb22015-08-17 13:41:45 -07001386
1387# This is a replacement for xmlrpc.client.Transport using urllib2
1388# and supporting persistent-http[s]. It cannot change hosts from
1389# request to request like the normal transport, the real url
1390# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001391
1392
Dan Willemsen0745bb22015-08-17 13:41:45 -07001393class PersistentTransport(xmlrpc.client.Transport):
1394 def __init__(self, orig_host):
1395 self.orig_host = orig_host
1396
1397 def request(self, host, handler, request_body, verbose=False):
1398 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1399 # Python doesn't understand cookies with the #HttpOnly_ prefix
1400 # Since we're only using them for HTTP, copy the file temporarily,
1401 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001402 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001403 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001404 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001405 try:
1406 with open(cookiefile) as f:
1407 for line in f:
1408 if line.startswith("#HttpOnly_"):
1409 line = line[len("#HttpOnly_"):]
1410 tmpcookiefile.write(line)
1411 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001412
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001413 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001414 try:
1415 cookiejar.load()
1416 except cookielib.LoadError:
1417 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001418 finally:
1419 tmpcookiefile.close()
1420 else:
1421 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001422
1423 proxyhandler = urllib.request.ProxyHandler
1424 if proxy:
1425 proxyhandler = urllib.request.ProxyHandler({
1426 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001427 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001428
1429 opener = urllib.request.build_opener(
1430 urllib.request.HTTPCookieProcessor(cookiejar),
1431 proxyhandler)
1432
1433 url = urllib.parse.urljoin(self.orig_host, handler)
1434 parse_results = urllib.parse.urlparse(url)
1435
1436 scheme = parse_results.scheme
1437 if scheme == 'persistent-http':
1438 scheme = 'http'
1439 if scheme == 'persistent-https':
1440 # If we're proxying through persistent-https, use http. The
1441 # proxy itself will do the https.
1442 if proxy:
1443 scheme = 'http'
1444 else:
1445 scheme = 'https'
1446
1447 # Parse out any authentication information using the base class
1448 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1449
1450 url = urllib.parse.urlunparse((
1451 scheme,
1452 host,
1453 parse_results.path,
1454 parse_results.params,
1455 parse_results.query,
1456 parse_results.fragment))
1457
1458 request = urllib.request.Request(url, request_body)
1459 if extra_headers is not None:
1460 for (name, header) in extra_headers:
1461 request.add_header(name, header)
1462 request.add_header('Content-Type', 'text/xml')
1463 try:
1464 response = opener.open(request)
1465 except urllib.error.HTTPError as e:
1466 if e.code == 501:
1467 # We may have been redirected through a login process
1468 # but our POST turned into a GET. Retry.
1469 response = opener.open(request)
1470 else:
1471 raise
1472
1473 p, u = xmlrpc.client.getparser()
Mike Frysinger5951e302022-05-20 23:34:44 -04001474 # Response should be fairly small, so read it all at once.
1475 # This way we can show it to the user in case of error (e.g. HTML).
1476 data = response.read()
1477 try:
Dan Willemsen0745bb22015-08-17 13:41:45 -07001478 p.feed(data)
Mike Frysinger5951e302022-05-20 23:34:44 -04001479 except xml.parsers.expat.ExpatError as e:
1480 raise IOError(
1481 f'Parsing the manifest failed: {e}\n'
1482 f'Please report this to your manifest server admin.\n'
1483 f'Here is the full response:\n{data.decode("utf-8")}')
Dan Willemsen0745bb22015-08-17 13:41:45 -07001484 p.close()
1485 return u.close()
1486
1487 def close(self):
1488 pass