blob: 8460bcecaed99adc107e94b62973d8cda7c04826 [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
Jason Changdaf2ad32023-08-31 17:06:36 -070024import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070025import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070026import time
Jason Changdaf2ad32023-08-31 17:06:36 -070027from typing import List, NamedTuple, Set, Union
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
Jason Changdaf2ad32023-08-31 17:06:36 -070058from error import RepoError
Mike Frysinger64477332023-08-21 21:20:32 -040059from error import RepoExitError
60from error import RepoUnhandledExceptionError
61from error import SyncError
62from error import UpdateManifestError
David Rileye0684ad2017-04-05 00:02:59 -070063import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040064from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090065from git_config import GetUrlCookieFile
Mike Frysinger64477332023-08-21 21:20:32 -040066from git_refs import HEAD
67from git_refs import R_HEADS
Raman Tenneti6a872c92021-01-14 19:17:50 -080068import git_superproject
Mike Frysinger64477332023-08-21 21:20:32 -040069import platform_utils
70from progress import elapsed_str
71from progress import jobs_str
72from progress import Progress
73from project import DeleteWorktreeError
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070074from project import Project
75from project import RemoteSpec
Mike Frysinger64477332023-08-21 21:20:32 -040076from project import SyncBuffer
Aravind Vasudevane914ec22023-08-31 20:57:31 +000077from repo_logging import RepoLogger
Joanna Wanga6c52f52022-11-03 16:51:19 -040078from repo_trace import Trace
Mike Frysinger19e409c2021-05-05 19:44:35 -040079import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080080from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070081
Mike Frysinger64477332023-08-21 21:20:32 -040082
Dave Borowitz67700e92012-10-23 15:00:54 -070083_ONE_DAY_S = 24 * 60 * 60
84
LaMont Jonesd7935532022-12-01 20:18:46 +000085# Env var to implicitly turn auto-gc back on. This was added to allow a user to
LaMont Jones100a2142022-12-02 22:54:11 +000086# revert a change in default behavior in v2.29.9. Remove after 2023-04-01.
Gavin Makea2e3302023-03-11 06:46:20 +000087_REPO_AUTO_GC = "REPO_AUTO_GC"
88_AUTO_GC = os.environ.get(_REPO_AUTO_GC) == "1"
LaMont Jones5ed8c632022-11-10 00:10:44 +000089
Jason Chang17833322023-05-23 13:06:55 -070090_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
91
Aravind Vasudevane914ec22023-08-31 20:57:31 +000092logger = RepoLogger(__file__)
93
David Pursehouse819827a2020-02-12 15:20:19 +090094
LaMont Jones1eddca82022-09-01 15:15:04 +000095class _FetchOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +000096 """_FetchOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +000097
Gavin Makea2e3302023-03-11 06:46:20 +000098 Attributes:
99 success (bool): True if successful.
100 project (Project): The fetched project.
101 start (float): The starting time.time().
102 finish (float): The ending time.time().
103 remote_fetched (bool): True if the remote was actually queried.
104 """
105
106 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700107 errors: List[Exception]
Gavin Makea2e3302023-03-11 06:46:20 +0000108 project: Project
109 start: float
110 finish: float
111 remote_fetched: bool
LaMont Jones1eddca82022-09-01 15:15:04 +0000112
113
114class _FetchResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000115 """_Fetch return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000116
Gavin Makea2e3302023-03-11 06:46:20 +0000117 Attributes:
118 success (bool): True if successful.
119 projects (Set[str]): The names of the git directories of fetched projects.
120 """
121
122 success: bool
123 projects: Set[str]
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]
LaMont Jones1eddca82022-09-01 15:15:04 +0000134
135
136class _CheckoutOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000137 """_CheckoutOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000138
Gavin Makea2e3302023-03-11 06:46:20 +0000139 Attributes:
140 success (bool): True if successful.
141 project (Project): The project.
142 start (float): The starting time.time().
143 finish (float): The ending time.time().
144 """
145
146 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700147 errors: List[Exception]
Gavin Makea2e3302023-03-11 06:46:20 +0000148 project: Project
149 start: float
150 finish: float
LaMont Jones1eddca82022-09-01 15:15:04 +0000151
152
Jason Chang32b59562023-07-14 16:45:35 -0700153class SuperprojectError(SyncError):
154 """Superproject sync repo."""
155
156
157class SyncFailFastError(SyncError):
158 """Sync exit error when --fail-fast set."""
159
160
161class SmartSyncError(SyncError):
162 """Smart sync exit error."""
163
164
Jason Changdaf2ad32023-08-31 17:06:36 -0700165class ManifestInterruptError(RepoError):
166 """Aggregate Error to be logged when a user interrupts a manifest update."""
167
168 def __init__(self, output, **kwargs):
169 super().__init__(output, **kwargs)
170 self.output = output
171
172 def __str__(self):
173 error_type = type(self).__name__
174 return f"{error_type}:{self.output}"
175
176
177class TeeStringIO(io.StringIO):
178 """StringIO class that can write to an additional destination."""
179
180 def __init__(
181 self, io: Union[io.TextIOWrapper, None], *args, **kwargs
182 ) -> None:
183 super().__init__(*args, **kwargs)
184 self.io = io
185
186 def write(self, s: str) -> int:
187 """Write to additional destination."""
188 super().write(s)
189 if self.io is not None:
190 self.io.write(s)
191
192
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800193class Sync(Command, MirrorSafeCommand):
Gavin Makea2e3302023-03-11 06:46:20 +0000194 COMMON = True
195 MULTI_MANIFEST_SUPPORT = True
196 helpSummary = "Update working tree to the latest revision"
197 helpUsage = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700198%prog [...]
199"""
Gavin Makea2e3302023-03-11 06:46:20 +0000200 helpDescription = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700201The '%prog' command synchronizes local project directories
202with the remote repositories specified in the manifest. If a local
203project does not yet exist, it will clone a new local directory from
204the remote repository and set up tracking branches as specified in
205the manifest. If the local project already exists, '%prog'
206will update the remote branches and rebase any new local changes
207on top of the new remote changes.
208
209'%prog' will synchronize all projects listed at the command
210line. Projects can be specified either by name, or by a relative
211or absolute path to the project's local directory. If no projects
212are specified, '%prog' will synchronize all projects listed in
213the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700214
215The -d/--detach option can be used to switch specified projects
216back to the manifest revision. This option is especially helpful
217if the project is currently on a topic branch, but the manifest
218revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700219
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700220The -s/--smart-sync option can be used to sync to a known good
221build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200222manifest. The -t/--smart-tag option is similar and allows you to
223specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700224
David Pursehousecf76b1b2012-09-14 10:31:42 +0900225The -u/--manifest-server-username and -p/--manifest-server-password
226options can be used to specify a username and password to authenticate
227with the manifest server when using the -s or -t option.
228
229If -u and -p are not specified when using the -s or -t option, '%prog'
230will attempt to read authentication credentials for the manifest server
231from the user's .netrc file.
232
233'%prog' will not use authentication credentials from -u/-p or .netrc
234if the manifest server specified in the manifest file already includes
235credentials.
236
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400237By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400238to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500239
Kevin Degiabaa7f32014-11-12 11:27:45 -0700240The --force-sync option can be used to overwrite existing git
241directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900242object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700243refs may be removed when overwriting.
244
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500245The --force-remove-dirty option can be used to remove previously used
246projects with uncommitted changes. WARNING: This may cause data to be
247lost since uncommitted changes may be removed with projects that no longer
248exist in the manifest.
249
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700250The --no-clone-bundle option disables any attempt to use
251$URL/clone.bundle to bootstrap a new Git repository from a
252resumeable bundle file on a content delivery network. This
253may be necessary if there are problems with the local Python
254HTTP client or proxy configuration, but the Git binary works.
255
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800256The --fetch-submodules option enables fetching Git submodules
257of a project from server.
258
David Pursehousef2fad612015-01-29 14:36:28 +0900259The -c/--current-branch option can be used to only fetch objects that
260are on the branch specified by a project's revision.
261
David Pursehouseb1553542014-09-04 21:28:09 +0900262The --optimized-fetch option can be used to only fetch projects that
263are fixed to a sha1 revision if the sha1 revision does not already
264exist locally.
265
David Pursehouse74cfd272015-10-14 10:50:15 +0900266The --prune option can be used to remove any refs that no longer
267exist on the remote.
268
LaMont Jones7efab532022-09-01 15:41:12 +0000269The --auto-gc option can be used to trigger garbage collection on all
270projects. By default, repo does not run garbage collection.
271
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400272# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700273
274If at least one project remote URL uses an SSH connection (ssh://,
275git+ssh://, or user@host:path syntax) repo will automatically
276enable the SSH ControlMaster option when connecting to that host.
277This feature permits other projects in the same '%prog' session to
278reuse the same SSH tunnel, saving connection setup overheads.
279
280To disable this behavior on UNIX platforms, set the GIT_SSH
281environment variable to 'ssh'. For example:
282
283 export GIT_SSH=ssh
284 %prog
285
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400286# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700287
288This feature is automatically disabled on Windows, due to the lack
289of UNIX domain socket support.
290
291This feature is not compatible with url.insteadof rewrites in the
292user's ~/.gitconfig. '%prog' is currently not able to perform the
293rewrite early enough to establish the ControlMaster tunnel.
294
295If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
296later is required to fix a server side protocol bug.
297
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700298"""
Gavin Makea2e3302023-03-11 06:46:20 +0000299 # A value of 0 means we want parallel jobs, but we'll determine the default
300 # value later on.
301 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700302
Gavin Makea2e3302023-03-11 06:46:20 +0000303 def _Options(self, p, show_smart=True):
304 p.add_option(
305 "--jobs-network",
306 default=None,
307 type=int,
308 metavar="JOBS",
309 help="number of network jobs to run in parallel (defaults to "
310 "--jobs or 1)",
311 )
312 p.add_option(
313 "--jobs-checkout",
314 default=None,
315 type=int,
316 metavar="JOBS",
317 help="number of local checkout jobs to run in parallel (defaults "
318 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
319 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400320
Gavin Makea2e3302023-03-11 06:46:20 +0000321 p.add_option(
322 "-f",
323 "--force-broken",
324 dest="force_broken",
325 action="store_true",
326 help="obsolete option (to be deleted in the future)",
327 )
328 p.add_option(
329 "--fail-fast",
330 dest="fail_fast",
331 action="store_true",
332 help="stop syncing after first error is hit",
333 )
334 p.add_option(
335 "--force-sync",
336 dest="force_sync",
337 action="store_true",
338 help="overwrite an existing git directory if it needs to "
339 "point to a different object directory. WARNING: this "
340 "may cause loss of data",
341 )
342 p.add_option(
343 "--force-remove-dirty",
344 dest="force_remove_dirty",
345 action="store_true",
346 help="force remove projects with uncommitted modifications if "
347 "projects no longer exist in the manifest. "
348 "WARNING: this may cause loss of data",
349 )
350 p.add_option(
351 "-l",
352 "--local-only",
353 dest="local_only",
354 action="store_true",
355 help="only update working tree, don't fetch",
356 )
357 p.add_option(
358 "--no-manifest-update",
359 "--nmu",
360 dest="mp_update",
361 action="store_false",
362 default="true",
363 help="use the existing manifest checkout as-is. "
364 "(do not update to the latest revision)",
365 )
366 p.add_option(
367 "-n",
368 "--network-only",
369 dest="network_only",
370 action="store_true",
371 help="fetch only, don't update working tree",
372 )
373 p.add_option(
374 "-d",
375 "--detach",
376 dest="detach_head",
377 action="store_true",
378 help="detach projects back to manifest revision",
379 )
380 p.add_option(
381 "-c",
382 "--current-branch",
383 dest="current_branch_only",
384 action="store_true",
385 help="fetch only current branch from server",
386 )
387 p.add_option(
388 "--no-current-branch",
389 dest="current_branch_only",
390 action="store_false",
391 help="fetch all branches from server",
392 )
393 p.add_option(
394 "-m",
395 "--manifest-name",
396 dest="manifest_name",
397 help="temporary manifest to use for this sync",
398 metavar="NAME.xml",
399 )
400 p.add_option(
401 "--clone-bundle",
402 action="store_true",
403 help="enable use of /clone.bundle on HTTP/HTTPS",
404 )
405 p.add_option(
406 "--no-clone-bundle",
407 dest="clone_bundle",
408 action="store_false",
409 help="disable use of /clone.bundle on HTTP/HTTPS",
410 )
411 p.add_option(
412 "-u",
413 "--manifest-server-username",
414 action="store",
415 dest="manifest_server_username",
416 help="username to authenticate with the manifest server",
417 )
418 p.add_option(
419 "-p",
420 "--manifest-server-password",
421 action="store",
422 dest="manifest_server_password",
423 help="password to authenticate with the manifest server",
424 )
425 p.add_option(
426 "--fetch-submodules",
427 dest="fetch_submodules",
428 action="store_true",
429 help="fetch submodules from server",
430 )
431 p.add_option(
432 "--use-superproject",
433 action="store_true",
434 help="use the manifest superproject to sync projects; implies -c",
435 )
436 p.add_option(
437 "--no-use-superproject",
438 action="store_false",
439 dest="use_superproject",
440 help="disable use of manifest superprojects",
441 )
442 p.add_option("--tags", action="store_true", help="fetch tags")
443 p.add_option(
444 "--no-tags",
445 dest="tags",
446 action="store_false",
447 help="don't fetch tags (default)",
448 )
449 p.add_option(
450 "--optimized-fetch",
451 dest="optimized_fetch",
452 action="store_true",
453 help="only fetch projects fixed to sha1 if revision does not exist "
454 "locally",
455 )
456 p.add_option(
457 "--retry-fetches",
458 default=0,
459 action="store",
460 type="int",
461 help="number of times to retry fetches on transient errors",
462 )
463 p.add_option(
464 "--prune",
465 action="store_true",
466 help="delete refs that no longer exist on the remote (default)",
467 )
468 p.add_option(
469 "--no-prune",
470 dest="prune",
471 action="store_false",
472 help="do not delete refs that no longer exist on the remote",
473 )
474 p.add_option(
475 "--auto-gc",
476 action="store_true",
477 default=None,
478 help="run garbage collection on all synced projects",
479 )
480 p.add_option(
481 "--no-auto-gc",
482 dest="auto_gc",
483 action="store_false",
484 help="do not run garbage collection on any projects (default)",
485 )
486 if show_smart:
487 p.add_option(
488 "-s",
489 "--smart-sync",
490 dest="smart_sync",
491 action="store_true",
492 help="smart sync using manifest from the latest known good "
493 "build",
494 )
495 p.add_option(
496 "-t",
497 "--smart-tag",
498 dest="smart_tag",
499 action="store",
500 help="smart sync using manifest from a known tag",
501 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700502
Gavin Makea2e3302023-03-11 06:46:20 +0000503 g = p.add_option_group("repo Version options")
504 g.add_option(
505 "--no-repo-verify",
506 dest="repo_verify",
507 default=True,
508 action="store_false",
509 help="do not verify repo source code",
510 )
511 g.add_option(
512 "--repo-upgraded",
513 dest="repo_upgraded",
514 action="store_true",
Mike Frysinger06ddc8c2023-08-21 21:26:51 -0400515 help=optparse.SUPPRESS_HELP,
Gavin Makea2e3302023-03-11 06:46:20 +0000516 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700517
Gavin Makea2e3302023-03-11 06:46:20 +0000518 def _GetBranch(self, manifest_project):
519 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000520
Gavin Makea2e3302023-03-11 06:46:20 +0000521 Args:
522 manifest_project: The manifestProject to query.
523 """
524 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
525 branch = b.merge
526 if branch.startswith(R_HEADS):
527 branch = branch[len(R_HEADS) :]
528 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800529
Gavin Makea2e3302023-03-11 06:46:20 +0000530 def _GetCurrentBranchOnly(self, opt, manifest):
531 """Returns whether current-branch or use-superproject options are
532 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200533
Gavin Makea2e3302023-03-11 06:46:20 +0000534 Args:
535 opt: Program options returned from optparse. See _Options().
536 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000537
Gavin Makea2e3302023-03-11 06:46:20 +0000538 Returns:
539 True if a superproject is requested, otherwise the value of the
540 current_branch option (True, False or None).
541 """
542 return (
543 git_superproject.UseSuperproject(opt.use_superproject, manifest)
544 or opt.current_branch_only
545 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700546
Gavin Makea2e3302023-03-11 06:46:20 +0000547 def _UpdateProjectsRevisionId(
548 self, opt, args, superproject_logging_data, manifest
549 ):
550 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800551
Gavin Makea2e3302023-03-11 06:46:20 +0000552 This function updates each project's revisionId with the commit hash
553 from the superproject. It writes the updated manifest into a file and
554 reloads the manifest from it. When appropriate, sub manifests are also
555 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800556
Gavin Makea2e3302023-03-11 06:46:20 +0000557 Args:
558 opt: Program options returned from optparse. See _Options().
559 args: Arguments to pass to GetProjects. See the GetProjects
560 docstring for details.
561 superproject_logging_data: A dictionary of superproject data to log.
562 manifest: The manifest to use.
563 """
564 have_superproject = manifest.superproject or any(
565 m.superproject for m in manifest.all_children
566 )
567 if not have_superproject:
568 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000569
Gavin Makea2e3302023-03-11 06:46:20 +0000570 if opt.local_only and manifest.superproject:
571 manifest_path = manifest.superproject.manifest_path
572 if manifest_path:
573 self._ReloadManifest(manifest_path, manifest)
574 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700575
Gavin Makea2e3302023-03-11 06:46:20 +0000576 all_projects = self.GetProjects(
577 args,
578 missing_ok=True,
579 submodules_ok=opt.fetch_submodules,
580 manifest=manifest,
581 all_manifests=not opt.this_manifest_only,
582 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000583
Gavin Makea2e3302023-03-11 06:46:20 +0000584 per_manifest = collections.defaultdict(list)
585 if opt.this_manifest_only:
586 per_manifest[manifest.path_prefix] = all_projects
587 else:
588 for p in all_projects:
589 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000590
Gavin Makea2e3302023-03-11 06:46:20 +0000591 superproject_logging_data = {}
592 need_unload = False
593 for m in self.ManifestList(opt):
594 if m.path_prefix not in per_manifest:
595 continue
596 use_super = git_superproject.UseSuperproject(
597 opt.use_superproject, m
598 )
599 if superproject_logging_data:
600 superproject_logging_data["multimanifest"] = True
601 superproject_logging_data.update(
602 superproject=use_super,
603 haslocalmanifests=bool(m.HasLocalManifests),
604 hassuperprojecttag=bool(m.superproject),
605 )
606 if use_super and (m.IsMirror or m.IsArchive):
607 # Don't use superproject, because we have no working tree.
608 use_super = False
609 superproject_logging_data["superproject"] = False
610 superproject_logging_data["noworktree"] = True
611 if opt.use_superproject is not False:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000612 logger.warning(
613 "%s: not using superproject because there is no "
614 "working tree.",
615 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000616 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000617
Gavin Makea2e3302023-03-11 06:46:20 +0000618 if not use_super:
619 continue
620 m.superproject.SetQuiet(opt.quiet)
621 print_messages = git_superproject.PrintMessages(
622 opt.use_superproject, m
623 )
624 m.superproject.SetPrintMessages(print_messages)
625 update_result = m.superproject.UpdateProjectsRevisionId(
626 per_manifest[m.path_prefix], git_event_log=self.git_event_log
627 )
628 manifest_path = update_result.manifest_path
629 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
630 if manifest_path:
631 m.SetManifestOverride(manifest_path)
632 need_unload = True
633 else:
634 if print_messages:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000635 logger.warning(
636 "%s: warning: Update of revisionId from superproject "
637 "has failed, repo sync will not use superproject to "
638 "fetch the source. Please resync with the "
639 "--no-use-superproject option to avoid this repo "
640 "warning.",
641 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000642 )
643 if update_result.fatal and opt.use_superproject is not None:
Jason Chang32b59562023-07-14 16:45:35 -0700644 raise SuperprojectError()
Gavin Makea2e3302023-03-11 06:46:20 +0000645 if need_unload:
646 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800647
Gavin Makea2e3302023-03-11 06:46:20 +0000648 def _FetchProjectList(self, opt, projects):
649 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500650
Gavin Makea2e3302023-03-11 06:46:20 +0000651 The projects we're given share the same underlying git object store, so
652 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800653
Gavin Mak551285f2023-05-04 04:48:43 +0000654 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700655
Gavin Makea2e3302023-03-11 06:46:20 +0000656 Args:
657 opt: Program options returned from optparse. See _Options().
658 projects: Projects to fetch.
659 """
660 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700661
Gavin Makea2e3302023-03-11 06:46:20 +0000662 def _FetchOne(self, opt, project):
663 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700664
Gavin Makea2e3302023-03-11 06:46:20 +0000665 Args:
666 opt: Program options returned from optparse. See _Options().
667 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700668
Gavin Makea2e3302023-03-11 06:46:20 +0000669 Returns:
670 Whether the fetch was successful.
671 """
672 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000673 k = f"{project.name} @ {project.relpath}"
674 self._sync_dict[k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000675 success = False
676 remote_fetched = False
Jason Chang32b59562023-07-14 16:45:35 -0700677 errors = []
Jason Changdaf2ad32023-08-31 17:06:36 -0700678 buf = TeeStringIO(sys.stdout if opt.verbose else None)
Gavin Makea2e3302023-03-11 06:46:20 +0000679 try:
680 sync_result = project.Sync_NetworkHalf(
681 quiet=opt.quiet,
682 verbose=opt.verbose,
683 output_redir=buf,
684 current_branch_only=self._GetCurrentBranchOnly(
685 opt, project.manifest
686 ),
687 force_sync=opt.force_sync,
688 clone_bundle=opt.clone_bundle,
689 tags=opt.tags,
690 archive=project.manifest.IsArchive,
691 optimized_fetch=opt.optimized_fetch,
692 retry_fetches=opt.retry_fetches,
693 prune=opt.prune,
694 ssh_proxy=self.ssh_proxy,
695 clone_filter=project.manifest.CloneFilter,
696 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700697 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000698 )
699 success = sync_result.success
700 remote_fetched = sync_result.remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700701 if sync_result.error:
702 errors.append(sync_result.error)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700703
Gavin Makea2e3302023-03-11 06:46:20 +0000704 output = buf.getvalue()
Jason Changdaf2ad32023-08-31 17:06:36 -0700705 if output and buf.io is None and not success:
Gavin Makea2e3302023-03-11 06:46:20 +0000706 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700707
Gavin Makea2e3302023-03-11 06:46:20 +0000708 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000709 logger.error(
710 "error: Cannot fetch %s from %s",
711 project.name,
712 project.remote.url,
Gavin Makea2e3302023-03-11 06:46:20 +0000713 )
714 except KeyboardInterrupt:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000715 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000716 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000717 logger.error("error.GitError: Cannot fetch %s", e)
Jason Chang32b59562023-07-14 16:45:35 -0700718 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000719 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000720 logger.error(
721 "error: Cannot fetch %s (%s: %s)",
722 project.name,
723 type(e).__name__,
724 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000725 )
Gavin Mak551285f2023-05-04 04:48:43 +0000726 del self._sync_dict[k]
Jason Chang32b59562023-07-14 16:45:35 -0700727 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000728 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500729
Gavin Makea2e3302023-03-11 06:46:20 +0000730 finish = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000731 del self._sync_dict[k]
Jason Chang32b59562023-07-14 16:45:35 -0700732 return _FetchOneResult(
733 success, errors, project, start, finish, remote_fetched
734 )
David James8d201162013-10-11 17:03:19 -0700735
Gavin Makea2e3302023-03-11 06:46:20 +0000736 @classmethod
737 def _FetchInitChild(cls, ssh_proxy):
738 cls.ssh_proxy = ssh_proxy
Mike Frysinger339f2df2021-05-06 00:44:42 -0400739
Gavin Mak04cba4a2023-05-24 21:28:28 +0000740 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000741 earliest_time = float("inf")
742 earliest_proj = None
Gavin Mak945c0062023-05-30 20:04:07 +0000743 items = self._sync_dict.items()
744 for project, t in items:
Gavin Mak551285f2023-05-04 04:48:43 +0000745 if t < earliest_time:
746 earliest_time = t
747 earliest_proj = project
748
Josip Sokcevic71122f92023-05-26 02:44:37 +0000749 if not earliest_proj:
Gavin Mak945c0062023-05-30 20:04:07 +0000750 # This function is called when sync is still running but in some
751 # cases (by chance), _sync_dict can contain no entries. Return some
752 # text to indicate that sync is still working.
753 return "..working.."
Josip Sokcevic71122f92023-05-26 02:44:37 +0000754
Gavin Mak551285f2023-05-04 04:48:43 +0000755 elapsed = time.time() - earliest_time
Gavin Mak945c0062023-05-30 20:04:07 +0000756 jobs = jobs_str(len(items))
Gavin Mak04cba4a2023-05-24 21:28:28 +0000757 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000758
Jason Changdaf2ad32023-08-31 17:06:36 -0700759 def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
Gavin Makea2e3302023-03-11 06:46:20 +0000760 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500761
Gavin Makea2e3302023-03-11 06:46:20 +0000762 jobs = opt.jobs_network
763 fetched = set()
764 remote_fetched = set()
Gavin Makedcaa942023-04-27 05:58:57 +0000765 pm = Progress(
766 "Fetching",
767 len(projects),
768 delay=False,
769 quiet=opt.quiet,
770 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000771 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000772 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800773
Gavin Mak551285f2023-05-04 04:48:43 +0000774 self._sync_dict = multiprocessing.Manager().dict()
775 sync_event = _threading.Event()
776
777 def _MonitorSyncLoop():
778 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000779 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000780 if sync_event.wait(timeout=1):
781 return
782
783 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
784 sync_progress_thread.daemon = True
785 sync_progress_thread.start()
786
Gavin Makea2e3302023-03-11 06:46:20 +0000787 objdir_project_map = dict()
788 for project in projects:
789 objdir_project_map.setdefault(project.objdir, []).append(project)
790 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700791
Gavin Makea2e3302023-03-11 06:46:20 +0000792 def _ProcessResults(results_sets):
793 ret = True
794 for results in results_sets:
795 for result in results:
796 success = result.success
797 project = result.project
798 start = result.start
799 finish = result.finish
800 self._fetch_times.Set(project, finish - start)
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000801 self._local_sync_state.SetFetchTime(project)
Gavin Makea2e3302023-03-11 06:46:20 +0000802 self.event_log.AddSync(
803 project,
804 event_log.TASK_SYNC_NETWORK,
805 start,
806 finish,
807 success,
808 )
Jason Chang32b59562023-07-14 16:45:35 -0700809 if result.errors:
810 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000811 if result.remote_fetched:
812 remote_fetched.add(project)
813 # Check for any errors before running any more tasks.
814 # ...we'll let existing jobs finish, though.
815 if not success:
816 ret = False
817 else:
818 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000819 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000820 if not ret and opt.fail_fast:
821 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500822 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700823
Gavin Makea2e3302023-03-11 06:46:20 +0000824 # We pass the ssh proxy settings via the class. This allows
825 # multiprocessing to pickle it up when spawning children. We can't pass
826 # it as an argument to _FetchProjectList below as multiprocessing is
827 # unable to pickle those.
828 Sync.ssh_proxy = None
Mike Frysingerebf04a42021-02-23 20:48:04 -0500829
Gavin Makea2e3302023-03-11 06:46:20 +0000830 # NB: Multiprocessing is heavy, so don't spin it up for one job.
831 if len(projects_list) == 1 or jobs == 1:
832 self._FetchInitChild(ssh_proxy)
833 if not _ProcessResults(
834 self._FetchProjectList(opt, x) for x in projects_list
835 ):
836 ret = False
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000837 else:
Gavin Makea2e3302023-03-11 06:46:20 +0000838 # Favor throughput over responsiveness when quiet. It seems that
839 # imap() will yield results in batches relative to chunksize, so
840 # even as the children finish a sync, we won't see the result until
841 # one child finishes ~chunksize jobs. When using a large --jobs
842 # with large chunksize, this can be jarring as there will be a large
843 # initial delay where repo looks like it isn't doing anything and
844 # sits at 0%, but then suddenly completes a lot of jobs all at once.
845 # Since this code is more network bound, we can accept a bit more
846 # CPU overhead with a smaller chunksize so that the user sees more
847 # immediate & continuous feedback.
848 if opt.quiet:
849 chunksize = WORKER_BATCH_SIZE
850 else:
851 pm.update(inc=0, msg="warming up")
852 chunksize = 4
853 with multiprocessing.Pool(
854 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)
855 ) as pool:
856 results = pool.imap_unordered(
857 functools.partial(self._FetchProjectList, opt),
858 projects_list,
859 chunksize=chunksize,
860 )
861 if not _ProcessResults(results):
862 ret = False
863 pool.close()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000864
Gavin Makea2e3302023-03-11 06:46:20 +0000865 # Cleanup the reference now that we're done with it, and we're going to
866 # release any resources it points to. If we don't, later
867 # multiprocessing usage (e.g. checkouts) will try to pickle and then
868 # crash.
869 del Sync.ssh_proxy
LaMont Jones7efab532022-09-01 15:41:12 +0000870
Gavin Mak551285f2023-05-04 04:48:43 +0000871 sync_event.set()
Gavin Makea2e3302023-03-11 06:46:20 +0000872 pm.end()
873 self._fetch_times.Save()
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000874 self._local_sync_state.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000875
Gavin Makea2e3302023-03-11 06:46:20 +0000876 if not self.outer_client.manifest.IsArchive:
877 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400878
Jason Changdaf2ad32023-08-31 17:06:36 -0700879 return _FetchResult(ret, fetched)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000880
Gavin Makea2e3302023-03-11 06:46:20 +0000881 def _FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -0700882 self, opt, args, all_projects, err_event, ssh_proxy, manifest, errors
Gavin Makea2e3302023-03-11 06:46:20 +0000883 ):
884 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400885
Gavin Makea2e3302023-03-11 06:46:20 +0000886 Args:
887 opt: Program options returned from optparse. See _Options().
888 args: Command line args used to filter out projects.
889 all_projects: List of all projects that should be fetched.
890 err_event: Whether an error was hit while processing.
891 ssh_proxy: SSH manager for clients & masters.
892 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700893
Gavin Makea2e3302023-03-11 06:46:20 +0000894 Returns:
895 List of all projects that should be checked out.
896 """
897 rp = manifest.repoProject
LaMont Jones891e8f72022-09-08 20:17:58 +0000898
Gavin Makea2e3302023-03-11 06:46:20 +0000899 to_fetch = []
900 now = time.time()
901 if _ONE_DAY_S <= (now - rp.LastFetch):
902 to_fetch.append(rp)
903 to_fetch.extend(all_projects)
904 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700905
Jason Changdaf2ad32023-08-31 17:06:36 -0700906 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000907 success = result.success
908 fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700909
Gavin Makea2e3302023-03-11 06:46:20 +0000910 if not success:
911 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700912
Gavin Makea2e3302023-03-11 06:46:20 +0000913 _PostRepoFetch(rp, opt.repo_verify)
914 if opt.network_only:
915 # Bail out now; the rest touches the working tree.
916 if err_event.is_set():
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000917 e = SyncError(
Jason Chang32b59562023-07-14 16:45:35 -0700918 "error: Exited sync due to fetch errors.",
919 aggregate_errors=errors,
920 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000921
922 logger.error(e)
923 raise e
Jason Changdaf2ad32023-08-31 17:06:36 -0700924 return _FetchMainResult([])
Dave Borowitz18857212012-10-23 17:02:59 -0700925
Gavin Makea2e3302023-03-11 06:46:20 +0000926 # Iteratively fetch missing and/or nested unregistered submodules.
927 previously_missing_set = set()
928 while True:
929 self._ReloadManifest(None, manifest)
930 all_projects = self.GetProjects(
931 args,
932 missing_ok=True,
933 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000934 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000935 all_manifests=not opt.this_manifest_only,
936 )
937 missing = []
938 for project in all_projects:
939 if project.gitdir not in fetched:
940 missing.append(project)
941 if not missing:
942 break
943 # Stop us from non-stopped fetching actually-missing repos: If set
944 # of missing repos has not been changed from last fetch, we break.
Jason R. Coombs0bcffd82023-10-20 23:29:42 +0545945 missing_set = {p.name for p in missing}
Gavin Makea2e3302023-03-11 06:46:20 +0000946 if previously_missing_set == missing_set:
947 break
948 previously_missing_set = missing_set
Jason Changdaf2ad32023-08-31 17:06:36 -0700949 result = self._Fetch(missing, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000950 success = result.success
951 new_fetched = result.projects
952 if not success:
953 err_event.set()
954 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700955
Jason Changdaf2ad32023-08-31 17:06:36 -0700956 return _FetchMainResult(all_projects)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700957
Gavin Makea2e3302023-03-11 06:46:20 +0000958 def _CheckoutOne(self, detach_head, force_sync, project):
959 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +0800960
Gavin Makea2e3302023-03-11 06:46:20 +0000961 Args:
962 detach_head: Whether to leave a detached HEAD.
963 force_sync: Force checking out of the repo.
964 project: Project object for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +0800965
Gavin Makea2e3302023-03-11 06:46:20 +0000966 Returns:
967 Whether the fetch was successful.
968 """
969 start = time.time()
970 syncbuf = SyncBuffer(
971 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000972 )
Gavin Makea2e3302023-03-11 06:46:20 +0000973 success = False
Jason Chang32b59562023-07-14 16:45:35 -0700974 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +0900975 try:
Jason Chang32b59562023-07-14 16:45:35 -0700976 project.Sync_LocalHalf(
977 syncbuf, force_sync=force_sync, errors=errors
978 )
Gavin Makea2e3302023-03-11 06:46:20 +0000979 success = syncbuf.Finish()
980 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000981 logger.error(
982 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +0000983 )
Jason Chang32b59562023-07-14 16:45:35 -0700984 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000985 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000986 logger.error(
987 "error: Cannot checkout %s: %s: %s",
988 project.name,
989 type(e).__name__,
990 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000991 )
992 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700993
Gavin Makea2e3302023-03-11 06:46:20 +0000994 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000995 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000996 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700997 return _CheckoutOneResult(success, errors, project, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -0400998
Jason Chang32b59562023-07-14 16:45:35 -0700999 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001000 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001001
Gavin Makea2e3302023-03-11 06:46:20 +00001002 Args:
1003 all_projects: List of all projects that should be checked out.
1004 opt: Program options returned from optparse. See _Options().
1005 err_results: A list of strings, paths to git repos where checkout
1006 failed.
1007 """
1008 # Only checkout projects with worktrees.
1009 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001010
Gavin Makea2e3302023-03-11 06:46:20 +00001011 def _ProcessResults(pool, pm, results):
1012 ret = True
1013 for result in results:
1014 success = result.success
1015 project = result.project
1016 start = result.start
1017 finish = result.finish
1018 self.event_log.AddSync(
1019 project, event_log.TASK_SYNC_LOCAL, start, finish, success
1020 )
Jason Chang32b59562023-07-14 16:45:35 -07001021
1022 if result.errors:
1023 checkout_errors.extend(result.errors)
1024
Gavin Makea2e3302023-03-11 06:46:20 +00001025 # Check for any errors before running any more tasks.
1026 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001027 if success:
1028 self._local_sync_state.SetCheckoutTime(project)
1029 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001030 ret = False
1031 err_results.append(
1032 project.RelPath(local=opt.this_manifest_only)
1033 )
1034 if opt.fail_fast:
1035 if pool:
1036 pool.close()
1037 return ret
1038 pm.update(msg=project.name)
1039 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001040
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001041 proc_res = self.ExecuteInParallel(
1042 opt.jobs_checkout,
1043 functools.partial(
1044 self._CheckoutOne, opt.detach_head, opt.force_sync
1045 ),
1046 all_projects,
1047 callback=_ProcessResults,
1048 output=Progress("Checking out", len(all_projects), quiet=opt.quiet),
Gavin Makea2e3302023-03-11 06:46:20 +00001049 )
Simran Basib9a1b732015-08-20 12:19:28 -07001050
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001051 self._local_sync_state.Save()
1052 return proc_res and not err_results
1053
Gavin Makea2e3302023-03-11 06:46:20 +00001054 @staticmethod
1055 def _GetPreciousObjectsState(project: Project, opt):
1056 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001057
Gavin Makea2e3302023-03-11 06:46:20 +00001058 Args:
1059 project (Project): the project to examine, and possibly correct.
1060 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001061
Gavin Makea2e3302023-03-11 06:46:20 +00001062 Returns:
1063 Expected state of extensions.preciousObjects:
1064 False: Should be disabled. (not present)
1065 True: Should be enabled.
1066 """
1067 if project.use_git_worktrees:
1068 return False
1069 projects = project.manifest.GetProjectsWithName(
1070 project.name, all_manifests=True
1071 )
1072 if len(projects) == 1:
1073 return False
1074 if len(projects) > 1:
1075 # Objects are potentially shared with another project.
1076 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1077 # - When False, shared projects share (via symlink)
1078 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1079 # objects directory. All objects are precious, since there is no
1080 # project with a complete set of refs.
1081 # - When True, shared projects share (via info/alternates)
1082 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1083 # store, which is written only on the first clone of the project,
1084 # and is not written subsequently. (When Sync_NetworkHalf sees
1085 # that it exists, it makes sure that the alternates file points
1086 # there, and uses a project-local .git/objects directory for all
1087 # syncs going forward.
1088 # We do not support switching between the options. The environment
1089 # variable is present for testing and migration only.
1090 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001091
Gavin Makea2e3302023-03-11 06:46:20 +00001092 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001093
Gavin Makea2e3302023-03-11 06:46:20 +00001094 def _SetPreciousObjectsState(self, project: Project, opt):
1095 """Correct the preciousObjects state for the project.
1096
1097 Args:
1098 project: the project to examine, and possibly correct.
1099 opt: options given to sync.
1100 """
1101 expected = self._GetPreciousObjectsState(project, opt)
1102 actual = (
1103 project.config.GetBoolean("extensions.preciousObjects") or False
1104 )
1105 relpath = project.RelPath(local=opt.this_manifest_only)
1106
1107 if expected != actual:
1108 # If this is unexpected, log it and repair.
1109 Trace(
1110 f"{relpath} expected preciousObjects={expected}, got {actual}"
1111 )
1112 if expected:
1113 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001114 print(
1115 "\r%s: Shared project %s found, disabling pruning."
1116 % (relpath, project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001117 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001118
Gavin Makea2e3302023-03-11 06:46:20 +00001119 if git_require((2, 7, 0)):
1120 project.EnableRepositoryExtension("preciousObjects")
1121 else:
1122 # This isn't perfect, but it's the best we can do with old
1123 # git.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001124 logger.warning(
1125 "%s: WARNING: shared projects are unreliable when "
Gavin Makea2e3302023-03-11 06:46:20 +00001126 "using old versions of git; please upgrade to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001127 "git-2.7.0+.",
1128 relpath,
Gavin Makea2e3302023-03-11 06:46:20 +00001129 )
1130 project.config.SetString("gc.pruneExpire", "never")
1131 else:
1132 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001133 print(f"\r{relpath}: not shared, disabling pruning.")
Gavin Makea2e3302023-03-11 06:46:20 +00001134 project.config.SetString("extensions.preciousObjects", None)
1135 project.config.SetString("gc.pruneExpire", None)
1136
1137 def _GCProjects(self, projects, opt, err_event):
1138 """Perform garbage collection.
1139
1140 If We are skipping garbage collection (opt.auto_gc not set), we still
1141 want to potentially mark objects precious, so that `git gc` does not
1142 discard shared objects.
1143 """
1144 if not opt.auto_gc:
1145 # Just repair preciousObjects state, and return.
1146 for project in projects:
1147 self._SetPreciousObjectsState(project, opt)
1148 return
1149
1150 pm = Progress(
1151 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1152 )
1153 pm.update(inc=0, msg="prescan")
1154
1155 tidy_dirs = {}
1156 for project in projects:
1157 self._SetPreciousObjectsState(project, opt)
1158
1159 project.config.SetString("gc.autoDetach", "false")
1160 # Only call git gc once per objdir, but call pack-refs for the
1161 # remainder.
1162 if project.objdir not in tidy_dirs:
1163 tidy_dirs[project.objdir] = (
1164 True, # Run a full gc.
1165 project.bare_git,
1166 )
1167 elif project.gitdir not in tidy_dirs:
1168 tidy_dirs[project.gitdir] = (
1169 False, # Do not run a full gc; just run pack-refs.
1170 project.bare_git,
1171 )
1172
1173 jobs = opt.jobs
1174
1175 if jobs < 2:
1176 for run_gc, bare_git in tidy_dirs.values():
1177 pm.update(msg=bare_git._project.name)
1178
1179 if run_gc:
1180 bare_git.gc("--auto")
1181 else:
1182 bare_git.pack_refs()
1183 pm.end()
1184 return
1185
1186 cpu_count = os.cpu_count()
1187 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1188
1189 threads = set()
1190 sem = _threading.Semaphore(jobs)
1191
1192 def tidy_up(run_gc, bare_git):
1193 pm.start(bare_git._project.name)
1194 try:
1195 try:
1196 if run_gc:
1197 bare_git.gc("--auto", config=config)
1198 else:
1199 bare_git.pack_refs(config=config)
1200 except GitError:
1201 err_event.set()
1202 except Exception:
1203 err_event.set()
1204 raise
1205 finally:
1206 pm.finish(bare_git._project.name)
1207 sem.release()
1208
1209 for run_gc, bare_git in tidy_dirs.values():
1210 if err_event.is_set() and opt.fail_fast:
1211 break
1212 sem.acquire()
1213 t = _threading.Thread(
1214 target=tidy_up,
1215 args=(
1216 run_gc,
1217 bare_git,
1218 ),
1219 )
1220 t.daemon = True
1221 threads.add(t)
1222 t.start()
1223
1224 for t in threads:
1225 t.join()
1226 pm.end()
1227
1228 def _ReloadManifest(self, manifest_name, manifest):
1229 """Reload the manfiest from the file specified by the |manifest_name|.
1230
1231 It unloads the manifest if |manifest_name| is None.
1232
1233 Args:
1234 manifest_name: Manifest file to be reloaded.
1235 manifest: The manifest to use.
1236 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001237 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001238 # Override calls Unload already.
1239 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001240 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001241 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001242
Gavin Makea2e3302023-03-11 06:46:20 +00001243 def UpdateProjectList(self, opt, manifest):
1244 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001245
Gavin Makea2e3302023-03-11 06:46:20 +00001246 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001247
Gavin Makea2e3302023-03-11 06:46:20 +00001248 Args:
1249 opt: Program options returned from optparse. See _Options().
1250 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001251
Gavin Makea2e3302023-03-11 06:46:20 +00001252 Returns:
1253 0: success
1254 1: failure
1255 """
1256 new_project_paths = []
1257 for project in self.GetProjects(
1258 None, missing_ok=True, manifest=manifest, all_manifests=False
1259 ):
1260 if project.relpath:
1261 new_project_paths.append(project.relpath)
1262 file_name = "project.list"
1263 file_path = os.path.join(manifest.subdir, file_name)
1264 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001265
Gavin Makea2e3302023-03-11 06:46:20 +00001266 if os.path.exists(file_path):
Jason R. Coombs034950b2023-10-20 23:32:02 +05451267 with open(file_path) as fd:
Gavin Makea2e3302023-03-11 06:46:20 +00001268 old_project_paths = fd.read().split("\n")
1269 # In reversed order, so subfolders are deleted before parent folder.
1270 for path in sorted(old_project_paths, reverse=True):
1271 if not path:
1272 continue
1273 if path not in new_project_paths:
1274 # If the path has already been deleted, we don't need to do
1275 # it.
1276 gitdir = os.path.join(manifest.topdir, path, ".git")
1277 if os.path.exists(gitdir):
1278 project = Project(
1279 manifest=manifest,
1280 name=path,
1281 remote=RemoteSpec("origin"),
1282 gitdir=gitdir,
1283 objdir=gitdir,
1284 use_git_worktrees=os.path.isfile(gitdir),
1285 worktree=os.path.join(manifest.topdir, path),
1286 relpath=path,
1287 revisionExpr="HEAD",
1288 revisionId=None,
1289 groups=None,
1290 )
Jason Chang32b59562023-07-14 16:45:35 -07001291 project.DeleteWorktree(
Gavin Makea2e3302023-03-11 06:46:20 +00001292 quiet=opt.quiet, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001293 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001294
Gavin Makea2e3302023-03-11 06:46:20 +00001295 new_project_paths.sort()
1296 with open(file_path, "w") as fd:
1297 fd.write("\n".join(new_project_paths))
1298 fd.write("\n")
1299 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001300
Gavin Makea2e3302023-03-11 06:46:20 +00001301 def UpdateCopyLinkfileList(self, manifest):
1302 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001303
Gavin Makea2e3302023-03-11 06:46:20 +00001304 Returns:
1305 Whether update was successful.
1306 """
1307 new_paths = {}
1308 new_linkfile_paths = []
1309 new_copyfile_paths = []
1310 for project in self.GetProjects(
1311 None, missing_ok=True, manifest=manifest, all_manifests=False
1312 ):
1313 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1314 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001315
Gavin Makea2e3302023-03-11 06:46:20 +00001316 new_paths = {
1317 "linkfile": new_linkfile_paths,
1318 "copyfile": new_copyfile_paths,
1319 }
jiajia tanga590e642021-04-25 20:02:02 +08001320
Gavin Makea2e3302023-03-11 06:46:20 +00001321 copylinkfile_name = "copy-link-files.json"
1322 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1323 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001324
Gavin Makea2e3302023-03-11 06:46:20 +00001325 if os.path.exists(copylinkfile_path):
1326 with open(copylinkfile_path, "rb") as fp:
1327 try:
1328 old_copylinkfile_paths = json.load(fp)
1329 except Exception:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001330 logger.error(
1331 "error: %s is not a json formatted file.",
1332 copylinkfile_path,
Gavin Makea2e3302023-03-11 06:46:20 +00001333 )
1334 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001335 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001336
Gavin Makea2e3302023-03-11 06:46:20 +00001337 need_remove_files = []
1338 need_remove_files.extend(
1339 set(old_copylinkfile_paths.get("linkfile", []))
1340 - set(new_linkfile_paths)
1341 )
1342 need_remove_files.extend(
1343 set(old_copylinkfile_paths.get("copyfile", []))
1344 - set(new_copyfile_paths)
1345 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001346
Gavin Makea2e3302023-03-11 06:46:20 +00001347 for need_remove_file in need_remove_files:
1348 # Try to remove the updated copyfile or linkfile.
1349 # So, if the file is not exist, nothing need to do.
1350 platform_utils.remove(need_remove_file, missing_ok=True)
Raman Tenneti7954de12021-07-28 14:36:49 -07001351
Gavin Makea2e3302023-03-11 06:46:20 +00001352 # Create copy-link-files.json, save dest path of "copyfile" and
1353 # "linkfile".
1354 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1355 json.dump(new_paths, fp)
1356 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001357
Gavin Makea2e3302023-03-11 06:46:20 +00001358 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1359 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001360 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001361 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001362 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001363 )
Gavin Makea2e3302023-03-11 06:46:20 +00001364
1365 manifest_server = manifest.manifest_server
1366 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001367 print("Using manifest server %s" % manifest_server)
Gavin Makea2e3302023-03-11 06:46:20 +00001368
1369 if "@" not in manifest_server:
1370 username = None
1371 password = None
1372 if opt.manifest_server_username and opt.manifest_server_password:
1373 username = opt.manifest_server_username
1374 password = opt.manifest_server_password
1375 else:
1376 try:
1377 info = netrc.netrc()
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451378 except OSError:
Gavin Makea2e3302023-03-11 06:46:20 +00001379 # .netrc file does not exist or could not be opened.
1380 pass
1381 else:
1382 try:
1383 parse_result = urllib.parse.urlparse(manifest_server)
1384 if parse_result.hostname:
1385 auth = info.authenticators(parse_result.hostname)
1386 if auth:
1387 username, _account, password = auth
1388 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001389 logger.error(
1390 "No credentials found for %s in .netrc",
1391 parse_result.hostname,
Gavin Makea2e3302023-03-11 06:46:20 +00001392 )
1393 except netrc.NetrcParseError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001394 logger.error("Error parsing .netrc file: %s", e)
Gavin Makea2e3302023-03-11 06:46:20 +00001395
1396 if username and password:
1397 manifest_server = manifest_server.replace(
1398 "://", "://%s:%s@" % (username, password), 1
1399 )
1400
1401 transport = PersistentTransport(manifest_server)
1402 if manifest_server.startswith("persistent-"):
1403 manifest_server = manifest_server[len("persistent-") :]
1404
1405 try:
1406 server = xmlrpc.client.Server(manifest_server, transport=transport)
1407 if opt.smart_sync:
1408 branch = self._GetBranch(manifest.manifestProject)
1409
1410 if "SYNC_TARGET" in os.environ:
1411 target = os.environ["SYNC_TARGET"]
1412 [success, manifest_str] = server.GetApprovedManifest(
1413 branch, target
1414 )
1415 elif (
1416 "TARGET_PRODUCT" in os.environ
1417 and "TARGET_BUILD_VARIANT" in os.environ
1418 ):
1419 target = "%s-%s" % (
1420 os.environ["TARGET_PRODUCT"],
1421 os.environ["TARGET_BUILD_VARIANT"],
1422 )
1423 [success, manifest_str] = server.GetApprovedManifest(
1424 branch, target
1425 )
1426 else:
1427 [success, manifest_str] = server.GetApprovedManifest(branch)
1428 else:
1429 assert opt.smart_tag
1430 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1431
1432 if success:
1433 manifest_name = os.path.basename(smart_sync_manifest_path)
1434 try:
1435 with open(smart_sync_manifest_path, "w") as f:
1436 f.write(manifest_str)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451437 except OSError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001438 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001439 "error: cannot write manifest to %s:\n%s"
1440 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001441 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001442 )
Gavin Makea2e3302023-03-11 06:46:20 +00001443 self._ReloadManifest(manifest_name, manifest)
1444 else:
Jason Chang32b59562023-07-14 16:45:35 -07001445 raise SmartSyncError(
1446 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001447 )
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451448 except (OSError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001449 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001450 "error: cannot connect to manifest server %s:\n%s"
1451 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001452 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001453 )
Gavin Makea2e3302023-03-11 06:46:20 +00001454 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001455 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001456 "error: cannot connect to manifest server %s:\n%d %s"
1457 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001458 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001459 )
Gavin Makea2e3302023-03-11 06:46:20 +00001460
1461 return manifest_name
1462
Jason Changdaf2ad32023-08-31 17:06:36 -07001463 def _UpdateAllManifestProjects(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001464 """Fetch & update the local manifest project.
1465
1466 After syncing the manifest project, if the manifest has any sub
1467 manifests, those are recursively processed.
1468
1469 Args:
1470 opt: Program options returned from optparse. See _Options().
1471 mp: the manifestProject to query.
1472 manifest_name: Manifest file to be reloaded.
1473 """
1474 if not mp.standalone_manifest_url:
Jason Changdaf2ad32023-08-31 17:06:36 -07001475 self._UpdateManifestProject(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001476
1477 if mp.manifest.submanifests:
1478 for submanifest in mp.manifest.submanifests.values():
1479 child = submanifest.repo_client.manifest
1480 child.manifestProject.SyncWithPossibleInit(
1481 submanifest,
1482 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1483 verbose=opt.verbose,
1484 tags=opt.tags,
1485 git_event_log=self.git_event_log,
1486 )
1487 self._UpdateAllManifestProjects(
Jason Changdaf2ad32023-08-31 17:06:36 -07001488 opt, child.manifestProject, None, errors
Gavin Makea2e3302023-03-11 06:46:20 +00001489 )
1490
Jason Changdaf2ad32023-08-31 17:06:36 -07001491 def _UpdateManifestProject(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001492 """Fetch & update the local manifest project.
1493
1494 Args:
1495 opt: Program options returned from optparse. See _Options().
1496 mp: the manifestProject to query.
1497 manifest_name: Manifest file to be reloaded.
1498 """
1499 if not opt.local_only:
1500 start = time.time()
Jason Changdaf2ad32023-08-31 17:06:36 -07001501 buf = TeeStringIO(sys.stdout)
1502 try:
1503 result = mp.Sync_NetworkHalf(
1504 quiet=opt.quiet,
1505 output_redir=buf,
1506 verbose=opt.verbose,
1507 current_branch_only=self._GetCurrentBranchOnly(
1508 opt, mp.manifest
1509 ),
1510 force_sync=opt.force_sync,
1511 tags=opt.tags,
1512 optimized_fetch=opt.optimized_fetch,
1513 retry_fetches=opt.retry_fetches,
1514 submodules=mp.manifest.HasSubmodules,
1515 clone_filter=mp.manifest.CloneFilter,
1516 partial_clone_exclude=mp.manifest.PartialCloneExclude,
1517 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
1518 )
1519 if result.error:
1520 errors.append(result.error)
1521 except KeyboardInterrupt:
1522 errors.append(
1523 ManifestInterruptError(buf.getvalue(), project=mp.name)
1524 )
1525 raise
1526
Gavin Makea2e3302023-03-11 06:46:20 +00001527 finish = time.time()
1528 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001529 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001530 )
1531
1532 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001533 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001534 syncbuf = SyncBuffer(mp.config)
1535 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001536 mp.Sync_LocalHalf(
1537 syncbuf, submodules=mp.manifest.HasSubmodules, errors=errors
1538 )
Gavin Makea2e3302023-03-11 06:46:20 +00001539 clean = syncbuf.Finish()
1540 self.event_log.AddSync(
1541 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1542 )
1543 if not clean:
Jason Chang32b59562023-07-14 16:45:35 -07001544 raise UpdateManifestError(
1545 aggregate_errors=errors, project=mp.name
1546 )
Gavin Makea2e3302023-03-11 06:46:20 +00001547 self._ReloadManifest(manifest_name, mp.manifest)
1548
1549 def ValidateOptions(self, opt, args):
1550 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001551 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001552 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001553 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001554 )
1555 if opt.network_only and opt.detach_head:
1556 self.OptionParser.error("cannot combine -n and -d")
1557 if opt.network_only and opt.local_only:
1558 self.OptionParser.error("cannot combine -n and -l")
1559 if opt.manifest_name and opt.smart_sync:
1560 self.OptionParser.error("cannot combine -m and -s")
1561 if opt.manifest_name and opt.smart_tag:
1562 self.OptionParser.error("cannot combine -m and -t")
1563 if opt.manifest_server_username or opt.manifest_server_password:
1564 if not (opt.smart_sync or opt.smart_tag):
1565 self.OptionParser.error(
1566 "-u and -p may only be combined with -s or -t"
1567 )
1568 if None in [
1569 opt.manifest_server_username,
1570 opt.manifest_server_password,
1571 ]:
1572 self.OptionParser.error("both -u and -p must be given")
1573
1574 if opt.prune is None:
1575 opt.prune = True
1576
1577 if opt.auto_gc is None and _AUTO_GC:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001578 logger.error(
1579 "Will run `git gc --auto` because %s is set. %s is deprecated "
1580 "and will be removed in a future release. Use `--auto-gc` "
1581 "instead.",
1582 _REPO_AUTO_GC,
1583 _REPO_AUTO_GC,
Gavin Makea2e3302023-03-11 06:46:20 +00001584 )
1585 opt.auto_gc = True
1586
1587 def _ValidateOptionsWithManifest(self, opt, mp):
1588 """Like ValidateOptions, but after we've updated the manifest.
1589
1590 Needed to handle sync-xxx option defaults in the manifest.
1591
1592 Args:
1593 opt: The options to process.
1594 mp: The manifest project to pull defaults from.
1595 """
1596 if not opt.jobs:
1597 # If the user hasn't made a choice, use the manifest value.
1598 opt.jobs = mp.manifest.default.sync_j
1599 if opt.jobs:
1600 # If --jobs has a non-default value, propagate it as the default for
1601 # --jobs-xxx flags too.
1602 if not opt.jobs_network:
1603 opt.jobs_network = opt.jobs
1604 if not opt.jobs_checkout:
1605 opt.jobs_checkout = opt.jobs
1606 else:
1607 # Neither user nor manifest have made a choice, so setup defaults.
1608 if not opt.jobs_network:
1609 opt.jobs_network = 1
1610 if not opt.jobs_checkout:
1611 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1612 opt.jobs = os.cpu_count()
1613
1614 # Try to stay under user rlimit settings.
1615 #
1616 # Since each worker requires at 3 file descriptors to run `git fetch`,
1617 # use that to scale down the number of jobs. Unfortunately there isn't
1618 # an easy way to determine this reliably as systems change, but it was
1619 # last measured by hand in 2011.
1620 soft_limit, _ = _rlimit_nofile()
1621 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1622 opt.jobs = min(opt.jobs, jobs_soft_limit)
1623 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1624 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1625
1626 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001627 errors = []
1628 try:
1629 self._ExecuteHelper(opt, args, errors)
1630 except RepoExitError:
1631 raise
1632 except (KeyboardInterrupt, Exception) as e:
1633 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1634
1635 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001636 manifest = self.outer_manifest
1637 if not opt.outer_manifest:
1638 manifest = self.manifest
1639
1640 if opt.manifest_name:
1641 manifest.Override(opt.manifest_name)
1642
1643 manifest_name = opt.manifest_name
1644 smart_sync_manifest_path = os.path.join(
1645 manifest.manifestProject.worktree, "smart_sync_override.xml"
1646 )
1647
1648 if opt.clone_bundle is None:
1649 opt.clone_bundle = manifest.CloneBundle
1650
1651 if opt.smart_sync or opt.smart_tag:
1652 manifest_name = self._SmartSyncSetup(
1653 opt, smart_sync_manifest_path, manifest
1654 )
1655 else:
1656 if os.path.isfile(smart_sync_manifest_path):
1657 try:
1658 platform_utils.remove(smart_sync_manifest_path)
1659 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001660 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001661 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001662 "manifest: %s",
1663 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001664 )
1665
1666 err_event = multiprocessing.Event()
1667
1668 rp = manifest.repoProject
1669 rp.PreSync()
1670 cb = rp.CurrentBranch
1671 if cb:
1672 base = rp.GetBranch(cb).merge
1673 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001674 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001675 "warning: repo is not tracking a remote branch, so it will "
1676 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001677 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001678 )
1679
1680 for m in self.ManifestList(opt):
1681 if not m.manifestProject.standalone_manifest_url:
1682 m.manifestProject.PreSync()
1683
1684 if opt.repo_upgraded:
1685 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1686
1687 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001688
1689 if _REPO_ALLOW_SHALLOW is not None:
1690 if _REPO_ALLOW_SHALLOW == "1":
1691 mp.ConfigureCloneFilterForDepth(None)
1692 elif (
1693 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1694 ):
1695 mp.ConfigureCloneFilterForDepth("blob:none")
1696
Gavin Makea2e3302023-03-11 06:46:20 +00001697 if opt.mp_update:
Jason Changdaf2ad32023-08-31 17:06:36 -07001698 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001699 else:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001700 print("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001701
1702 # Now that the manifests are up-to-date, setup options whose defaults
1703 # might be in the manifest.
1704 self._ValidateOptionsWithManifest(opt, mp)
1705
1706 superproject_logging_data = {}
1707 self._UpdateProjectsRevisionId(
1708 opt, args, superproject_logging_data, manifest
1709 )
1710
Gavin Makea2e3302023-03-11 06:46:20 +00001711 all_projects = self.GetProjects(
1712 args,
1713 missing_ok=True,
1714 submodules_ok=opt.fetch_submodules,
1715 manifest=manifest,
1716 all_manifests=not opt.this_manifest_only,
1717 )
1718
1719 err_network_sync = False
1720 err_update_projects = False
1721 err_update_linkfiles = False
1722
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001723 # Log the repo projects by existing and new.
1724 existing = [x for x in all_projects if x.Exists]
1725 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1726 mp.config.SetString(
1727 "repo.newprojectcount", str(len(all_projects) - len(existing))
1728 )
1729
Gavin Makea2e3302023-03-11 06:46:20 +00001730 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001731 self._local_sync_state = LocalSyncState(manifest)
Gavin Makea2e3302023-03-11 06:46:20 +00001732 if not opt.local_only:
1733 with multiprocessing.Manager() as manager:
1734 with ssh.ProxyManager(manager) as ssh_proxy:
1735 # Initialize the socket dir once in the parent.
1736 ssh_proxy.sock()
1737 result = self._FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -07001738 opt,
1739 args,
1740 all_projects,
1741 err_event,
1742 ssh_proxy,
1743 manifest,
1744 errors,
Gavin Makea2e3302023-03-11 06:46:20 +00001745 )
1746 all_projects = result.all_projects
1747
1748 if opt.network_only:
1749 return
1750
1751 # If we saw an error, exit with code 1 so that other scripts can
1752 # check.
1753 if err_event.is_set():
1754 err_network_sync = True
1755 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001756 logger.error(
1757 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001758 "Local checkouts *not* updated. Resolve network issues "
1759 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001760 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001761 )
Jason Chang32b59562023-07-14 16:45:35 -07001762 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001763
1764 for m in self.ManifestList(opt):
1765 if m.IsMirror or m.IsArchive:
1766 # Bail out now, we have no working tree.
1767 continue
1768
Jason Chang32b59562023-07-14 16:45:35 -07001769 try:
1770 self.UpdateProjectList(opt, m)
1771 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001772 err_event.set()
1773 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001774 errors.append(e)
1775 if isinstance(e, DeleteWorktreeError):
1776 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001777 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001778 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001779 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001780
Jason Chang32b59562023-07-14 16:45:35 -07001781 err_update_linkfiles = False
1782 try:
1783 self.UpdateCopyLinkfileList(m)
1784 except Exception as e:
1785 err_update_linkfiles = True
1786 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001787 err_event.set()
1788 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001789 logger.error(
1790 "error: Local update copyfile or linkfile failed."
Gavin Makea2e3302023-03-11 06:46:20 +00001791 )
Jason Chang32b59562023-07-14 16:45:35 -07001792 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001793
1794 err_results = []
1795 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001796 err_checkout = not self._Checkout(
1797 all_projects, opt, err_results, errors
1798 )
Gavin Makea2e3302023-03-11 06:46:20 +00001799 if err_checkout:
1800 err_event.set()
1801
1802 printed_notices = set()
1803 # If there's a notice that's supposed to print at the end of the sync,
1804 # print it now... But avoid printing duplicate messages, and preserve
1805 # order.
1806 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1807 if m.notice and m.notice not in printed_notices:
1808 print(m.notice)
1809 printed_notices.add(m.notice)
1810
1811 # If we saw an error, exit with code 1 so that other scripts can check.
1812 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001813
1814 def print_and_log(err_msg):
1815 self.git_event_log.ErrorEvent(err_msg)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001816 logger.error("%s", err_msg)
Josip Sokcevic131fc962023-05-12 17:00:46 -07001817
1818 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001819 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001820 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001821 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001822 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001823 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001824 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001825 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001826 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001827 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001828 # Don't log repositories, as it may contain sensitive info.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001829 logger.error("Failing repos:\n%s", "\n".join(err_results))
Josip Sokcevic131fc962023-05-12 17:00:46 -07001830 # Not useful to log.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001831 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001832 'Try re-running with "-j1 --fail-fast" to exit at the first '
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001833 "error."
Gavin Makea2e3302023-03-11 06:46:20 +00001834 )
Jason Chang32b59562023-07-14 16:45:35 -07001835 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001836
1837 # Log the previous sync analysis state from the config.
1838 self.git_event_log.LogDataConfigEvents(
1839 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1840 )
1841
1842 # Update and log with the new sync analysis state.
1843 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1844 self.git_event_log.LogDataConfigEvents(
1845 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1846 )
1847
Gavin Makf0aeb222023-08-08 04:43:36 +00001848 self._local_sync_state.PruneRemovedProjects()
1849 if self._local_sync_state.IsPartiallySynced():
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001850 logger.warning(
Gavin Makf0aeb222023-08-08 04:43:36 +00001851 "warning: Partial syncs are not supported. For the best "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001852 "experience, sync the entire tree."
Gavin Makf0aeb222023-08-08 04:43:36 +00001853 )
1854
Gavin Makea2e3302023-03-11 06:46:20 +00001855 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001856 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001857
David Pursehouse819827a2020-02-12 15:20:19 +09001858
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001859def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001860 # Link the docs for the internal .repo/ layout for people.
1861 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1862 if not platform_utils.islink(link):
1863 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1864 try:
1865 platform_utils.symlink(target, link)
1866 except Exception:
1867 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001868
Gavin Makea2e3302023-03-11 06:46:20 +00001869 wrapper = Wrapper()
1870 if wrapper.NeedSetupGnuPG():
1871 wrapper.SetupGnuPG(quiet)
1872 for project in manifest.projects:
1873 if project.Exists:
1874 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001875
David Pursehouse819827a2020-02-12 15:20:19 +09001876
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001877def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001878 if rp.HasChanges:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +00001879 logger.warning("info: A new version of repo is available")
Gavin Makea2e3302023-03-11 06:46:20 +00001880 wrapper = Wrapper()
1881 try:
1882 rev = rp.bare_git.describe(rp.GetRevisionId())
1883 except GitError:
1884 rev = None
1885 _, new_rev = wrapper.check_repo_rev(
1886 rp.gitdir, rev, repo_verify=repo_verify
1887 )
1888 # See if we're held back due to missing signed tag.
1889 current_revid = rp.bare_git.rev_parse("HEAD")
1890 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1891 if current_revid != new_revid:
1892 # We want to switch to the new rev, but also not trash any
1893 # uncommitted changes. This helps with local testing/hacking.
1894 # If a local change has been made, we will throw that away.
1895 # We also have to make sure this will switch to an older commit if
1896 # that's the latest tag in order to support release rollback.
1897 try:
1898 rp.work_git.reset("--keep", new_rev)
1899 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001900 raise RepoUnhandledExceptionError(e)
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001901 print("info: Restarting repo with latest version")
Gavin Makea2e3302023-03-11 06:46:20 +00001902 raise RepoChangedException(["--repo-upgraded"])
1903 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001904 logger.warning("warning: Skipped upgrade to unverified version")
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001905 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001906 if verbose:
Aravind Vasudevance0ed792023-10-06 18:36:22 +00001907 print("repo version %s is current" % rp.work_git.describe(HEAD))
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001908
David Pursehouse819827a2020-02-12 15:20:19 +09001909
Mike Frysingerd4aee652023-10-19 05:13:32 -04001910class _FetchTimes:
Gavin Makea2e3302023-03-11 06:46:20 +00001911 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07001912
Gavin Makea2e3302023-03-11 06:46:20 +00001913 def __init__(self, manifest):
1914 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00001915 self._saved = None
1916 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001917
Gavin Makea2e3302023-03-11 06:46:20 +00001918 def Get(self, project):
1919 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00001920 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07001921
Gavin Makea2e3302023-03-11 06:46:20 +00001922 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00001923 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00001924
1925 # For shared projects, save the longest time.
1926 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07001927
Gavin Makea2e3302023-03-11 06:46:20 +00001928 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001929 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001930 try:
1931 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001932 self._saved = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451933 except (OSError, ValueError):
Gavin Makea2e3302023-03-11 06:46:20 +00001934 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00001935 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001936
Gavin Makea2e3302023-03-11 06:46:20 +00001937 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001938 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001939 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001940
Gavin Mak041f9772023-05-10 20:41:12 +00001941 for name, t in self._seen.items():
1942 # Keep a moving average across the previous/current sync runs.
1943 old = self._saved.get(name, t)
1944 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07001945
Gavin Makea2e3302023-03-11 06:46:20 +00001946 try:
1947 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001948 json.dump(self._seen, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451949 except (OSError, TypeError):
Gavin Makea2e3302023-03-11 06:46:20 +00001950 platform_utils.remove(self._path, missing_ok=True)
1951
Dan Willemsen0745bb22015-08-17 13:41:45 -07001952
Mike Frysingerd4aee652023-10-19 05:13:32 -04001953class LocalSyncState:
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001954 _LAST_FETCH = "last_fetch"
1955 _LAST_CHECKOUT = "last_checkout"
1956
1957 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00001958 self._manifest = manifest
1959 self._path = os.path.join(
1960 self._manifest.repodir, ".repo_localsyncstate.json"
1961 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001962 self._time = time.time()
1963 self._state = None
1964 self._Load()
1965
1966 def SetFetchTime(self, project):
1967 self._Set(project, self._LAST_FETCH)
1968
1969 def SetCheckoutTime(self, project):
1970 self._Set(project, self._LAST_CHECKOUT)
1971
1972 def GetFetchTime(self, project):
1973 return self._Get(project, self._LAST_FETCH)
1974
1975 def GetCheckoutTime(self, project):
1976 return self._Get(project, self._LAST_CHECKOUT)
1977
1978 def _Get(self, project, key):
1979 self._Load()
1980 p = project.relpath
1981 if p not in self._state:
1982 return
1983 return self._state[p].get(key)
1984
1985 def _Set(self, project, key):
1986 p = project.relpath
1987 if p not in self._state:
1988 self._state[p] = {}
1989 self._state[p][key] = self._time
1990
1991 def _Load(self):
1992 if self._state is None:
1993 try:
1994 with open(self._path) as f:
1995 self._state = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451996 except (OSError, ValueError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001997 platform_utils.remove(self._path, missing_ok=True)
1998 self._state = {}
1999
2000 def Save(self):
2001 if not self._state:
2002 return
2003 try:
2004 with open(self._path, "w") as f:
2005 json.dump(self._state, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452006 except (OSError, TypeError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002007 platform_utils.remove(self._path, missing_ok=True)
2008
Gavin Makf0aeb222023-08-08 04:43:36 +00002009 def PruneRemovedProjects(self):
2010 """Remove entries don't exist on disk and save."""
2011 if not self._state:
2012 return
2013 delete = set()
2014 for path in self._state:
2015 gitdir = os.path.join(self._manifest.topdir, path, ".git")
2016 if not os.path.exists(gitdir):
2017 delete.add(path)
2018 if not delete:
2019 return
2020 for path in delete:
2021 del self._state[path]
2022 self.Save()
2023
2024 def IsPartiallySynced(self):
2025 """Return whether a partial sync state is detected."""
2026 self._Load()
2027 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002028 for path, data in self._state.items():
2029 if path == self._manifest.repoProject.relpath:
2030 # The repo project isn't included in most syncs so we should
2031 # ignore it here.
2032 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002033 checkout_t = data.get(self._LAST_CHECKOUT)
2034 if not checkout_t:
2035 return True
2036 prev_checkout_t = prev_checkout_t or checkout_t
2037 if prev_checkout_t != checkout_t:
2038 return True
2039 return False
2040
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002041
Dan Willemsen0745bb22015-08-17 13:41:45 -07002042# This is a replacement for xmlrpc.client.Transport using urllib2
2043# and supporting persistent-http[s]. It cannot change hosts from
2044# request to request like the normal transport, the real url
2045# is passed during initialization.
2046class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002047 def __init__(self, orig_host):
2048 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002049
Gavin Makea2e3302023-03-11 06:46:20 +00002050 def request(self, host, handler, request_body, verbose=False):
2051 with GetUrlCookieFile(self.orig_host, not verbose) as (
2052 cookiefile,
2053 proxy,
2054 ):
2055 # Python doesn't understand cookies with the #HttpOnly_ prefix
2056 # Since we're only using them for HTTP, copy the file temporarily,
2057 # stripping those prefixes away.
2058 if cookiefile:
2059 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2060 tmpcookiefile.write("# HTTP Cookie File")
2061 try:
2062 with open(cookiefile) as f:
2063 for line in f:
2064 if line.startswith("#HttpOnly_"):
2065 line = line[len("#HttpOnly_") :]
2066 tmpcookiefile.write(line)
2067 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002068
Gavin Makea2e3302023-03-11 06:46:20 +00002069 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2070 try:
2071 cookiejar.load()
2072 except cookielib.LoadError:
2073 cookiejar = cookielib.CookieJar()
2074 finally:
2075 tmpcookiefile.close()
2076 else:
2077 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002078
Gavin Makea2e3302023-03-11 06:46:20 +00002079 proxyhandler = urllib.request.ProxyHandler
2080 if proxy:
2081 proxyhandler = urllib.request.ProxyHandler(
2082 {"http": proxy, "https": proxy}
2083 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002084
Gavin Makea2e3302023-03-11 06:46:20 +00002085 opener = urllib.request.build_opener(
2086 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2087 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002088
Gavin Makea2e3302023-03-11 06:46:20 +00002089 url = urllib.parse.urljoin(self.orig_host, handler)
2090 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002091
Gavin Makea2e3302023-03-11 06:46:20 +00002092 scheme = parse_results.scheme
2093 if scheme == "persistent-http":
2094 scheme = "http"
2095 if scheme == "persistent-https":
2096 # If we're proxying through persistent-https, use http. The
2097 # proxy itself will do the https.
2098 if proxy:
2099 scheme = "http"
2100 else:
2101 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002102
Gavin Makea2e3302023-03-11 06:46:20 +00002103 # Parse out any authentication information using the base class.
2104 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002105
Gavin Makea2e3302023-03-11 06:46:20 +00002106 url = urllib.parse.urlunparse(
2107 (
2108 scheme,
2109 host,
2110 parse_results.path,
2111 parse_results.params,
2112 parse_results.query,
2113 parse_results.fragment,
2114 )
2115 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002116
Gavin Makea2e3302023-03-11 06:46:20 +00002117 request = urllib.request.Request(url, request_body)
2118 if extra_headers is not None:
2119 for name, header in extra_headers:
2120 request.add_header(name, header)
2121 request.add_header("Content-Type", "text/xml")
2122 try:
2123 response = opener.open(request)
2124 except urllib.error.HTTPError as e:
2125 if e.code == 501:
2126 # We may have been redirected through a login process
2127 # but our POST turned into a GET. Retry.
2128 response = opener.open(request)
2129 else:
2130 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002131
Gavin Makea2e3302023-03-11 06:46:20 +00002132 p, u = xmlrpc.client.getparser()
2133 # Response should be fairly small, so read it all at once.
2134 # This way we can show it to the user in case of error (e.g. HTML).
2135 data = response.read()
2136 try:
2137 p.feed(data)
2138 except xml.parsers.expat.ExpatError as e:
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452139 raise OSError(
Gavin Makea2e3302023-03-11 06:46:20 +00002140 f"Parsing the manifest failed: {e}\n"
2141 f"Please report this to your manifest server admin.\n"
2142 f'Here is the full response:\n{data.decode("utf-8")}'
2143 )
2144 p.close()
2145 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002146
Gavin Makea2e3302023-03-11 06:46:20 +00002147 def close(self):
2148 pass