blob: bbe0372226c0fd2b02070edaa6142a83351461e0 [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:
Gavin Makea2e3302023-03-11 06:46:20 +000036 import threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080037except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000038 import dummy_threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080039
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070040try:
Gavin Makea2e3302023-03-11 06:46:20 +000041 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090042
Gavin Makea2e3302023-03-11 06:46:20 +000043 def _rlimit_nofile():
44 return resource.getrlimit(resource.RLIMIT_NOFILE)
45
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070046except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000047
48 def _rlimit_nofile():
49 return (256, 256)
50
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070051
David Rileye0684ad2017-04-05 00:02:59 -070052import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040053from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090054from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090055from git_refs import R_HEADS, HEAD
Raman Tenneti6a872c92021-01-14 19:17:50 -080056import git_superproject
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070057from project import Project
58from project import RemoteSpec
Gavin Makea2e3302023-03-11 06:46:20 +000059from command import (
60 Command,
61 DEFAULT_LOCAL_JOBS,
62 MirrorSafeCommand,
63 WORKER_BATCH_SIZE,
64)
Jason Chang32b59562023-07-14 16:45:35 -070065from error import (
66 RepoChangedException,
67 GitError,
68 RepoExitError,
69 SyncError,
70 UpdateManifestError,
71 RepoUnhandledExceptionError,
72)
Renaud Paquaya65adf72016-11-03 10:37:53 -070073import platform_utils
Jason Chang32b59562023-07-14 16:45:35 -070074from project import SyncBuffer, DeleteWorktreeError
Gavin Mak04cba4a2023-05-24 21:28:28 +000075from progress import Progress, elapsed_str, jobs_str
Joanna Wanga6c52f52022-11-03 16:51:19 -040076from repo_trace import Trace
Mike Frysinger19e409c2021-05-05 19:44:35 -040077import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080078from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070079
Dave Borowitz67700e92012-10-23 15:00:54 -070080_ONE_DAY_S = 24 * 60 * 60
81
LaMont Jonesd7935532022-12-01 20:18:46 +000082# Env var to implicitly turn auto-gc back on. This was added to allow a user to
LaMont Jones100a2142022-12-02 22:54:11 +000083# revert a change in default behavior in v2.29.9. Remove after 2023-04-01.
Gavin Makea2e3302023-03-11 06:46:20 +000084_REPO_AUTO_GC = "REPO_AUTO_GC"
85_AUTO_GC = os.environ.get(_REPO_AUTO_GC) == "1"
LaMont Jones5ed8c632022-11-10 00:10:44 +000086
Jason Chang17833322023-05-23 13:06:55 -070087_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
88
David Pursehouse819827a2020-02-12 15:20:19 +090089
LaMont Jones1eddca82022-09-01 15:15:04 +000090class _FetchOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +000091 """_FetchOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +000092
Gavin Makea2e3302023-03-11 06:46:20 +000093 Attributes:
94 success (bool): True if successful.
95 project (Project): The fetched project.
96 start (float): The starting time.time().
97 finish (float): The ending time.time().
98 remote_fetched (bool): True if the remote was actually queried.
99 """
100
101 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700102 errors: List[Exception]
Gavin Makea2e3302023-03-11 06:46:20 +0000103 project: Project
104 start: float
105 finish: float
106 remote_fetched: bool
LaMont Jones1eddca82022-09-01 15:15:04 +0000107
108
109class _FetchResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000110 """_Fetch return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000111
Gavin Makea2e3302023-03-11 06:46:20 +0000112 Attributes:
113 success (bool): True if successful.
114 projects (Set[str]): The names of the git directories of fetched projects.
115 """
116
117 success: bool
118 projects: Set[str]
Jason Chang32b59562023-07-14 16:45:35 -0700119 errors: List[Exception]
LaMont Jones1eddca82022-09-01 15:15:04 +0000120
121
122class _FetchMainResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000123 """_FetchMain return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000124
Gavin Makea2e3302023-03-11 06:46:20 +0000125 Attributes:
126 all_projects (List[Project]): The fetched projects.
127 """
128
129 all_projects: List[Project]
Jason Chang32b59562023-07-14 16:45:35 -0700130 errors: List[Exception]
LaMont Jones1eddca82022-09-01 15:15:04 +0000131
132
133class _CheckoutOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000134 """_CheckoutOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000135
Gavin Makea2e3302023-03-11 06:46:20 +0000136 Attributes:
137 success (bool): True if successful.
138 project (Project): The project.
139 start (float): The starting time.time().
140 finish (float): The ending time.time().
141 """
142
143 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700144 errors: List[Exception]
Gavin Makea2e3302023-03-11 06:46:20 +0000145 project: Project
146 start: float
147 finish: float
LaMont Jones1eddca82022-09-01 15:15:04 +0000148
149
Jason Chang32b59562023-07-14 16:45:35 -0700150class SuperprojectError(SyncError):
151 """Superproject sync repo."""
152
153
154class SyncFailFastError(SyncError):
155 """Sync exit error when --fail-fast set."""
156
157
158class SmartSyncError(SyncError):
159 """Smart sync exit error."""
160
161
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800162class Sync(Command, MirrorSafeCommand):
Gavin Makea2e3302023-03-11 06:46:20 +0000163 COMMON = True
164 MULTI_MANIFEST_SUPPORT = True
165 helpSummary = "Update working tree to the latest revision"
166 helpUsage = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700167%prog [...]
168"""
Gavin Makea2e3302023-03-11 06:46:20 +0000169 helpDescription = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700170The '%prog' command synchronizes local project directories
171with the remote repositories specified in the manifest. If a local
172project does not yet exist, it will clone a new local directory from
173the remote repository and set up tracking branches as specified in
174the manifest. If the local project already exists, '%prog'
175will update the remote branches and rebase any new local changes
176on top of the new remote changes.
177
178'%prog' will synchronize all projects listed at the command
179line. Projects can be specified either by name, or by a relative
180or absolute path to the project's local directory. If no projects
181are specified, '%prog' will synchronize all projects listed in
182the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700183
184The -d/--detach option can be used to switch specified projects
185back to the manifest revision. This option is especially helpful
186if the project is currently on a topic branch, but the manifest
187revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700188
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700189The -s/--smart-sync option can be used to sync to a known good
190build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200191manifest. The -t/--smart-tag option is similar and allows you to
192specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700193
David Pursehousecf76b1b2012-09-14 10:31:42 +0900194The -u/--manifest-server-username and -p/--manifest-server-password
195options can be used to specify a username and password to authenticate
196with the manifest server when using the -s or -t option.
197
198If -u and -p are not specified when using the -s or -t option, '%prog'
199will attempt to read authentication credentials for the manifest server
200from the user's .netrc file.
201
202'%prog' will not use authentication credentials from -u/-p or .netrc
203if the manifest server specified in the manifest file already includes
204credentials.
205
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400206By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400207to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500208
Kevin Degiabaa7f32014-11-12 11:27:45 -0700209The --force-sync option can be used to overwrite existing git
210directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900211object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700212refs may be removed when overwriting.
213
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500214The --force-remove-dirty option can be used to remove previously used
215projects with uncommitted changes. WARNING: This may cause data to be
216lost since uncommitted changes may be removed with projects that no longer
217exist in the manifest.
218
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700219The --no-clone-bundle option disables any attempt to use
220$URL/clone.bundle to bootstrap a new Git repository from a
221resumeable bundle file on a content delivery network. This
222may be necessary if there are problems with the local Python
223HTTP client or proxy configuration, but the Git binary works.
224
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800225The --fetch-submodules option enables fetching Git submodules
226of a project from server.
227
David Pursehousef2fad612015-01-29 14:36:28 +0900228The -c/--current-branch option can be used to only fetch objects that
229are on the branch specified by a project's revision.
230
David Pursehouseb1553542014-09-04 21:28:09 +0900231The --optimized-fetch option can be used to only fetch projects that
232are fixed to a sha1 revision if the sha1 revision does not already
233exist locally.
234
David Pursehouse74cfd272015-10-14 10:50:15 +0900235The --prune option can be used to remove any refs that no longer
236exist on the remote.
237
LaMont Jones7efab532022-09-01 15:41:12 +0000238The --auto-gc option can be used to trigger garbage collection on all
239projects. By default, repo does not run garbage collection.
240
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400241# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700242
243If at least one project remote URL uses an SSH connection (ssh://,
244git+ssh://, or user@host:path syntax) repo will automatically
245enable the SSH ControlMaster option when connecting to that host.
246This feature permits other projects in the same '%prog' session to
247reuse the same SSH tunnel, saving connection setup overheads.
248
249To disable this behavior on UNIX platforms, set the GIT_SSH
250environment variable to 'ssh'. For example:
251
252 export GIT_SSH=ssh
253 %prog
254
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400255# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700256
257This feature is automatically disabled on Windows, due to the lack
258of UNIX domain socket support.
259
260This feature is not compatible with url.insteadof rewrites in the
261user's ~/.gitconfig. '%prog' is currently not able to perform the
262rewrite early enough to establish the ControlMaster tunnel.
263
264If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
265later is required to fix a server side protocol bug.
266
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700267"""
Gavin Makea2e3302023-03-11 06:46:20 +0000268 # A value of 0 means we want parallel jobs, but we'll determine the default
269 # value later on.
270 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700271
Gavin Makea2e3302023-03-11 06:46:20 +0000272 def _Options(self, p, show_smart=True):
273 p.add_option(
274 "--jobs-network",
275 default=None,
276 type=int,
277 metavar="JOBS",
278 help="number of network jobs to run in parallel (defaults to "
279 "--jobs or 1)",
280 )
281 p.add_option(
282 "--jobs-checkout",
283 default=None,
284 type=int,
285 metavar="JOBS",
286 help="number of local checkout jobs to run in parallel (defaults "
287 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
288 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400289
Gavin Makea2e3302023-03-11 06:46:20 +0000290 p.add_option(
291 "-f",
292 "--force-broken",
293 dest="force_broken",
294 action="store_true",
295 help="obsolete option (to be deleted in the future)",
296 )
297 p.add_option(
298 "--fail-fast",
299 dest="fail_fast",
300 action="store_true",
301 help="stop syncing after first error is hit",
302 )
303 p.add_option(
304 "--force-sync",
305 dest="force_sync",
306 action="store_true",
307 help="overwrite an existing git directory if it needs to "
308 "point to a different object directory. WARNING: this "
309 "may cause loss of data",
310 )
311 p.add_option(
312 "--force-remove-dirty",
313 dest="force_remove_dirty",
314 action="store_true",
315 help="force remove projects with uncommitted modifications if "
316 "projects no longer exist in the manifest. "
317 "WARNING: this may cause loss of data",
318 )
319 p.add_option(
320 "-l",
321 "--local-only",
322 dest="local_only",
323 action="store_true",
324 help="only update working tree, don't fetch",
325 )
326 p.add_option(
327 "--no-manifest-update",
328 "--nmu",
329 dest="mp_update",
330 action="store_false",
331 default="true",
332 help="use the existing manifest checkout as-is. "
333 "(do not update to the latest revision)",
334 )
335 p.add_option(
336 "-n",
337 "--network-only",
338 dest="network_only",
339 action="store_true",
340 help="fetch only, don't update working tree",
341 )
342 p.add_option(
343 "-d",
344 "--detach",
345 dest="detach_head",
346 action="store_true",
347 help="detach projects back to manifest revision",
348 )
349 p.add_option(
350 "-c",
351 "--current-branch",
352 dest="current_branch_only",
353 action="store_true",
354 help="fetch only current branch from server",
355 )
356 p.add_option(
357 "--no-current-branch",
358 dest="current_branch_only",
359 action="store_false",
360 help="fetch all branches from server",
361 )
362 p.add_option(
363 "-m",
364 "--manifest-name",
365 dest="manifest_name",
366 help="temporary manifest to use for this sync",
367 metavar="NAME.xml",
368 )
369 p.add_option(
370 "--clone-bundle",
371 action="store_true",
372 help="enable use of /clone.bundle on HTTP/HTTPS",
373 )
374 p.add_option(
375 "--no-clone-bundle",
376 dest="clone_bundle",
377 action="store_false",
378 help="disable use of /clone.bundle on HTTP/HTTPS",
379 )
380 p.add_option(
381 "-u",
382 "--manifest-server-username",
383 action="store",
384 dest="manifest_server_username",
385 help="username to authenticate with the manifest server",
386 )
387 p.add_option(
388 "-p",
389 "--manifest-server-password",
390 action="store",
391 dest="manifest_server_password",
392 help="password to authenticate with the manifest server",
393 )
394 p.add_option(
395 "--fetch-submodules",
396 dest="fetch_submodules",
397 action="store_true",
398 help="fetch submodules from server",
399 )
400 p.add_option(
401 "--use-superproject",
402 action="store_true",
403 help="use the manifest superproject to sync projects; implies -c",
404 )
405 p.add_option(
406 "--no-use-superproject",
407 action="store_false",
408 dest="use_superproject",
409 help="disable use of manifest superprojects",
410 )
411 p.add_option("--tags", action="store_true", help="fetch tags")
412 p.add_option(
413 "--no-tags",
414 dest="tags",
415 action="store_false",
416 help="don't fetch tags (default)",
417 )
418 p.add_option(
419 "--optimized-fetch",
420 dest="optimized_fetch",
421 action="store_true",
422 help="only fetch projects fixed to sha1 if revision does not exist "
423 "locally",
424 )
425 p.add_option(
426 "--retry-fetches",
427 default=0,
428 action="store",
429 type="int",
430 help="number of times to retry fetches on transient errors",
431 )
432 p.add_option(
433 "--prune",
434 action="store_true",
435 help="delete refs that no longer exist on the remote (default)",
436 )
437 p.add_option(
438 "--no-prune",
439 dest="prune",
440 action="store_false",
441 help="do not delete refs that no longer exist on the remote",
442 )
443 p.add_option(
444 "--auto-gc",
445 action="store_true",
446 default=None,
447 help="run garbage collection on all synced projects",
448 )
449 p.add_option(
450 "--no-auto-gc",
451 dest="auto_gc",
452 action="store_false",
453 help="do not run garbage collection on any projects (default)",
454 )
455 if show_smart:
456 p.add_option(
457 "-s",
458 "--smart-sync",
459 dest="smart_sync",
460 action="store_true",
461 help="smart sync using manifest from the latest known good "
462 "build",
463 )
464 p.add_option(
465 "-t",
466 "--smart-tag",
467 dest="smart_tag",
468 action="store",
469 help="smart sync using manifest from a known tag",
470 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700471
Gavin Makea2e3302023-03-11 06:46:20 +0000472 g = p.add_option_group("repo Version options")
473 g.add_option(
474 "--no-repo-verify",
475 dest="repo_verify",
476 default=True,
477 action="store_false",
478 help="do not verify repo source code",
479 )
480 g.add_option(
481 "--repo-upgraded",
482 dest="repo_upgraded",
483 action="store_true",
484 help=SUPPRESS_HELP,
485 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700486
Gavin Makea2e3302023-03-11 06:46:20 +0000487 def _GetBranch(self, manifest_project):
488 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000489
Gavin Makea2e3302023-03-11 06:46:20 +0000490 Args:
491 manifest_project: The manifestProject to query.
492 """
493 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
494 branch = b.merge
495 if branch.startswith(R_HEADS):
496 branch = branch[len(R_HEADS) :]
497 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800498
Gavin Makea2e3302023-03-11 06:46:20 +0000499 def _GetCurrentBranchOnly(self, opt, manifest):
500 """Returns whether current-branch or use-superproject options are
501 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200502
Gavin Makea2e3302023-03-11 06:46:20 +0000503 Args:
504 opt: Program options returned from optparse. See _Options().
505 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000506
Gavin Makea2e3302023-03-11 06:46:20 +0000507 Returns:
508 True if a superproject is requested, otherwise the value of the
509 current_branch option (True, False or None).
510 """
511 return (
512 git_superproject.UseSuperproject(opt.use_superproject, manifest)
513 or opt.current_branch_only
514 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700515
Gavin Makea2e3302023-03-11 06:46:20 +0000516 def _UpdateProjectsRevisionId(
517 self, opt, args, superproject_logging_data, manifest
518 ):
519 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800520
Gavin Makea2e3302023-03-11 06:46:20 +0000521 This function updates each project's revisionId with the commit hash
522 from the superproject. It writes the updated manifest into a file and
523 reloads the manifest from it. When appropriate, sub manifests are also
524 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800525
Gavin Makea2e3302023-03-11 06:46:20 +0000526 Args:
527 opt: Program options returned from optparse. See _Options().
528 args: Arguments to pass to GetProjects. See the GetProjects
529 docstring for details.
530 superproject_logging_data: A dictionary of superproject data to log.
531 manifest: The manifest to use.
532 """
533 have_superproject = manifest.superproject or any(
534 m.superproject for m in manifest.all_children
535 )
536 if not have_superproject:
537 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000538
Gavin Makea2e3302023-03-11 06:46:20 +0000539 if opt.local_only and manifest.superproject:
540 manifest_path = manifest.superproject.manifest_path
541 if manifest_path:
542 self._ReloadManifest(manifest_path, manifest)
543 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700544
Gavin Makea2e3302023-03-11 06:46:20 +0000545 all_projects = self.GetProjects(
546 args,
547 missing_ok=True,
548 submodules_ok=opt.fetch_submodules,
549 manifest=manifest,
550 all_manifests=not opt.this_manifest_only,
551 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000552
Gavin Makea2e3302023-03-11 06:46:20 +0000553 per_manifest = collections.defaultdict(list)
554 if opt.this_manifest_only:
555 per_manifest[manifest.path_prefix] = all_projects
556 else:
557 for p in all_projects:
558 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000559
Gavin Makea2e3302023-03-11 06:46:20 +0000560 superproject_logging_data = {}
561 need_unload = False
562 for m in self.ManifestList(opt):
563 if m.path_prefix not in per_manifest:
564 continue
565 use_super = git_superproject.UseSuperproject(
566 opt.use_superproject, m
567 )
568 if superproject_logging_data:
569 superproject_logging_data["multimanifest"] = True
570 superproject_logging_data.update(
571 superproject=use_super,
572 haslocalmanifests=bool(m.HasLocalManifests),
573 hassuperprojecttag=bool(m.superproject),
574 )
575 if use_super and (m.IsMirror or m.IsArchive):
576 # Don't use superproject, because we have no working tree.
577 use_super = False
578 superproject_logging_data["superproject"] = False
579 superproject_logging_data["noworktree"] = True
580 if opt.use_superproject is not False:
581 print(
582 f"{m.path_prefix}: not using superproject because "
583 "there is no working tree."
584 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000585
Gavin Makea2e3302023-03-11 06:46:20 +0000586 if not use_super:
587 continue
588 m.superproject.SetQuiet(opt.quiet)
589 print_messages = git_superproject.PrintMessages(
590 opt.use_superproject, m
591 )
592 m.superproject.SetPrintMessages(print_messages)
593 update_result = m.superproject.UpdateProjectsRevisionId(
594 per_manifest[m.path_prefix], git_event_log=self.git_event_log
595 )
596 manifest_path = update_result.manifest_path
597 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
598 if manifest_path:
599 m.SetManifestOverride(manifest_path)
600 need_unload = True
601 else:
602 if print_messages:
603 print(
604 f"{m.path_prefix}: warning: Update of revisionId from "
605 "superproject has failed, repo sync will not use "
606 "superproject to fetch the source. ",
607 "Please resync with the --no-use-superproject option "
608 "to avoid this repo warning.",
609 file=sys.stderr,
610 )
611 if update_result.fatal and opt.use_superproject is not None:
Jason Chang32b59562023-07-14 16:45:35 -0700612 raise SuperprojectError()
Gavin Makea2e3302023-03-11 06:46:20 +0000613 if need_unload:
614 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800615
Gavin Makea2e3302023-03-11 06:46:20 +0000616 def _FetchProjectList(self, opt, projects):
617 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500618
Gavin Makea2e3302023-03-11 06:46:20 +0000619 The projects we're given share the same underlying git object store, so
620 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800621
Gavin Mak551285f2023-05-04 04:48:43 +0000622 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700623
Gavin Makea2e3302023-03-11 06:46:20 +0000624 Args:
625 opt: Program options returned from optparse. See _Options().
626 projects: Projects to fetch.
627 """
628 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700629
Gavin Makea2e3302023-03-11 06:46:20 +0000630 def _FetchOne(self, opt, project):
631 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700632
Gavin Makea2e3302023-03-11 06:46:20 +0000633 Args:
634 opt: Program options returned from optparse. See _Options().
635 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700636
Gavin Makea2e3302023-03-11 06:46:20 +0000637 Returns:
638 Whether the fetch was successful.
639 """
640 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000641 k = f"{project.name} @ {project.relpath}"
642 self._sync_dict[k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000643 success = False
644 remote_fetched = False
Jason Chang32b59562023-07-14 16:45:35 -0700645 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +0000646 buf = io.StringIO()
647 try:
648 sync_result = project.Sync_NetworkHalf(
649 quiet=opt.quiet,
650 verbose=opt.verbose,
651 output_redir=buf,
652 current_branch_only=self._GetCurrentBranchOnly(
653 opt, project.manifest
654 ),
655 force_sync=opt.force_sync,
656 clone_bundle=opt.clone_bundle,
657 tags=opt.tags,
658 archive=project.manifest.IsArchive,
659 optimized_fetch=opt.optimized_fetch,
660 retry_fetches=opt.retry_fetches,
661 prune=opt.prune,
662 ssh_proxy=self.ssh_proxy,
663 clone_filter=project.manifest.CloneFilter,
664 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700665 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000666 )
667 success = sync_result.success
668 remote_fetched = sync_result.remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700669 if sync_result.error:
670 errors.append(sync_result.error)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700671
Gavin Makea2e3302023-03-11 06:46:20 +0000672 output = buf.getvalue()
673 if (opt.verbose or not success) and output:
674 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700675
Gavin Makea2e3302023-03-11 06:46:20 +0000676 if not success:
677 print(
678 "error: Cannot fetch %s from %s"
679 % (project.name, project.remote.url),
680 file=sys.stderr,
681 )
682 except KeyboardInterrupt:
683 print(f"Keyboard interrupt while processing {project.name}")
684 except GitError as e:
685 print("error.GitError: Cannot fetch %s" % str(e), file=sys.stderr)
Jason Chang32b59562023-07-14 16:45:35 -0700686 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000687 except Exception as e:
688 print(
689 "error: Cannot fetch %s (%s: %s)"
690 % (project.name, type(e).__name__, str(e)),
691 file=sys.stderr,
692 )
Gavin Mak551285f2023-05-04 04:48:43 +0000693 del self._sync_dict[k]
Jason Chang32b59562023-07-14 16:45:35 -0700694 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000695 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500696
Gavin Makea2e3302023-03-11 06:46:20 +0000697 finish = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000698 del self._sync_dict[k]
Jason Chang32b59562023-07-14 16:45:35 -0700699 return _FetchOneResult(
700 success, errors, project, start, finish, remote_fetched
701 )
David James8d201162013-10-11 17:03:19 -0700702
Gavin Makea2e3302023-03-11 06:46:20 +0000703 @classmethod
704 def _FetchInitChild(cls, ssh_proxy):
705 cls.ssh_proxy = ssh_proxy
Mike Frysinger339f2df2021-05-06 00:44:42 -0400706
Gavin Mak04cba4a2023-05-24 21:28:28 +0000707 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000708 earliest_time = float("inf")
709 earliest_proj = None
Gavin Mak945c0062023-05-30 20:04:07 +0000710 items = self._sync_dict.items()
711 for project, t in items:
Gavin Mak551285f2023-05-04 04:48:43 +0000712 if t < earliest_time:
713 earliest_time = t
714 earliest_proj = project
715
Josip Sokcevic71122f92023-05-26 02:44:37 +0000716 if not earliest_proj:
Gavin Mak945c0062023-05-30 20:04:07 +0000717 # This function is called when sync is still running but in some
718 # cases (by chance), _sync_dict can contain no entries. Return some
719 # text to indicate that sync is still working.
720 return "..working.."
Josip Sokcevic71122f92023-05-26 02:44:37 +0000721
Gavin Mak551285f2023-05-04 04:48:43 +0000722 elapsed = time.time() - earliest_time
Gavin Mak945c0062023-05-30 20:04:07 +0000723 jobs = jobs_str(len(items))
Gavin Mak04cba4a2023-05-24 21:28:28 +0000724 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000725
Gavin Makea2e3302023-03-11 06:46:20 +0000726 def _Fetch(self, projects, opt, err_event, ssh_proxy):
727 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500728
Gavin Makea2e3302023-03-11 06:46:20 +0000729 jobs = opt.jobs_network
730 fetched = set()
731 remote_fetched = set()
Jason Chang32b59562023-07-14 16:45:35 -0700732 errors = []
Gavin Makedcaa942023-04-27 05:58:57 +0000733 pm = Progress(
734 "Fetching",
735 len(projects),
736 delay=False,
737 quiet=opt.quiet,
738 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000739 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000740 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800741
Gavin Mak551285f2023-05-04 04:48:43 +0000742 self._sync_dict = multiprocessing.Manager().dict()
743 sync_event = _threading.Event()
744
745 def _MonitorSyncLoop():
746 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000747 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000748 if sync_event.wait(timeout=1):
749 return
750
751 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
752 sync_progress_thread.daemon = True
753 sync_progress_thread.start()
754
Gavin Makea2e3302023-03-11 06:46:20 +0000755 objdir_project_map = dict()
756 for project in projects:
757 objdir_project_map.setdefault(project.objdir, []).append(project)
758 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700759
Gavin Makea2e3302023-03-11 06:46:20 +0000760 def _ProcessResults(results_sets):
761 ret = True
762 for results in results_sets:
763 for result in results:
764 success = result.success
765 project = result.project
766 start = result.start
767 finish = result.finish
768 self._fetch_times.Set(project, finish - start)
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000769 self._local_sync_state.SetFetchTime(project)
Gavin Makea2e3302023-03-11 06:46:20 +0000770 self.event_log.AddSync(
771 project,
772 event_log.TASK_SYNC_NETWORK,
773 start,
774 finish,
775 success,
776 )
Jason Chang32b59562023-07-14 16:45:35 -0700777 if result.errors:
778 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000779 if result.remote_fetched:
780 remote_fetched.add(project)
781 # Check for any errors before running any more tasks.
782 # ...we'll let existing jobs finish, though.
783 if not success:
784 ret = False
785 else:
786 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000787 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000788 if not ret and opt.fail_fast:
789 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500790 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700791
Gavin Makea2e3302023-03-11 06:46:20 +0000792 # We pass the ssh proxy settings via the class. This allows
793 # multiprocessing to pickle it up when spawning children. We can't pass
794 # it as an argument to _FetchProjectList below as multiprocessing is
795 # unable to pickle those.
796 Sync.ssh_proxy = None
Mike Frysingerebf04a42021-02-23 20:48:04 -0500797
Gavin Makea2e3302023-03-11 06:46:20 +0000798 # NB: Multiprocessing is heavy, so don't spin it up for one job.
799 if len(projects_list) == 1 or jobs == 1:
800 self._FetchInitChild(ssh_proxy)
801 if not _ProcessResults(
802 self._FetchProjectList(opt, x) for x in projects_list
803 ):
804 ret = False
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000805 else:
Gavin Makea2e3302023-03-11 06:46:20 +0000806 # Favor throughput over responsiveness when quiet. It seems that
807 # imap() will yield results in batches relative to chunksize, so
808 # even as the children finish a sync, we won't see the result until
809 # one child finishes ~chunksize jobs. When using a large --jobs
810 # with large chunksize, this can be jarring as there will be a large
811 # initial delay where repo looks like it isn't doing anything and
812 # sits at 0%, but then suddenly completes a lot of jobs all at once.
813 # Since this code is more network bound, we can accept a bit more
814 # CPU overhead with a smaller chunksize so that the user sees more
815 # immediate & continuous feedback.
816 if opt.quiet:
817 chunksize = WORKER_BATCH_SIZE
818 else:
819 pm.update(inc=0, msg="warming up")
820 chunksize = 4
821 with multiprocessing.Pool(
822 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)
823 ) as pool:
824 results = pool.imap_unordered(
825 functools.partial(self._FetchProjectList, opt),
826 projects_list,
827 chunksize=chunksize,
828 )
829 if not _ProcessResults(results):
830 ret = False
831 pool.close()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000832
Gavin Makea2e3302023-03-11 06:46:20 +0000833 # Cleanup the reference now that we're done with it, and we're going to
834 # release any resources it points to. If we don't, later
835 # multiprocessing usage (e.g. checkouts) will try to pickle and then
836 # crash.
837 del Sync.ssh_proxy
LaMont Jones7efab532022-09-01 15:41:12 +0000838
Gavin Mak551285f2023-05-04 04:48:43 +0000839 sync_event.set()
Gavin Makea2e3302023-03-11 06:46:20 +0000840 pm.end()
841 self._fetch_times.Save()
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000842 self._local_sync_state.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000843
Gavin Makea2e3302023-03-11 06:46:20 +0000844 if not self.outer_client.manifest.IsArchive:
845 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400846
Jason Chang32b59562023-07-14 16:45:35 -0700847 return _FetchResult(ret, fetched, errors)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000848
Gavin Makea2e3302023-03-11 06:46:20 +0000849 def _FetchMain(
850 self, opt, args, all_projects, err_event, ssh_proxy, manifest
851 ):
852 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400853
Gavin Makea2e3302023-03-11 06:46:20 +0000854 Args:
855 opt: Program options returned from optparse. See _Options().
856 args: Command line args used to filter out projects.
857 all_projects: List of all projects that should be fetched.
858 err_event: Whether an error was hit while processing.
859 ssh_proxy: SSH manager for clients & masters.
860 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700861
Gavin Makea2e3302023-03-11 06:46:20 +0000862 Returns:
863 List of all projects that should be checked out.
864 """
865 rp = manifest.repoProject
Jason Chang32b59562023-07-14 16:45:35 -0700866 errors = []
LaMont Jones891e8f72022-09-08 20:17:58 +0000867
Gavin Makea2e3302023-03-11 06:46:20 +0000868 to_fetch = []
869 now = time.time()
870 if _ONE_DAY_S <= (now - rp.LastFetch):
871 to_fetch.append(rp)
872 to_fetch.extend(all_projects)
873 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700874
Gavin Makea2e3302023-03-11 06:46:20 +0000875 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
876 success = result.success
877 fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700878 if result.errors:
879 errors.extend(result.errors)
880
Gavin Makea2e3302023-03-11 06:46:20 +0000881 if not success:
882 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700883
Gavin Makea2e3302023-03-11 06:46:20 +0000884 _PostRepoFetch(rp, opt.repo_verify)
885 if opt.network_only:
886 # Bail out now; the rest touches the working tree.
887 if err_event.is_set():
888 print(
889 "\nerror: Exited sync due to fetch errors.\n",
890 file=sys.stderr,
891 )
Jason Chang32b59562023-07-14 16:45:35 -0700892 raise SyncError(
893 "error: Exited sync due to fetch errors.",
894 aggregate_errors=errors,
895 )
896 return _FetchMainResult([], errors)
Dave Borowitz18857212012-10-23 17:02:59 -0700897
Gavin Makea2e3302023-03-11 06:46:20 +0000898 # Iteratively fetch missing and/or nested unregistered submodules.
899 previously_missing_set = set()
900 while True:
901 self._ReloadManifest(None, manifest)
902 all_projects = self.GetProjects(
903 args,
904 missing_ok=True,
905 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000906 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000907 all_manifests=not opt.this_manifest_only,
908 )
909 missing = []
910 for project in all_projects:
911 if project.gitdir not in fetched:
912 missing.append(project)
913 if not missing:
914 break
915 # Stop us from non-stopped fetching actually-missing repos: If set
916 # of missing repos has not been changed from last fetch, we break.
917 missing_set = set(p.name for p in missing)
918 if previously_missing_set == missing_set:
919 break
920 previously_missing_set = missing_set
921 result = self._Fetch(missing, opt, err_event, ssh_proxy)
922 success = result.success
923 new_fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700924 if result.errors:
925 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000926 if not success:
927 err_event.set()
928 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700929
Jason Chang32b59562023-07-14 16:45:35 -0700930 return _FetchMainResult(all_projects, errors)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700931
Gavin Makea2e3302023-03-11 06:46:20 +0000932 def _CheckoutOne(self, detach_head, force_sync, project):
933 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +0800934
Gavin Makea2e3302023-03-11 06:46:20 +0000935 Args:
936 detach_head: Whether to leave a detached HEAD.
937 force_sync: Force checking out of the repo.
938 project: Project object for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +0800939
Gavin Makea2e3302023-03-11 06:46:20 +0000940 Returns:
941 Whether the fetch was successful.
942 """
943 start = time.time()
944 syncbuf = SyncBuffer(
945 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000946 )
Gavin Makea2e3302023-03-11 06:46:20 +0000947 success = False
Jason Chang32b59562023-07-14 16:45:35 -0700948 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +0900949 try:
Jason Chang32b59562023-07-14 16:45:35 -0700950 project.Sync_LocalHalf(
951 syncbuf, force_sync=force_sync, errors=errors
952 )
Gavin Makea2e3302023-03-11 06:46:20 +0000953 success = syncbuf.Finish()
954 except GitError as e:
955 print(
956 "error.GitError: Cannot checkout %s: %s"
957 % (project.name, str(e)),
958 file=sys.stderr,
959 )
Jason Chang32b59562023-07-14 16:45:35 -0700960 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000961 except Exception as e:
962 print(
963 "error: Cannot checkout %s: %s: %s"
964 % (project.name, type(e).__name__, str(e)),
965 file=sys.stderr,
966 )
967 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700968
Gavin Makea2e3302023-03-11 06:46:20 +0000969 if not success:
970 print("error: Cannot checkout %s" % (project.name), file=sys.stderr)
971 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700972 return _CheckoutOneResult(success, errors, project, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -0400973
Jason Chang32b59562023-07-14 16:45:35 -0700974 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +0000975 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700976
Gavin Makea2e3302023-03-11 06:46:20 +0000977 Args:
978 all_projects: List of all projects that should be checked out.
979 opt: Program options returned from optparse. See _Options().
980 err_results: A list of strings, paths to git repos where checkout
981 failed.
982 """
983 # Only checkout projects with worktrees.
984 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700985
Gavin Makea2e3302023-03-11 06:46:20 +0000986 def _ProcessResults(pool, pm, results):
987 ret = True
988 for result in results:
989 success = result.success
990 project = result.project
991 start = result.start
992 finish = result.finish
993 self.event_log.AddSync(
994 project, event_log.TASK_SYNC_LOCAL, start, finish, success
995 )
Jason Chang32b59562023-07-14 16:45:35 -0700996
997 if result.errors:
998 checkout_errors.extend(result.errors)
999
Gavin Makea2e3302023-03-11 06:46:20 +00001000 # Check for any errors before running any more tasks.
1001 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001002 if success:
1003 self._local_sync_state.SetCheckoutTime(project)
1004 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001005 ret = False
1006 err_results.append(
1007 project.RelPath(local=opt.this_manifest_only)
1008 )
1009 if opt.fail_fast:
1010 if pool:
1011 pool.close()
1012 return ret
1013 pm.update(msg=project.name)
1014 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001015
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001016 proc_res = self.ExecuteInParallel(
1017 opt.jobs_checkout,
1018 functools.partial(
1019 self._CheckoutOne, opt.detach_head, opt.force_sync
1020 ),
1021 all_projects,
1022 callback=_ProcessResults,
1023 output=Progress("Checking out", len(all_projects), quiet=opt.quiet),
Gavin Makea2e3302023-03-11 06:46:20 +00001024 )
Simran Basib9a1b732015-08-20 12:19:28 -07001025
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001026 self._local_sync_state.Save()
1027 return proc_res and not err_results
1028
Gavin Makea2e3302023-03-11 06:46:20 +00001029 @staticmethod
1030 def _GetPreciousObjectsState(project: Project, opt):
1031 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001032
Gavin Makea2e3302023-03-11 06:46:20 +00001033 Args:
1034 project (Project): the project to examine, and possibly correct.
1035 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001036
Gavin Makea2e3302023-03-11 06:46:20 +00001037 Returns:
1038 Expected state of extensions.preciousObjects:
1039 False: Should be disabled. (not present)
1040 True: Should be enabled.
1041 """
1042 if project.use_git_worktrees:
1043 return False
1044 projects = project.manifest.GetProjectsWithName(
1045 project.name, all_manifests=True
1046 )
1047 if len(projects) == 1:
1048 return False
1049 if len(projects) > 1:
1050 # Objects are potentially shared with another project.
1051 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1052 # - When False, shared projects share (via symlink)
1053 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1054 # objects directory. All objects are precious, since there is no
1055 # project with a complete set of refs.
1056 # - When True, shared projects share (via info/alternates)
1057 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1058 # store, which is written only on the first clone of the project,
1059 # and is not written subsequently. (When Sync_NetworkHalf sees
1060 # that it exists, it makes sure that the alternates file points
1061 # there, and uses a project-local .git/objects directory for all
1062 # syncs going forward.
1063 # We do not support switching between the options. The environment
1064 # variable is present for testing and migration only.
1065 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001066
Gavin Makea2e3302023-03-11 06:46:20 +00001067 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001068
Gavin Makea2e3302023-03-11 06:46:20 +00001069 def _SetPreciousObjectsState(self, project: Project, opt):
1070 """Correct the preciousObjects state for the project.
1071
1072 Args:
1073 project: the project to examine, and possibly correct.
1074 opt: options given to sync.
1075 """
1076 expected = self._GetPreciousObjectsState(project, opt)
1077 actual = (
1078 project.config.GetBoolean("extensions.preciousObjects") or False
1079 )
1080 relpath = project.RelPath(local=opt.this_manifest_only)
1081
1082 if expected != actual:
1083 # If this is unexpected, log it and repair.
1084 Trace(
1085 f"{relpath} expected preciousObjects={expected}, got {actual}"
1086 )
1087 if expected:
1088 if not opt.quiet:
1089 print(
1090 "\r%s: Shared project %s found, disabling pruning."
1091 % (relpath, project.name)
1092 )
1093 if git_require((2, 7, 0)):
1094 project.EnableRepositoryExtension("preciousObjects")
1095 else:
1096 # This isn't perfect, but it's the best we can do with old
1097 # git.
1098 print(
1099 "\r%s: WARNING: shared projects are unreliable when "
1100 "using old versions of git; please upgrade to "
1101 "git-2.7.0+." % (relpath,),
1102 file=sys.stderr,
1103 )
1104 project.config.SetString("gc.pruneExpire", "never")
1105 else:
1106 if not opt.quiet:
1107 print(f"\r{relpath}: not shared, disabling pruning.")
1108 project.config.SetString("extensions.preciousObjects", None)
1109 project.config.SetString("gc.pruneExpire", None)
1110
1111 def _GCProjects(self, projects, opt, err_event):
1112 """Perform garbage collection.
1113
1114 If We are skipping garbage collection (opt.auto_gc not set), we still
1115 want to potentially mark objects precious, so that `git gc` does not
1116 discard shared objects.
1117 """
1118 if not opt.auto_gc:
1119 # Just repair preciousObjects state, and return.
1120 for project in projects:
1121 self._SetPreciousObjectsState(project, opt)
1122 return
1123
1124 pm = Progress(
1125 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1126 )
1127 pm.update(inc=0, msg="prescan")
1128
1129 tidy_dirs = {}
1130 for project in projects:
1131 self._SetPreciousObjectsState(project, opt)
1132
1133 project.config.SetString("gc.autoDetach", "false")
1134 # Only call git gc once per objdir, but call pack-refs for the
1135 # remainder.
1136 if project.objdir not in tidy_dirs:
1137 tidy_dirs[project.objdir] = (
1138 True, # Run a full gc.
1139 project.bare_git,
1140 )
1141 elif project.gitdir not in tidy_dirs:
1142 tidy_dirs[project.gitdir] = (
1143 False, # Do not run a full gc; just run pack-refs.
1144 project.bare_git,
1145 )
1146
1147 jobs = opt.jobs
1148
1149 if jobs < 2:
1150 for run_gc, bare_git in tidy_dirs.values():
1151 pm.update(msg=bare_git._project.name)
1152
1153 if run_gc:
1154 bare_git.gc("--auto")
1155 else:
1156 bare_git.pack_refs()
1157 pm.end()
1158 return
1159
1160 cpu_count = os.cpu_count()
1161 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1162
1163 threads = set()
1164 sem = _threading.Semaphore(jobs)
1165
1166 def tidy_up(run_gc, bare_git):
1167 pm.start(bare_git._project.name)
1168 try:
1169 try:
1170 if run_gc:
1171 bare_git.gc("--auto", config=config)
1172 else:
1173 bare_git.pack_refs(config=config)
1174 except GitError:
1175 err_event.set()
1176 except Exception:
1177 err_event.set()
1178 raise
1179 finally:
1180 pm.finish(bare_git._project.name)
1181 sem.release()
1182
1183 for run_gc, bare_git in tidy_dirs.values():
1184 if err_event.is_set() and opt.fail_fast:
1185 break
1186 sem.acquire()
1187 t = _threading.Thread(
1188 target=tidy_up,
1189 args=(
1190 run_gc,
1191 bare_git,
1192 ),
1193 )
1194 t.daemon = True
1195 threads.add(t)
1196 t.start()
1197
1198 for t in threads:
1199 t.join()
1200 pm.end()
1201
1202 def _ReloadManifest(self, manifest_name, manifest):
1203 """Reload the manfiest from the file specified by the |manifest_name|.
1204
1205 It unloads the manifest if |manifest_name| is None.
1206
1207 Args:
1208 manifest_name: Manifest file to be reloaded.
1209 manifest: The manifest to use.
1210 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001211 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001212 # Override calls Unload already.
1213 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001214 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001215 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001216
Gavin Makea2e3302023-03-11 06:46:20 +00001217 def UpdateProjectList(self, opt, manifest):
1218 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001219
Gavin Makea2e3302023-03-11 06:46:20 +00001220 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001221
Gavin Makea2e3302023-03-11 06:46:20 +00001222 Args:
1223 opt: Program options returned from optparse. See _Options().
1224 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001225
Gavin Makea2e3302023-03-11 06:46:20 +00001226 Returns:
1227 0: success
1228 1: failure
1229 """
1230 new_project_paths = []
1231 for project in self.GetProjects(
1232 None, missing_ok=True, manifest=manifest, all_manifests=False
1233 ):
1234 if project.relpath:
1235 new_project_paths.append(project.relpath)
1236 file_name = "project.list"
1237 file_path = os.path.join(manifest.subdir, file_name)
1238 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001239
Gavin Makea2e3302023-03-11 06:46:20 +00001240 if os.path.exists(file_path):
1241 with open(file_path, "r") as fd:
1242 old_project_paths = fd.read().split("\n")
1243 # In reversed order, so subfolders are deleted before parent folder.
1244 for path in sorted(old_project_paths, reverse=True):
1245 if not path:
1246 continue
1247 if path not in new_project_paths:
1248 # If the path has already been deleted, we don't need to do
1249 # it.
1250 gitdir = os.path.join(manifest.topdir, path, ".git")
1251 if os.path.exists(gitdir):
1252 project = Project(
1253 manifest=manifest,
1254 name=path,
1255 remote=RemoteSpec("origin"),
1256 gitdir=gitdir,
1257 objdir=gitdir,
1258 use_git_worktrees=os.path.isfile(gitdir),
1259 worktree=os.path.join(manifest.topdir, path),
1260 relpath=path,
1261 revisionExpr="HEAD",
1262 revisionId=None,
1263 groups=None,
1264 )
Jason Chang32b59562023-07-14 16:45:35 -07001265 project.DeleteWorktree(
Gavin Makea2e3302023-03-11 06:46:20 +00001266 quiet=opt.quiet, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001267 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001268
Gavin Makea2e3302023-03-11 06:46:20 +00001269 new_project_paths.sort()
1270 with open(file_path, "w") as fd:
1271 fd.write("\n".join(new_project_paths))
1272 fd.write("\n")
1273 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001274
Gavin Makea2e3302023-03-11 06:46:20 +00001275 def UpdateCopyLinkfileList(self, manifest):
1276 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001277
Gavin Makea2e3302023-03-11 06:46:20 +00001278 Returns:
1279 Whether update was successful.
1280 """
1281 new_paths = {}
1282 new_linkfile_paths = []
1283 new_copyfile_paths = []
1284 for project in self.GetProjects(
1285 None, missing_ok=True, manifest=manifest, all_manifests=False
1286 ):
1287 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1288 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001289
Gavin Makea2e3302023-03-11 06:46:20 +00001290 new_paths = {
1291 "linkfile": new_linkfile_paths,
1292 "copyfile": new_copyfile_paths,
1293 }
jiajia tanga590e642021-04-25 20:02:02 +08001294
Gavin Makea2e3302023-03-11 06:46:20 +00001295 copylinkfile_name = "copy-link-files.json"
1296 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1297 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001298
Gavin Makea2e3302023-03-11 06:46:20 +00001299 if os.path.exists(copylinkfile_path):
1300 with open(copylinkfile_path, "rb") as fp:
1301 try:
1302 old_copylinkfile_paths = json.load(fp)
1303 except Exception:
1304 print(
1305 "error: %s is not a json formatted file."
1306 % copylinkfile_path,
1307 file=sys.stderr,
1308 )
1309 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001310 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001311
Gavin Makea2e3302023-03-11 06:46:20 +00001312 need_remove_files = []
1313 need_remove_files.extend(
1314 set(old_copylinkfile_paths.get("linkfile", []))
1315 - set(new_linkfile_paths)
1316 )
1317 need_remove_files.extend(
1318 set(old_copylinkfile_paths.get("copyfile", []))
1319 - set(new_copyfile_paths)
1320 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001321
Gavin Makea2e3302023-03-11 06:46:20 +00001322 for need_remove_file in need_remove_files:
1323 # Try to remove the updated copyfile or linkfile.
1324 # So, if the file is not exist, nothing need to do.
1325 platform_utils.remove(need_remove_file, missing_ok=True)
Raman Tenneti7954de12021-07-28 14:36:49 -07001326
Gavin Makea2e3302023-03-11 06:46:20 +00001327 # Create copy-link-files.json, save dest path of "copyfile" and
1328 # "linkfile".
1329 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1330 json.dump(new_paths, fp)
1331 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001332
Gavin Makea2e3302023-03-11 06:46:20 +00001333 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1334 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001335 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001336 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001337 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001338 )
Gavin Makea2e3302023-03-11 06:46:20 +00001339
1340 manifest_server = manifest.manifest_server
1341 if not opt.quiet:
1342 print("Using manifest server %s" % manifest_server)
1343
1344 if "@" not in manifest_server:
1345 username = None
1346 password = None
1347 if opt.manifest_server_username and opt.manifest_server_password:
1348 username = opt.manifest_server_username
1349 password = opt.manifest_server_password
1350 else:
1351 try:
1352 info = netrc.netrc()
1353 except IOError:
1354 # .netrc file does not exist or could not be opened.
1355 pass
1356 else:
1357 try:
1358 parse_result = urllib.parse.urlparse(manifest_server)
1359 if parse_result.hostname:
1360 auth = info.authenticators(parse_result.hostname)
1361 if auth:
1362 username, _account, password = auth
1363 else:
1364 print(
1365 "No credentials found for %s in .netrc"
1366 % parse_result.hostname,
1367 file=sys.stderr,
1368 )
1369 except netrc.NetrcParseError as e:
1370 print(
1371 "Error parsing .netrc file: %s" % e, file=sys.stderr
1372 )
1373
1374 if username and password:
1375 manifest_server = manifest_server.replace(
1376 "://", "://%s:%s@" % (username, password), 1
1377 )
1378
1379 transport = PersistentTransport(manifest_server)
1380 if manifest_server.startswith("persistent-"):
1381 manifest_server = manifest_server[len("persistent-") :]
1382
1383 try:
1384 server = xmlrpc.client.Server(manifest_server, transport=transport)
1385 if opt.smart_sync:
1386 branch = self._GetBranch(manifest.manifestProject)
1387
1388 if "SYNC_TARGET" in os.environ:
1389 target = os.environ["SYNC_TARGET"]
1390 [success, manifest_str] = server.GetApprovedManifest(
1391 branch, target
1392 )
1393 elif (
1394 "TARGET_PRODUCT" in os.environ
1395 and "TARGET_BUILD_VARIANT" in os.environ
1396 ):
1397 target = "%s-%s" % (
1398 os.environ["TARGET_PRODUCT"],
1399 os.environ["TARGET_BUILD_VARIANT"],
1400 )
1401 [success, manifest_str] = server.GetApprovedManifest(
1402 branch, target
1403 )
1404 else:
1405 [success, manifest_str] = server.GetApprovedManifest(branch)
1406 else:
1407 assert opt.smart_tag
1408 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1409
1410 if success:
1411 manifest_name = os.path.basename(smart_sync_manifest_path)
1412 try:
1413 with open(smart_sync_manifest_path, "w") as f:
1414 f.write(manifest_str)
1415 except IOError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001416 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001417 "error: cannot write manifest to %s:\n%s"
1418 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001419 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001420 )
Gavin Makea2e3302023-03-11 06:46:20 +00001421 self._ReloadManifest(manifest_name, manifest)
1422 else:
Jason Chang32b59562023-07-14 16:45:35 -07001423 raise SmartSyncError(
1424 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001425 )
Gavin Makea2e3302023-03-11 06:46:20 +00001426 except (socket.error, IOError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001427 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001428 "error: cannot connect to manifest server %s:\n%s"
1429 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001430 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001431 )
Gavin Makea2e3302023-03-11 06:46:20 +00001432 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001433 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001434 "error: cannot connect to manifest server %s:\n%d %s"
1435 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001436 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001437 )
Gavin Makea2e3302023-03-11 06:46:20 +00001438
1439 return manifest_name
1440
1441 def _UpdateAllManifestProjects(self, opt, mp, manifest_name):
1442 """Fetch & update the local manifest project.
1443
1444 After syncing the manifest project, if the manifest has any sub
1445 manifests, those are recursively processed.
1446
1447 Args:
1448 opt: Program options returned from optparse. See _Options().
1449 mp: the manifestProject to query.
1450 manifest_name: Manifest file to be reloaded.
1451 """
1452 if not mp.standalone_manifest_url:
1453 self._UpdateManifestProject(opt, mp, manifest_name)
1454
1455 if mp.manifest.submanifests:
1456 for submanifest in mp.manifest.submanifests.values():
1457 child = submanifest.repo_client.manifest
1458 child.manifestProject.SyncWithPossibleInit(
1459 submanifest,
1460 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1461 verbose=opt.verbose,
1462 tags=opt.tags,
1463 git_event_log=self.git_event_log,
1464 )
1465 self._UpdateAllManifestProjects(
1466 opt, child.manifestProject, None
1467 )
1468
1469 def _UpdateManifestProject(self, opt, mp, manifest_name):
1470 """Fetch & update the local manifest project.
1471
1472 Args:
1473 opt: Program options returned from optparse. See _Options().
1474 mp: the manifestProject to query.
1475 manifest_name: Manifest file to be reloaded.
1476 """
1477 if not opt.local_only:
1478 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001479 result = mp.Sync_NetworkHalf(
Gavin Makea2e3302023-03-11 06:46:20 +00001480 quiet=opt.quiet,
1481 verbose=opt.verbose,
1482 current_branch_only=self._GetCurrentBranchOnly(
1483 opt, mp.manifest
1484 ),
1485 force_sync=opt.force_sync,
1486 tags=opt.tags,
1487 optimized_fetch=opt.optimized_fetch,
1488 retry_fetches=opt.retry_fetches,
1489 submodules=mp.manifest.HasSubmodules,
1490 clone_filter=mp.manifest.CloneFilter,
1491 partial_clone_exclude=mp.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -07001492 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +00001493 )
1494 finish = time.time()
1495 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001496 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001497 )
1498
1499 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001500 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001501 syncbuf = SyncBuffer(mp.config)
1502 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001503 mp.Sync_LocalHalf(
1504 syncbuf, submodules=mp.manifest.HasSubmodules, errors=errors
1505 )
Gavin Makea2e3302023-03-11 06:46:20 +00001506 clean = syncbuf.Finish()
1507 self.event_log.AddSync(
1508 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1509 )
1510 if not clean:
Jason Chang32b59562023-07-14 16:45:35 -07001511 raise UpdateManifestError(
1512 aggregate_errors=errors, project=mp.name
1513 )
Gavin Makea2e3302023-03-11 06:46:20 +00001514 self._ReloadManifest(manifest_name, mp.manifest)
1515
1516 def ValidateOptions(self, opt, args):
1517 if opt.force_broken:
1518 print(
1519 "warning: -f/--force-broken is now the default behavior, and "
1520 "the options are deprecated",
1521 file=sys.stderr,
1522 )
1523 if opt.network_only and opt.detach_head:
1524 self.OptionParser.error("cannot combine -n and -d")
1525 if opt.network_only and opt.local_only:
1526 self.OptionParser.error("cannot combine -n and -l")
1527 if opt.manifest_name and opt.smart_sync:
1528 self.OptionParser.error("cannot combine -m and -s")
1529 if opt.manifest_name and opt.smart_tag:
1530 self.OptionParser.error("cannot combine -m and -t")
1531 if opt.manifest_server_username or opt.manifest_server_password:
1532 if not (opt.smart_sync or opt.smart_tag):
1533 self.OptionParser.error(
1534 "-u and -p may only be combined with -s or -t"
1535 )
1536 if None in [
1537 opt.manifest_server_username,
1538 opt.manifest_server_password,
1539 ]:
1540 self.OptionParser.error("both -u and -p must be given")
1541
1542 if opt.prune is None:
1543 opt.prune = True
1544
1545 if opt.auto_gc is None and _AUTO_GC:
1546 print(
1547 f"Will run `git gc --auto` because {_REPO_AUTO_GC} is set.",
1548 f"{_REPO_AUTO_GC} is deprecated and will be removed in a ",
1549 "future release. Use `--auto-gc` instead.",
1550 file=sys.stderr,
1551 )
1552 opt.auto_gc = True
1553
1554 def _ValidateOptionsWithManifest(self, opt, mp):
1555 """Like ValidateOptions, but after we've updated the manifest.
1556
1557 Needed to handle sync-xxx option defaults in the manifest.
1558
1559 Args:
1560 opt: The options to process.
1561 mp: The manifest project to pull defaults from.
1562 """
1563 if not opt.jobs:
1564 # If the user hasn't made a choice, use the manifest value.
1565 opt.jobs = mp.manifest.default.sync_j
1566 if opt.jobs:
1567 # If --jobs has a non-default value, propagate it as the default for
1568 # --jobs-xxx flags too.
1569 if not opt.jobs_network:
1570 opt.jobs_network = opt.jobs
1571 if not opt.jobs_checkout:
1572 opt.jobs_checkout = opt.jobs
1573 else:
1574 # Neither user nor manifest have made a choice, so setup defaults.
1575 if not opt.jobs_network:
1576 opt.jobs_network = 1
1577 if not opt.jobs_checkout:
1578 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1579 opt.jobs = os.cpu_count()
1580
1581 # Try to stay under user rlimit settings.
1582 #
1583 # Since each worker requires at 3 file descriptors to run `git fetch`,
1584 # use that to scale down the number of jobs. Unfortunately there isn't
1585 # an easy way to determine this reliably as systems change, but it was
1586 # last measured by hand in 2011.
1587 soft_limit, _ = _rlimit_nofile()
1588 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1589 opt.jobs = min(opt.jobs, jobs_soft_limit)
1590 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1591 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1592
1593 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001594 errors = []
1595 try:
1596 self._ExecuteHelper(opt, args, errors)
1597 except RepoExitError:
1598 raise
1599 except (KeyboardInterrupt, Exception) as e:
1600 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1601
1602 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001603 manifest = self.outer_manifest
1604 if not opt.outer_manifest:
1605 manifest = self.manifest
1606
1607 if opt.manifest_name:
1608 manifest.Override(opt.manifest_name)
1609
1610 manifest_name = opt.manifest_name
1611 smart_sync_manifest_path = os.path.join(
1612 manifest.manifestProject.worktree, "smart_sync_override.xml"
1613 )
1614
1615 if opt.clone_bundle is None:
1616 opt.clone_bundle = manifest.CloneBundle
1617
1618 if opt.smart_sync or opt.smart_tag:
1619 manifest_name = self._SmartSyncSetup(
1620 opt, smart_sync_manifest_path, manifest
1621 )
1622 else:
1623 if os.path.isfile(smart_sync_manifest_path):
1624 try:
1625 platform_utils.remove(smart_sync_manifest_path)
1626 except OSError as e:
1627 print(
1628 "error: failed to remove existing smart sync override "
1629 "manifest: %s" % e,
1630 file=sys.stderr,
1631 )
1632
1633 err_event = multiprocessing.Event()
1634
1635 rp = manifest.repoProject
1636 rp.PreSync()
1637 cb = rp.CurrentBranch
1638 if cb:
1639 base = rp.GetBranch(cb).merge
1640 if not base or not base.startswith("refs/heads/"):
1641 print(
1642 "warning: repo is not tracking a remote branch, so it will "
1643 "not receive updates; run `repo init --repo-rev=stable` to "
1644 "fix.",
1645 file=sys.stderr,
1646 )
1647
1648 for m in self.ManifestList(opt):
1649 if not m.manifestProject.standalone_manifest_url:
1650 m.manifestProject.PreSync()
1651
1652 if opt.repo_upgraded:
1653 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1654
1655 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001656
1657 if _REPO_ALLOW_SHALLOW is not None:
1658 if _REPO_ALLOW_SHALLOW == "1":
1659 mp.ConfigureCloneFilterForDepth(None)
1660 elif (
1661 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1662 ):
1663 mp.ConfigureCloneFilterForDepth("blob:none")
1664
Gavin Makea2e3302023-03-11 06:46:20 +00001665 if opt.mp_update:
1666 self._UpdateAllManifestProjects(opt, mp, manifest_name)
1667 else:
1668 print("Skipping update of local manifest project.")
1669
1670 # Now that the manifests are up-to-date, setup options whose defaults
1671 # might be in the manifest.
1672 self._ValidateOptionsWithManifest(opt, mp)
1673
1674 superproject_logging_data = {}
1675 self._UpdateProjectsRevisionId(
1676 opt, args, superproject_logging_data, manifest
1677 )
1678
Gavin Makea2e3302023-03-11 06:46:20 +00001679 all_projects = self.GetProjects(
1680 args,
1681 missing_ok=True,
1682 submodules_ok=opt.fetch_submodules,
1683 manifest=manifest,
1684 all_manifests=not opt.this_manifest_only,
1685 )
1686
1687 err_network_sync = False
1688 err_update_projects = False
1689 err_update_linkfiles = False
1690
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001691 # Log the repo projects by existing and new.
1692 existing = [x for x in all_projects if x.Exists]
1693 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1694 mp.config.SetString(
1695 "repo.newprojectcount", str(len(all_projects) - len(existing))
1696 )
1697
Gavin Makea2e3302023-03-11 06:46:20 +00001698 self._fetch_times = _FetchTimes(manifest)
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001699 self._local_sync_state = _LocalSyncState(manifest)
Gavin Makea2e3302023-03-11 06:46:20 +00001700 if not opt.local_only:
1701 with multiprocessing.Manager() as manager:
1702 with ssh.ProxyManager(manager) as ssh_proxy:
1703 # Initialize the socket dir once in the parent.
1704 ssh_proxy.sock()
1705 result = self._FetchMain(
1706 opt, args, all_projects, err_event, ssh_proxy, manifest
1707 )
Jason Chang32b59562023-07-14 16:45:35 -07001708 if result.errors:
1709 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001710 all_projects = result.all_projects
1711
1712 if opt.network_only:
1713 return
1714
1715 # If we saw an error, exit with code 1 so that other scripts can
1716 # check.
1717 if err_event.is_set():
1718 err_network_sync = True
1719 if opt.fail_fast:
1720 print(
1721 "\nerror: Exited sync due to fetch errors.\n"
1722 "Local checkouts *not* updated. Resolve network issues "
1723 "& retry.\n"
1724 "`repo sync -l` will update some local checkouts.",
1725 file=sys.stderr,
1726 )
Jason Chang32b59562023-07-14 16:45:35 -07001727 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001728
1729 for m in self.ManifestList(opt):
1730 if m.IsMirror or m.IsArchive:
1731 # Bail out now, we have no working tree.
1732 continue
1733
Jason Chang32b59562023-07-14 16:45:35 -07001734 try:
1735 self.UpdateProjectList(opt, m)
1736 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001737 err_event.set()
1738 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001739 errors.append(e)
1740 if isinstance(e, DeleteWorktreeError):
1741 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001742 if opt.fail_fast:
1743 print(
1744 "\nerror: Local checkouts *not* updated.",
1745 file=sys.stderr,
1746 )
Jason Chang32b59562023-07-14 16:45:35 -07001747 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001748
Jason Chang32b59562023-07-14 16:45:35 -07001749 err_update_linkfiles = False
1750 try:
1751 self.UpdateCopyLinkfileList(m)
1752 except Exception as e:
1753 err_update_linkfiles = True
1754 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001755 err_event.set()
1756 if opt.fail_fast:
1757 print(
1758 "\nerror: Local update copyfile or linkfile failed.",
1759 file=sys.stderr,
1760 )
Jason Chang32b59562023-07-14 16:45:35 -07001761 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001762
1763 err_results = []
1764 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001765 err_checkout = not self._Checkout(
1766 all_projects, opt, err_results, errors
1767 )
Gavin Makea2e3302023-03-11 06:46:20 +00001768 if err_checkout:
1769 err_event.set()
1770
1771 printed_notices = set()
1772 # If there's a notice that's supposed to print at the end of the sync,
1773 # print it now... But avoid printing duplicate messages, and preserve
1774 # order.
1775 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1776 if m.notice and m.notice not in printed_notices:
1777 print(m.notice)
1778 printed_notices.add(m.notice)
1779
1780 # If we saw an error, exit with code 1 so that other scripts can check.
1781 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001782 # Add a new line so it's easier to read.
1783 print("\n", file=sys.stderr)
1784
1785 def print_and_log(err_msg):
1786 self.git_event_log.ErrorEvent(err_msg)
1787 print(err_msg, file=sys.stderr)
1788
1789 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001790 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001791 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001792 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001793 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001794 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001795 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001796 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001797 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001798 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001799 # Don't log repositories, as it may contain sensitive info.
Gavin Makea2e3302023-03-11 06:46:20 +00001800 print(
1801 "Failing repos:\n%s" % "\n".join(err_results),
1802 file=sys.stderr,
1803 )
Josip Sokcevic131fc962023-05-12 17:00:46 -07001804 # Not useful to log.
Gavin Makea2e3302023-03-11 06:46:20 +00001805 print(
1806 'Try re-running with "-j1 --fail-fast" to exit at the first '
1807 "error.",
1808 file=sys.stderr,
1809 )
Jason Chang32b59562023-07-14 16:45:35 -07001810 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001811
1812 # Log the previous sync analysis state from the config.
1813 self.git_event_log.LogDataConfigEvents(
1814 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1815 )
1816
1817 # Update and log with the new sync analysis state.
1818 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1819 self.git_event_log.LogDataConfigEvents(
1820 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1821 )
1822
Gavin Makf0aeb222023-08-08 04:43:36 +00001823 self._local_sync_state.PruneRemovedProjects()
1824 if self._local_sync_state.IsPartiallySynced():
1825 print(
1826 "warning: Partial syncs are not supported. For the best "
1827 "experience, sync the entire tree.",
1828 file=sys.stderr,
1829 )
1830
Gavin Makea2e3302023-03-11 06:46:20 +00001831 if not opt.quiet:
1832 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001833
David Pursehouse819827a2020-02-12 15:20:19 +09001834
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001835def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001836 # Link the docs for the internal .repo/ layout for people.
1837 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1838 if not platform_utils.islink(link):
1839 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1840 try:
1841 platform_utils.symlink(target, link)
1842 except Exception:
1843 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001844
Gavin Makea2e3302023-03-11 06:46:20 +00001845 wrapper = Wrapper()
1846 if wrapper.NeedSetupGnuPG():
1847 wrapper.SetupGnuPG(quiet)
1848 for project in manifest.projects:
1849 if project.Exists:
1850 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001851
David Pursehouse819827a2020-02-12 15:20:19 +09001852
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001853def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001854 if rp.HasChanges:
1855 print("info: A new version of repo is available", file=sys.stderr)
1856 wrapper = Wrapper()
1857 try:
1858 rev = rp.bare_git.describe(rp.GetRevisionId())
1859 except GitError:
1860 rev = None
1861 _, new_rev = wrapper.check_repo_rev(
1862 rp.gitdir, rev, repo_verify=repo_verify
1863 )
1864 # See if we're held back due to missing signed tag.
1865 current_revid = rp.bare_git.rev_parse("HEAD")
1866 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1867 if current_revid != new_revid:
1868 # We want to switch to the new rev, but also not trash any
1869 # uncommitted changes. This helps with local testing/hacking.
1870 # If a local change has been made, we will throw that away.
1871 # We also have to make sure this will switch to an older commit if
1872 # that's the latest tag in order to support release rollback.
1873 try:
1874 rp.work_git.reset("--keep", new_rev)
1875 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001876 raise RepoUnhandledExceptionError(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001877 print("info: Restarting repo with latest version", file=sys.stderr)
1878 raise RepoChangedException(["--repo-upgraded"])
1879 else:
1880 print(
1881 "warning: Skipped upgrade to unverified version",
1882 file=sys.stderr,
1883 )
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001884 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001885 if verbose:
1886 print(
1887 "repo version %s is current" % rp.work_git.describe(HEAD),
1888 file=sys.stderr,
1889 )
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001890
David Pursehouse819827a2020-02-12 15:20:19 +09001891
Dave Borowitz67700e92012-10-23 15:00:54 -07001892class _FetchTimes(object):
Gavin Makea2e3302023-03-11 06:46:20 +00001893 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07001894
Gavin Makea2e3302023-03-11 06:46:20 +00001895 def __init__(self, manifest):
1896 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00001897 self._saved = None
1898 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001899
Gavin Makea2e3302023-03-11 06:46:20 +00001900 def Get(self, project):
1901 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00001902 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07001903
Gavin Makea2e3302023-03-11 06:46:20 +00001904 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00001905 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00001906
1907 # For shared projects, save the longest time.
1908 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07001909
Gavin Makea2e3302023-03-11 06:46:20 +00001910 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001911 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001912 try:
1913 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001914 self._saved = json.load(f)
Gavin Makea2e3302023-03-11 06:46:20 +00001915 except (IOError, ValueError):
1916 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00001917 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001918
Gavin Makea2e3302023-03-11 06:46:20 +00001919 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001920 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001921 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001922
Gavin Mak041f9772023-05-10 20:41:12 +00001923 for name, t in self._seen.items():
1924 # Keep a moving average across the previous/current sync runs.
1925 old = self._saved.get(name, t)
1926 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07001927
Gavin Makea2e3302023-03-11 06:46:20 +00001928 try:
1929 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001930 json.dump(self._seen, f, indent=2)
Gavin Makea2e3302023-03-11 06:46:20 +00001931 except (IOError, TypeError):
1932 platform_utils.remove(self._path, missing_ok=True)
1933
Dan Willemsen0745bb22015-08-17 13:41:45 -07001934
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001935class _LocalSyncState(object):
1936 _LAST_FETCH = "last_fetch"
1937 _LAST_CHECKOUT = "last_checkout"
1938
1939 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00001940 self._manifest = manifest
1941 self._path = os.path.join(
1942 self._manifest.repodir, ".repo_localsyncstate.json"
1943 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001944 self._time = time.time()
1945 self._state = None
1946 self._Load()
1947
1948 def SetFetchTime(self, project):
1949 self._Set(project, self._LAST_FETCH)
1950
1951 def SetCheckoutTime(self, project):
1952 self._Set(project, self._LAST_CHECKOUT)
1953
1954 def GetFetchTime(self, project):
1955 return self._Get(project, self._LAST_FETCH)
1956
1957 def GetCheckoutTime(self, project):
1958 return self._Get(project, self._LAST_CHECKOUT)
1959
1960 def _Get(self, project, key):
1961 self._Load()
1962 p = project.relpath
1963 if p not in self._state:
1964 return
1965 return self._state[p].get(key)
1966
1967 def _Set(self, project, key):
1968 p = project.relpath
1969 if p not in self._state:
1970 self._state[p] = {}
1971 self._state[p][key] = self._time
1972
1973 def _Load(self):
1974 if self._state is None:
1975 try:
1976 with open(self._path) as f:
1977 self._state = json.load(f)
1978 except (IOError, ValueError):
1979 platform_utils.remove(self._path, missing_ok=True)
1980 self._state = {}
1981
1982 def Save(self):
1983 if not self._state:
1984 return
1985 try:
1986 with open(self._path, "w") as f:
1987 json.dump(self._state, f, indent=2)
1988 except (IOError, TypeError):
1989 platform_utils.remove(self._path, missing_ok=True)
1990
Gavin Makf0aeb222023-08-08 04:43:36 +00001991 def PruneRemovedProjects(self):
1992 """Remove entries don't exist on disk and save."""
1993 if not self._state:
1994 return
1995 delete = set()
1996 for path in self._state:
1997 gitdir = os.path.join(self._manifest.topdir, path, ".git")
1998 if not os.path.exists(gitdir):
1999 delete.add(path)
2000 if not delete:
2001 return
2002 for path in delete:
2003 del self._state[path]
2004 self.Save()
2005
2006 def IsPartiallySynced(self):
2007 """Return whether a partial sync state is detected."""
2008 self._Load()
2009 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002010 for path, data in self._state.items():
2011 if path == self._manifest.repoProject.relpath:
2012 # The repo project isn't included in most syncs so we should
2013 # ignore it here.
2014 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002015 checkout_t = data.get(self._LAST_CHECKOUT)
2016 if not checkout_t:
2017 return True
2018 prev_checkout_t = prev_checkout_t or checkout_t
2019 if prev_checkout_t != checkout_t:
2020 return True
2021 return False
2022
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002023
Dan Willemsen0745bb22015-08-17 13:41:45 -07002024# This is a replacement for xmlrpc.client.Transport using urllib2
2025# and supporting persistent-http[s]. It cannot change hosts from
2026# request to request like the normal transport, the real url
2027# is passed during initialization.
2028class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002029 def __init__(self, orig_host):
2030 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002031
Gavin Makea2e3302023-03-11 06:46:20 +00002032 def request(self, host, handler, request_body, verbose=False):
2033 with GetUrlCookieFile(self.orig_host, not verbose) as (
2034 cookiefile,
2035 proxy,
2036 ):
2037 # Python doesn't understand cookies with the #HttpOnly_ prefix
2038 # Since we're only using them for HTTP, copy the file temporarily,
2039 # stripping those prefixes away.
2040 if cookiefile:
2041 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2042 tmpcookiefile.write("# HTTP Cookie File")
2043 try:
2044 with open(cookiefile) as f:
2045 for line in f:
2046 if line.startswith("#HttpOnly_"):
2047 line = line[len("#HttpOnly_") :]
2048 tmpcookiefile.write(line)
2049 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002050
Gavin Makea2e3302023-03-11 06:46:20 +00002051 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2052 try:
2053 cookiejar.load()
2054 except cookielib.LoadError:
2055 cookiejar = cookielib.CookieJar()
2056 finally:
2057 tmpcookiefile.close()
2058 else:
2059 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002060
Gavin Makea2e3302023-03-11 06:46:20 +00002061 proxyhandler = urllib.request.ProxyHandler
2062 if proxy:
2063 proxyhandler = urllib.request.ProxyHandler(
2064 {"http": proxy, "https": proxy}
2065 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002066
Gavin Makea2e3302023-03-11 06:46:20 +00002067 opener = urllib.request.build_opener(
2068 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2069 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002070
Gavin Makea2e3302023-03-11 06:46:20 +00002071 url = urllib.parse.urljoin(self.orig_host, handler)
2072 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002073
Gavin Makea2e3302023-03-11 06:46:20 +00002074 scheme = parse_results.scheme
2075 if scheme == "persistent-http":
2076 scheme = "http"
2077 if scheme == "persistent-https":
2078 # If we're proxying through persistent-https, use http. The
2079 # proxy itself will do the https.
2080 if proxy:
2081 scheme = "http"
2082 else:
2083 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002084
Gavin Makea2e3302023-03-11 06:46:20 +00002085 # Parse out any authentication information using the base class.
2086 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002087
Gavin Makea2e3302023-03-11 06:46:20 +00002088 url = urllib.parse.urlunparse(
2089 (
2090 scheme,
2091 host,
2092 parse_results.path,
2093 parse_results.params,
2094 parse_results.query,
2095 parse_results.fragment,
2096 )
2097 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002098
Gavin Makea2e3302023-03-11 06:46:20 +00002099 request = urllib.request.Request(url, request_body)
2100 if extra_headers is not None:
2101 for name, header in extra_headers:
2102 request.add_header(name, header)
2103 request.add_header("Content-Type", "text/xml")
2104 try:
2105 response = opener.open(request)
2106 except urllib.error.HTTPError as e:
2107 if e.code == 501:
2108 # We may have been redirected through a login process
2109 # but our POST turned into a GET. Retry.
2110 response = opener.open(request)
2111 else:
2112 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002113
Gavin Makea2e3302023-03-11 06:46:20 +00002114 p, u = xmlrpc.client.getparser()
2115 # Response should be fairly small, so read it all at once.
2116 # This way we can show it to the user in case of error (e.g. HTML).
2117 data = response.read()
2118 try:
2119 p.feed(data)
2120 except xml.parsers.expat.ExpatError as e:
2121 raise IOError(
2122 f"Parsing the manifest failed: {e}\n"
2123 f"Please report this to your manifest server admin.\n"
2124 f'Here is the full response:\n{data.decode("utf-8")}'
2125 )
2126 p.close()
2127 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002128
Gavin Makea2e3302023-03-11 06:46:20 +00002129 def close(self):
2130 pass