blob: 8cd1bcac2a84581af7240ed66f79145c1cd4f233 [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
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070024import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
LaMont Jones78dcd372022-10-25 22:38:07 +000028from typing import NamedTuple, List, Set
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
Joanna Wanga6c52f52022-11-03 16:51:19 -040062from repo_trace import 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
68
LaMont Jonesd7935532022-12-01 20:18:46 +000069# Env var to implicitly turn auto-gc back on. This was added to allow a user to
70# revert a change in default behavior in v2.29.9, and will be removed in a
71# future release.
LaMont Jones5ed8c632022-11-10 00:10:44 +000072_REPO_AUTO_GC = 'REPO_AUTO_GC'
73_AUTO_GC = os.environ.get(_REPO_AUTO_GC) == '1'
74
David Pursehouse819827a2020-02-12 15:20:19 +090075
LaMont Jones1eddca82022-09-01 15:15:04 +000076class _FetchOneResult(NamedTuple):
77 """_FetchOne return value.
78
79 Attributes:
80 success (bool): True if successful.
81 project (Project): The fetched project.
82 start (float): The starting time.time().
83 finish (float): The ending time.time().
84 remote_fetched (bool): True if the remote was actually queried.
85 """
86 success: bool
87 project: Project
88 start: float
89 finish: float
90 remote_fetched: bool
91
92
93class _FetchResult(NamedTuple):
94 """_Fetch return value.
95
96 Attributes:
97 success (bool): True if successful.
LaMont Jones78dcd372022-10-25 22:38:07 +000098 projects (Set[str]): The names of the git directories of fetched projects.
LaMont Jones1eddca82022-09-01 15:15:04 +000099 """
100 success: bool
LaMont Jones78dcd372022-10-25 22:38:07 +0000101 projects: Set[str]
LaMont Jones1eddca82022-09-01 15:15:04 +0000102
103
104class _FetchMainResult(NamedTuple):
105 """_FetchMain return value.
106
107 Attributes:
LaMont Jones78dcd372022-10-25 22:38:07 +0000108 all_projects (List[Project]): The fetched projects.
LaMont Jones1eddca82022-09-01 15:15:04 +0000109 """
LaMont Jones78dcd372022-10-25 22:38:07 +0000110 all_projects: List[Project]
LaMont Jones1eddca82022-09-01 15:15:04 +0000111
112
113class _CheckoutOneResult(NamedTuple):
114 """_CheckoutOne return value.
115
116 Attributes:
117 success (bool): True if successful.
118 project (Project): The project.
119 start (float): The starting time.time().
120 finish (float): The ending time.time().
121 """
122 success: bool
123 project: Project
124 start: float
125 finish: float
126
127
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800128class Sync(Command, MirrorSafeCommand):
Mike Frysinger4f210542021-06-14 16:05:19 -0400129 COMMON = True
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000130 MULTI_MANIFEST_SUPPORT = True
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700131 helpSummary = "Update working tree to the latest revision"
132 helpUsage = """
133%prog [<project>...]
134"""
135 helpDescription = """
136The '%prog' command synchronizes local project directories
137with the remote repositories specified in the manifest. If a local
138project does not yet exist, it will clone a new local directory from
139the remote repository and set up tracking branches as specified in
140the manifest. If the local project already exists, '%prog'
141will update the remote branches and rebase any new local changes
142on top of the new remote changes.
143
144'%prog' will synchronize all projects listed at the command
145line. Projects can be specified either by name, or by a relative
146or absolute path to the project's local directory. If no projects
147are specified, '%prog' will synchronize all projects listed in
148the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700149
150The -d/--detach option can be used to switch specified projects
151back to the manifest revision. This option is especially helpful
152if the project is currently on a topic branch, but the manifest
153revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700154
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700155The -s/--smart-sync option can be used to sync to a known good
156build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200157manifest. The -t/--smart-tag option is similar and allows you to
158specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700159
David Pursehousecf76b1b2012-09-14 10:31:42 +0900160The -u/--manifest-server-username and -p/--manifest-server-password
161options can be used to specify a username and password to authenticate
162with the manifest server when using the -s or -t option.
163
164If -u and -p are not specified when using the -s or -t option, '%prog'
165will attempt to read authentication credentials for the manifest server
166from the user's .netrc file.
167
168'%prog' will not use authentication credentials from -u/-p or .netrc
169if the manifest server specified in the manifest file already includes
170credentials.
171
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400172By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400173to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500174
Kevin Degiabaa7f32014-11-12 11:27:45 -0700175The --force-sync option can be used to overwrite existing git
176directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900177object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700178refs may be removed when overwriting.
179
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500180The --force-remove-dirty option can be used to remove previously used
181projects with uncommitted changes. WARNING: This may cause data to be
182lost since uncommitted changes may be removed with projects that no longer
183exist in the manifest.
184
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700185The --no-clone-bundle option disables any attempt to use
186$URL/clone.bundle to bootstrap a new Git repository from a
187resumeable bundle file on a content delivery network. This
188may be necessary if there are problems with the local Python
189HTTP client or proxy configuration, but the Git binary works.
190
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800191The --fetch-submodules option enables fetching Git submodules
192of a project from server.
193
David Pursehousef2fad612015-01-29 14:36:28 +0900194The -c/--current-branch option can be used to only fetch objects that
195are on the branch specified by a project's revision.
196
David Pursehouseb1553542014-09-04 21:28:09 +0900197The --optimized-fetch option can be used to only fetch projects that
198are fixed to a sha1 revision if the sha1 revision does not already
199exist locally.
200
David Pursehouse74cfd272015-10-14 10:50:15 +0900201The --prune option can be used to remove any refs that no longer
202exist on the remote.
203
LaMont Jones7efab532022-09-01 15:41:12 +0000204The --auto-gc option can be used to trigger garbage collection on all
205projects. By default, repo does not run garbage collection.
206
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400207# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700208
209If at least one project remote URL uses an SSH connection (ssh://,
210git+ssh://, or user@host:path syntax) repo will automatically
211enable the SSH ControlMaster option when connecting to that host.
212This feature permits other projects in the same '%prog' session to
213reuse the same SSH tunnel, saving connection setup overheads.
214
215To disable this behavior on UNIX platforms, set the GIT_SSH
216environment variable to 'ssh'. For example:
217
218 export GIT_SSH=ssh
219 %prog
220
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400221# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700222
223This feature is automatically disabled on Windows, due to the lack
224of UNIX domain socket support.
225
226This feature is not compatible with url.insteadof rewrites in the
227user's ~/.gitconfig. '%prog' is currently not able to perform the
228rewrite early enough to establish the ControlMaster tunnel.
229
230If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
231later is required to fix a server side protocol bug.
232
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700233"""
Mike Frysinger355f4392022-07-20 17:15:29 -0400234 # A value of 0 means we want parallel jobs, but we'll determine the default
235 # value later on.
236 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700237
Mike Frysinger9180a072021-04-13 14:57:40 -0400238 def _Options(self, p, show_smart=True):
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400239 p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
Mike Frysinger355f4392022-07-20 17:15:29 -0400240 help='number of network jobs to run in parallel (defaults to --jobs or 1)')
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400241 p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
Mike Frysinger355f4392022-07-20 17:15:29 -0400242 help='number of local checkout jobs to run in parallel (defaults to --jobs or '
243 f'{DEFAULT_LOCAL_JOBS})')
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400244
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500245 p.add_option('-f', '--force-broken',
Stefan Müller-Klieser46702ed2019-08-30 10:20:15 +0200246 dest='force_broken', action='store_true',
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400247 help='obsolete option (to be deleted in the future)')
248 p.add_option('--fail-fast',
249 dest='fail_fast', action='store_true',
250 help='stop syncing after first error is hit')
Kevin Degiabaa7f32014-11-12 11:27:45 -0700251 p.add_option('--force-sync',
252 dest='force_sync', action='store_true',
253 help="overwrite an existing git directory if it needs to "
254 "point to a different object directory. WARNING: this "
255 "may cause loss of data")
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500256 p.add_option('--force-remove-dirty',
257 dest='force_remove_dirty', action='store_true',
258 help="force remove projects with uncommitted modifications if "
259 "projects no longer exist in the manifest. "
260 "WARNING: this may cause loss of data")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900261 p.add_option('-l', '--local-only',
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -0700262 dest='local_only', action='store_true',
263 help="only update working tree, don't fetch")
David Pursehouse54a4e602020-02-12 14:31:05 +0900264 p.add_option('--no-manifest-update', '--nmu',
Fredrik de Grootcc960972019-11-22 09:04:31 +0100265 dest='mp_update', action='store_false', default='true',
266 help='use the existing manifest checkout as-is. '
267 '(do not update to the latest revision)')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900268 p.add_option('-n', '--network-only',
Shawn O. Pearce96fdcef2009-04-10 16:29:20 -0700269 dest='network_only', action='store_true',
270 help="fetch only, don't update working tree")
David Pursehouse8f62fb72012-11-14 12:09:38 +0900271 p.add_option('-d', '--detach',
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700272 dest='detach_head', action='store_true',
273 help='detach projects back to manifest revision')
David Pursehouse8f62fb72012-11-14 12:09:38 +0900274 p.add_option('-c', '--current-branch',
Anatol Pomazau53d6f4d2011-08-25 17:21:47 -0700275 dest='current_branch_only', action='store_true',
276 help='fetch only current branch from server')
Mike Frysinger73561142021-05-03 01:10:09 -0400277 p.add_option('--no-current-branch',
278 dest='current_branch_only', action='store_false',
279 help='fetch all branches from server')
Chris Wolfee9dc3b32012-01-26 11:36:18 -0500280 p.add_option('-m', '--manifest-name',
281 dest='manifest_name',
282 help='temporary manifest to use for this sync', metavar='NAME.xml')
Xin Lid79a4bc2020-05-20 16:03:45 -0700283 p.add_option('--clone-bundle', action='store_true',
284 help='enable use of /clone.bundle on HTTP/HTTPS')
285 p.add_option('--no-clone-bundle', dest='clone_bundle', action='store_false',
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700286 help='disable use of /clone.bundle on HTTP/HTTPS')
Conley Owens8d070cf2012-11-06 13:14:31 -0800287 p.add_option('-u', '--manifest-server-username', action='store',
288 dest='manifest_server_username',
289 help='username to authenticate with the manifest server')
290 p.add_option('-p', '--manifest-server-password', action='store',
291 dest='manifest_server_password',
292 help='password to authenticate with the manifest server')
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800293 p.add_option('--fetch-submodules',
294 dest='fetch_submodules', action='store_true',
295 help='fetch submodules from server')
Raman Tenneti6a872c92021-01-14 19:17:50 -0800296 p.add_option('--use-superproject', action='store_true',
Raman Tenneti62517292021-11-01 14:49:16 -0700297 help='use the manifest superproject to sync projects; implies -c')
Raman Tenneti23ea7542021-05-07 14:01:54 -0700298 p.add_option('--no-use-superproject', action='store_false',
299 dest='use_superproject',
300 help='disable use of manifest superprojects')
Mike Frysinger2273f462021-11-05 15:10:33 -0400301 p.add_option('--tags', action='store_true',
Mike Frysingerd68ed632021-05-03 01:21:35 -0400302 help='fetch tags')
Mitchel Humpherys597868b2012-10-29 10:18:34 -0700303 p.add_option('--no-tags',
Mike Frysingerd68ed632021-05-03 01:21:35 -0400304 dest='tags', action='store_false',
Mike Frysinger2273f462021-11-05 15:10:33 -0400305 help="don't fetch tags (default)")
David Pursehouseb1553542014-09-04 21:28:09 +0900306 p.add_option('--optimized-fetch',
307 dest='optimized_fetch', action='store_true',
308 help='only fetch projects fixed to sha1 if revision does not exist locally')
George Engelbrecht9bc283e2020-04-02 12:36:09 -0600309 p.add_option('--retry-fetches',
310 default=0, action='store', type='int',
311 help='number of times to retry fetches on transient errors')
Mike Frysinger0531a622021-11-05 15:22:01 -0400312 p.add_option('--prune', action='store_true',
313 help='delete refs that no longer exist on the remote (default)')
314 p.add_option('--no-prune', dest='prune', action='store_false',
315 help='do not delete refs that no longer exist on the remote')
LaMont Jones5ed8c632022-11-10 00:10:44 +0000316 p.add_option('--auto-gc', action='store_true', default=None,
LaMont Jones7efab532022-09-01 15:41:12 +0000317 help='run garbage collection on all synced projects')
318 p.add_option('--no-auto-gc', dest='auto_gc', action='store_false',
319 help='do not run garbage collection on any projects (default)')
Nico Sallembien6623b212010-05-11 12:57:01 -0700320 if show_smart:
321 p.add_option('-s', '--smart-sync',
322 dest='smart_sync', action='store_true',
David Pursehouse79fba682016-04-13 18:03:00 +0900323 help='smart sync using manifest from the latest known good build')
Victor Boivie08c880d2011-04-19 10:32:52 +0200324 p.add_option('-t', '--smart-tag',
325 dest='smart_tag', action='store',
326 help='smart sync using manifest from a known tag')
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700327
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700328 g = p.add_option_group('repo Version options')
329 g.add_option('--no-repo-verify',
Mike Frysingerc58ec4d2020-02-17 14:36:08 -0500330 dest='repo_verify', default=True, action='store_false',
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700331 help='do not verify repo source code')
Shawn O. Pearcefd89b672009-04-18 11:28:57 -0700332 g.add_option('--repo-upgraded',
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800333 dest='repo_upgraded', action='store_true',
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -0700334 help=SUPPRESS_HELP)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700335
LaMont Jonesa46047a2022-04-07 21:57:06 +0000336 def _GetBranch(self, manifest_project):
337 """Returns the branch name for getting the approved smartsync manifest.
338
339 Args:
340 manifest_project: the manifestProject to query.
341 """
342 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800343 branch = b.merge
344 if branch.startswith(R_HEADS):
345 branch = branch[len(R_HEADS):]
346 return branch
347
LaMont Jonesa46047a2022-04-07 21:57:06 +0000348 def _GetCurrentBranchOnly(self, opt, manifest):
Daniel Anderssond52ca422022-04-01 12:55:38 +0200349 """Returns whether current-branch or use-superproject options are enabled.
350
LaMont Jonesa46047a2022-04-07 21:57:06 +0000351 Args:
352 opt: Program options returned from optparse. See _Options().
353 manifest: The manifest to use.
354
Daniel Anderssond52ca422022-04-01 12:55:38 +0200355 Returns:
356 True if a superproject is requested, otherwise the value of the
357 current_branch option (True, False or None).
358 """
LaMont Jonesa46047a2022-04-07 21:57:06 +0000359 return git_superproject.UseSuperproject(opt.use_superproject, manifest) or opt.current_branch_only
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700360
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000361 def _UpdateProjectsRevisionId(self, opt, args, superproject_logging_data,
362 manifest):
363 """Update revisionId of projects with the commit hash from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800364
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000365 This function updates each project's revisionId with the commit hash from
366 the superproject. It writes the updated manifest into a file and reloads
367 the manifest from it. When appropriate, sub manifests are also processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800368
369 Args:
370 opt: Program options returned from optparse. See _Options().
371 args: Arguments to pass to GetProjects. See the GetProjects
372 docstring for details.
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000373 superproject_logging_data: A dictionary of superproject data to log.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000374 manifest: The manifest to use.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800375 """
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000376 have_superproject = manifest.superproject or any(
377 m.superproject for m in manifest.all_children)
378 if not have_superproject:
379 return
380
LaMont Jonesff6b1da2022-06-01 21:03:34 +0000381 if opt.local_only and manifest.superproject:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000382 manifest_path = manifest.superproject.manifest_path
Raman Tennetiae86a462021-07-27 08:54:59 -0700383 if manifest_path:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000384 self._ReloadManifest(manifest_path, manifest)
385 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700386
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800387 all_projects = self.GetProjects(args,
388 missing_ok=True,
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000389 submodules_ok=opt.fetch_submodules,
390 manifest=manifest,
391 all_manifests=not opt.this_manifest_only)
392
393 per_manifest = collections.defaultdict(list)
394 manifest_paths = {}
395 if opt.this_manifest_only:
396 per_manifest[manifest.path_prefix] = all_projects
Raman Tenneti784e16f2021-06-11 17:29:45 -0700397 else:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000398 for p in all_projects:
399 per_manifest[p.manifest.path_prefix].append(p)
400
401 superproject_logging_data = {}
402 need_unload = False
403 for m in self.ManifestList(opt):
404 if not m.path_prefix in per_manifest:
405 continue
406 use_super = git_superproject.UseSuperproject(opt.use_superproject, m)
407 if superproject_logging_data:
408 superproject_logging_data['multimanifest'] = True
409 superproject_logging_data.update(
410 superproject=use_super,
411 haslocalmanifests=bool(m.HasLocalManifests),
412 hassuperprojecttag=bool(m.superproject),
413 )
414 if use_super and (m.IsMirror or m.IsArchive):
415 # Don't use superproject, because we have no working tree.
416 use_super = False
417 superproject_logging_data['superproject'] = False
418 superproject_logging_data['noworktree'] = True
419 if opt.use_superproject is not False:
420 print(f'{m.path_prefix}: not using superproject because there is no '
421 'working tree.')
422
423 if not use_super:
424 continue
425 m.superproject.SetQuiet(opt.quiet)
426 print_messages = git_superproject.PrintMessages(opt.use_superproject, m)
427 m.superproject.SetPrintMessages(print_messages)
428 update_result = m.superproject.UpdateProjectsRevisionId(
429 per_manifest[m.path_prefix], git_event_log=self.git_event_log)
430 manifest_path = update_result.manifest_path
431 superproject_logging_data['updatedrevisionid'] = bool(manifest_path)
432 if manifest_path:
433 m.SetManifestOverride(manifest_path)
434 need_unload = True
435 else:
436 if print_messages:
437 print(f'{m.path_prefix}: warning: Update of revisionId from '
438 'superproject has failed, repo sync will not use superproject '
439 'to fetch the source. ',
440 'Please resync with the --no-use-superproject option to avoid '
441 'this repo warning.',
442 file=sys.stderr)
443 if update_result.fatal and opt.use_superproject is not None:
444 sys.exit(1)
445 if need_unload:
446 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800447
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500448 def _FetchProjectList(self, opt, projects):
449 """Main function of the fetch worker.
450
451 The projects we're given share the same underlying git object store, so we
452 have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800453
David James8d201162013-10-11 17:03:19 -0700454 Delegates most of the work to _FetchHelper.
455
456 Args:
457 opt: Program options returned from optparse. See _Options().
458 projects: Projects to fetch.
David James8d201162013-10-11 17:03:19 -0700459 """
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500460 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700461
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500462 def _FetchOne(self, opt, project):
David James8d201162013-10-11 17:03:19 -0700463 """Fetch git objects for a single project.
464
David Pursehousec1b86a22012-11-14 11:36:51 +0900465 Args:
466 opt: Program options returned from optparse. See _Options().
467 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700468
469 Returns:
470 Whether the fetch was successful.
David Pursehousec1b86a22012-11-14 11:36:51 +0900471 """
David Rileye0684ad2017-04-05 00:02:59 -0700472 start = time.time()
473 success = False
Mike Frysinger7b586f22021-02-23 18:38:39 -0500474 buf = io.StringIO()
David Pursehousec1b86a22012-11-14 11:36:51 +0900475 try:
LaMont Jones1eddca82022-09-01 15:15:04 +0000476 sync_result = project.Sync_NetworkHalf(
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500477 quiet=opt.quiet,
478 verbose=opt.verbose,
479 output_redir=buf,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000480 current_branch_only=self._GetCurrentBranchOnly(opt, project.manifest),
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500481 force_sync=opt.force_sync,
482 clone_bundle=opt.clone_bundle,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000483 tags=opt.tags, archive=project.manifest.IsArchive,
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500484 optimized_fetch=opt.optimized_fetch,
485 retry_fetches=opt.retry_fetches,
486 prune=opt.prune,
Mike Frysinger339f2df2021-05-06 00:44:42 -0400487 ssh_proxy=self.ssh_proxy,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000488 clone_filter=project.manifest.CloneFilter,
489 partial_clone_exclude=project.manifest.PartialCloneExclude)
LaMont Jones1eddca82022-09-01 15:15:04 +0000490 success = sync_result.success
Doug Andersonfc06ced2011-03-16 15:49:18 -0700491
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500492 output = buf.getvalue()
Mike Frysinger58929732021-07-02 00:29:35 -0400493 if (opt.verbose or not success) and output:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500494 print('\n' + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700495
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500496 if not success:
497 print('error: Cannot fetch %s from %s'
498 % (project.name, project.remote.url),
499 file=sys.stderr)
Raman Tennetiad8aa692021-04-15 09:20:51 -0700500 except GitError as e:
501 print('error.GitError: Cannot fetch %s' % str(e), file=sys.stderr)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500502 except Exception as e:
503 print('error: Cannot fetch %s (%s: %s)'
504 % (project.name, type(e).__name__, str(e)), file=sys.stderr)
505 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500506
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500507 finish = time.time()
LaMont Jones1eddca82022-09-01 15:15:04 +0000508 return _FetchOneResult(success, project, start, finish,
509 sync_result.remote_fetched)
David James8d201162013-10-11 17:03:19 -0700510
Mike Frysinger339f2df2021-05-06 00:44:42 -0400511 @classmethod
512 def _FetchInitChild(cls, ssh_proxy):
513 cls.ssh_proxy = ssh_proxy
514
515 def _Fetch(self, projects, opt, err_event, ssh_proxy):
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500516 ret = True
517
Mike Frysinger355f4392022-07-20 17:15:29 -0400518 jobs = opt.jobs_network
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700519 fetched = set()
LaMont Jones1eddca82022-09-01 15:15:04 +0000520 remote_fetched = set()
Mike Frysinger151701e2021-04-13 15:07:21 -0400521 pm = Progress('Fetching', len(projects), delay=False, quiet=opt.quiet)
Roy Lee18afd7f2010-05-09 04:32:08 +0800522
David James89ece422014-01-09 18:51:58 -0800523 objdir_project_map = dict()
524 for project in projects:
525 objdir_project_map.setdefault(project.objdir, []).append(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500526 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700527
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500528 def _ProcessResults(results_sets):
529 ret = True
530 for results in results_sets:
LaMont Jones1eddca82022-09-01 15:15:04 +0000531 for result in results:
532 success = result.success
533 project = result.project
534 start = result.start
535 finish = result.finish
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500536 self._fetch_times.Set(project, finish - start)
537 self.event_log.AddSync(project, event_log.TASK_SYNC_NETWORK,
538 start, finish, success)
LaMont Jones1eddca82022-09-01 15:15:04 +0000539 if result.remote_fetched:
540 remote_fetched.add(project)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500541 # Check for any errors before running any more tasks.
542 # ...we'll let existing jobs finish, though.
543 if not success:
544 ret = False
545 else:
546 fetched.add(project.gitdir)
547 pm.update(msg=project.name)
548 if not ret and opt.fail_fast:
549 break
550 return ret
Doug Andersonfc06ced2011-03-16 15:49:18 -0700551
Mike Frysinger339f2df2021-05-06 00:44:42 -0400552 # We pass the ssh proxy settings via the class. This allows multiprocessing
553 # to pickle it up when spawning children. We can't pass it as an argument
554 # to _FetchProjectList below as multiprocessing is unable to pickle those.
555 Sync.ssh_proxy = None
556
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500557 # NB: Multiprocessing is heavy, so don't spin it up for one job.
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400558 if len(projects_list) == 1 or jobs == 1:
Mike Frysinger339f2df2021-05-06 00:44:42 -0400559 self._FetchInitChild(ssh_proxy)
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500560 if not _ProcessResults(self._FetchProjectList(opt, x) for x in projects_list):
561 ret = False
562 else:
563 # Favor throughput over responsiveness when quiet. It seems that imap()
564 # will yield results in batches relative to chunksize, so even as the
565 # children finish a sync, we won't see the result until one child finishes
566 # ~chunksize jobs. When using a large --jobs with large chunksize, this
567 # can be jarring as there will be a large initial delay where repo looks
568 # like it isn't doing anything and sits at 0%, but then suddenly completes
569 # a lot of jobs all at once. Since this code is more network bound, we
570 # can accept a bit more CPU overhead with a smaller chunksize so that the
571 # user sees more immediate & continuous feedback.
572 if opt.quiet:
573 chunksize = WORKER_BATCH_SIZE
David James89ece422014-01-09 18:51:58 -0800574 else:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500575 pm.update(inc=0, msg='warming up')
576 chunksize = 4
Raman Tenneti4a478ed2021-11-17 18:38:24 -0800577 with multiprocessing.Pool(jobs, initializer=self._FetchInitChild,
578 initargs=(ssh_proxy,)) as pool:
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500579 results = pool.imap_unordered(
580 functools.partial(self._FetchProjectList, opt),
581 projects_list,
582 chunksize=chunksize)
583 if not _ProcessResults(results):
584 ret = False
585 pool.close()
Roy Lee18afd7f2010-05-09 04:32:08 +0800586
Mike Frysinger339f2df2021-05-06 00:44:42 -0400587 # Cleanup the reference now that we're done with it, and we're going to
588 # release any resources it points to. If we don't, later multiprocessing
589 # usage (e.g. checkouts) will try to pickle and then crash.
590 del Sync.ssh_proxy
591
Shawn O. Pearce68194f42009-04-10 16:48:52 -0700592 pm.end()
Dave Borowitz67700e92012-10-23 15:00:54 -0700593 self._fetch_times.Save()
Dave Borowitz18857212012-10-23 17:02:59 -0700594
LaMont Jonesa46047a2022-04-07 21:57:06 +0000595 if not self.outer_client.manifest.IsArchive:
Mike Frysinger5a033082019-09-23 19:21:20 -0400596 self._GCProjects(projects, opt, err_event)
Julien Campergue335f5ef2013-10-16 11:02:35 +0200597
LaMont Jones1eddca82022-09-01 15:15:04 +0000598 return _FetchResult(ret, fetched)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700599
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000600 def _FetchMain(self, opt, args, all_projects, err_event,
601 ssh_proxy, manifest):
Mike Frysingerb4429432021-05-05 20:03:26 -0400602 """The main network fetch loop.
603
604 Args:
605 opt: Program options returned from optparse. See _Options().
606 args: Command line args used to filter out projects.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200607 all_projects: List of all projects that should be fetched.
Mike Frysingerb4429432021-05-05 20:03:26 -0400608 err_event: Whether an error was hit while processing.
Mike Frysinger339f2df2021-05-06 00:44:42 -0400609 ssh_proxy: SSH manager for clients & masters.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000610 manifest: The manifest to use.
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200611
612 Returns:
613 List of all projects that should be checked out.
Mike Frysingerb4429432021-05-05 20:03:26 -0400614 """
LaMont Jonesa46047a2022-04-07 21:57:06 +0000615 rp = manifest.repoProject
Mike Frysingerb4429432021-05-05 20:03:26 -0400616
617 to_fetch = []
618 now = time.time()
619 if _ONE_DAY_S <= (now - rp.LastFetch):
620 to_fetch.append(rp)
621 to_fetch.extend(all_projects)
622 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
623
LaMont Jones1eddca82022-09-01 15:15:04 +0000624 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
625 success = result.success
626 fetched = result.projects
Mike Frysingerb4429432021-05-05 20:03:26 -0400627 if not success:
628 err_event.set()
629
630 _PostRepoFetch(rp, opt.repo_verify)
631 if opt.network_only:
632 # bail out now; the rest touches the working tree
633 if err_event.is_set():
634 print('\nerror: Exited sync due to fetch errors.\n', file=sys.stderr)
635 sys.exit(1)
LaMont Jones1eddca82022-09-01 15:15:04 +0000636 return _FetchMainResult([])
Mike Frysingerb4429432021-05-05 20:03:26 -0400637
638 # Iteratively fetch missing and/or nested unregistered submodules
639 previously_missing_set = set()
640 while True:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000641 self._ReloadManifest(None, manifest)
Mike Frysingerb4429432021-05-05 20:03:26 -0400642 all_projects = self.GetProjects(args,
643 missing_ok=True,
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000644 submodules_ok=opt.fetch_submodules,
645 manifest=manifest,
646 all_manifests=not opt.this_manifest_only)
Mike Frysingerb4429432021-05-05 20:03:26 -0400647 missing = []
648 for project in all_projects:
649 if project.gitdir not in fetched:
650 missing.append(project)
651 if not missing:
652 break
653 # Stop us from non-stopped fetching actually-missing repos: If set of
654 # missing repos has not been changed from last fetch, we break.
655 missing_set = set(p.name for p in missing)
656 if previously_missing_set == missing_set:
657 break
658 previously_missing_set = missing_set
LaMont Jones1eddca82022-09-01 15:15:04 +0000659 result = self._Fetch(missing, opt, err_event, ssh_proxy)
660 success = result.success
661 new_fetched = result.projects
Mike Frysingerb4429432021-05-05 20:03:26 -0400662 if not success:
663 err_event.set()
664 fetched.update(new_fetched)
665
LaMont Jones1eddca82022-09-01 15:15:04 +0000666 return _FetchMainResult(all_projects)
Peter Kjellerstedtd1776092021-05-19 19:37:23 +0200667
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500668 def _CheckoutOne(self, detach_head, force_sync, project):
Xin Li745be2e2019-06-03 11:24:30 -0700669 """Checkout work tree for one project
670
671 Args:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500672 detach_head: Whether to leave a detached HEAD.
673 force_sync: Force checking out of the repo.
Xin Li745be2e2019-06-03 11:24:30 -0700674 project: Project object for the project to checkout.
Xin Li745be2e2019-06-03 11:24:30 -0700675
676 Returns:
677 Whether the fetch was successful.
678 """
Xin Li745be2e2019-06-03 11:24:30 -0700679 start = time.time()
LaMont Jonesa46047a2022-04-07 21:57:06 +0000680 syncbuf = SyncBuffer(project.manifest.manifestProject.config,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500681 detach_head=detach_head)
Xin Li745be2e2019-06-03 11:24:30 -0700682 success = False
683 try:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500684 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500685 success = syncbuf.Finish()
Raman Tennetiad8aa692021-04-15 09:20:51 -0700686 except GitError as e:
687 print('error.GitError: Cannot checkout %s: %s' %
688 (project.name, str(e)), file=sys.stderr)
Mike Frysingerebf04a42021-02-23 20:48:04 -0500689 except Exception as e:
690 print('error: Cannot checkout %s: %s: %s' %
691 (project.name, type(e).__name__, str(e)),
692 file=sys.stderr)
693 raise
Xin Li745be2e2019-06-03 11:24:30 -0700694
Mike Frysingerebf04a42021-02-23 20:48:04 -0500695 if not success:
696 print('error: Cannot checkout %s' % (project.name), file=sys.stderr)
697 finish = time.time()
LaMont Jones1eddca82022-09-01 15:15:04 +0000698 return _CheckoutOneResult(success, project, start, finish)
Xin Li745be2e2019-06-03 11:24:30 -0700699
Mike Frysingerebf04a42021-02-23 20:48:04 -0500700 def _Checkout(self, all_projects, opt, err_results):
Xin Li745be2e2019-06-03 11:24:30 -0700701 """Checkout projects listed in all_projects
702
703 Args:
704 all_projects: List of all projects that should be checked out.
705 opt: Program options returned from optparse. See _Options().
Mike Frysingerebf04a42021-02-23 20:48:04 -0500706 err_results: A list of strings, paths to git repos where checkout failed.
Xin Li745be2e2019-06-03 11:24:30 -0700707 """
Mike Frysingerebf04a42021-02-23 20:48:04 -0500708 # Only checkout projects with worktrees.
709 all_projects = [x for x in all_projects if x.worktree]
Xin Li745be2e2019-06-03 11:24:30 -0700710
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500711 def _ProcessResults(pool, pm, results):
712 ret = True
LaMont Jones1eddca82022-09-01 15:15:04 +0000713 for result in results:
714 success = result.success
715 project = result.project
716 start = result.start
717 finish = result.finish
Mike Frysingerebf04a42021-02-23 20:48:04 -0500718 self.event_log.AddSync(project, event_log.TASK_SYNC_LOCAL,
719 start, finish, success)
720 # Check for any errors before running any more tasks.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500721 # ...we'll let existing jobs finish, though.
Mike Frysingerebf04a42021-02-23 20:48:04 -0500722 if not success:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500723 ret = False
LaMont Jonesbee4efb2022-09-30 17:46:52 +0000724 err_results.append(project.RelPath(local=opt.this_manifest_only))
Mike Frysingerebf04a42021-02-23 20:48:04 -0500725 if opt.fail_fast:
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500726 if pool:
727 pool.close()
728 return ret
Mike Frysingerebf04a42021-02-23 20:48:04 -0500729 pm.update(msg=project.name)
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500730 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700731
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500732 return self.ExecuteInParallel(
Mike Frysinger355f4392022-07-20 17:15:29 -0400733 opt.jobs_checkout,
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500734 functools.partial(self._CheckoutOne, opt.detach_head, opt.force_sync),
735 all_projects,
736 callback=_ProcessResults,
737 output=Progress('Checking out', len(all_projects), quiet=opt.quiet)) and not err_results
Mike Frysingerebf04a42021-02-23 20:48:04 -0500738
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000739 @staticmethod
740 def _GetPreciousObjectsState(project: Project, opt):
741 """Get the preciousObjects state for the project.
742
743 Args:
744 project (Project): the project to examine, and possibly correct.
745 opt (optparse.Values): options given to sync.
746
747 Returns:
748 Expected state of extensions.preciousObjects:
749 False: Should be disabled. (not present)
750 True: Should be enabled.
751 """
752 if project.use_git_worktrees:
753 return False
754 projects = project.manifest.GetProjectsWithName(project.name,
755 all_manifests=True)
756 if len(projects) == 1:
757 return False
758 relpath = project.RelPath(local=opt.this_manifest_only)
759 if len(projects) > 1:
760 # Objects are potentially shared with another project.
761 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
762 # - When False, shared projects share (via symlink)
763 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only objects
764 # directory. All objects are precious, since there is no project with a
765 # complete set of refs.
766 # - When True, shared projects share (via info/alternates)
767 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object store,
768 # which is written only on the first clone of the project, and is not
769 # written subsequently. (When Sync_NetworkHalf sees that it exists, it
770 # makes sure that the alternates file points there, and uses a
771 # project-local .git/objects directory for all syncs going forward.
772 # We do not support switching between the options. The environment
773 # variable is present for testing and migration only.
774 return not project.UseAlternates
775 print(f'\r{relpath}: project not found in manifest.', file=sys.stderr)
776 return False
777
778 def _RepairPreciousObjectsState(self, project: Project, opt):
779 """Correct the preciousObjects state for the project.
780
781 Args:
782 project (Project): the project to examine, and possibly correct.
783 opt (optparse.Values): options given to sync.
784 """
785 expected = self._GetPreciousObjectsState(project, opt)
786 actual = project.config.GetBoolean('extensions.preciousObjects') or False
787 relpath = project.RelPath(local = opt.this_manifest_only)
788
789 if (expected != actual and
790 not project.config.GetBoolean('repo.preservePreciousObjects')):
791 # If this is unexpected, log it and repair.
792 Trace(f'{relpath} expected preciousObjects={expected}, got {actual}')
793 if expected:
794 if not opt.quiet:
795 print('\r%s: Shared project %s found, disabling pruning.' %
796 (relpath, project.name))
797 if git_require((2, 7, 0)):
798 project.EnableRepositoryExtension('preciousObjects')
799 else:
800 # This isn't perfect, but it's the best we can do with old git.
801 print('\r%s: WARNING: shared projects are unreliable when using '
802 'old versions of git; please upgrade to git-2.7.0+.'
803 % (relpath,),
804 file=sys.stderr)
805 project.config.SetString('gc.pruneExpire', 'never')
806 else:
807 if not opt.quiet:
808 print(f'\r{relpath}: not shared, disabling pruning.')
809 project.config.SetString('extensions.preciousObjects', None)
810 project.config.SetString('gc.pruneExpire', None)
811
Mike Frysinger5a033082019-09-23 19:21:20 -0400812 def _GCProjects(self, projects, opt, err_event):
LaMont Jones7efab532022-09-01 15:41:12 +0000813 """Perform garbage collection.
814
815 If We are skipping garbage collection (opt.auto_gc not set), we still want
816 to potentially mark objects precious, so that `git gc` does not discard
817 shared objects.
818 """
819 pm = Progress(f'{"" if opt.auto_gc else "NOT "}Garbage collecting',
820 len(projects), delay=False, quiet=opt.quiet)
Mike Frysinger65af2602021-04-08 22:47:44 -0400821 pm.update(inc=0, msg='prescan')
822
Allen Webb4ee4a452021-10-07 10:42:38 -0500823 tidy_dirs = {}
David James8d201162013-10-11 17:03:19 -0700824 for project in projects:
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000825 self._RepairPreciousObjectsState(project, opt)
826
Allen Webb669efd02021-10-01 15:25:31 -0500827 project.config.SetString('gc.autoDetach', 'false')
Allen Webb4ee4a452021-10-07 10:42:38 -0500828 # Only call git gc once per objdir, but call pack-refs for the remainder.
829 if project.objdir not in tidy_dirs:
830 tidy_dirs[project.objdir] = (
831 True, # Run a full gc.
832 project.bare_git,
833 )
834 elif project.gitdir not in tidy_dirs:
835 tidy_dirs[project.gitdir] = (
836 False, # Do not run a full gc; just run pack-refs.
837 project.bare_git,
838 )
Mike Frysinger65af2602021-04-08 22:47:44 -0400839
LaMont Jones7efab532022-09-01 15:41:12 +0000840 if not opt.auto_gc:
841 pm.end()
842 return
843
Mike Frysinger355f4392022-07-20 17:15:29 -0400844 jobs = opt.jobs
Dave Borowitz18857212012-10-23 17:02:59 -0700845
846 if jobs < 2:
Allen Webb4ee4a452021-10-07 10:42:38 -0500847 for (run_gc, bare_git) in tidy_dirs.values():
Mike Frysinger65af2602021-04-08 22:47:44 -0400848 pm.update(msg=bare_git._project.name)
LaMont Jones891e8f72022-09-08 20:17:58 +0000849
Allen Webb4ee4a452021-10-07 10:42:38 -0500850 if run_gc:
LaMont Jones55b71252022-12-01 21:17:15 +0000851 bare_git.gc('--auto')
Allen Webb4ee4a452021-10-07 10:42:38 -0500852 else:
LaMont Jones55b71252022-12-01 21:17:15 +0000853 bare_git.pack_refs()
Mike Frysinger65af2602021-04-08 22:47:44 -0400854 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700855 return
856
Mike Frysinger355f4392022-07-20 17:15:29 -0400857 cpu_count = os.cpu_count()
Mike Frysinger0c0e9342019-06-13 12:42:39 -0400858 config = {'pack.threads': cpu_count // jobs if cpu_count > jobs else 1}
Dave Borowitz18857212012-10-23 17:02:59 -0700859
860 threads = set()
861 sem = _threading.Semaphore(jobs)
Dave Borowitz18857212012-10-23 17:02:59 -0700862
Allen Webb4ee4a452021-10-07 10:42:38 -0500863 def tidy_up(run_gc, bare_git):
Mike Frysinger65af2602021-04-08 22:47:44 -0400864 pm.start(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700865 try:
866 try:
Allen Webb4ee4a452021-10-07 10:42:38 -0500867 if run_gc:
LaMont Jones55b71252022-12-01 21:17:15 +0000868 bare_git.gc('--auto', config=config)
Allen Webb4ee4a452021-10-07 10:42:38 -0500869 else:
LaMont Jones55b71252022-12-01 21:17:15 +0000870 bare_git.pack_refs(config=config)
Dave Borowitz18857212012-10-23 17:02:59 -0700871 except GitError:
872 err_event.set()
David Pursehouse145e35b2020-02-12 15:40:47 +0900873 except Exception:
Dave Borowitz18857212012-10-23 17:02:59 -0700874 err_event.set()
875 raise
876 finally:
Mike Frysinger65af2602021-04-08 22:47:44 -0400877 pm.finish(bare_git._project.name)
Dave Borowitz18857212012-10-23 17:02:59 -0700878 sem.release()
879
Allen Webb4ee4a452021-10-07 10:42:38 -0500880 for (run_gc, bare_git) in tidy_dirs.values():
Mike Frysingerbe24a542021-02-23 03:24:12 -0500881 if err_event.is_set() and opt.fail_fast:
Dave Borowitz18857212012-10-23 17:02:59 -0700882 break
883 sem.acquire()
Allen Webb4ee4a452021-10-07 10:42:38 -0500884 t = _threading.Thread(target=tidy_up, args=(run_gc, bare_git,))
Dave Borowitz18857212012-10-23 17:02:59 -0700885 t.daemon = True
886 threads.add(t)
887 t.start()
888
889 for t in threads:
890 t.join()
Mike Frysinger65af2602021-04-08 22:47:44 -0400891 pm.end()
Dave Borowitz18857212012-10-23 17:02:59 -0700892
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000893 def _ReloadManifest(self, manifest_name, manifest):
Raman Tennetifeb28912021-05-02 19:47:29 -0700894 """Reload the manfiest from the file specified by the |manifest_name|.
895
896 It unloads the manifest if |manifest_name| is None.
897
898 Args:
899 manifest_name: Manifest file to be reloaded.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000900 manifest: The manifest to use.
Raman Tennetifeb28912021-05-02 19:47:29 -0700901 """
Tim Kilbourn07669002013-03-08 15:02:49 -0800902 if manifest_name:
LaMont Jonesa2ff20d2022-04-07 16:49:06 +0000903 # Override calls Unload already
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000904 manifest.Override(manifest_name)
Tim Kilbourn07669002013-03-08 15:02:49 -0800905 else:
LaMont Jonesa46047a2022-04-07 21:57:06 +0000906 manifest.Unload()
Tim Kilbourn07669002013-03-08 15:02:49 -0800907
LaMont Jonesa46047a2022-04-07 21:57:06 +0000908 def UpdateProjectList(self, opt, manifest):
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000909 """Update the cached projects list for |manifest|
910
911 In a multi-manifest checkout, each manifest has its own project.list.
912
913 Args:
914 opt: Program options returned from optparse. See _Options().
915 manifest: The manifest to use.
916
917 Returns:
918 0: success
919 1: failure
920 """
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700921 new_project_paths = []
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000922 for project in self.GetProjects(None, missing_ok=True, manifest=manifest,
923 all_manifests=False):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700924 if project.relpath:
925 new_project_paths.append(project.relpath)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700926 file_name = 'project.list'
LaMont Jonesa46047a2022-04-07 21:57:06 +0000927 file_path = os.path.join(manifest.subdir, file_name)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700928 old_project_paths = []
929
930 if os.path.exists(file_path):
Mike Frysinger3164d402019-11-11 05:40:22 -0500931 with open(file_path, 'r') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700932 old_project_paths = fd.read().split('\n')
Kuang-che Wu0d9b16d2019-04-06 00:49:47 +0800933 # In reversed order, so subfolders are deleted before parent folder.
934 for path in sorted(old_project_paths, reverse=True):
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700935 if not path:
936 continue
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700937 if path not in new_project_paths:
David Pursehouse8a68ff92012-09-24 12:15:13 +0900938 # If the path has already been deleted, we don't need to do it
LaMont Jonesa46047a2022-04-07 21:57:06 +0000939 gitdir = os.path.join(manifest.topdir, path, '.git')
Dan Willemsen43507912016-09-01 16:26:02 -0700940 if os.path.exists(gitdir):
David Pursehousec1b86a22012-11-14 11:36:51 +0900941 project = Project(
LaMont Jonesa46047a2022-04-07 21:57:06 +0000942 manifest=manifest,
David Pursehouseabdf7502020-02-12 14:58:39 +0900943 name=path,
944 remote=RemoteSpec('origin'),
945 gitdir=gitdir,
946 objdir=gitdir,
Mike Frysingerc0d18662020-02-19 19:19:18 -0500947 use_git_worktrees=os.path.isfile(gitdir),
LaMont Jonesa46047a2022-04-07 21:57:06 +0000948 worktree=os.path.join(manifest.topdir, path),
David Pursehouseabdf7502020-02-12 14:58:39 +0900949 relpath=path,
950 revisionExpr='HEAD',
951 revisionId=None,
952 groups=None)
Mike Frysingerc0d18662020-02-19 19:19:18 -0500953 if not project.DeleteWorktree(
David Pursehouseaa611a22020-02-20 10:47:26 +0900954 quiet=opt.quiet,
955 force=opt.force_remove_dirty):
Mike Frysingera850ca22019-08-07 17:19:24 -0400956 return 1
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700957
Shawn O. Pearce9fb29ce2009-06-04 20:41:02 -0700958 new_project_paths.sort()
Mike Frysinger3164d402019-11-11 05:40:22 -0500959 with open(file_path, 'w') as fd:
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700960 fd.write('\n'.join(new_project_paths))
Shawn O. Pearce3a68bb42009-06-04 16:18:09 -0700961 fd.write('\n')
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700962 return 0
963
LaMont Jonesa46047a2022-04-07 21:57:06 +0000964 def UpdateCopyLinkfileList(self, manifest):
jiajia tanga590e642021-04-25 20:02:02 +0800965 """Save all dests of copyfile and linkfile, and update them if needed.
966
967 Returns:
968 Whether update was successful.
969 """
970 new_paths = {}
971 new_linkfile_paths = []
972 new_copyfile_paths = []
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000973 for project in self.GetProjects(None, missing_ok=True,
974 manifest=manifest, all_manifests=False):
jiajia tanga590e642021-04-25 20:02:02 +0800975 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
976 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
977
978 new_paths = {
979 'linkfile': new_linkfile_paths,
980 'copyfile': new_copyfile_paths,
981 }
982
983 copylinkfile_name = 'copy-link-files.json'
LaMont Jonesa46047a2022-04-07 21:57:06 +0000984 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
jiajia tanga590e642021-04-25 20:02:02 +0800985 old_copylinkfile_paths = {}
986
987 if os.path.exists(copylinkfile_path):
988 with open(copylinkfile_path, 'rb') as fp:
989 try:
990 old_copylinkfile_paths = json.load(fp)
Raman Tenneti4a478ed2021-11-17 18:38:24 -0800991 except Exception:
jiajia tanga590e642021-04-25 20:02:02 +0800992 print('error: %s is not a json formatted file.' %
993 copylinkfile_path, file=sys.stderr)
994 platform_utils.remove(copylinkfile_path)
995 return False
996
997 need_remove_files = []
998 need_remove_files.extend(
999 set(old_copylinkfile_paths.get('linkfile', [])) -
1000 set(new_linkfile_paths))
1001 need_remove_files.extend(
1002 set(old_copylinkfile_paths.get('copyfile', [])) -
1003 set(new_copyfile_paths))
1004
1005 for need_remove_file in need_remove_files:
Mike Frysinger9d96f582021-09-28 11:27:24 -04001006 # Try to remove the updated copyfile or linkfile.
1007 # So, if the file is not exist, nothing need to do.
1008 platform_utils.remove(need_remove_file, missing_ok=True)
jiajia tanga590e642021-04-25 20:02:02 +08001009
1010 # Create copy-link-files.json, save dest path of "copyfile" and "linkfile".
1011 with open(copylinkfile_path, 'w', encoding='utf-8') as fp:
1012 json.dump(new_paths, fp)
1013 return True
1014
LaMont Jonesa46047a2022-04-07 21:57:06 +00001015 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1016 if not manifest.manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001017 print('error: cannot smart sync: no manifest server defined in '
1018 'manifest', file=sys.stderr)
1019 sys.exit(1)
1020
LaMont Jonesa46047a2022-04-07 21:57:06 +00001021 manifest_server = manifest.manifest_server
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001022 if not opt.quiet:
1023 print('Using manifest server %s' % manifest_server)
1024
David Pursehouseeeff3532020-02-12 11:24:10 +09001025 if '@' not in manifest_server:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001026 username = None
1027 password = None
1028 if opt.manifest_server_username and opt.manifest_server_password:
1029 username = opt.manifest_server_username
1030 password = opt.manifest_server_password
1031 else:
1032 try:
1033 info = netrc.netrc()
1034 except IOError:
1035 # .netrc file does not exist or could not be opened
1036 pass
1037 else:
1038 try:
1039 parse_result = urllib.parse.urlparse(manifest_server)
1040 if parse_result.hostname:
1041 auth = info.authenticators(parse_result.hostname)
1042 if auth:
1043 username, _account, password = auth
1044 else:
1045 print('No credentials found for %s in .netrc'
1046 % parse_result.hostname, file=sys.stderr)
1047 except netrc.NetrcParseError as e:
1048 print('Error parsing .netrc file: %s' % e, file=sys.stderr)
1049
1050 if (username and password):
1051 manifest_server = manifest_server.replace('://', '://%s:%s@' %
1052 (username, password),
1053 1)
1054
1055 transport = PersistentTransport(manifest_server)
1056 if manifest_server.startswith('persistent-'):
1057 manifest_server = manifest_server[len('persistent-'):]
1058
1059 try:
1060 server = xmlrpc.client.Server(manifest_server, transport=transport)
1061 if opt.smart_sync:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001062 branch = self._GetBranch(manifest.manifestProject)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001063
Mike Frysinger56ce3462019-12-04 19:30:48 -05001064 if 'SYNC_TARGET' in os.environ:
Mike Frysingere20da3e2020-03-07 01:53:53 -05001065 target = os.environ['SYNC_TARGET']
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001066 [success, manifest_str] = server.GetApprovedManifest(branch, target)
Mike Frysinger56ce3462019-12-04 19:30:48 -05001067 elif ('TARGET_PRODUCT' in os.environ and
1068 'TARGET_BUILD_VARIANT' in os.environ):
Mike Frysingere20da3e2020-03-07 01:53:53 -05001069 target = '%s-%s' % (os.environ['TARGET_PRODUCT'],
1070 os.environ['TARGET_BUILD_VARIANT'])
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001071 [success, manifest_str] = server.GetApprovedManifest(branch, target)
1072 else:
1073 [success, manifest_str] = server.GetApprovedManifest(branch)
1074 else:
1075 assert(opt.smart_tag)
1076 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1077
1078 if success:
1079 manifest_name = os.path.basename(smart_sync_manifest_path)
1080 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001081 with open(smart_sync_manifest_path, 'w') as f:
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001082 f.write(manifest_str)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001083 except IOError as e:
1084 print('error: cannot write manifest to %s:\n%s'
1085 % (smart_sync_manifest_path, e),
1086 file=sys.stderr)
1087 sys.exit(1)
LaMont Jonesa46047a2022-04-07 21:57:06 +00001088 self._ReloadManifest(manifest_name, manifest)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001089 else:
1090 print('error: manifest server RPC call failed: %s' %
1091 manifest_str, file=sys.stderr)
1092 sys.exit(1)
1093 except (socket.error, IOError, xmlrpc.client.Fault) as e:
1094 print('error: cannot connect to manifest server %s:\n%s'
LaMont Jonesa46047a2022-04-07 21:57:06 +00001095 % (manifest.manifest_server, e), file=sys.stderr)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001096 sys.exit(1)
1097 except xmlrpc.client.ProtocolError as e:
1098 print('error: cannot connect to manifest server %s:\n%d %s'
LaMont Jonesa46047a2022-04-07 21:57:06 +00001099 % (manifest.manifest_server, e.errcode, e.errmsg),
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001100 file=sys.stderr)
1101 sys.exit(1)
1102
1103 return manifest_name
1104
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001105 def _UpdateAllManifestProjects(self, opt, mp, manifest_name):
1106 """Fetch & update the local manifest project.
1107
1108 After syncing the manifest project, if the manifest has any sub manifests,
1109 those are recursively processed.
1110
1111 Args:
1112 opt: Program options returned from optparse. See _Options().
1113 mp: the manifestProject to query.
1114 manifest_name: Manifest file to be reloaded.
1115 """
1116 if not mp.standalone_manifest_url:
1117 self._UpdateManifestProject(opt, mp, manifest_name)
1118
1119 if mp.manifest.submanifests:
1120 for submanifest in mp.manifest.submanifests.values():
1121 child = submanifest.repo_client.manifest
1122 child.manifestProject.SyncWithPossibleInit(
1123 submanifest,
1124 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1125 verbose=opt.verbose,
1126 tags=opt.tags,
1127 git_event_log=self.git_event_log,
1128 )
1129 self._UpdateAllManifestProjects(opt, child.manifestProject, None)
1130
Mike Frysingerfb527e32019-08-27 02:34:32 -04001131 def _UpdateManifestProject(self, opt, mp, manifest_name):
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001132 """Fetch & update the local manifest project.
1133
1134 Args:
1135 opt: Program options returned from optparse. See _Options().
1136 mp: the manifestProject to query.
1137 manifest_name: Manifest file to be reloaded.
1138 """
Mike Frysingerfb527e32019-08-27 02:34:32 -04001139 if not opt.local_only:
1140 start = time.time()
Mike Frysinger521d01b2020-02-17 01:51:49 -05001141 success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
LaMont Jonesa46047a2022-04-07 21:57:06 +00001142 current_branch_only=self._GetCurrentBranchOnly(opt, mp.manifest),
Erwan Yvindc5c4d12019-06-18 13:49:12 +02001143 force_sync=opt.force_sync,
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001144 tags=opt.tags,
Mike Frysingerfb527e32019-08-27 02:34:32 -04001145 optimized_fetch=opt.optimized_fetch,
George Engelbrecht9bc283e2020-04-02 12:36:09 -06001146 retry_fetches=opt.retry_fetches,
LaMont Jonesa46047a2022-04-07 21:57:06 +00001147 submodules=mp.manifest.HasSubmodules,
1148 clone_filter=mp.manifest.CloneFilter,
1149 partial_clone_exclude=mp.manifest.PartialCloneExclude)
Mike Frysingerfb527e32019-08-27 02:34:32 -04001150 finish = time.time()
1151 self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
1152 start, finish, success)
1153
1154 if mp.HasChanges:
1155 syncbuf = SyncBuffer(mp.config)
1156 start = time.time()
LaMont Jonesa46047a2022-04-07 21:57:06 +00001157 mp.Sync_LocalHalf(syncbuf, submodules=mp.manifest.HasSubmodules)
Mike Frysingerfb527e32019-08-27 02:34:32 -04001158 clean = syncbuf.Finish()
1159 self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
1160 start, time.time(), clean)
1161 if not clean:
1162 sys.exit(1)
LaMont Jonesa46047a2022-04-07 21:57:06 +00001163 self._ReloadManifest(manifest_name, mp.manifest)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001164
Mike Frysingerae6cb082019-08-27 01:10:59 -04001165 def ValidateOptions(self, opt, args):
1166 if opt.force_broken:
1167 print('warning: -f/--force-broken is now the default behavior, and the '
1168 'options are deprecated', file=sys.stderr)
1169 if opt.network_only and opt.detach_head:
1170 self.OptionParser.error('cannot combine -n and -d')
1171 if opt.network_only and opt.local_only:
1172 self.OptionParser.error('cannot combine -n and -l')
1173 if opt.manifest_name and opt.smart_sync:
1174 self.OptionParser.error('cannot combine -m and -s')
1175 if opt.manifest_name and opt.smart_tag:
1176 self.OptionParser.error('cannot combine -m and -t')
1177 if opt.manifest_server_username or opt.manifest_server_password:
1178 if not (opt.smart_sync or opt.smart_tag):
1179 self.OptionParser.error('-u and -p may only be combined with -s or -t')
1180 if None in [opt.manifest_server_username, opt.manifest_server_password]:
1181 self.OptionParser.error('both -u and -p must be given')
1182
Mike Frysinger0531a622021-11-05 15:22:01 -04001183 if opt.prune is None:
1184 opt.prune = True
1185
LaMont Jones5ed8c632022-11-10 00:10:44 +00001186 if opt.auto_gc is None and _AUTO_GC:
1187 print(f"Will run `git gc --auto` because {_REPO_AUTO_GC} is set.",
1188 file=sys.stderr)
1189 opt.auto_gc = True
LaMont Jonesd7935532022-12-01 20:18:46 +00001190 print(f'{_REPO_AUTO_GC} is deprecated and will be removed in a future'
1191 'release. Use `--auto-gc` instead.', file=sys.stderr)
LaMont Jones5ed8c632022-11-10 00:10:44 +00001192
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001193 def Execute(self, opt, args):
LaMont Jonesa46047a2022-04-07 21:57:06 +00001194 manifest = self.outer_manifest
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001195 if not opt.outer_manifest:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001196 manifest = self.manifest
1197
Chris Wolfee9dc3b32012-01-26 11:36:18 -05001198 if opt.manifest_name:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001199 manifest.Override(opt.manifest_name)
Shawn O. Pearce3e768c92009-04-10 16:59:36 -07001200
Chirayu Desaia892b102013-06-11 14:18:46 +05301201 manifest_name = opt.manifest_name
David Pursehouse59b41742015-05-07 14:36:09 +09001202 smart_sync_manifest_path = os.path.join(
LaMont Jonesa46047a2022-04-07 21:57:06 +00001203 manifest.manifestProject.worktree, 'smart_sync_override.xml')
Chirayu Desaia892b102013-06-11 14:18:46 +05301204
Xin Lid79a4bc2020-05-20 16:03:45 -07001205 if opt.clone_bundle is None:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001206 opt.clone_bundle = manifest.CloneBundle
Xin Lid79a4bc2020-05-20 16:03:45 -07001207
Victor Boivie08c880d2011-04-19 10:32:52 +02001208 if opt.smart_sync or opt.smart_tag:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001209 manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path, manifest)
Mike Frysinger01d6c3c2019-08-27 01:56:43 -04001210 else:
David Pursehouse59b41742015-05-07 14:36:09 +09001211 if os.path.isfile(smart_sync_manifest_path):
1212 try:
Renaud Paquay010fed72016-11-11 14:25:29 -08001213 platform_utils.remove(smart_sync_manifest_path)
David Pursehouse59b41742015-05-07 14:36:09 +09001214 except OSError as e:
1215 print('error: failed to remove existing smart sync override manifest: %s' %
1216 e, file=sys.stderr)
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -07001217
Mike Frysingerc99322a2021-05-04 15:32:43 -04001218 err_event = multiprocessing.Event()
Mike Frysinger5a033082019-09-23 19:21:20 -04001219
LaMont Jonesa46047a2022-04-07 21:57:06 +00001220 rp = manifest.repoProject
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221 rp.PreSync()
Mike Frysinger23c900f2020-02-29 02:52:44 -05001222 cb = rp.CurrentBranch
1223 if cb:
1224 base = rp.GetBranch(cb).merge
1225 if not base or not base.startswith('refs/heads/'):
1226 print('warning: repo is not tracking a remote branch, so it will not '
Mike Frysinger58ac1672020-03-14 14:35:26 -04001227 'receive updates; run `repo init --repo-rev=stable` to fix.',
Mike Frysinger23c900f2020-02-29 02:52:44 -05001228 file=sys.stderr)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001229
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001230 for m in self.ManifestList(opt):
LaMont Jones4112c072022-08-24 17:32:25 +00001231 if not m.manifestProject.standalone_manifest_url:
1232 m.manifestProject.PreSync()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001233
LaMont Jones4112c072022-08-24 17:32:25 +00001234 if opt.repo_upgraded:
1235 _PostRepoUpgrade(manifest, quiet=opt.quiet)
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001236
LaMont Jones4112c072022-08-24 17:32:25 +00001237 mp = manifest.manifestProject
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001238 if opt.mp_update:
1239 self._UpdateAllManifestProjects(opt, mp, manifest_name)
1240 else:
Fredrik de Grootcc960972019-11-22 09:04:31 +01001241 print('Skipping update of local manifest project.')
Simran Basib9a1b732015-08-20 12:19:28 -07001242
Mike Frysinger355f4392022-07-20 17:15:29 -04001243 # Now that the manifests are up-to-date, setup the jobs value.
1244 if opt.jobs is None:
1245 # User has not made a choice, so use the manifest settings.
1246 opt.jobs = mp.default.sync_j
1247 if opt.jobs is not None:
1248 # Neither user nor manifest have made a choice.
1249 if opt.jobs_network is None:
1250 opt.jobs_network = opt.jobs
1251 if opt.jobs_checkout is None:
1252 opt.jobs_checkout = opt.jobs
1253 # Setup defaults if jobs==0.
1254 if not opt.jobs:
1255 if not opt.jobs_network:
1256 opt.jobs_network = 1
1257 if not opt.jobs_checkout:
1258 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1259 opt.jobs = os.cpu_count()
1260
1261 # Try to stay under user rlimit settings.
1262 #
1263 # Since each worker requires at 3 file descriptors to run `git fetch`, use
1264 # that to scale down the number of jobs. Unfortunately there isn't an easy
1265 # way to determine this reliably as systems change, but it was last measured
1266 # by hand in 2011.
1267 soft_limit, _ = _rlimit_nofile()
1268 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1269 opt.jobs = min(opt.jobs, jobs_soft_limit)
1270 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1271 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1272
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001273 superproject_logging_data = {}
1274 self._UpdateProjectsRevisionId(opt, args, superproject_logging_data,
1275 manifest)
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001276
Simran Basib9a1b732015-08-20 12:19:28 -07001277 if self.gitc_manifest:
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001278 gitc_manifest_projects = self.GetProjects(args, missing_ok=True)
Simran Basib9a1b732015-08-20 12:19:28 -07001279 gitc_projects = []
1280 opened_projects = []
1281 for project in gitc_manifest_projects:
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001282 if project.relpath in self.gitc_manifest.paths and \
1283 self.gitc_manifest.paths[project.relpath].old_revision:
1284 opened_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -07001285 else:
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001286 gitc_projects.append(project.relpath)
Simran Basib9a1b732015-08-20 12:19:28 -07001287
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001288 if not args:
1289 gitc_projects = None
1290
1291 if gitc_projects != [] and not opt.local_only:
Simran Basib9a1b732015-08-20 12:19:28 -07001292 print('Updating GITC client: %s' % self.gitc_manifest.gitc_client_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001293 manifest = GitcManifest(self.repodir, self.gitc_manifest.gitc_client_name)
1294 if manifest_name:
1295 manifest.Override(manifest_name)
1296 else:
LaMont Jonesa46047a2022-04-07 21:57:06 +00001297 manifest.Override(manifest.manifestFile)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001298 gitc_utils.generate_gitc_manifest(self.gitc_manifest,
1299 manifest,
Simran Basib9a1b732015-08-20 12:19:28 -07001300 gitc_projects)
1301 print('GITC client successfully synced.')
1302
1303 # The opened projects need to be synced as normal, therefore we
1304 # generate a new args list to represent the opened projects.
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001305 # TODO: make this more reliable -- if there's a project name/path overlap,
1306 # this may choose the wrong project.
LaMont Jonesa46047a2022-04-07 21:57:06 +00001307 args = [os.path.relpath(manifest.paths[path].worktree, os.getcwd())
David Pursehouse3bcd3052017-07-10 22:42:22 +09001308 for path in opened_projects]
Simran Basib9a1b732015-08-20 12:19:28 -07001309 if not args:
1310 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001311
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001312 all_projects = self.GetProjects(args,
1313 missing_ok=True,
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001314 submodules_ok=opt.fetch_submodules,
1315 manifest=manifest,
1316 all_manifests=not opt.this_manifest_only)
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001317
Mike Frysinger5a033082019-09-23 19:21:20 -04001318 err_network_sync = False
1319 err_update_projects = False
LaMont Jonesb6cfa092022-10-26 16:34:40 +00001320 err_update_linkfiles = False
Mike Frysinger5a033082019-09-23 19:21:20 -04001321
LaMont Jonesa46047a2022-04-07 21:57:06 +00001322 self._fetch_times = _FetchTimes(manifest)
Shawn O. Pearceb1562fa2009-04-10 17:04:08 -07001323 if not opt.local_only:
Mike Frysinger339f2df2021-05-06 00:44:42 -04001324 with multiprocessing.Manager() as manager:
1325 with ssh.ProxyManager(manager) as ssh_proxy:
1326 # Initialize the socket dir once in the parent.
1327 ssh_proxy.sock()
LaMont Jones1eddca82022-09-01 15:15:04 +00001328 result = self._FetchMain(opt, args, all_projects, err_event,
1329 ssh_proxy, manifest)
1330 all_projects = result.all_projects
Mike Frysinger339f2df2021-05-06 00:44:42 -04001331
1332 if opt.network_only:
1333 return
Mike Frysinger5a033082019-09-23 19:21:20 -04001334
1335 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001336 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001337 err_network_sync = True
1338 if opt.fail_fast:
1339 print('\nerror: Exited sync due to fetch errors.\n'
1340 'Local checkouts *not* updated. Resolve network issues & '
1341 'retry.\n'
1342 '`repo sync -l` will update some local checkouts.',
1343 file=sys.stderr)
1344 sys.exit(1)
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001345
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001346 for m in self.ManifestList(opt):
1347 if m.IsMirror or m.IsArchive:
1348 # bail out now, we have no working tree
1349 continue
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001350
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001351 if self.UpdateProjectList(opt, m):
1352 err_event.set()
1353 err_update_projects = True
1354 if opt.fail_fast:
1355 print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
1356 sys.exit(1)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001357
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001358 err_update_linkfiles = not self.UpdateCopyLinkfileList(m)
1359 if err_update_linkfiles:
1360 err_event.set()
1361 if opt.fail_fast:
1362 print('\nerror: Local update copyfile or linkfile failed.', file=sys.stderr)
1363 sys.exit(1)
jiajia tanga590e642021-04-25 20:02:02 +08001364
Mike Frysinger5a033082019-09-23 19:21:20 -04001365 err_results = []
Mike Frysingerebf04a42021-02-23 20:48:04 -05001366 # NB: We don't exit here because this is the last step.
1367 err_checkout = not self._Checkout(all_projects, opt, err_results)
1368 if err_checkout:
1369 err_event.set()
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001370
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001371 printed_notices = set()
1372 # If there's a notice that's supposed to print at the end of the sync,
1373 # print it now... But avoid printing duplicate messages, and preserve
1374 # order.
1375 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1376 if m.notice and m.notice not in printed_notices:
1377 print(m.notice)
1378 printed_notices.add(m.notice)
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001379
Mike Frysinger5a033082019-09-23 19:21:20 -04001380 # If we saw an error, exit with code 1 so that other scripts can check.
Mike Frysingerbe24a542021-02-23 03:24:12 -05001381 if err_event.is_set():
Mike Frysinger5a033082019-09-23 19:21:20 -04001382 print('\nerror: Unable to fully sync the tree.', file=sys.stderr)
1383 if err_network_sync:
1384 print('error: Downloading network changes failed.', file=sys.stderr)
1385 if err_update_projects:
1386 print('error: Updating local project lists failed.', file=sys.stderr)
jiajia tanga590e642021-04-25 20:02:02 +08001387 if err_update_linkfiles:
1388 print('error: Updating copyfiles or linkfiles failed.', file=sys.stderr)
Mike Frysinger5a033082019-09-23 19:21:20 -04001389 if err_checkout:
1390 print('error: Checking out local projects failed.', file=sys.stderr)
1391 if err_results:
1392 print('Failing repos:\n%s' % '\n'.join(err_results), file=sys.stderr)
1393 print('Try re-running with "-j1 --fail-fast" to exit at the first error.',
1394 file=sys.stderr)
1395 sys.exit(1)
1396
Raman Tenneti7954de12021-07-28 14:36:49 -07001397 # Log the previous sync analysis state from the config.
Raman Tenneti6448a4f2021-09-13 17:40:07 -07001398 self.git_event_log.LogDataConfigEvents(mp.config.GetSyncAnalysisStateData(),
1399 'previous_sync_state')
Raman Tenneti7954de12021-07-28 14:36:49 -07001400
1401 # Update and log with the new sync analysis state.
1402 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
Raman Tenneti6448a4f2021-09-13 17:40:07 -07001403 self.git_event_log.LogDataConfigEvents(mp.config.GetSyncAnalysisStateData(),
1404 'current_sync_state')
Raman Tenneti7954de12021-07-28 14:36:49 -07001405
Mike Frysingere19d9e12020-02-12 11:23:32 -05001406 if not opt.quiet:
1407 print('repo sync has finished successfully.')
1408
David Pursehouse819827a2020-02-12 15:20:19 +09001409
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001410def _PostRepoUpgrade(manifest, quiet=False):
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001411 # Link the docs for the internal .repo/ layout for people
1412 link = os.path.join(manifest.repodir, 'internal-fs-layout.md')
1413 if not platform_utils.islink(link):
1414 target = os.path.join('repo', 'docs', 'internal-fs-layout.md')
1415 try:
1416 platform_utils.symlink(target, link)
Raman Tenneti4a478ed2021-11-17 18:38:24 -08001417 except Exception:
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001418 pass
1419
Conley Owens094cdbe2014-01-30 15:09:59 -08001420 wrapper = Wrapper()
Conley Owensc9129d92012-10-01 16:12:28 -07001421 if wrapper.NeedSetupGnuPG():
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001422 wrapper.SetupGnuPG(quiet)
Conley Owensf2fe2d92014-01-29 13:53:43 -08001423 for project in manifest.projects:
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001424 if project.Exists:
1425 project.PostRepoUpgrade()
1426
David Pursehouse819827a2020-02-12 15:20:19 +09001427
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001428def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001429 if rp.HasChanges:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001430 print('info: A new version of repo is available', file=sys.stderr)
Mike Frysinger347f9ed2021-03-15 14:58:52 -04001431 wrapper = Wrapper()
1432 try:
1433 rev = rp.bare_git.describe(rp.GetRevisionId())
1434 except GitError:
1435 rev = None
1436 _, new_rev = wrapper.check_repo_rev(rp.gitdir, rev, repo_verify=repo_verify)
1437 # See if we're held back due to missing signed tag.
1438 current_revid = rp.bare_git.rev_parse('HEAD')
1439 new_revid = rp.bare_git.rev_parse('--verify', new_rev)
1440 if current_revid != new_revid:
1441 # We want to switch to the new rev, but also not trash any uncommitted
1442 # changes. This helps with local testing/hacking.
1443 # If a local change has been made, we will throw that away.
1444 # We also have to make sure this will switch to an older commit if that's
1445 # the latest tag in order to support release rollback.
1446 try:
1447 rp.work_git.reset('--keep', new_rev)
1448 except GitError as e:
1449 sys.exit(str(e))
Sarah Owenscecd1d82012-11-01 22:59:27 -07001450 print('info: Restarting repo with latest version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001451 raise RepoChangedException(['--repo-upgraded'])
1452 else:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001453 print('warning: Skipped upgrade to unverified version', file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001454 else:
1455 if verbose:
Sarah Owenscecd1d82012-11-01 22:59:27 -07001456 print('repo version %s is current' % rp.work_git.describe(HEAD),
1457 file=sys.stderr)
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001458
David Pursehouse819827a2020-02-12 15:20:19 +09001459
Dave Borowitz67700e92012-10-23 15:00:54 -07001460class _FetchTimes(object):
Dave Borowitzd9478582012-10-23 16:35:39 -07001461 _ALPHA = 0.5
1462
Dave Borowitz67700e92012-10-23 15:00:54 -07001463 def __init__(self, manifest):
Anthony King85b24ac2014-05-06 15:57:48 +01001464 self._path = os.path.join(manifest.repodir, '.repo_fetchtimes.json')
Dave Borowitz67700e92012-10-23 15:00:54 -07001465 self._times = None
Dave Borowitzd9478582012-10-23 16:35:39 -07001466 self._seen = set()
Dave Borowitz67700e92012-10-23 15:00:54 -07001467
1468 def Get(self, project):
1469 self._Load()
1470 return self._times.get(project.name, _ONE_DAY_S)
1471
1472 def Set(self, project, t):
Dave Borowitzd9478582012-10-23 16:35:39 -07001473 self._Load()
1474 name = project.name
1475 old = self._times.get(name, t)
1476 self._seen.add(name)
1477 a = self._ALPHA
David Pursehouse54a4e602020-02-12 14:31:05 +09001478 self._times[name] = (a * t) + ((1 - a) * old)
Dave Borowitz67700e92012-10-23 15:00:54 -07001479
1480 def _Load(self):
1481 if self._times is None:
1482 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001483 with open(self._path) as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001484 self._times = json.load(f)
Anthony King85b24ac2014-05-06 15:57:48 +01001485 except (IOError, ValueError):
Mike Frysinger9d96f582021-09-28 11:27:24 -04001486 platform_utils.remove(self._path, missing_ok=True)
Anthony King85b24ac2014-05-06 15:57:48 +01001487 self._times = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001488
1489 def Save(self):
1490 if self._times is None:
1491 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001492
1493 to_delete = []
1494 for name in self._times:
1495 if name not in self._seen:
1496 to_delete.append(name)
1497 for name in to_delete:
1498 del self._times[name]
1499
Dave Borowitz67700e92012-10-23 15:00:54 -07001500 try:
Mike Frysinger3164d402019-11-11 05:40:22 -05001501 with open(self._path, 'w') as f:
Anthony King85b24ac2014-05-06 15:57:48 +01001502 json.dump(self._times, f, indent=2)
Anthony King85b24ac2014-05-06 15:57:48 +01001503 except (IOError, TypeError):
Mike Frysinger9d96f582021-09-28 11:27:24 -04001504 platform_utils.remove(self._path, missing_ok=True)
Dan Willemsen0745bb22015-08-17 13:41:45 -07001505
1506# This is a replacement for xmlrpc.client.Transport using urllib2
1507# and supporting persistent-http[s]. It cannot change hosts from
1508# request to request like the normal transport, the real url
1509# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001510
1511
Dan Willemsen0745bb22015-08-17 13:41:45 -07001512class PersistentTransport(xmlrpc.client.Transport):
1513 def __init__(self, orig_host):
1514 self.orig_host = orig_host
1515
1516 def request(self, host, handler, request_body, verbose=False):
1517 with GetUrlCookieFile(self.orig_host, not verbose) as (cookiefile, proxy):
1518 # Python doesn't understand cookies with the #HttpOnly_ prefix
1519 # Since we're only using them for HTTP, copy the file temporarily,
1520 # stripping those prefixes away.
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001521 if cookiefile:
Collin Fijalkoviche1191b32020-02-18 10:57:32 -08001522 tmpcookiefile = tempfile.NamedTemporaryFile(mode='w')
David Pursehouse4c5f74e2015-10-02 11:10:10 +09001523 tmpcookiefile.write("# HTTP Cookie File")
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001524 try:
1525 with open(cookiefile) as f:
1526 for line in f:
1527 if line.startswith("#HttpOnly_"):
1528 line = line[len("#HttpOnly_"):]
1529 tmpcookiefile.write(line)
1530 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001531
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001532 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
David Pursehouseb1ad2192015-09-30 10:35:43 +09001533 try:
1534 cookiejar.load()
1535 except cookielib.LoadError:
1536 cookiejar = cookielib.CookieJar()
Dan Willemsen3010e5b2015-08-20 10:09:20 -07001537 finally:
1538 tmpcookiefile.close()
1539 else:
1540 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001541
1542 proxyhandler = urllib.request.ProxyHandler
1543 if proxy:
1544 proxyhandler = urllib.request.ProxyHandler({
1545 "http": proxy,
David Pursehouse54a4e602020-02-12 14:31:05 +09001546 "https": proxy})
Dan Willemsen0745bb22015-08-17 13:41:45 -07001547
1548 opener = urllib.request.build_opener(
1549 urllib.request.HTTPCookieProcessor(cookiejar),
1550 proxyhandler)
1551
1552 url = urllib.parse.urljoin(self.orig_host, handler)
1553 parse_results = urllib.parse.urlparse(url)
1554
1555 scheme = parse_results.scheme
1556 if scheme == 'persistent-http':
1557 scheme = 'http'
1558 if scheme == 'persistent-https':
1559 # If we're proxying through persistent-https, use http. The
1560 # proxy itself will do the https.
1561 if proxy:
1562 scheme = 'http'
1563 else:
1564 scheme = 'https'
1565
1566 # Parse out any authentication information using the base class
1567 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
1568
1569 url = urllib.parse.urlunparse((
1570 scheme,
1571 host,
1572 parse_results.path,
1573 parse_results.params,
1574 parse_results.query,
1575 parse_results.fragment))
1576
1577 request = urllib.request.Request(url, request_body)
1578 if extra_headers is not None:
1579 for (name, header) in extra_headers:
1580 request.add_header(name, header)
1581 request.add_header('Content-Type', 'text/xml')
1582 try:
1583 response = opener.open(request)
1584 except urllib.error.HTTPError as e:
1585 if e.code == 501:
1586 # We may have been redirected through a login process
1587 # but our POST turned into a GET. Retry.
1588 response = opener.open(request)
1589 else:
1590 raise
1591
1592 p, u = xmlrpc.client.getparser()
Mike Frysinger5951e302022-05-20 23:34:44 -04001593 # Response should be fairly small, so read it all at once.
1594 # This way we can show it to the user in case of error (e.g. HTML).
1595 data = response.read()
1596 try:
Dan Willemsen0745bb22015-08-17 13:41:45 -07001597 p.feed(data)
Mike Frysinger5951e302022-05-20 23:34:44 -04001598 except xml.parsers.expat.ExpatError as e:
1599 raise IOError(
1600 f'Parsing the manifest failed: {e}\n'
1601 f'Please report this to your manifest server admin.\n'
1602 f'Here is the full response:\n{data.decode("utf-8")}')
Dan Willemsen0745bb22015-08-17 13:41:45 -07001603 p.close()
1604 return u.close()
1605
1606 def close(self):
1607 pass