blob: 7ccd680f7dba078985fe1682043c7bb9279ce5ce [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
Mike Frysinger06ddc8c2023-08-21 21:26:51 -040022import optparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import os
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070024import socket
Dan Willemsen0745bb22015-08-17 13:41:45 -070025import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070026import time
Mike Frysinger64477332023-08-21 21:20:32 -040027from typing import List, NamedTuple, Set
Mike Frysingeracf63b22019-06-13 02:24:21 -040028import urllib.error
29import urllib.parse
30import urllib.request
Mike Frysinger5951e302022-05-20 23:34:44 -040031import xml.parsers.expat
Mike Frysingeracf63b22019-06-13 02:24:21 -040032import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070033
Mike Frysinger64477332023-08-21 21:20:32 -040034
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
Mike Frysinger64477332023-08-21 21:20:32 -040052from command import Command
53from command import DEFAULT_LOCAL_JOBS
54from command import MirrorSafeCommand
55from command import WORKER_BATCH_SIZE
56from error import GitError
57from error import RepoChangedException
58from error import RepoExitError
59from error import RepoUnhandledExceptionError
60from error import SyncError
61from error import UpdateManifestError
David Rileye0684ad2017-04-05 00:02:59 -070062import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040063from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090064from git_config import GetUrlCookieFile
Mike Frysinger64477332023-08-21 21:20:32 -040065from git_refs import HEAD
66from git_refs import R_HEADS
Raman Tenneti6a872c92021-01-14 19:17:50 -080067import git_superproject
Mike Frysinger64477332023-08-21 21:20:32 -040068import platform_utils
69from progress import elapsed_str
70from progress import jobs_str
71from progress import Progress
72from project import DeleteWorktreeError
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070073from project import Project
74from project import RemoteSpec
Mike Frysinger64477332023-08-21 21:20:32 -040075from project import SyncBuffer
Aravind Vasudevane914ec22023-08-31 20:57:31 +000076from repo_logging import RepoLogger
Joanna Wanga6c52f52022-11-03 16:51:19 -040077from repo_trace import Trace
Mike Frysinger19e409c2021-05-05 19:44:35 -040078import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080079from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070080
Mike Frysinger64477332023-08-21 21:20:32 -040081
Dave Borowitz67700e92012-10-23 15:00:54 -070082_ONE_DAY_S = 24 * 60 * 60
83
LaMont Jonesd7935532022-12-01 20:18:46 +000084# Env var to implicitly turn auto-gc back on. This was added to allow a user to
LaMont Jones100a2142022-12-02 22:54:11 +000085# revert a change in default behavior in v2.29.9. Remove after 2023-04-01.
Gavin Makea2e3302023-03-11 06:46:20 +000086_REPO_AUTO_GC = "REPO_AUTO_GC"
87_AUTO_GC = os.environ.get(_REPO_AUTO_GC) == "1"
LaMont Jones5ed8c632022-11-10 00:10:44 +000088
Jason Chang17833322023-05-23 13:06:55 -070089_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
90
Aravind Vasudevane914ec22023-08-31 20:57:31 +000091logger = RepoLogger(__file__)
92
David Pursehouse819827a2020-02-12 15:20:19 +090093
LaMont Jones1eddca82022-09-01 15:15:04 +000094class _FetchOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +000095 """_FetchOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +000096
Gavin Makea2e3302023-03-11 06:46:20 +000097 Attributes:
98 success (bool): True if successful.
99 project (Project): The fetched project.
100 start (float): The starting time.time().
101 finish (float): The ending time.time().
102 remote_fetched (bool): True if the remote was actually queried.
103 """
104
105 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700106 errors: List[Exception]
Gavin Makea2e3302023-03-11 06:46:20 +0000107 project: Project
108 start: float
109 finish: float
110 remote_fetched: bool
LaMont Jones1eddca82022-09-01 15:15:04 +0000111
112
113class _FetchResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000114 """_Fetch return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000115
Gavin Makea2e3302023-03-11 06:46:20 +0000116 Attributes:
117 success (bool): True if successful.
118 projects (Set[str]): The names of the git directories of fetched projects.
119 """
120
121 success: bool
122 projects: Set[str]
Jason Chang32b59562023-07-14 16:45:35 -0700123 errors: List[Exception]
LaMont Jones1eddca82022-09-01 15:15:04 +0000124
125
126class _FetchMainResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000127 """_FetchMain return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000128
Gavin Makea2e3302023-03-11 06:46:20 +0000129 Attributes:
130 all_projects (List[Project]): The fetched projects.
131 """
132
133 all_projects: List[Project]
Jason Chang32b59562023-07-14 16:45:35 -0700134 errors: List[Exception]
LaMont Jones1eddca82022-09-01 15:15:04 +0000135
136
137class _CheckoutOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000138 """_CheckoutOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000139
Gavin Makea2e3302023-03-11 06:46:20 +0000140 Attributes:
141 success (bool): True if successful.
142 project (Project): The project.
143 start (float): The starting time.time().
144 finish (float): The ending time.time().
145 """
146
147 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700148 errors: List[Exception]
Gavin Makea2e3302023-03-11 06:46:20 +0000149 project: Project
150 start: float
151 finish: float
LaMont Jones1eddca82022-09-01 15:15:04 +0000152
153
Jason Chang32b59562023-07-14 16:45:35 -0700154class SuperprojectError(SyncError):
155 """Superproject sync repo."""
156
157
158class SyncFailFastError(SyncError):
159 """Sync exit error when --fail-fast set."""
160
161
162class SmartSyncError(SyncError):
163 """Smart sync exit error."""
164
165
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800166class Sync(Command, MirrorSafeCommand):
Gavin Makea2e3302023-03-11 06:46:20 +0000167 COMMON = True
168 MULTI_MANIFEST_SUPPORT = True
169 helpSummary = "Update working tree to the latest revision"
170 helpUsage = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700171%prog [...]
172"""
Gavin Makea2e3302023-03-11 06:46:20 +0000173 helpDescription = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700174The '%prog' command synchronizes local project directories
175with the remote repositories specified in the manifest. If a local
176project does not yet exist, it will clone a new local directory from
177the remote repository and set up tracking branches as specified in
178the manifest. If the local project already exists, '%prog'
179will update the remote branches and rebase any new local changes
180on top of the new remote changes.
181
182'%prog' will synchronize all projects listed at the command
183line. Projects can be specified either by name, or by a relative
184or absolute path to the project's local directory. If no projects
185are specified, '%prog' will synchronize all projects listed in
186the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700187
188The -d/--detach option can be used to switch specified projects
189back to the manifest revision. This option is especially helpful
190if the project is currently on a topic branch, but the manifest
191revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700192
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700193The -s/--smart-sync option can be used to sync to a known good
194build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200195manifest. The -t/--smart-tag option is similar and allows you to
196specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700197
David Pursehousecf76b1b2012-09-14 10:31:42 +0900198The -u/--manifest-server-username and -p/--manifest-server-password
199options can be used to specify a username and password to authenticate
200with the manifest server when using the -s or -t option.
201
202If -u and -p are not specified when using the -s or -t option, '%prog'
203will attempt to read authentication credentials for the manifest server
204from the user's .netrc file.
205
206'%prog' will not use authentication credentials from -u/-p or .netrc
207if the manifest server specified in the manifest file already includes
208credentials.
209
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400210By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400211to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500212
Kevin Degiabaa7f32014-11-12 11:27:45 -0700213The --force-sync option can be used to overwrite existing git
214directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900215object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700216refs may be removed when overwriting.
217
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500218The --force-remove-dirty option can be used to remove previously used
219projects with uncommitted changes. WARNING: This may cause data to be
220lost since uncommitted changes may be removed with projects that no longer
221exist in the manifest.
222
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700223The --no-clone-bundle option disables any attempt to use
224$URL/clone.bundle to bootstrap a new Git repository from a
225resumeable bundle file on a content delivery network. This
226may be necessary if there are problems with the local Python
227HTTP client or proxy configuration, but the Git binary works.
228
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800229The --fetch-submodules option enables fetching Git submodules
230of a project from server.
231
David Pursehousef2fad612015-01-29 14:36:28 +0900232The -c/--current-branch option can be used to only fetch objects that
233are on the branch specified by a project's revision.
234
David Pursehouseb1553542014-09-04 21:28:09 +0900235The --optimized-fetch option can be used to only fetch projects that
236are fixed to a sha1 revision if the sha1 revision does not already
237exist locally.
238
David Pursehouse74cfd272015-10-14 10:50:15 +0900239The --prune option can be used to remove any refs that no longer
240exist on the remote.
241
LaMont Jones7efab532022-09-01 15:41:12 +0000242The --auto-gc option can be used to trigger garbage collection on all
243projects. By default, repo does not run garbage collection.
244
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400245# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700246
247If at least one project remote URL uses an SSH connection (ssh://,
248git+ssh://, or user@host:path syntax) repo will automatically
249enable the SSH ControlMaster option when connecting to that host.
250This feature permits other projects in the same '%prog' session to
251reuse the same SSH tunnel, saving connection setup overheads.
252
253To disable this behavior on UNIX platforms, set the GIT_SSH
254environment variable to 'ssh'. For example:
255
256 export GIT_SSH=ssh
257 %prog
258
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400259# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700260
261This feature is automatically disabled on Windows, due to the lack
262of UNIX domain socket support.
263
264This feature is not compatible with url.insteadof rewrites in the
265user's ~/.gitconfig. '%prog' is currently not able to perform the
266rewrite early enough to establish the ControlMaster tunnel.
267
268If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
269later is required to fix a server side protocol bug.
270
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700271"""
Gavin Makea2e3302023-03-11 06:46:20 +0000272 # A value of 0 means we want parallel jobs, but we'll determine the default
273 # value later on.
274 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700275
Gavin Makea2e3302023-03-11 06:46:20 +0000276 def _Options(self, p, show_smart=True):
277 p.add_option(
278 "--jobs-network",
279 default=None,
280 type=int,
281 metavar="JOBS",
282 help="number of network jobs to run in parallel (defaults to "
283 "--jobs or 1)",
284 )
285 p.add_option(
286 "--jobs-checkout",
287 default=None,
288 type=int,
289 metavar="JOBS",
290 help="number of local checkout jobs to run in parallel (defaults "
291 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
292 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400293
Gavin Makea2e3302023-03-11 06:46:20 +0000294 p.add_option(
295 "-f",
296 "--force-broken",
297 dest="force_broken",
298 action="store_true",
299 help="obsolete option (to be deleted in the future)",
300 )
301 p.add_option(
302 "--fail-fast",
303 dest="fail_fast",
304 action="store_true",
305 help="stop syncing after first error is hit",
306 )
307 p.add_option(
308 "--force-sync",
309 dest="force_sync",
310 action="store_true",
311 help="overwrite an existing git directory if it needs to "
312 "point to a different object directory. WARNING: this "
313 "may cause loss of data",
314 )
315 p.add_option(
316 "--force-remove-dirty",
317 dest="force_remove_dirty",
318 action="store_true",
319 help="force remove projects with uncommitted modifications if "
320 "projects no longer exist in the manifest. "
321 "WARNING: this may cause loss of data",
322 )
323 p.add_option(
324 "-l",
325 "--local-only",
326 dest="local_only",
327 action="store_true",
328 help="only update working tree, don't fetch",
329 )
330 p.add_option(
331 "--no-manifest-update",
332 "--nmu",
333 dest="mp_update",
334 action="store_false",
335 default="true",
336 help="use the existing manifest checkout as-is. "
337 "(do not update to the latest revision)",
338 )
339 p.add_option(
340 "-n",
341 "--network-only",
342 dest="network_only",
343 action="store_true",
344 help="fetch only, don't update working tree",
345 )
346 p.add_option(
347 "-d",
348 "--detach",
349 dest="detach_head",
350 action="store_true",
351 help="detach projects back to manifest revision",
352 )
353 p.add_option(
354 "-c",
355 "--current-branch",
356 dest="current_branch_only",
357 action="store_true",
358 help="fetch only current branch from server",
359 )
360 p.add_option(
361 "--no-current-branch",
362 dest="current_branch_only",
363 action="store_false",
364 help="fetch all branches from server",
365 )
366 p.add_option(
367 "-m",
368 "--manifest-name",
369 dest="manifest_name",
370 help="temporary manifest to use for this sync",
371 metavar="NAME.xml",
372 )
373 p.add_option(
374 "--clone-bundle",
375 action="store_true",
376 help="enable use of /clone.bundle on HTTP/HTTPS",
377 )
378 p.add_option(
379 "--no-clone-bundle",
380 dest="clone_bundle",
381 action="store_false",
382 help="disable use of /clone.bundle on HTTP/HTTPS",
383 )
384 p.add_option(
385 "-u",
386 "--manifest-server-username",
387 action="store",
388 dest="manifest_server_username",
389 help="username to authenticate with the manifest server",
390 )
391 p.add_option(
392 "-p",
393 "--manifest-server-password",
394 action="store",
395 dest="manifest_server_password",
396 help="password to authenticate with the manifest server",
397 )
398 p.add_option(
399 "--fetch-submodules",
400 dest="fetch_submodules",
401 action="store_true",
402 help="fetch submodules from server",
403 )
404 p.add_option(
405 "--use-superproject",
406 action="store_true",
407 help="use the manifest superproject to sync projects; implies -c",
408 )
409 p.add_option(
410 "--no-use-superproject",
411 action="store_false",
412 dest="use_superproject",
413 help="disable use of manifest superprojects",
414 )
415 p.add_option("--tags", action="store_true", help="fetch tags")
416 p.add_option(
417 "--no-tags",
418 dest="tags",
419 action="store_false",
420 help="don't fetch tags (default)",
421 )
422 p.add_option(
423 "--optimized-fetch",
424 dest="optimized_fetch",
425 action="store_true",
426 help="only fetch projects fixed to sha1 if revision does not exist "
427 "locally",
428 )
429 p.add_option(
430 "--retry-fetches",
431 default=0,
432 action="store",
433 type="int",
434 help="number of times to retry fetches on transient errors",
435 )
436 p.add_option(
437 "--prune",
438 action="store_true",
439 help="delete refs that no longer exist on the remote (default)",
440 )
441 p.add_option(
442 "--no-prune",
443 dest="prune",
444 action="store_false",
445 help="do not delete refs that no longer exist on the remote",
446 )
447 p.add_option(
448 "--auto-gc",
449 action="store_true",
450 default=None,
451 help="run garbage collection on all synced projects",
452 )
453 p.add_option(
454 "--no-auto-gc",
455 dest="auto_gc",
456 action="store_false",
457 help="do not run garbage collection on any projects (default)",
458 )
459 if show_smart:
460 p.add_option(
461 "-s",
462 "--smart-sync",
463 dest="smart_sync",
464 action="store_true",
465 help="smart sync using manifest from the latest known good "
466 "build",
467 )
468 p.add_option(
469 "-t",
470 "--smart-tag",
471 dest="smart_tag",
472 action="store",
473 help="smart sync using manifest from a known tag",
474 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700475
Gavin Makea2e3302023-03-11 06:46:20 +0000476 g = p.add_option_group("repo Version options")
477 g.add_option(
478 "--no-repo-verify",
479 dest="repo_verify",
480 default=True,
481 action="store_false",
482 help="do not verify repo source code",
483 )
484 g.add_option(
485 "--repo-upgraded",
486 dest="repo_upgraded",
487 action="store_true",
Mike Frysinger06ddc8c2023-08-21 21:26:51 -0400488 help=optparse.SUPPRESS_HELP,
Gavin Makea2e3302023-03-11 06:46:20 +0000489 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700490
Gavin Makea2e3302023-03-11 06:46:20 +0000491 def _GetBranch(self, manifest_project):
492 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000493
Gavin Makea2e3302023-03-11 06:46:20 +0000494 Args:
495 manifest_project: The manifestProject to query.
496 """
497 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
498 branch = b.merge
499 if branch.startswith(R_HEADS):
500 branch = branch[len(R_HEADS) :]
501 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800502
Gavin Makea2e3302023-03-11 06:46:20 +0000503 def _GetCurrentBranchOnly(self, opt, manifest):
504 """Returns whether current-branch or use-superproject options are
505 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200506
Gavin Makea2e3302023-03-11 06:46:20 +0000507 Args:
508 opt: Program options returned from optparse. See _Options().
509 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000510
Gavin Makea2e3302023-03-11 06:46:20 +0000511 Returns:
512 True if a superproject is requested, otherwise the value of the
513 current_branch option (True, False or None).
514 """
515 return (
516 git_superproject.UseSuperproject(opt.use_superproject, manifest)
517 or opt.current_branch_only
518 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700519
Gavin Makea2e3302023-03-11 06:46:20 +0000520 def _UpdateProjectsRevisionId(
521 self, opt, args, superproject_logging_data, manifest
522 ):
523 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800524
Gavin Makea2e3302023-03-11 06:46:20 +0000525 This function updates each project's revisionId with the commit hash
526 from the superproject. It writes the updated manifest into a file and
527 reloads the manifest from it. When appropriate, sub manifests are also
528 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800529
Gavin Makea2e3302023-03-11 06:46:20 +0000530 Args:
531 opt: Program options returned from optparse. See _Options().
532 args: Arguments to pass to GetProjects. See the GetProjects
533 docstring for details.
534 superproject_logging_data: A dictionary of superproject data to log.
535 manifest: The manifest to use.
536 """
537 have_superproject = manifest.superproject or any(
538 m.superproject for m in manifest.all_children
539 )
540 if not have_superproject:
541 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000542
Gavin Makea2e3302023-03-11 06:46:20 +0000543 if opt.local_only and manifest.superproject:
544 manifest_path = manifest.superproject.manifest_path
545 if manifest_path:
546 self._ReloadManifest(manifest_path, manifest)
547 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700548
Gavin Makea2e3302023-03-11 06:46:20 +0000549 all_projects = self.GetProjects(
550 args,
551 missing_ok=True,
552 submodules_ok=opt.fetch_submodules,
553 manifest=manifest,
554 all_manifests=not opt.this_manifest_only,
555 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000556
Gavin Makea2e3302023-03-11 06:46:20 +0000557 per_manifest = collections.defaultdict(list)
558 if opt.this_manifest_only:
559 per_manifest[manifest.path_prefix] = all_projects
560 else:
561 for p in all_projects:
562 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000563
Gavin Makea2e3302023-03-11 06:46:20 +0000564 superproject_logging_data = {}
565 need_unload = False
566 for m in self.ManifestList(opt):
567 if m.path_prefix not in per_manifest:
568 continue
569 use_super = git_superproject.UseSuperproject(
570 opt.use_superproject, m
571 )
572 if superproject_logging_data:
573 superproject_logging_data["multimanifest"] = True
574 superproject_logging_data.update(
575 superproject=use_super,
576 haslocalmanifests=bool(m.HasLocalManifests),
577 hassuperprojecttag=bool(m.superproject),
578 )
579 if use_super and (m.IsMirror or m.IsArchive):
580 # Don't use superproject, because we have no working tree.
581 use_super = False
582 superproject_logging_data["superproject"] = False
583 superproject_logging_data["noworktree"] = True
584 if opt.use_superproject is not False:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000585 logger.warning(
586 "%s: not using superproject because there is no "
587 "working tree.",
588 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000589 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000590
Gavin Makea2e3302023-03-11 06:46:20 +0000591 if not use_super:
592 continue
593 m.superproject.SetQuiet(opt.quiet)
594 print_messages = git_superproject.PrintMessages(
595 opt.use_superproject, m
596 )
597 m.superproject.SetPrintMessages(print_messages)
598 update_result = m.superproject.UpdateProjectsRevisionId(
599 per_manifest[m.path_prefix], git_event_log=self.git_event_log
600 )
601 manifest_path = update_result.manifest_path
602 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
603 if manifest_path:
604 m.SetManifestOverride(manifest_path)
605 need_unload = True
606 else:
607 if print_messages:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000608 logger.warning(
609 "%s: warning: Update of revisionId from superproject "
610 "has failed, repo sync will not use superproject to "
611 "fetch the source. Please resync with the "
612 "--no-use-superproject option to avoid this repo "
613 "warning.",
614 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000615 )
616 if update_result.fatal and opt.use_superproject is not None:
Jason Chang32b59562023-07-14 16:45:35 -0700617 raise SuperprojectError()
Gavin Makea2e3302023-03-11 06:46:20 +0000618 if need_unload:
619 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800620
Gavin Makea2e3302023-03-11 06:46:20 +0000621 def _FetchProjectList(self, opt, projects):
622 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500623
Gavin Makea2e3302023-03-11 06:46:20 +0000624 The projects we're given share the same underlying git object store, so
625 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800626
Gavin Mak551285f2023-05-04 04:48:43 +0000627 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700628
Gavin Makea2e3302023-03-11 06:46:20 +0000629 Args:
630 opt: Program options returned from optparse. See _Options().
631 projects: Projects to fetch.
632 """
633 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700634
Gavin Makea2e3302023-03-11 06:46:20 +0000635 def _FetchOne(self, opt, project):
636 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700637
Gavin Makea2e3302023-03-11 06:46:20 +0000638 Args:
639 opt: Program options returned from optparse. See _Options().
640 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700641
Gavin Makea2e3302023-03-11 06:46:20 +0000642 Returns:
643 Whether the fetch was successful.
644 """
645 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000646 k = f"{project.name} @ {project.relpath}"
647 self._sync_dict[k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000648 success = False
649 remote_fetched = False
Jason Chang32b59562023-07-14 16:45:35 -0700650 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +0000651 buf = io.StringIO()
652 try:
653 sync_result = project.Sync_NetworkHalf(
654 quiet=opt.quiet,
655 verbose=opt.verbose,
656 output_redir=buf,
657 current_branch_only=self._GetCurrentBranchOnly(
658 opt, project.manifest
659 ),
660 force_sync=opt.force_sync,
661 clone_bundle=opt.clone_bundle,
662 tags=opt.tags,
663 archive=project.manifest.IsArchive,
664 optimized_fetch=opt.optimized_fetch,
665 retry_fetches=opt.retry_fetches,
666 prune=opt.prune,
667 ssh_proxy=self.ssh_proxy,
668 clone_filter=project.manifest.CloneFilter,
669 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700670 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000671 )
672 success = sync_result.success
673 remote_fetched = sync_result.remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700674 if sync_result.error:
675 errors.append(sync_result.error)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700676
Gavin Makea2e3302023-03-11 06:46:20 +0000677 output = buf.getvalue()
678 if (opt.verbose or not success) and output:
679 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700680
Gavin Makea2e3302023-03-11 06:46:20 +0000681 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000682 logger.error(
683 "error: Cannot fetch %s from %s",
684 project.name,
685 project.remote.url,
Gavin Makea2e3302023-03-11 06:46:20 +0000686 )
687 except KeyboardInterrupt:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000688 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000689 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000690 logger.error("error.GitError: Cannot fetch %s", e)
Jason Chang32b59562023-07-14 16:45:35 -0700691 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000692 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000693 logger.error(
694 "error: Cannot fetch %s (%s: %s)",
695 project.name,
696 type(e).__name__,
697 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000698 )
Gavin Mak551285f2023-05-04 04:48:43 +0000699 del self._sync_dict[k]
Jason Chang32b59562023-07-14 16:45:35 -0700700 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000701 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500702
Gavin Makea2e3302023-03-11 06:46:20 +0000703 finish = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000704 del self._sync_dict[k]
Jason Chang32b59562023-07-14 16:45:35 -0700705 return _FetchOneResult(
706 success, errors, project, start, finish, remote_fetched
707 )
David James8d201162013-10-11 17:03:19 -0700708
Gavin Makea2e3302023-03-11 06:46:20 +0000709 @classmethod
710 def _FetchInitChild(cls, ssh_proxy):
711 cls.ssh_proxy = ssh_proxy
Mike Frysinger339f2df2021-05-06 00:44:42 -0400712
Gavin Mak04cba4a2023-05-24 21:28:28 +0000713 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000714 earliest_time = float("inf")
715 earliest_proj = None
Gavin Mak945c0062023-05-30 20:04:07 +0000716 items = self._sync_dict.items()
717 for project, t in items:
Gavin Mak551285f2023-05-04 04:48:43 +0000718 if t < earliest_time:
719 earliest_time = t
720 earliest_proj = project
721
Josip Sokcevic71122f92023-05-26 02:44:37 +0000722 if not earliest_proj:
Gavin Mak945c0062023-05-30 20:04:07 +0000723 # This function is called when sync is still running but in some
724 # cases (by chance), _sync_dict can contain no entries. Return some
725 # text to indicate that sync is still working.
726 return "..working.."
Josip Sokcevic71122f92023-05-26 02:44:37 +0000727
Gavin Mak551285f2023-05-04 04:48:43 +0000728 elapsed = time.time() - earliest_time
Gavin Mak945c0062023-05-30 20:04:07 +0000729 jobs = jobs_str(len(items))
Gavin Mak04cba4a2023-05-24 21:28:28 +0000730 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000731
Gavin Makea2e3302023-03-11 06:46:20 +0000732 def _Fetch(self, projects, opt, err_event, ssh_proxy):
733 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500734
Gavin Makea2e3302023-03-11 06:46:20 +0000735 jobs = opt.jobs_network
736 fetched = set()
737 remote_fetched = set()
Jason Chang32b59562023-07-14 16:45:35 -0700738 errors = []
Gavin Makedcaa942023-04-27 05:58:57 +0000739 pm = Progress(
740 "Fetching",
741 len(projects),
742 delay=False,
743 quiet=opt.quiet,
744 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000745 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000746 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800747
Gavin Mak551285f2023-05-04 04:48:43 +0000748 self._sync_dict = multiprocessing.Manager().dict()
749 sync_event = _threading.Event()
750
751 def _MonitorSyncLoop():
752 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000753 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000754 if sync_event.wait(timeout=1):
755 return
756
757 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
758 sync_progress_thread.daemon = True
759 sync_progress_thread.start()
760
Gavin Makea2e3302023-03-11 06:46:20 +0000761 objdir_project_map = dict()
762 for project in projects:
763 objdir_project_map.setdefault(project.objdir, []).append(project)
764 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700765
Gavin Makea2e3302023-03-11 06:46:20 +0000766 def _ProcessResults(results_sets):
767 ret = True
768 for results in results_sets:
769 for result in results:
770 success = result.success
771 project = result.project
772 start = result.start
773 finish = result.finish
774 self._fetch_times.Set(project, finish - start)
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000775 self._local_sync_state.SetFetchTime(project)
Gavin Makea2e3302023-03-11 06:46:20 +0000776 self.event_log.AddSync(
777 project,
778 event_log.TASK_SYNC_NETWORK,
779 start,
780 finish,
781 success,
782 )
Jason Chang32b59562023-07-14 16:45:35 -0700783 if result.errors:
784 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000785 if result.remote_fetched:
786 remote_fetched.add(project)
787 # Check for any errors before running any more tasks.
788 # ...we'll let existing jobs finish, though.
789 if not success:
790 ret = False
791 else:
792 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000793 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000794 if not ret and opt.fail_fast:
795 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500796 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700797
Gavin Makea2e3302023-03-11 06:46:20 +0000798 # We pass the ssh proxy settings via the class. This allows
799 # multiprocessing to pickle it up when spawning children. We can't pass
800 # it as an argument to _FetchProjectList below as multiprocessing is
801 # unable to pickle those.
802 Sync.ssh_proxy = None
Mike Frysingerebf04a42021-02-23 20:48:04 -0500803
Gavin Makea2e3302023-03-11 06:46:20 +0000804 # NB: Multiprocessing is heavy, so don't spin it up for one job.
805 if len(projects_list) == 1 or jobs == 1:
806 self._FetchInitChild(ssh_proxy)
807 if not _ProcessResults(
808 self._FetchProjectList(opt, x) for x in projects_list
809 ):
810 ret = False
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000811 else:
Gavin Makea2e3302023-03-11 06:46:20 +0000812 # Favor throughput over responsiveness when quiet. It seems that
813 # imap() will yield results in batches relative to chunksize, so
814 # even as the children finish a sync, we won't see the result until
815 # one child finishes ~chunksize jobs. When using a large --jobs
816 # with large chunksize, this can be jarring as there will be a large
817 # initial delay where repo looks like it isn't doing anything and
818 # sits at 0%, but then suddenly completes a lot of jobs all at once.
819 # Since this code is more network bound, we can accept a bit more
820 # CPU overhead with a smaller chunksize so that the user sees more
821 # immediate & continuous feedback.
822 if opt.quiet:
823 chunksize = WORKER_BATCH_SIZE
824 else:
825 pm.update(inc=0, msg="warming up")
826 chunksize = 4
827 with multiprocessing.Pool(
828 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)
829 ) as pool:
830 results = pool.imap_unordered(
831 functools.partial(self._FetchProjectList, opt),
832 projects_list,
833 chunksize=chunksize,
834 )
835 if not _ProcessResults(results):
836 ret = False
837 pool.close()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000838
Gavin Makea2e3302023-03-11 06:46:20 +0000839 # Cleanup the reference now that we're done with it, and we're going to
840 # release any resources it points to. If we don't, later
841 # multiprocessing usage (e.g. checkouts) will try to pickle and then
842 # crash.
843 del Sync.ssh_proxy
LaMont Jones7efab532022-09-01 15:41:12 +0000844
Gavin Mak551285f2023-05-04 04:48:43 +0000845 sync_event.set()
Gavin Makea2e3302023-03-11 06:46:20 +0000846 pm.end()
847 self._fetch_times.Save()
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000848 self._local_sync_state.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000849
Gavin Makea2e3302023-03-11 06:46:20 +0000850 if not self.outer_client.manifest.IsArchive:
851 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400852
Jason Chang32b59562023-07-14 16:45:35 -0700853 return _FetchResult(ret, fetched, errors)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000854
Gavin Makea2e3302023-03-11 06:46:20 +0000855 def _FetchMain(
856 self, opt, args, all_projects, err_event, ssh_proxy, manifest
857 ):
858 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400859
Gavin Makea2e3302023-03-11 06:46:20 +0000860 Args:
861 opt: Program options returned from optparse. See _Options().
862 args: Command line args used to filter out projects.
863 all_projects: List of all projects that should be fetched.
864 err_event: Whether an error was hit while processing.
865 ssh_proxy: SSH manager for clients & masters.
866 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700867
Gavin Makea2e3302023-03-11 06:46:20 +0000868 Returns:
869 List of all projects that should be checked out.
870 """
871 rp = manifest.repoProject
Jason Chang32b59562023-07-14 16:45:35 -0700872 errors = []
LaMont Jones891e8f72022-09-08 20:17:58 +0000873
Gavin Makea2e3302023-03-11 06:46:20 +0000874 to_fetch = []
875 now = time.time()
876 if _ONE_DAY_S <= (now - rp.LastFetch):
877 to_fetch.append(rp)
878 to_fetch.extend(all_projects)
879 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700880
Gavin Makea2e3302023-03-11 06:46:20 +0000881 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
882 success = result.success
883 fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700884 if result.errors:
885 errors.extend(result.errors)
886
Gavin Makea2e3302023-03-11 06:46:20 +0000887 if not success:
888 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700889
Gavin Makea2e3302023-03-11 06:46:20 +0000890 _PostRepoFetch(rp, opt.repo_verify)
891 if opt.network_only:
892 # Bail out now; the rest touches the working tree.
893 if err_event.is_set():
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000894 e = SyncError(
Jason Chang32b59562023-07-14 16:45:35 -0700895 "error: Exited sync due to fetch errors.",
896 aggregate_errors=errors,
897 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000898
899 logger.error(e)
900 raise e
Jason Chang32b59562023-07-14 16:45:35 -0700901 return _FetchMainResult([], errors)
Dave Borowitz18857212012-10-23 17:02:59 -0700902
Gavin Makea2e3302023-03-11 06:46:20 +0000903 # Iteratively fetch missing and/or nested unregistered submodules.
904 previously_missing_set = set()
905 while True:
906 self._ReloadManifest(None, manifest)
907 all_projects = self.GetProjects(
908 args,
909 missing_ok=True,
910 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000911 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000912 all_manifests=not opt.this_manifest_only,
913 )
914 missing = []
915 for project in all_projects:
916 if project.gitdir not in fetched:
917 missing.append(project)
918 if not missing:
919 break
920 # Stop us from non-stopped fetching actually-missing repos: If set
921 # of missing repos has not been changed from last fetch, we break.
922 missing_set = set(p.name for p in missing)
923 if previously_missing_set == missing_set:
924 break
925 previously_missing_set = missing_set
926 result = self._Fetch(missing, opt, err_event, ssh_proxy)
927 success = result.success
928 new_fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700929 if result.errors:
930 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000931 if not success:
932 err_event.set()
933 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700934
Jason Chang32b59562023-07-14 16:45:35 -0700935 return _FetchMainResult(all_projects, errors)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700936
Gavin Makea2e3302023-03-11 06:46:20 +0000937 def _CheckoutOne(self, detach_head, force_sync, project):
938 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +0800939
Gavin Makea2e3302023-03-11 06:46:20 +0000940 Args:
941 detach_head: Whether to leave a detached HEAD.
942 force_sync: Force checking out of the repo.
943 project: Project object for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +0800944
Gavin Makea2e3302023-03-11 06:46:20 +0000945 Returns:
946 Whether the fetch was successful.
947 """
948 start = time.time()
949 syncbuf = SyncBuffer(
950 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000951 )
Gavin Makea2e3302023-03-11 06:46:20 +0000952 success = False
Jason Chang32b59562023-07-14 16:45:35 -0700953 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +0900954 try:
Jason Chang32b59562023-07-14 16:45:35 -0700955 project.Sync_LocalHalf(
956 syncbuf, force_sync=force_sync, errors=errors
957 )
Gavin Makea2e3302023-03-11 06:46:20 +0000958 success = syncbuf.Finish()
959 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000960 logger.error(
961 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +0000962 )
Jason Chang32b59562023-07-14 16:45:35 -0700963 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000964 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000965 logger.error(
966 "error: Cannot checkout %s: %s: %s",
967 project.name,
968 type(e).__name__,
969 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000970 )
971 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700972
Gavin Makea2e3302023-03-11 06:46:20 +0000973 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000974 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000975 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700976 return _CheckoutOneResult(success, errors, project, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -0400977
Jason Chang32b59562023-07-14 16:45:35 -0700978 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +0000979 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700980
Gavin Makea2e3302023-03-11 06:46:20 +0000981 Args:
982 all_projects: List of all projects that should be checked out.
983 opt: Program options returned from optparse. See _Options().
984 err_results: A list of strings, paths to git repos where checkout
985 failed.
986 """
987 # Only checkout projects with worktrees.
988 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700989
Gavin Makea2e3302023-03-11 06:46:20 +0000990 def _ProcessResults(pool, pm, results):
991 ret = True
992 for result in results:
993 success = result.success
994 project = result.project
995 start = result.start
996 finish = result.finish
997 self.event_log.AddSync(
998 project, event_log.TASK_SYNC_LOCAL, start, finish, success
999 )
Jason Chang32b59562023-07-14 16:45:35 -07001000
1001 if result.errors:
1002 checkout_errors.extend(result.errors)
1003
Gavin Makea2e3302023-03-11 06:46:20 +00001004 # Check for any errors before running any more tasks.
1005 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001006 if success:
1007 self._local_sync_state.SetCheckoutTime(project)
1008 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001009 ret = False
1010 err_results.append(
1011 project.RelPath(local=opt.this_manifest_only)
1012 )
1013 if opt.fail_fast:
1014 if pool:
1015 pool.close()
1016 return ret
1017 pm.update(msg=project.name)
1018 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001019
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001020 proc_res = self.ExecuteInParallel(
1021 opt.jobs_checkout,
1022 functools.partial(
1023 self._CheckoutOne, opt.detach_head, opt.force_sync
1024 ),
1025 all_projects,
1026 callback=_ProcessResults,
1027 output=Progress("Checking out", len(all_projects), quiet=opt.quiet),
Gavin Makea2e3302023-03-11 06:46:20 +00001028 )
Simran Basib9a1b732015-08-20 12:19:28 -07001029
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001030 self._local_sync_state.Save()
1031 return proc_res and not err_results
1032
Gavin Makea2e3302023-03-11 06:46:20 +00001033 @staticmethod
1034 def _GetPreciousObjectsState(project: Project, opt):
1035 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001036
Gavin Makea2e3302023-03-11 06:46:20 +00001037 Args:
1038 project (Project): the project to examine, and possibly correct.
1039 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001040
Gavin Makea2e3302023-03-11 06:46:20 +00001041 Returns:
1042 Expected state of extensions.preciousObjects:
1043 False: Should be disabled. (not present)
1044 True: Should be enabled.
1045 """
1046 if project.use_git_worktrees:
1047 return False
1048 projects = project.manifest.GetProjectsWithName(
1049 project.name, all_manifests=True
1050 )
1051 if len(projects) == 1:
1052 return False
1053 if len(projects) > 1:
1054 # Objects are potentially shared with another project.
1055 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1056 # - When False, shared projects share (via symlink)
1057 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1058 # objects directory. All objects are precious, since there is no
1059 # project with a complete set of refs.
1060 # - When True, shared projects share (via info/alternates)
1061 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1062 # store, which is written only on the first clone of the project,
1063 # and is not written subsequently. (When Sync_NetworkHalf sees
1064 # that it exists, it makes sure that the alternates file points
1065 # there, and uses a project-local .git/objects directory for all
1066 # syncs going forward.
1067 # We do not support switching between the options. The environment
1068 # variable is present for testing and migration only.
1069 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001070
Gavin Makea2e3302023-03-11 06:46:20 +00001071 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001072
Gavin Makea2e3302023-03-11 06:46:20 +00001073 def _SetPreciousObjectsState(self, project: Project, opt):
1074 """Correct the preciousObjects state for the project.
1075
1076 Args:
1077 project: the project to examine, and possibly correct.
1078 opt: options given to sync.
1079 """
1080 expected = self._GetPreciousObjectsState(project, opt)
1081 actual = (
1082 project.config.GetBoolean("extensions.preciousObjects") or False
1083 )
1084 relpath = project.RelPath(local=opt.this_manifest_only)
1085
1086 if expected != actual:
1087 # If this is unexpected, log it and repair.
1088 Trace(
1089 f"{relpath} expected preciousObjects={expected}, got {actual}"
1090 )
1091 if expected:
1092 if not opt.quiet:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001093 logger.info(
1094 "%s: Shared project %s found, disabling pruning.",
1095 relpath,
1096 project.name,
Gavin Makea2e3302023-03-11 06:46:20 +00001097 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001098
Gavin Makea2e3302023-03-11 06:46:20 +00001099 if git_require((2, 7, 0)):
1100 project.EnableRepositoryExtension("preciousObjects")
1101 else:
1102 # This isn't perfect, but it's the best we can do with old
1103 # git.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001104 logger.warning(
1105 "%s: WARNING: shared projects are unreliable when "
Gavin Makea2e3302023-03-11 06:46:20 +00001106 "using old versions of git; please upgrade to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001107 "git-2.7.0+.",
1108 relpath,
Gavin Makea2e3302023-03-11 06:46:20 +00001109 )
1110 project.config.SetString("gc.pruneExpire", "never")
1111 else:
1112 if not opt.quiet:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001113 logger.info("%s: not shared, disabling pruning.", relpath)
Gavin Makea2e3302023-03-11 06:46:20 +00001114 project.config.SetString("extensions.preciousObjects", None)
1115 project.config.SetString("gc.pruneExpire", None)
1116
1117 def _GCProjects(self, projects, opt, err_event):
1118 """Perform garbage collection.
1119
1120 If We are skipping garbage collection (opt.auto_gc not set), we still
1121 want to potentially mark objects precious, so that `git gc` does not
1122 discard shared objects.
1123 """
1124 if not opt.auto_gc:
1125 # Just repair preciousObjects state, and return.
1126 for project in projects:
1127 self._SetPreciousObjectsState(project, opt)
1128 return
1129
1130 pm = Progress(
1131 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1132 )
1133 pm.update(inc=0, msg="prescan")
1134
1135 tidy_dirs = {}
1136 for project in projects:
1137 self._SetPreciousObjectsState(project, opt)
1138
1139 project.config.SetString("gc.autoDetach", "false")
1140 # Only call git gc once per objdir, but call pack-refs for the
1141 # remainder.
1142 if project.objdir not in tidy_dirs:
1143 tidy_dirs[project.objdir] = (
1144 True, # Run a full gc.
1145 project.bare_git,
1146 )
1147 elif project.gitdir not in tidy_dirs:
1148 tidy_dirs[project.gitdir] = (
1149 False, # Do not run a full gc; just run pack-refs.
1150 project.bare_git,
1151 )
1152
1153 jobs = opt.jobs
1154
1155 if jobs < 2:
1156 for run_gc, bare_git in tidy_dirs.values():
1157 pm.update(msg=bare_git._project.name)
1158
1159 if run_gc:
1160 bare_git.gc("--auto")
1161 else:
1162 bare_git.pack_refs()
1163 pm.end()
1164 return
1165
1166 cpu_count = os.cpu_count()
1167 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1168
1169 threads = set()
1170 sem = _threading.Semaphore(jobs)
1171
1172 def tidy_up(run_gc, bare_git):
1173 pm.start(bare_git._project.name)
1174 try:
1175 try:
1176 if run_gc:
1177 bare_git.gc("--auto", config=config)
1178 else:
1179 bare_git.pack_refs(config=config)
1180 except GitError:
1181 err_event.set()
1182 except Exception:
1183 err_event.set()
1184 raise
1185 finally:
1186 pm.finish(bare_git._project.name)
1187 sem.release()
1188
1189 for run_gc, bare_git in tidy_dirs.values():
1190 if err_event.is_set() and opt.fail_fast:
1191 break
1192 sem.acquire()
1193 t = _threading.Thread(
1194 target=tidy_up,
1195 args=(
1196 run_gc,
1197 bare_git,
1198 ),
1199 )
1200 t.daemon = True
1201 threads.add(t)
1202 t.start()
1203
1204 for t in threads:
1205 t.join()
1206 pm.end()
1207
1208 def _ReloadManifest(self, manifest_name, manifest):
1209 """Reload the manfiest from the file specified by the |manifest_name|.
1210
1211 It unloads the manifest if |manifest_name| is None.
1212
1213 Args:
1214 manifest_name: Manifest file to be reloaded.
1215 manifest: The manifest to use.
1216 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001217 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001218 # Override calls Unload already.
1219 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001220 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001221 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001222
Gavin Makea2e3302023-03-11 06:46:20 +00001223 def UpdateProjectList(self, opt, manifest):
1224 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001225
Gavin Makea2e3302023-03-11 06:46:20 +00001226 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001227
Gavin Makea2e3302023-03-11 06:46:20 +00001228 Args:
1229 opt: Program options returned from optparse. See _Options().
1230 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001231
Gavin Makea2e3302023-03-11 06:46:20 +00001232 Returns:
1233 0: success
1234 1: failure
1235 """
1236 new_project_paths = []
1237 for project in self.GetProjects(
1238 None, missing_ok=True, manifest=manifest, all_manifests=False
1239 ):
1240 if project.relpath:
1241 new_project_paths.append(project.relpath)
1242 file_name = "project.list"
1243 file_path = os.path.join(manifest.subdir, file_name)
1244 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001245
Gavin Makea2e3302023-03-11 06:46:20 +00001246 if os.path.exists(file_path):
1247 with open(file_path, "r") as fd:
1248 old_project_paths = fd.read().split("\n")
1249 # In reversed order, so subfolders are deleted before parent folder.
1250 for path in sorted(old_project_paths, reverse=True):
1251 if not path:
1252 continue
1253 if path not in new_project_paths:
1254 # If the path has already been deleted, we don't need to do
1255 # it.
1256 gitdir = os.path.join(manifest.topdir, path, ".git")
1257 if os.path.exists(gitdir):
1258 project = Project(
1259 manifest=manifest,
1260 name=path,
1261 remote=RemoteSpec("origin"),
1262 gitdir=gitdir,
1263 objdir=gitdir,
1264 use_git_worktrees=os.path.isfile(gitdir),
1265 worktree=os.path.join(manifest.topdir, path),
1266 relpath=path,
1267 revisionExpr="HEAD",
1268 revisionId=None,
1269 groups=None,
1270 )
Jason Chang32b59562023-07-14 16:45:35 -07001271 project.DeleteWorktree(
Gavin Makea2e3302023-03-11 06:46:20 +00001272 quiet=opt.quiet, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001273 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001274
Gavin Makea2e3302023-03-11 06:46:20 +00001275 new_project_paths.sort()
1276 with open(file_path, "w") as fd:
1277 fd.write("\n".join(new_project_paths))
1278 fd.write("\n")
1279 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001280
Gavin Makea2e3302023-03-11 06:46:20 +00001281 def UpdateCopyLinkfileList(self, manifest):
1282 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001283
Gavin Makea2e3302023-03-11 06:46:20 +00001284 Returns:
1285 Whether update was successful.
1286 """
1287 new_paths = {}
1288 new_linkfile_paths = []
1289 new_copyfile_paths = []
1290 for project in self.GetProjects(
1291 None, missing_ok=True, manifest=manifest, all_manifests=False
1292 ):
1293 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1294 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001295
Gavin Makea2e3302023-03-11 06:46:20 +00001296 new_paths = {
1297 "linkfile": new_linkfile_paths,
1298 "copyfile": new_copyfile_paths,
1299 }
jiajia tanga590e642021-04-25 20:02:02 +08001300
Gavin Makea2e3302023-03-11 06:46:20 +00001301 copylinkfile_name = "copy-link-files.json"
1302 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1303 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001304
Gavin Makea2e3302023-03-11 06:46:20 +00001305 if os.path.exists(copylinkfile_path):
1306 with open(copylinkfile_path, "rb") as fp:
1307 try:
1308 old_copylinkfile_paths = json.load(fp)
1309 except Exception:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001310 logger.error(
1311 "error: %s is not a json formatted file.",
1312 copylinkfile_path,
Gavin Makea2e3302023-03-11 06:46:20 +00001313 )
1314 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001315 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001316
Gavin Makea2e3302023-03-11 06:46:20 +00001317 need_remove_files = []
1318 need_remove_files.extend(
1319 set(old_copylinkfile_paths.get("linkfile", []))
1320 - set(new_linkfile_paths)
1321 )
1322 need_remove_files.extend(
1323 set(old_copylinkfile_paths.get("copyfile", []))
1324 - set(new_copyfile_paths)
1325 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001326
Gavin Makea2e3302023-03-11 06:46:20 +00001327 for need_remove_file in need_remove_files:
1328 # Try to remove the updated copyfile or linkfile.
1329 # So, if the file is not exist, nothing need to do.
1330 platform_utils.remove(need_remove_file, missing_ok=True)
Raman Tenneti7954de12021-07-28 14:36:49 -07001331
Gavin Makea2e3302023-03-11 06:46:20 +00001332 # Create copy-link-files.json, save dest path of "copyfile" and
1333 # "linkfile".
1334 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1335 json.dump(new_paths, fp)
1336 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001337
Gavin Makea2e3302023-03-11 06:46:20 +00001338 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1339 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001340 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001341 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001342 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001343 )
Gavin Makea2e3302023-03-11 06:46:20 +00001344
1345 manifest_server = manifest.manifest_server
1346 if not opt.quiet:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001347 logger.info("Using manifest server %s", manifest_server)
Gavin Makea2e3302023-03-11 06:46:20 +00001348
1349 if "@" not in manifest_server:
1350 username = None
1351 password = None
1352 if opt.manifest_server_username and opt.manifest_server_password:
1353 username = opt.manifest_server_username
1354 password = opt.manifest_server_password
1355 else:
1356 try:
1357 info = netrc.netrc()
1358 except IOError:
1359 # .netrc file does not exist or could not be opened.
1360 pass
1361 else:
1362 try:
1363 parse_result = urllib.parse.urlparse(manifest_server)
1364 if parse_result.hostname:
1365 auth = info.authenticators(parse_result.hostname)
1366 if auth:
1367 username, _account, password = auth
1368 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001369 logger.error(
1370 "No credentials found for %s in .netrc",
1371 parse_result.hostname,
Gavin Makea2e3302023-03-11 06:46:20 +00001372 )
1373 except netrc.NetrcParseError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001374 logger.error("Error parsing .netrc file: %s", e)
Gavin Makea2e3302023-03-11 06:46:20 +00001375
1376 if username and password:
1377 manifest_server = manifest_server.replace(
1378 "://", "://%s:%s@" % (username, password), 1
1379 )
1380
1381 transport = PersistentTransport(manifest_server)
1382 if manifest_server.startswith("persistent-"):
1383 manifest_server = manifest_server[len("persistent-") :]
1384
1385 try:
1386 server = xmlrpc.client.Server(manifest_server, transport=transport)
1387 if opt.smart_sync:
1388 branch = self._GetBranch(manifest.manifestProject)
1389
1390 if "SYNC_TARGET" in os.environ:
1391 target = os.environ["SYNC_TARGET"]
1392 [success, manifest_str] = server.GetApprovedManifest(
1393 branch, target
1394 )
1395 elif (
1396 "TARGET_PRODUCT" in os.environ
1397 and "TARGET_BUILD_VARIANT" in os.environ
1398 ):
1399 target = "%s-%s" % (
1400 os.environ["TARGET_PRODUCT"],
1401 os.environ["TARGET_BUILD_VARIANT"],
1402 )
1403 [success, manifest_str] = server.GetApprovedManifest(
1404 branch, target
1405 )
1406 else:
1407 [success, manifest_str] = server.GetApprovedManifest(branch)
1408 else:
1409 assert opt.smart_tag
1410 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1411
1412 if success:
1413 manifest_name = os.path.basename(smart_sync_manifest_path)
1414 try:
1415 with open(smart_sync_manifest_path, "w") as f:
1416 f.write(manifest_str)
1417 except IOError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001418 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001419 "error: cannot write manifest to %s:\n%s"
1420 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001421 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001422 )
Gavin Makea2e3302023-03-11 06:46:20 +00001423 self._ReloadManifest(manifest_name, manifest)
1424 else:
Jason Chang32b59562023-07-14 16:45:35 -07001425 raise SmartSyncError(
1426 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001427 )
Gavin Makea2e3302023-03-11 06:46:20 +00001428 except (socket.error, IOError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001429 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001430 "error: cannot connect to manifest server %s:\n%s"
1431 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001432 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001433 )
Gavin Makea2e3302023-03-11 06:46:20 +00001434 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001435 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001436 "error: cannot connect to manifest server %s:\n%d %s"
1437 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001438 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001439 )
Gavin Makea2e3302023-03-11 06:46:20 +00001440
1441 return manifest_name
1442
1443 def _UpdateAllManifestProjects(self, opt, mp, manifest_name):
1444 """Fetch & update the local manifest project.
1445
1446 After syncing the manifest project, if the manifest has any sub
1447 manifests, those are recursively processed.
1448
1449 Args:
1450 opt: Program options returned from optparse. See _Options().
1451 mp: the manifestProject to query.
1452 manifest_name: Manifest file to be reloaded.
1453 """
1454 if not mp.standalone_manifest_url:
1455 self._UpdateManifestProject(opt, mp, manifest_name)
1456
1457 if mp.manifest.submanifests:
1458 for submanifest in mp.manifest.submanifests.values():
1459 child = submanifest.repo_client.manifest
1460 child.manifestProject.SyncWithPossibleInit(
1461 submanifest,
1462 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1463 verbose=opt.verbose,
1464 tags=opt.tags,
1465 git_event_log=self.git_event_log,
1466 )
1467 self._UpdateAllManifestProjects(
1468 opt, child.manifestProject, None
1469 )
1470
1471 def _UpdateManifestProject(self, opt, mp, manifest_name):
1472 """Fetch & update the local manifest project.
1473
1474 Args:
1475 opt: Program options returned from optparse. See _Options().
1476 mp: the manifestProject to query.
1477 manifest_name: Manifest file to be reloaded.
1478 """
1479 if not opt.local_only:
1480 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001481 result = mp.Sync_NetworkHalf(
Gavin Makea2e3302023-03-11 06:46:20 +00001482 quiet=opt.quiet,
1483 verbose=opt.verbose,
1484 current_branch_only=self._GetCurrentBranchOnly(
1485 opt, mp.manifest
1486 ),
1487 force_sync=opt.force_sync,
1488 tags=opt.tags,
1489 optimized_fetch=opt.optimized_fetch,
1490 retry_fetches=opt.retry_fetches,
1491 submodules=mp.manifest.HasSubmodules,
1492 clone_filter=mp.manifest.CloneFilter,
1493 partial_clone_exclude=mp.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -07001494 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +00001495 )
1496 finish = time.time()
1497 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001498 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001499 )
1500
1501 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001502 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001503 syncbuf = SyncBuffer(mp.config)
1504 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001505 mp.Sync_LocalHalf(
1506 syncbuf, submodules=mp.manifest.HasSubmodules, errors=errors
1507 )
Gavin Makea2e3302023-03-11 06:46:20 +00001508 clean = syncbuf.Finish()
1509 self.event_log.AddSync(
1510 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1511 )
1512 if not clean:
Jason Chang32b59562023-07-14 16:45:35 -07001513 raise UpdateManifestError(
1514 aggregate_errors=errors, project=mp.name
1515 )
Gavin Makea2e3302023-03-11 06:46:20 +00001516 self._ReloadManifest(manifest_name, mp.manifest)
1517
1518 def ValidateOptions(self, opt, args):
1519 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001520 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001521 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001522 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001523 )
1524 if opt.network_only and opt.detach_head:
1525 self.OptionParser.error("cannot combine -n and -d")
1526 if opt.network_only and opt.local_only:
1527 self.OptionParser.error("cannot combine -n and -l")
1528 if opt.manifest_name and opt.smart_sync:
1529 self.OptionParser.error("cannot combine -m and -s")
1530 if opt.manifest_name and opt.smart_tag:
1531 self.OptionParser.error("cannot combine -m and -t")
1532 if opt.manifest_server_username or opt.manifest_server_password:
1533 if not (opt.smart_sync or opt.smart_tag):
1534 self.OptionParser.error(
1535 "-u and -p may only be combined with -s or -t"
1536 )
1537 if None in [
1538 opt.manifest_server_username,
1539 opt.manifest_server_password,
1540 ]:
1541 self.OptionParser.error("both -u and -p must be given")
1542
1543 if opt.prune is None:
1544 opt.prune = True
1545
1546 if opt.auto_gc is None and _AUTO_GC:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001547 logger.error(
1548 "Will run `git gc --auto` because %s is set. %s is deprecated "
1549 "and will be removed in a future release. Use `--auto-gc` "
1550 "instead.",
1551 _REPO_AUTO_GC,
1552 _REPO_AUTO_GC,
Gavin Makea2e3302023-03-11 06:46:20 +00001553 )
1554 opt.auto_gc = True
1555
1556 def _ValidateOptionsWithManifest(self, opt, mp):
1557 """Like ValidateOptions, but after we've updated the manifest.
1558
1559 Needed to handle sync-xxx option defaults in the manifest.
1560
1561 Args:
1562 opt: The options to process.
1563 mp: The manifest project to pull defaults from.
1564 """
1565 if not opt.jobs:
1566 # If the user hasn't made a choice, use the manifest value.
1567 opt.jobs = mp.manifest.default.sync_j
1568 if opt.jobs:
1569 # If --jobs has a non-default value, propagate it as the default for
1570 # --jobs-xxx flags too.
1571 if not opt.jobs_network:
1572 opt.jobs_network = opt.jobs
1573 if not opt.jobs_checkout:
1574 opt.jobs_checkout = opt.jobs
1575 else:
1576 # Neither user nor manifest have made a choice, so setup defaults.
1577 if not opt.jobs_network:
1578 opt.jobs_network = 1
1579 if not opt.jobs_checkout:
1580 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1581 opt.jobs = os.cpu_count()
1582
1583 # Try to stay under user rlimit settings.
1584 #
1585 # Since each worker requires at 3 file descriptors to run `git fetch`,
1586 # use that to scale down the number of jobs. Unfortunately there isn't
1587 # an easy way to determine this reliably as systems change, but it was
1588 # last measured by hand in 2011.
1589 soft_limit, _ = _rlimit_nofile()
1590 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1591 opt.jobs = min(opt.jobs, jobs_soft_limit)
1592 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1593 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1594
1595 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001596 errors = []
1597 try:
1598 self._ExecuteHelper(opt, args, errors)
1599 except RepoExitError:
1600 raise
1601 except (KeyboardInterrupt, Exception) as e:
1602 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1603
1604 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001605 manifest = self.outer_manifest
1606 if not opt.outer_manifest:
1607 manifest = self.manifest
1608
1609 if opt.manifest_name:
1610 manifest.Override(opt.manifest_name)
1611
1612 manifest_name = opt.manifest_name
1613 smart_sync_manifest_path = os.path.join(
1614 manifest.manifestProject.worktree, "smart_sync_override.xml"
1615 )
1616
1617 if opt.clone_bundle is None:
1618 opt.clone_bundle = manifest.CloneBundle
1619
1620 if opt.smart_sync or opt.smart_tag:
1621 manifest_name = self._SmartSyncSetup(
1622 opt, smart_sync_manifest_path, manifest
1623 )
1624 else:
1625 if os.path.isfile(smart_sync_manifest_path):
1626 try:
1627 platform_utils.remove(smart_sync_manifest_path)
1628 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001629 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001630 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001631 "manifest: %s",
1632 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001633 )
1634
1635 err_event = multiprocessing.Event()
1636
1637 rp = manifest.repoProject
1638 rp.PreSync()
1639 cb = rp.CurrentBranch
1640 if cb:
1641 base = rp.GetBranch(cb).merge
1642 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001643 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001644 "warning: repo is not tracking a remote branch, so it will "
1645 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001646 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001647 )
1648
1649 for m in self.ManifestList(opt):
1650 if not m.manifestProject.standalone_manifest_url:
1651 m.manifestProject.PreSync()
1652
1653 if opt.repo_upgraded:
1654 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1655
1656 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001657
1658 if _REPO_ALLOW_SHALLOW is not None:
1659 if _REPO_ALLOW_SHALLOW == "1":
1660 mp.ConfigureCloneFilterForDepth(None)
1661 elif (
1662 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1663 ):
1664 mp.ConfigureCloneFilterForDepth("blob:none")
1665
Gavin Makea2e3302023-03-11 06:46:20 +00001666 if opt.mp_update:
1667 self._UpdateAllManifestProjects(opt, mp, manifest_name)
1668 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001669 logger.info("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001670
1671 # Now that the manifests are up-to-date, setup options whose defaults
1672 # might be in the manifest.
1673 self._ValidateOptionsWithManifest(opt, mp)
1674
1675 superproject_logging_data = {}
1676 self._UpdateProjectsRevisionId(
1677 opt, args, superproject_logging_data, manifest
1678 )
1679
Gavin Makea2e3302023-03-11 06:46:20 +00001680 all_projects = self.GetProjects(
1681 args,
1682 missing_ok=True,
1683 submodules_ok=opt.fetch_submodules,
1684 manifest=manifest,
1685 all_manifests=not opt.this_manifest_only,
1686 )
1687
1688 err_network_sync = False
1689 err_update_projects = False
1690 err_update_linkfiles = False
1691
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001692 # Log the repo projects by existing and new.
1693 existing = [x for x in all_projects if x.Exists]
1694 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1695 mp.config.SetString(
1696 "repo.newprojectcount", str(len(all_projects) - len(existing))
1697 )
1698
Gavin Makea2e3302023-03-11 06:46:20 +00001699 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001700 self._local_sync_state = LocalSyncState(manifest)
Gavin Makea2e3302023-03-11 06:46:20 +00001701 if not opt.local_only:
1702 with multiprocessing.Manager() as manager:
1703 with ssh.ProxyManager(manager) as ssh_proxy:
1704 # Initialize the socket dir once in the parent.
1705 ssh_proxy.sock()
1706 result = self._FetchMain(
1707 opt, args, all_projects, err_event, ssh_proxy, manifest
1708 )
Jason Chang32b59562023-07-14 16:45:35 -07001709 if result.errors:
1710 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001711 all_projects = result.all_projects
1712
1713 if opt.network_only:
1714 return
1715
1716 # If we saw an error, exit with code 1 so that other scripts can
1717 # check.
1718 if err_event.is_set():
1719 err_network_sync = True
1720 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001721 logger.error(
1722 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001723 "Local checkouts *not* updated. Resolve network issues "
1724 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001725 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001726 )
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:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001743 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001744 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001745
Jason Chang32b59562023-07-14 16:45:35 -07001746 err_update_linkfiles = False
1747 try:
1748 self.UpdateCopyLinkfileList(m)
1749 except Exception as e:
1750 err_update_linkfiles = True
1751 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001752 err_event.set()
1753 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001754 logger.error(
1755 "error: Local update copyfile or linkfile failed."
Gavin Makea2e3302023-03-11 06:46:20 +00001756 )
Jason Chang32b59562023-07-14 16:45:35 -07001757 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001758
1759 err_results = []
1760 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001761 err_checkout = not self._Checkout(
1762 all_projects, opt, err_results, errors
1763 )
Gavin Makea2e3302023-03-11 06:46:20 +00001764 if err_checkout:
1765 err_event.set()
1766
1767 printed_notices = set()
1768 # If there's a notice that's supposed to print at the end of the sync,
1769 # print it now... But avoid printing duplicate messages, and preserve
1770 # order.
1771 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1772 if m.notice and m.notice not in printed_notices:
1773 print(m.notice)
1774 printed_notices.add(m.notice)
1775
1776 # If we saw an error, exit with code 1 so that other scripts can check.
1777 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001778
1779 def print_and_log(err_msg):
1780 self.git_event_log.ErrorEvent(err_msg)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001781 logger.error("%s", err_msg)
Josip Sokcevic131fc962023-05-12 17:00:46 -07001782
1783 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001784 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001785 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001786 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001787 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001788 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001789 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001790 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001791 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001792 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001793 # Don't log repositories, as it may contain sensitive info.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001794 logger.error("Failing repos:\n%s", "\n".join(err_results))
Josip Sokcevic131fc962023-05-12 17:00:46 -07001795 # Not useful to log.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001796 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001797 'Try re-running with "-j1 --fail-fast" to exit at the first '
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001798 "error."
Gavin Makea2e3302023-03-11 06:46:20 +00001799 )
Jason Chang32b59562023-07-14 16:45:35 -07001800 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001801
1802 # Log the previous sync analysis state from the config.
1803 self.git_event_log.LogDataConfigEvents(
1804 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1805 )
1806
1807 # Update and log with the new sync analysis state.
1808 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1809 self.git_event_log.LogDataConfigEvents(
1810 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1811 )
1812
Gavin Makf0aeb222023-08-08 04:43:36 +00001813 self._local_sync_state.PruneRemovedProjects()
1814 if self._local_sync_state.IsPartiallySynced():
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001815 logger.warning(
Gavin Makf0aeb222023-08-08 04:43:36 +00001816 "warning: Partial syncs are not supported. For the best "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001817 "experience, sync the entire tree."
Gavin Makf0aeb222023-08-08 04:43:36 +00001818 )
1819
Gavin Makea2e3302023-03-11 06:46:20 +00001820 if not opt.quiet:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001821 logger.info("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001822
David Pursehouse819827a2020-02-12 15:20:19 +09001823
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001824def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001825 # Link the docs for the internal .repo/ layout for people.
1826 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1827 if not platform_utils.islink(link):
1828 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1829 try:
1830 platform_utils.symlink(target, link)
1831 except Exception:
1832 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001833
Gavin Makea2e3302023-03-11 06:46:20 +00001834 wrapper = Wrapper()
1835 if wrapper.NeedSetupGnuPG():
1836 wrapper.SetupGnuPG(quiet)
1837 for project in manifest.projects:
1838 if project.Exists:
1839 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001840
David Pursehouse819827a2020-02-12 15:20:19 +09001841
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001842def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001843 if rp.HasChanges:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001844 logger.info("info: A new version of repo is available")
Gavin Makea2e3302023-03-11 06:46:20 +00001845 wrapper = Wrapper()
1846 try:
1847 rev = rp.bare_git.describe(rp.GetRevisionId())
1848 except GitError:
1849 rev = None
1850 _, new_rev = wrapper.check_repo_rev(
1851 rp.gitdir, rev, repo_verify=repo_verify
1852 )
1853 # See if we're held back due to missing signed tag.
1854 current_revid = rp.bare_git.rev_parse("HEAD")
1855 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1856 if current_revid != new_revid:
1857 # We want to switch to the new rev, but also not trash any
1858 # uncommitted changes. This helps with local testing/hacking.
1859 # If a local change has been made, we will throw that away.
1860 # We also have to make sure this will switch to an older commit if
1861 # that's the latest tag in order to support release rollback.
1862 try:
1863 rp.work_git.reset("--keep", new_rev)
1864 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001865 raise RepoUnhandledExceptionError(e)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001866 logger.info("info: Restarting repo with latest version")
Gavin Makea2e3302023-03-11 06:46:20 +00001867 raise RepoChangedException(["--repo-upgraded"])
1868 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001869 logger.warning("warning: Skipped upgrade to unverified version")
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001870 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001871 if verbose:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001872 logger.info(
1873 "repo version %s is current", rp.work_git.describe(HEAD)
Gavin Makea2e3302023-03-11 06:46:20 +00001874 )
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001875
David Pursehouse819827a2020-02-12 15:20:19 +09001876
Dave Borowitz67700e92012-10-23 15:00:54 -07001877class _FetchTimes(object):
Gavin Makea2e3302023-03-11 06:46:20 +00001878 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07001879
Gavin Makea2e3302023-03-11 06:46:20 +00001880 def __init__(self, manifest):
1881 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00001882 self._saved = None
1883 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001884
Gavin Makea2e3302023-03-11 06:46:20 +00001885 def Get(self, project):
1886 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00001887 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07001888
Gavin Makea2e3302023-03-11 06:46:20 +00001889 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00001890 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00001891
1892 # For shared projects, save the longest time.
1893 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07001894
Gavin Makea2e3302023-03-11 06:46:20 +00001895 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001896 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001897 try:
1898 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001899 self._saved = json.load(f)
Gavin Makea2e3302023-03-11 06:46:20 +00001900 except (IOError, ValueError):
1901 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00001902 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001903
Gavin Makea2e3302023-03-11 06:46:20 +00001904 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001905 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001906 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001907
Gavin Mak041f9772023-05-10 20:41:12 +00001908 for name, t in self._seen.items():
1909 # Keep a moving average across the previous/current sync runs.
1910 old = self._saved.get(name, t)
1911 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07001912
Gavin Makea2e3302023-03-11 06:46:20 +00001913 try:
1914 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001915 json.dump(self._seen, f, indent=2)
Gavin Makea2e3302023-03-11 06:46:20 +00001916 except (IOError, TypeError):
1917 platform_utils.remove(self._path, missing_ok=True)
1918
Dan Willemsen0745bb22015-08-17 13:41:45 -07001919
Gavin Mak16109a62023-08-22 01:24:46 +00001920class LocalSyncState(object):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001921 _LAST_FETCH = "last_fetch"
1922 _LAST_CHECKOUT = "last_checkout"
1923
1924 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00001925 self._manifest = manifest
1926 self._path = os.path.join(
1927 self._manifest.repodir, ".repo_localsyncstate.json"
1928 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001929 self._time = time.time()
1930 self._state = None
1931 self._Load()
1932
1933 def SetFetchTime(self, project):
1934 self._Set(project, self._LAST_FETCH)
1935
1936 def SetCheckoutTime(self, project):
1937 self._Set(project, self._LAST_CHECKOUT)
1938
1939 def GetFetchTime(self, project):
1940 return self._Get(project, self._LAST_FETCH)
1941
1942 def GetCheckoutTime(self, project):
1943 return self._Get(project, self._LAST_CHECKOUT)
1944
1945 def _Get(self, project, key):
1946 self._Load()
1947 p = project.relpath
1948 if p not in self._state:
1949 return
1950 return self._state[p].get(key)
1951
1952 def _Set(self, project, key):
1953 p = project.relpath
1954 if p not in self._state:
1955 self._state[p] = {}
1956 self._state[p][key] = self._time
1957
1958 def _Load(self):
1959 if self._state is None:
1960 try:
1961 with open(self._path) as f:
1962 self._state = json.load(f)
1963 except (IOError, ValueError):
1964 platform_utils.remove(self._path, missing_ok=True)
1965 self._state = {}
1966
1967 def Save(self):
1968 if not self._state:
1969 return
1970 try:
1971 with open(self._path, "w") as f:
1972 json.dump(self._state, f, indent=2)
1973 except (IOError, TypeError):
1974 platform_utils.remove(self._path, missing_ok=True)
1975
Gavin Makf0aeb222023-08-08 04:43:36 +00001976 def PruneRemovedProjects(self):
1977 """Remove entries don't exist on disk and save."""
1978 if not self._state:
1979 return
1980 delete = set()
1981 for path in self._state:
1982 gitdir = os.path.join(self._manifest.topdir, path, ".git")
1983 if not os.path.exists(gitdir):
1984 delete.add(path)
1985 if not delete:
1986 return
1987 for path in delete:
1988 del self._state[path]
1989 self.Save()
1990
1991 def IsPartiallySynced(self):
1992 """Return whether a partial sync state is detected."""
1993 self._Load()
1994 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00001995 for path, data in self._state.items():
1996 if path == self._manifest.repoProject.relpath:
1997 # The repo project isn't included in most syncs so we should
1998 # ignore it here.
1999 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002000 checkout_t = data.get(self._LAST_CHECKOUT)
2001 if not checkout_t:
2002 return True
2003 prev_checkout_t = prev_checkout_t or checkout_t
2004 if prev_checkout_t != checkout_t:
2005 return True
2006 return False
2007
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002008
Dan Willemsen0745bb22015-08-17 13:41:45 -07002009# This is a replacement for xmlrpc.client.Transport using urllib2
2010# and supporting persistent-http[s]. It cannot change hosts from
2011# request to request like the normal transport, the real url
2012# is passed during initialization.
2013class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002014 def __init__(self, orig_host):
2015 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002016
Gavin Makea2e3302023-03-11 06:46:20 +00002017 def request(self, host, handler, request_body, verbose=False):
2018 with GetUrlCookieFile(self.orig_host, not verbose) as (
2019 cookiefile,
2020 proxy,
2021 ):
2022 # Python doesn't understand cookies with the #HttpOnly_ prefix
2023 # Since we're only using them for HTTP, copy the file temporarily,
2024 # stripping those prefixes away.
2025 if cookiefile:
2026 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2027 tmpcookiefile.write("# HTTP Cookie File")
2028 try:
2029 with open(cookiefile) as f:
2030 for line in f:
2031 if line.startswith("#HttpOnly_"):
2032 line = line[len("#HttpOnly_") :]
2033 tmpcookiefile.write(line)
2034 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002035
Gavin Makea2e3302023-03-11 06:46:20 +00002036 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2037 try:
2038 cookiejar.load()
2039 except cookielib.LoadError:
2040 cookiejar = cookielib.CookieJar()
2041 finally:
2042 tmpcookiefile.close()
2043 else:
2044 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002045
Gavin Makea2e3302023-03-11 06:46:20 +00002046 proxyhandler = urllib.request.ProxyHandler
2047 if proxy:
2048 proxyhandler = urllib.request.ProxyHandler(
2049 {"http": proxy, "https": proxy}
2050 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002051
Gavin Makea2e3302023-03-11 06:46:20 +00002052 opener = urllib.request.build_opener(
2053 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2054 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002055
Gavin Makea2e3302023-03-11 06:46:20 +00002056 url = urllib.parse.urljoin(self.orig_host, handler)
2057 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002058
Gavin Makea2e3302023-03-11 06:46:20 +00002059 scheme = parse_results.scheme
2060 if scheme == "persistent-http":
2061 scheme = "http"
2062 if scheme == "persistent-https":
2063 # If we're proxying through persistent-https, use http. The
2064 # proxy itself will do the https.
2065 if proxy:
2066 scheme = "http"
2067 else:
2068 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002069
Gavin Makea2e3302023-03-11 06:46:20 +00002070 # Parse out any authentication information using the base class.
2071 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002072
Gavin Makea2e3302023-03-11 06:46:20 +00002073 url = urllib.parse.urlunparse(
2074 (
2075 scheme,
2076 host,
2077 parse_results.path,
2078 parse_results.params,
2079 parse_results.query,
2080 parse_results.fragment,
2081 )
2082 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002083
Gavin Makea2e3302023-03-11 06:46:20 +00002084 request = urllib.request.Request(url, request_body)
2085 if extra_headers is not None:
2086 for name, header in extra_headers:
2087 request.add_header(name, header)
2088 request.add_header("Content-Type", "text/xml")
2089 try:
2090 response = opener.open(request)
2091 except urllib.error.HTTPError as e:
2092 if e.code == 501:
2093 # We may have been redirected through a login process
2094 # but our POST turned into a GET. Retry.
2095 response = opener.open(request)
2096 else:
2097 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002098
Gavin Makea2e3302023-03-11 06:46:20 +00002099 p, u = xmlrpc.client.getparser()
2100 # Response should be fairly small, so read it all at once.
2101 # This way we can show it to the user in case of error (e.g. HTML).
2102 data = response.read()
2103 try:
2104 p.feed(data)
2105 except xml.parsers.expat.ExpatError as e:
2106 raise IOError(
2107 f"Parsing the manifest failed: {e}\n"
2108 f"Please report this to your manifest server admin.\n"
2109 f'Here is the full response:\n{data.decode("utf-8")}'
2110 )
2111 p.close()
2112 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002113
Gavin Makea2e3302023-03-11 06:46:20 +00002114 def close(self):
2115 pass