blob: ac6a451b83a4970904bccc7f0f81a8ca0fbf0beb [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."""
Daniel Kutikb0430b52023-10-23 21:16:04 +0200188 ret = super().write(s)
Jason Changdaf2ad32023-08-31 17:06:36 -0700189 if self.io is not None:
190 self.io.write(s)
Daniel Kutikb0430b52023-10-23 21:16:04 +0200191 return ret
Jason Changdaf2ad32023-08-31 17:06:36 -0700192
193
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800194class Sync(Command, MirrorSafeCommand):
Gavin Makea2e3302023-03-11 06:46:20 +0000195 COMMON = True
196 MULTI_MANIFEST_SUPPORT = True
197 helpSummary = "Update working tree to the latest revision"
198 helpUsage = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700199%prog [...]
200"""
Gavin Makea2e3302023-03-11 06:46:20 +0000201 helpDescription = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700202The '%prog' command synchronizes local project directories
203with the remote repositories specified in the manifest. If a local
204project does not yet exist, it will clone a new local directory from
205the remote repository and set up tracking branches as specified in
206the manifest. If the local project already exists, '%prog'
207will update the remote branches and rebase any new local changes
208on top of the new remote changes.
209
210'%prog' will synchronize all projects listed at the command
211line. Projects can be specified either by name, or by a relative
212or absolute path to the project's local directory. If no projects
213are specified, '%prog' will synchronize all projects listed in
214the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700215
216The -d/--detach option can be used to switch specified projects
217back to the manifest revision. This option is especially helpful
218if the project is currently on a topic branch, but the manifest
219revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700220
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700221The -s/--smart-sync option can be used to sync to a known good
222build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200223manifest. The -t/--smart-tag option is similar and allows you to
224specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700225
David Pursehousecf76b1b2012-09-14 10:31:42 +0900226The -u/--manifest-server-username and -p/--manifest-server-password
227options can be used to specify a username and password to authenticate
228with the manifest server when using the -s or -t option.
229
230If -u and -p are not specified when using the -s or -t option, '%prog'
231will attempt to read authentication credentials for the manifest server
232from the user's .netrc file.
233
234'%prog' will not use authentication credentials from -u/-p or .netrc
235if the manifest server specified in the manifest file already includes
236credentials.
237
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400238By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400239to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500240
Kevin Degiabaa7f32014-11-12 11:27:45 -0700241The --force-sync option can be used to overwrite existing git
242directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900243object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700244refs may be removed when overwriting.
245
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500246The --force-remove-dirty option can be used to remove previously used
247projects with uncommitted changes. WARNING: This may cause data to be
248lost since uncommitted changes may be removed with projects that no longer
249exist in the manifest.
250
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700251The --no-clone-bundle option disables any attempt to use
252$URL/clone.bundle to bootstrap a new Git repository from a
253resumeable bundle file on a content delivery network. This
254may be necessary if there are problems with the local Python
255HTTP client or proxy configuration, but the Git binary works.
256
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800257The --fetch-submodules option enables fetching Git submodules
258of a project from server.
259
David Pursehousef2fad612015-01-29 14:36:28 +0900260The -c/--current-branch option can be used to only fetch objects that
261are on the branch specified by a project's revision.
262
David Pursehouseb1553542014-09-04 21:28:09 +0900263The --optimized-fetch option can be used to only fetch projects that
264are fixed to a sha1 revision if the sha1 revision does not already
265exist locally.
266
David Pursehouse74cfd272015-10-14 10:50:15 +0900267The --prune option can be used to remove any refs that no longer
268exist on the remote.
269
LaMont Jones7efab532022-09-01 15:41:12 +0000270The --auto-gc option can be used to trigger garbage collection on all
271projects. By default, repo does not run garbage collection.
272
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400273# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700274
275If at least one project remote URL uses an SSH connection (ssh://,
276git+ssh://, or user@host:path syntax) repo will automatically
277enable the SSH ControlMaster option when connecting to that host.
278This feature permits other projects in the same '%prog' session to
279reuse the same SSH tunnel, saving connection setup overheads.
280
281To disable this behavior on UNIX platforms, set the GIT_SSH
282environment variable to 'ssh'. For example:
283
284 export GIT_SSH=ssh
285 %prog
286
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400287# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700288
289This feature is automatically disabled on Windows, due to the lack
290of UNIX domain socket support.
291
292This feature is not compatible with url.insteadof rewrites in the
293user's ~/.gitconfig. '%prog' is currently not able to perform the
294rewrite early enough to establish the ControlMaster tunnel.
295
296If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
297later is required to fix a server side protocol bug.
298
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700299"""
Gavin Makea2e3302023-03-11 06:46:20 +0000300 # A value of 0 means we want parallel jobs, but we'll determine the default
301 # value later on.
302 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700303
Gavin Makea2e3302023-03-11 06:46:20 +0000304 def _Options(self, p, show_smart=True):
305 p.add_option(
306 "--jobs-network",
307 default=None,
308 type=int,
309 metavar="JOBS",
310 help="number of network jobs to run in parallel (defaults to "
311 "--jobs or 1)",
312 )
313 p.add_option(
314 "--jobs-checkout",
315 default=None,
316 type=int,
317 metavar="JOBS",
318 help="number of local checkout jobs to run in parallel (defaults "
319 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
320 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400321
Gavin Makea2e3302023-03-11 06:46:20 +0000322 p.add_option(
323 "-f",
324 "--force-broken",
325 dest="force_broken",
326 action="store_true",
327 help="obsolete option (to be deleted in the future)",
328 )
329 p.add_option(
330 "--fail-fast",
331 dest="fail_fast",
332 action="store_true",
333 help="stop syncing after first error is hit",
334 )
335 p.add_option(
336 "--force-sync",
337 dest="force_sync",
338 action="store_true",
339 help="overwrite an existing git directory if it needs to "
340 "point to a different object directory. WARNING: this "
341 "may cause loss of data",
342 )
343 p.add_option(
344 "--force-remove-dirty",
345 dest="force_remove_dirty",
346 action="store_true",
347 help="force remove projects with uncommitted modifications if "
348 "projects no longer exist in the manifest. "
349 "WARNING: this may cause loss of data",
350 )
351 p.add_option(
352 "-l",
353 "--local-only",
354 dest="local_only",
355 action="store_true",
356 help="only update working tree, don't fetch",
357 )
358 p.add_option(
359 "--no-manifest-update",
360 "--nmu",
361 dest="mp_update",
362 action="store_false",
363 default="true",
364 help="use the existing manifest checkout as-is. "
365 "(do not update to the latest revision)",
366 )
367 p.add_option(
368 "-n",
369 "--network-only",
370 dest="network_only",
371 action="store_true",
372 help="fetch only, don't update working tree",
373 )
374 p.add_option(
375 "-d",
376 "--detach",
377 dest="detach_head",
378 action="store_true",
379 help="detach projects back to manifest revision",
380 )
381 p.add_option(
382 "-c",
383 "--current-branch",
384 dest="current_branch_only",
385 action="store_true",
386 help="fetch only current branch from server",
387 )
388 p.add_option(
389 "--no-current-branch",
390 dest="current_branch_only",
391 action="store_false",
392 help="fetch all branches from server",
393 )
394 p.add_option(
395 "-m",
396 "--manifest-name",
397 dest="manifest_name",
398 help="temporary manifest to use for this sync",
399 metavar="NAME.xml",
400 )
401 p.add_option(
402 "--clone-bundle",
403 action="store_true",
404 help="enable use of /clone.bundle on HTTP/HTTPS",
405 )
406 p.add_option(
407 "--no-clone-bundle",
408 dest="clone_bundle",
409 action="store_false",
410 help="disable use of /clone.bundle on HTTP/HTTPS",
411 )
412 p.add_option(
413 "-u",
414 "--manifest-server-username",
415 action="store",
416 dest="manifest_server_username",
417 help="username to authenticate with the manifest server",
418 )
419 p.add_option(
420 "-p",
421 "--manifest-server-password",
422 action="store",
423 dest="manifest_server_password",
424 help="password to authenticate with the manifest server",
425 )
426 p.add_option(
427 "--fetch-submodules",
428 dest="fetch_submodules",
429 action="store_true",
430 help="fetch submodules from server",
431 )
432 p.add_option(
433 "--use-superproject",
434 action="store_true",
435 help="use the manifest superproject to sync projects; implies -c",
436 )
437 p.add_option(
438 "--no-use-superproject",
439 action="store_false",
440 dest="use_superproject",
441 help="disable use of manifest superprojects",
442 )
443 p.add_option("--tags", action="store_true", help="fetch tags")
444 p.add_option(
445 "--no-tags",
446 dest="tags",
447 action="store_false",
448 help="don't fetch tags (default)",
449 )
450 p.add_option(
451 "--optimized-fetch",
452 dest="optimized_fetch",
453 action="store_true",
454 help="only fetch projects fixed to sha1 if revision does not exist "
455 "locally",
456 )
457 p.add_option(
458 "--retry-fetches",
459 default=0,
460 action="store",
461 type="int",
462 help="number of times to retry fetches on transient errors",
463 )
464 p.add_option(
465 "--prune",
466 action="store_true",
467 help="delete refs that no longer exist on the remote (default)",
468 )
469 p.add_option(
470 "--no-prune",
471 dest="prune",
472 action="store_false",
473 help="do not delete refs that no longer exist on the remote",
474 )
475 p.add_option(
476 "--auto-gc",
477 action="store_true",
478 default=None,
479 help="run garbage collection on all synced projects",
480 )
481 p.add_option(
482 "--no-auto-gc",
483 dest="auto_gc",
484 action="store_false",
485 help="do not run garbage collection on any projects (default)",
486 )
487 if show_smart:
488 p.add_option(
489 "-s",
490 "--smart-sync",
491 dest="smart_sync",
492 action="store_true",
493 help="smart sync using manifest from the latest known good "
494 "build",
495 )
496 p.add_option(
497 "-t",
498 "--smart-tag",
499 dest="smart_tag",
500 action="store",
501 help="smart sync using manifest from a known tag",
502 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700503
Gavin Makea2e3302023-03-11 06:46:20 +0000504 g = p.add_option_group("repo Version options")
505 g.add_option(
506 "--no-repo-verify",
507 dest="repo_verify",
508 default=True,
509 action="store_false",
510 help="do not verify repo source code",
511 )
512 g.add_option(
513 "--repo-upgraded",
514 dest="repo_upgraded",
515 action="store_true",
Mike Frysinger06ddc8c2023-08-21 21:26:51 -0400516 help=optparse.SUPPRESS_HELP,
Gavin Makea2e3302023-03-11 06:46:20 +0000517 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700518
Gavin Makea2e3302023-03-11 06:46:20 +0000519 def _GetBranch(self, manifest_project):
520 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000521
Gavin Makea2e3302023-03-11 06:46:20 +0000522 Args:
523 manifest_project: The manifestProject to query.
524 """
525 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
526 branch = b.merge
527 if branch.startswith(R_HEADS):
528 branch = branch[len(R_HEADS) :]
529 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800530
Gavin Makea2e3302023-03-11 06:46:20 +0000531 def _GetCurrentBranchOnly(self, opt, manifest):
532 """Returns whether current-branch or use-superproject options are
533 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200534
Gavin Makea2e3302023-03-11 06:46:20 +0000535 Args:
536 opt: Program options returned from optparse. See _Options().
537 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000538
Gavin Makea2e3302023-03-11 06:46:20 +0000539 Returns:
540 True if a superproject is requested, otherwise the value of the
541 current_branch option (True, False or None).
542 """
543 return (
544 git_superproject.UseSuperproject(opt.use_superproject, manifest)
545 or opt.current_branch_only
546 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700547
Gavin Makea2e3302023-03-11 06:46:20 +0000548 def _UpdateProjectsRevisionId(
549 self, opt, args, superproject_logging_data, manifest
550 ):
551 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800552
Gavin Makea2e3302023-03-11 06:46:20 +0000553 This function updates each project's revisionId with the commit hash
554 from the superproject. It writes the updated manifest into a file and
555 reloads the manifest from it. When appropriate, sub manifests are also
556 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800557
Gavin Makea2e3302023-03-11 06:46:20 +0000558 Args:
559 opt: Program options returned from optparse. See _Options().
560 args: Arguments to pass to GetProjects. See the GetProjects
561 docstring for details.
562 superproject_logging_data: A dictionary of superproject data to log.
563 manifest: The manifest to use.
564 """
565 have_superproject = manifest.superproject or any(
566 m.superproject for m in manifest.all_children
567 )
568 if not have_superproject:
569 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000570
Gavin Makea2e3302023-03-11 06:46:20 +0000571 if opt.local_only and manifest.superproject:
572 manifest_path = manifest.superproject.manifest_path
573 if manifest_path:
574 self._ReloadManifest(manifest_path, manifest)
575 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700576
Gavin Makea2e3302023-03-11 06:46:20 +0000577 all_projects = self.GetProjects(
578 args,
579 missing_ok=True,
580 submodules_ok=opt.fetch_submodules,
581 manifest=manifest,
582 all_manifests=not opt.this_manifest_only,
583 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000584
Gavin Makea2e3302023-03-11 06:46:20 +0000585 per_manifest = collections.defaultdict(list)
586 if opt.this_manifest_only:
587 per_manifest[manifest.path_prefix] = all_projects
588 else:
589 for p in all_projects:
590 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000591
Gavin Makea2e3302023-03-11 06:46:20 +0000592 superproject_logging_data = {}
593 need_unload = False
594 for m in self.ManifestList(opt):
595 if m.path_prefix not in per_manifest:
596 continue
597 use_super = git_superproject.UseSuperproject(
598 opt.use_superproject, m
599 )
600 if superproject_logging_data:
601 superproject_logging_data["multimanifest"] = True
602 superproject_logging_data.update(
603 superproject=use_super,
604 haslocalmanifests=bool(m.HasLocalManifests),
605 hassuperprojecttag=bool(m.superproject),
606 )
607 if use_super and (m.IsMirror or m.IsArchive):
608 # Don't use superproject, because we have no working tree.
609 use_super = False
610 superproject_logging_data["superproject"] = False
611 superproject_logging_data["noworktree"] = True
612 if opt.use_superproject is not False:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000613 logger.warning(
614 "%s: not using superproject because there is no "
615 "working tree.",
616 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000617 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000618
Gavin Makea2e3302023-03-11 06:46:20 +0000619 if not use_super:
620 continue
621 m.superproject.SetQuiet(opt.quiet)
622 print_messages = git_superproject.PrintMessages(
623 opt.use_superproject, m
624 )
625 m.superproject.SetPrintMessages(print_messages)
626 update_result = m.superproject.UpdateProjectsRevisionId(
627 per_manifest[m.path_prefix], git_event_log=self.git_event_log
628 )
629 manifest_path = update_result.manifest_path
630 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
631 if manifest_path:
632 m.SetManifestOverride(manifest_path)
633 need_unload = True
634 else:
635 if print_messages:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000636 logger.warning(
637 "%s: warning: Update of revisionId from superproject "
638 "has failed, repo sync will not use superproject to "
639 "fetch the source. Please resync with the "
640 "--no-use-superproject option to avoid this repo "
641 "warning.",
642 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000643 )
644 if update_result.fatal and opt.use_superproject is not None:
Jason Chang32b59562023-07-14 16:45:35 -0700645 raise SuperprojectError()
Gavin Makea2e3302023-03-11 06:46:20 +0000646 if need_unload:
647 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800648
Gavin Makea2e3302023-03-11 06:46:20 +0000649 def _FetchProjectList(self, opt, projects):
650 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500651
Gavin Makea2e3302023-03-11 06:46:20 +0000652 The projects we're given share the same underlying git object store, so
653 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800654
Gavin Mak551285f2023-05-04 04:48:43 +0000655 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700656
Gavin Makea2e3302023-03-11 06:46:20 +0000657 Args:
658 opt: Program options returned from optparse. See _Options().
659 projects: Projects to fetch.
660 """
661 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700662
Gavin Makea2e3302023-03-11 06:46:20 +0000663 def _FetchOne(self, opt, project):
664 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700665
Gavin Makea2e3302023-03-11 06:46:20 +0000666 Args:
667 opt: Program options returned from optparse. See _Options().
668 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700669
Gavin Makea2e3302023-03-11 06:46:20 +0000670 Returns:
671 Whether the fetch was successful.
672 """
673 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000674 k = f"{project.name} @ {project.relpath}"
675 self._sync_dict[k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000676 success = False
677 remote_fetched = False
Jason Chang32b59562023-07-14 16:45:35 -0700678 errors = []
Jason Changdaf2ad32023-08-31 17:06:36 -0700679 buf = TeeStringIO(sys.stdout if opt.verbose else None)
Gavin Makea2e3302023-03-11 06:46:20 +0000680 try:
681 sync_result = project.Sync_NetworkHalf(
682 quiet=opt.quiet,
683 verbose=opt.verbose,
684 output_redir=buf,
685 current_branch_only=self._GetCurrentBranchOnly(
686 opt, project.manifest
687 ),
688 force_sync=opt.force_sync,
689 clone_bundle=opt.clone_bundle,
690 tags=opt.tags,
691 archive=project.manifest.IsArchive,
692 optimized_fetch=opt.optimized_fetch,
693 retry_fetches=opt.retry_fetches,
694 prune=opt.prune,
695 ssh_proxy=self.ssh_proxy,
696 clone_filter=project.manifest.CloneFilter,
697 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700698 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000699 )
700 success = sync_result.success
701 remote_fetched = sync_result.remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700702 if sync_result.error:
703 errors.append(sync_result.error)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700704
Gavin Makea2e3302023-03-11 06:46:20 +0000705 output = buf.getvalue()
Jason Changdaf2ad32023-08-31 17:06:36 -0700706 if output and buf.io is None and not success:
Gavin Makea2e3302023-03-11 06:46:20 +0000707 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700708
Gavin Makea2e3302023-03-11 06:46:20 +0000709 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000710 logger.error(
711 "error: Cannot fetch %s from %s",
712 project.name,
713 project.remote.url,
Gavin Makea2e3302023-03-11 06:46:20 +0000714 )
715 except KeyboardInterrupt:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000716 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000717 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000718 logger.error("error.GitError: Cannot fetch %s", e)
Jason Chang32b59562023-07-14 16:45:35 -0700719 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000720 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000721 logger.error(
722 "error: Cannot fetch %s (%s: %s)",
723 project.name,
724 type(e).__name__,
725 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000726 )
Gavin Mak551285f2023-05-04 04:48:43 +0000727 del self._sync_dict[k]
Jason Chang32b59562023-07-14 16:45:35 -0700728 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000729 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500730
Gavin Makea2e3302023-03-11 06:46:20 +0000731 finish = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000732 del self._sync_dict[k]
Jason Chang32b59562023-07-14 16:45:35 -0700733 return _FetchOneResult(
734 success, errors, project, start, finish, remote_fetched
735 )
David James8d201162013-10-11 17:03:19 -0700736
Gavin Makea2e3302023-03-11 06:46:20 +0000737 @classmethod
738 def _FetchInitChild(cls, ssh_proxy):
739 cls.ssh_proxy = ssh_proxy
Mike Frysinger339f2df2021-05-06 00:44:42 -0400740
Gavin Mak04cba4a2023-05-24 21:28:28 +0000741 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000742 earliest_time = float("inf")
743 earliest_proj = None
Gavin Mak945c0062023-05-30 20:04:07 +0000744 items = self._sync_dict.items()
745 for project, t in items:
Gavin Mak551285f2023-05-04 04:48:43 +0000746 if t < earliest_time:
747 earliest_time = t
748 earliest_proj = project
749
Josip Sokcevic71122f92023-05-26 02:44:37 +0000750 if not earliest_proj:
Gavin Mak945c0062023-05-30 20:04:07 +0000751 # This function is called when sync is still running but in some
752 # cases (by chance), _sync_dict can contain no entries. Return some
753 # text to indicate that sync is still working.
754 return "..working.."
Josip Sokcevic71122f92023-05-26 02:44:37 +0000755
Gavin Mak551285f2023-05-04 04:48:43 +0000756 elapsed = time.time() - earliest_time
Gavin Mak945c0062023-05-30 20:04:07 +0000757 jobs = jobs_str(len(items))
Gavin Mak04cba4a2023-05-24 21:28:28 +0000758 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000759
Jason Changdaf2ad32023-08-31 17:06:36 -0700760 def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
Gavin Makea2e3302023-03-11 06:46:20 +0000761 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500762
Gavin Makea2e3302023-03-11 06:46:20 +0000763 jobs = opt.jobs_network
764 fetched = set()
765 remote_fetched = set()
Gavin Makedcaa942023-04-27 05:58:57 +0000766 pm = Progress(
767 "Fetching",
768 len(projects),
769 delay=False,
770 quiet=opt.quiet,
771 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000772 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000773 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800774
Gavin Mak551285f2023-05-04 04:48:43 +0000775 self._sync_dict = multiprocessing.Manager().dict()
776 sync_event = _threading.Event()
777
778 def _MonitorSyncLoop():
779 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000780 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000781 if sync_event.wait(timeout=1):
782 return
783
784 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
785 sync_progress_thread.daemon = True
786 sync_progress_thread.start()
787
Gavin Makea2e3302023-03-11 06:46:20 +0000788 objdir_project_map = dict()
789 for project in projects:
790 objdir_project_map.setdefault(project.objdir, []).append(project)
791 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700792
Gavin Makea2e3302023-03-11 06:46:20 +0000793 def _ProcessResults(results_sets):
794 ret = True
795 for results in results_sets:
796 for result in results:
797 success = result.success
798 project = result.project
799 start = result.start
800 finish = result.finish
801 self._fetch_times.Set(project, finish - start)
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000802 self._local_sync_state.SetFetchTime(project)
Gavin Makea2e3302023-03-11 06:46:20 +0000803 self.event_log.AddSync(
804 project,
805 event_log.TASK_SYNC_NETWORK,
806 start,
807 finish,
808 success,
809 )
Jason Chang32b59562023-07-14 16:45:35 -0700810 if result.errors:
811 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000812 if result.remote_fetched:
813 remote_fetched.add(project)
814 # Check for any errors before running any more tasks.
815 # ...we'll let existing jobs finish, though.
816 if not success:
817 ret = False
818 else:
819 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000820 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000821 if not ret and opt.fail_fast:
822 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500823 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700824
Gavin Makea2e3302023-03-11 06:46:20 +0000825 # We pass the ssh proxy settings via the class. This allows
826 # multiprocessing to pickle it up when spawning children. We can't pass
827 # it as an argument to _FetchProjectList below as multiprocessing is
828 # unable to pickle those.
829 Sync.ssh_proxy = None
Mike Frysingerebf04a42021-02-23 20:48:04 -0500830
Gavin Makea2e3302023-03-11 06:46:20 +0000831 # NB: Multiprocessing is heavy, so don't spin it up for one job.
832 if len(projects_list) == 1 or jobs == 1:
833 self._FetchInitChild(ssh_proxy)
834 if not _ProcessResults(
835 self._FetchProjectList(opt, x) for x in projects_list
836 ):
837 ret = False
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000838 else:
Gavin Makea2e3302023-03-11 06:46:20 +0000839 # Favor throughput over responsiveness when quiet. It seems that
840 # imap() will yield results in batches relative to chunksize, so
841 # even as the children finish a sync, we won't see the result until
842 # one child finishes ~chunksize jobs. When using a large --jobs
843 # with large chunksize, this can be jarring as there will be a large
844 # initial delay where repo looks like it isn't doing anything and
845 # sits at 0%, but then suddenly completes a lot of jobs all at once.
846 # Since this code is more network bound, we can accept a bit more
847 # CPU overhead with a smaller chunksize so that the user sees more
848 # immediate & continuous feedback.
849 if opt.quiet:
850 chunksize = WORKER_BATCH_SIZE
851 else:
852 pm.update(inc=0, msg="warming up")
853 chunksize = 4
854 with multiprocessing.Pool(
855 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)
856 ) as pool:
857 results = pool.imap_unordered(
858 functools.partial(self._FetchProjectList, opt),
859 projects_list,
860 chunksize=chunksize,
861 )
862 if not _ProcessResults(results):
863 ret = False
864 pool.close()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000865
Gavin Makea2e3302023-03-11 06:46:20 +0000866 # Cleanup the reference now that we're done with it, and we're going to
867 # release any resources it points to. If we don't, later
868 # multiprocessing usage (e.g. checkouts) will try to pickle and then
869 # crash.
870 del Sync.ssh_proxy
LaMont Jones7efab532022-09-01 15:41:12 +0000871
Gavin Mak551285f2023-05-04 04:48:43 +0000872 sync_event.set()
Gavin Makea2e3302023-03-11 06:46:20 +0000873 pm.end()
874 self._fetch_times.Save()
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000875 self._local_sync_state.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000876
Gavin Makea2e3302023-03-11 06:46:20 +0000877 if not self.outer_client.manifest.IsArchive:
878 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400879
Jason Changdaf2ad32023-08-31 17:06:36 -0700880 return _FetchResult(ret, fetched)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000881
Gavin Makea2e3302023-03-11 06:46:20 +0000882 def _FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -0700883 self, opt, args, all_projects, err_event, ssh_proxy, manifest, errors
Gavin Makea2e3302023-03-11 06:46:20 +0000884 ):
885 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400886
Gavin Makea2e3302023-03-11 06:46:20 +0000887 Args:
888 opt: Program options returned from optparse. See _Options().
889 args: Command line args used to filter out projects.
890 all_projects: List of all projects that should be fetched.
891 err_event: Whether an error was hit while processing.
892 ssh_proxy: SSH manager for clients & masters.
893 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700894
Gavin Makea2e3302023-03-11 06:46:20 +0000895 Returns:
896 List of all projects that should be checked out.
897 """
898 rp = manifest.repoProject
LaMont Jones891e8f72022-09-08 20:17:58 +0000899
Gavin Makea2e3302023-03-11 06:46:20 +0000900 to_fetch = []
901 now = time.time()
902 if _ONE_DAY_S <= (now - rp.LastFetch):
903 to_fetch.append(rp)
904 to_fetch.extend(all_projects)
905 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700906
Jason Changdaf2ad32023-08-31 17:06:36 -0700907 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000908 success = result.success
909 fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700910
Gavin Makea2e3302023-03-11 06:46:20 +0000911 if not success:
912 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700913
Gavin Makea2e3302023-03-11 06:46:20 +0000914 _PostRepoFetch(rp, opt.repo_verify)
915 if opt.network_only:
916 # Bail out now; the rest touches the working tree.
917 if err_event.is_set():
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000918 e = SyncError(
Jason Chang32b59562023-07-14 16:45:35 -0700919 "error: Exited sync due to fetch errors.",
920 aggregate_errors=errors,
921 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000922
923 logger.error(e)
924 raise e
Jason Changdaf2ad32023-08-31 17:06:36 -0700925 return _FetchMainResult([])
Dave Borowitz18857212012-10-23 17:02:59 -0700926
Gavin Makea2e3302023-03-11 06:46:20 +0000927 # Iteratively fetch missing and/or nested unregistered submodules.
928 previously_missing_set = set()
929 while True:
930 self._ReloadManifest(None, manifest)
931 all_projects = self.GetProjects(
932 args,
933 missing_ok=True,
934 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000935 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000936 all_manifests=not opt.this_manifest_only,
937 )
938 missing = []
939 for project in all_projects:
940 if project.gitdir not in fetched:
941 missing.append(project)
942 if not missing:
943 break
944 # Stop us from non-stopped fetching actually-missing repos: If set
945 # of missing repos has not been changed from last fetch, we break.
Jason R. Coombs0bcffd82023-10-20 23:29:42 +0545946 missing_set = {p.name for p in missing}
Gavin Makea2e3302023-03-11 06:46:20 +0000947 if previously_missing_set == missing_set:
948 break
949 previously_missing_set = missing_set
Jason Changdaf2ad32023-08-31 17:06:36 -0700950 result = self._Fetch(missing, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000951 success = result.success
952 new_fetched = result.projects
953 if not success:
954 err_event.set()
955 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700956
Jason Changdaf2ad32023-08-31 17:06:36 -0700957 return _FetchMainResult(all_projects)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700958
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -0800959 def _CheckoutOne(self, detach_head, force_sync, verbose, project):
Gavin Makea2e3302023-03-11 06:46:20 +0000960 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +0800961
Gavin Makea2e3302023-03-11 06:46:20 +0000962 Args:
963 detach_head: Whether to leave a detached HEAD.
964 force_sync: Force checking out of the repo.
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -0800965 verbose: Whether to show verbose messages.
Gavin Makea2e3302023-03-11 06:46:20 +0000966 project: Project object for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +0800967
Gavin Makea2e3302023-03-11 06:46:20 +0000968 Returns:
969 Whether the fetch was successful.
970 """
971 start = time.time()
972 syncbuf = SyncBuffer(
973 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000974 )
Gavin Makea2e3302023-03-11 06:46:20 +0000975 success = False
Jason Chang32b59562023-07-14 16:45:35 -0700976 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +0900977 try:
Jason Chang32b59562023-07-14 16:45:35 -0700978 project.Sync_LocalHalf(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -0800979 syncbuf, force_sync=force_sync, errors=errors, verbose=verbose
Jason Chang32b59562023-07-14 16:45:35 -0700980 )
Gavin Makea2e3302023-03-11 06:46:20 +0000981 success = syncbuf.Finish()
982 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000983 logger.error(
984 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +0000985 )
Jason Chang32b59562023-07-14 16:45:35 -0700986 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000987 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000988 logger.error(
989 "error: Cannot checkout %s: %s: %s",
990 project.name,
991 type(e).__name__,
992 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000993 )
994 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700995
Gavin Makea2e3302023-03-11 06:46:20 +0000996 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000997 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000998 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700999 return _CheckoutOneResult(success, errors, project, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -04001000
Jason Chang32b59562023-07-14 16:45:35 -07001001 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001002 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001003
Gavin Makea2e3302023-03-11 06:46:20 +00001004 Args:
1005 all_projects: List of all projects that should be checked out.
1006 opt: Program options returned from optparse. See _Options().
1007 err_results: A list of strings, paths to git repos where checkout
1008 failed.
1009 """
1010 # Only checkout projects with worktrees.
1011 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001012
Gavin Makea2e3302023-03-11 06:46:20 +00001013 def _ProcessResults(pool, pm, results):
1014 ret = True
1015 for result in results:
1016 success = result.success
1017 project = result.project
1018 start = result.start
1019 finish = result.finish
1020 self.event_log.AddSync(
1021 project, event_log.TASK_SYNC_LOCAL, start, finish, success
1022 )
Jason Chang32b59562023-07-14 16:45:35 -07001023
1024 if result.errors:
1025 checkout_errors.extend(result.errors)
1026
Gavin Makea2e3302023-03-11 06:46:20 +00001027 # Check for any errors before running any more tasks.
1028 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001029 if success:
1030 self._local_sync_state.SetCheckoutTime(project)
1031 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001032 ret = False
1033 err_results.append(
1034 project.RelPath(local=opt.this_manifest_only)
1035 )
1036 if opt.fail_fast:
1037 if pool:
1038 pool.close()
1039 return ret
1040 pm.update(msg=project.name)
1041 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001042
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001043 proc_res = self.ExecuteInParallel(
1044 opt.jobs_checkout,
1045 functools.partial(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001046 self._CheckoutOne, opt.detach_head, opt.force_sync, opt.verbose
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001047 ),
1048 all_projects,
1049 callback=_ProcessResults,
1050 output=Progress("Checking out", len(all_projects), quiet=opt.quiet),
Gavin Makea2e3302023-03-11 06:46:20 +00001051 )
Simran Basib9a1b732015-08-20 12:19:28 -07001052
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001053 self._local_sync_state.Save()
1054 return proc_res and not err_results
1055
Gavin Makea2e3302023-03-11 06:46:20 +00001056 @staticmethod
1057 def _GetPreciousObjectsState(project: Project, opt):
1058 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001059
Gavin Makea2e3302023-03-11 06:46:20 +00001060 Args:
1061 project (Project): the project to examine, and possibly correct.
1062 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001063
Gavin Makea2e3302023-03-11 06:46:20 +00001064 Returns:
1065 Expected state of extensions.preciousObjects:
1066 False: Should be disabled. (not present)
1067 True: Should be enabled.
1068 """
1069 if project.use_git_worktrees:
1070 return False
1071 projects = project.manifest.GetProjectsWithName(
1072 project.name, all_manifests=True
1073 )
1074 if len(projects) == 1:
1075 return False
1076 if len(projects) > 1:
1077 # Objects are potentially shared with another project.
1078 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1079 # - When False, shared projects share (via symlink)
1080 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1081 # objects directory. All objects are precious, since there is no
1082 # project with a complete set of refs.
1083 # - When True, shared projects share (via info/alternates)
1084 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1085 # store, which is written only on the first clone of the project,
1086 # and is not written subsequently. (When Sync_NetworkHalf sees
1087 # that it exists, it makes sure that the alternates file points
1088 # there, and uses a project-local .git/objects directory for all
1089 # syncs going forward.
1090 # We do not support switching between the options. The environment
1091 # variable is present for testing and migration only.
1092 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001093
Gavin Makea2e3302023-03-11 06:46:20 +00001094 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001095
Gavin Makea2e3302023-03-11 06:46:20 +00001096 def _SetPreciousObjectsState(self, project: Project, opt):
1097 """Correct the preciousObjects state for the project.
1098
1099 Args:
1100 project: the project to examine, and possibly correct.
1101 opt: options given to sync.
1102 """
1103 expected = self._GetPreciousObjectsState(project, opt)
1104 actual = (
1105 project.config.GetBoolean("extensions.preciousObjects") or False
1106 )
1107 relpath = project.RelPath(local=opt.this_manifest_only)
1108
1109 if expected != actual:
1110 # If this is unexpected, log it and repair.
1111 Trace(
1112 f"{relpath} expected preciousObjects={expected}, got {actual}"
1113 )
1114 if expected:
1115 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001116 print(
1117 "\r%s: Shared project %s found, disabling pruning."
1118 % (relpath, project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001119 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001120
Gavin Makea2e3302023-03-11 06:46:20 +00001121 if git_require((2, 7, 0)):
1122 project.EnableRepositoryExtension("preciousObjects")
1123 else:
1124 # This isn't perfect, but it's the best we can do with old
1125 # git.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001126 logger.warning(
1127 "%s: WARNING: shared projects are unreliable when "
Gavin Makea2e3302023-03-11 06:46:20 +00001128 "using old versions of git; please upgrade to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001129 "git-2.7.0+.",
1130 relpath,
Gavin Makea2e3302023-03-11 06:46:20 +00001131 )
1132 project.config.SetString("gc.pruneExpire", "never")
1133 else:
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(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001292 verbose=opt.verbose, 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(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001398 "://", f"://{username}:{password}@", 1
Gavin Makea2e3302023-03-11 06:46:20 +00001399 )
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(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001537 syncbuf,
1538 submodules=mp.manifest.HasSubmodules,
1539 errors=errors,
1540 verbose=opt.verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001541 )
Gavin Makea2e3302023-03-11 06:46:20 +00001542 clean = syncbuf.Finish()
1543 self.event_log.AddSync(
1544 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1545 )
1546 if not clean:
Jason Chang32b59562023-07-14 16:45:35 -07001547 raise UpdateManifestError(
1548 aggregate_errors=errors, project=mp.name
1549 )
Gavin Makea2e3302023-03-11 06:46:20 +00001550 self._ReloadManifest(manifest_name, mp.manifest)
1551
1552 def ValidateOptions(self, opt, args):
1553 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001554 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001555 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001556 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001557 )
1558 if opt.network_only and opt.detach_head:
1559 self.OptionParser.error("cannot combine -n and -d")
1560 if opt.network_only and opt.local_only:
1561 self.OptionParser.error("cannot combine -n and -l")
1562 if opt.manifest_name and opt.smart_sync:
1563 self.OptionParser.error("cannot combine -m and -s")
1564 if opt.manifest_name and opt.smart_tag:
1565 self.OptionParser.error("cannot combine -m and -t")
1566 if opt.manifest_server_username or opt.manifest_server_password:
1567 if not (opt.smart_sync or opt.smart_tag):
1568 self.OptionParser.error(
1569 "-u and -p may only be combined with -s or -t"
1570 )
1571 if None in [
1572 opt.manifest_server_username,
1573 opt.manifest_server_password,
1574 ]:
1575 self.OptionParser.error("both -u and -p must be given")
1576
1577 if opt.prune is None:
1578 opt.prune = True
1579
1580 if opt.auto_gc is None and _AUTO_GC:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001581 logger.error(
1582 "Will run `git gc --auto` because %s is set. %s is deprecated "
1583 "and will be removed in a future release. Use `--auto-gc` "
1584 "instead.",
1585 _REPO_AUTO_GC,
1586 _REPO_AUTO_GC,
Gavin Makea2e3302023-03-11 06:46:20 +00001587 )
1588 opt.auto_gc = True
1589
1590 def _ValidateOptionsWithManifest(self, opt, mp):
1591 """Like ValidateOptions, but after we've updated the manifest.
1592
1593 Needed to handle sync-xxx option defaults in the manifest.
1594
1595 Args:
1596 opt: The options to process.
1597 mp: The manifest project to pull defaults from.
1598 """
1599 if not opt.jobs:
1600 # If the user hasn't made a choice, use the manifest value.
1601 opt.jobs = mp.manifest.default.sync_j
1602 if opt.jobs:
1603 # If --jobs has a non-default value, propagate it as the default for
1604 # --jobs-xxx flags too.
1605 if not opt.jobs_network:
1606 opt.jobs_network = opt.jobs
1607 if not opt.jobs_checkout:
1608 opt.jobs_checkout = opt.jobs
1609 else:
1610 # Neither user nor manifest have made a choice, so setup defaults.
1611 if not opt.jobs_network:
1612 opt.jobs_network = 1
1613 if not opt.jobs_checkout:
1614 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1615 opt.jobs = os.cpu_count()
1616
1617 # Try to stay under user rlimit settings.
1618 #
1619 # Since each worker requires at 3 file descriptors to run `git fetch`,
1620 # use that to scale down the number of jobs. Unfortunately there isn't
1621 # an easy way to determine this reliably as systems change, but it was
1622 # last measured by hand in 2011.
1623 soft_limit, _ = _rlimit_nofile()
1624 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1625 opt.jobs = min(opt.jobs, jobs_soft_limit)
1626 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1627 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1628
1629 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001630 errors = []
1631 try:
1632 self._ExecuteHelper(opt, args, errors)
1633 except RepoExitError:
1634 raise
1635 except (KeyboardInterrupt, Exception) as e:
1636 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1637
1638 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001639 manifest = self.outer_manifest
1640 if not opt.outer_manifest:
1641 manifest = self.manifest
1642
1643 if opt.manifest_name:
1644 manifest.Override(opt.manifest_name)
1645
1646 manifest_name = opt.manifest_name
1647 smart_sync_manifest_path = os.path.join(
1648 manifest.manifestProject.worktree, "smart_sync_override.xml"
1649 )
1650
1651 if opt.clone_bundle is None:
1652 opt.clone_bundle = manifest.CloneBundle
1653
1654 if opt.smart_sync or opt.smart_tag:
1655 manifest_name = self._SmartSyncSetup(
1656 opt, smart_sync_manifest_path, manifest
1657 )
1658 else:
1659 if os.path.isfile(smart_sync_manifest_path):
1660 try:
1661 platform_utils.remove(smart_sync_manifest_path)
1662 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001663 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001664 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001665 "manifest: %s",
1666 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001667 )
1668
1669 err_event = multiprocessing.Event()
1670
1671 rp = manifest.repoProject
1672 rp.PreSync()
1673 cb = rp.CurrentBranch
1674 if cb:
1675 base = rp.GetBranch(cb).merge
1676 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001677 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001678 "warning: repo is not tracking a remote branch, so it will "
1679 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001680 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001681 )
1682
1683 for m in self.ManifestList(opt):
1684 if not m.manifestProject.standalone_manifest_url:
1685 m.manifestProject.PreSync()
1686
1687 if opt.repo_upgraded:
1688 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1689
1690 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001691
1692 if _REPO_ALLOW_SHALLOW is not None:
1693 if _REPO_ALLOW_SHALLOW == "1":
1694 mp.ConfigureCloneFilterForDepth(None)
1695 elif (
1696 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1697 ):
1698 mp.ConfigureCloneFilterForDepth("blob:none")
1699
Gavin Makea2e3302023-03-11 06:46:20 +00001700 if opt.mp_update:
Jason Changdaf2ad32023-08-31 17:06:36 -07001701 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001702 else:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001703 print("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001704
1705 # Now that the manifests are up-to-date, setup options whose defaults
1706 # might be in the manifest.
1707 self._ValidateOptionsWithManifest(opt, mp)
1708
1709 superproject_logging_data = {}
1710 self._UpdateProjectsRevisionId(
1711 opt, args, superproject_logging_data, manifest
1712 )
1713
Gavin Makea2e3302023-03-11 06:46:20 +00001714 all_projects = self.GetProjects(
1715 args,
1716 missing_ok=True,
1717 submodules_ok=opt.fetch_submodules,
1718 manifest=manifest,
1719 all_manifests=not opt.this_manifest_only,
1720 )
1721
1722 err_network_sync = False
1723 err_update_projects = False
1724 err_update_linkfiles = False
1725
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001726 # Log the repo projects by existing and new.
1727 existing = [x for x in all_projects if x.Exists]
1728 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1729 mp.config.SetString(
1730 "repo.newprojectcount", str(len(all_projects) - len(existing))
1731 )
1732
Gavin Makea2e3302023-03-11 06:46:20 +00001733 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001734 self._local_sync_state = LocalSyncState(manifest)
Gavin Makea2e3302023-03-11 06:46:20 +00001735 if not opt.local_only:
1736 with multiprocessing.Manager() as manager:
1737 with ssh.ProxyManager(manager) as ssh_proxy:
1738 # Initialize the socket dir once in the parent.
1739 ssh_proxy.sock()
1740 result = self._FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -07001741 opt,
1742 args,
1743 all_projects,
1744 err_event,
1745 ssh_proxy,
1746 manifest,
1747 errors,
Gavin Makea2e3302023-03-11 06:46:20 +00001748 )
1749 all_projects = result.all_projects
1750
1751 if opt.network_only:
1752 return
1753
1754 # If we saw an error, exit with code 1 so that other scripts can
1755 # check.
1756 if err_event.is_set():
1757 err_network_sync = True
1758 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001759 logger.error(
1760 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001761 "Local checkouts *not* updated. Resolve network issues "
1762 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001763 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001764 )
Jason Chang32b59562023-07-14 16:45:35 -07001765 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001766
1767 for m in self.ManifestList(opt):
1768 if m.IsMirror or m.IsArchive:
1769 # Bail out now, we have no working tree.
1770 continue
1771
Jason Chang32b59562023-07-14 16:45:35 -07001772 try:
1773 self.UpdateProjectList(opt, m)
1774 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001775 err_event.set()
1776 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001777 errors.append(e)
1778 if isinstance(e, DeleteWorktreeError):
1779 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001780 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001781 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001782 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001783
Jason Chang32b59562023-07-14 16:45:35 -07001784 try:
1785 self.UpdateCopyLinkfileList(m)
1786 except Exception as e:
1787 err_update_linkfiles = True
1788 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001789 err_event.set()
1790 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001791 logger.error(
1792 "error: Local update copyfile or linkfile failed."
Gavin Makea2e3302023-03-11 06:46:20 +00001793 )
Jason Chang32b59562023-07-14 16:45:35 -07001794 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001795
1796 err_results = []
1797 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001798 err_checkout = not self._Checkout(
1799 all_projects, opt, err_results, errors
1800 )
Gavin Makea2e3302023-03-11 06:46:20 +00001801 if err_checkout:
1802 err_event.set()
1803
1804 printed_notices = set()
1805 # If there's a notice that's supposed to print at the end of the sync,
1806 # print it now... But avoid printing duplicate messages, and preserve
1807 # order.
1808 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1809 if m.notice and m.notice not in printed_notices:
1810 print(m.notice)
1811 printed_notices.add(m.notice)
1812
1813 # If we saw an error, exit with code 1 so that other scripts can check.
1814 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001815
1816 def print_and_log(err_msg):
1817 self.git_event_log.ErrorEvent(err_msg)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001818 logger.error("%s", err_msg)
Josip Sokcevic131fc962023-05-12 17:00:46 -07001819
1820 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001821 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001822 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001823 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001824 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001825 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001826 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001827 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001828 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001829 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001830 # Don't log repositories, as it may contain sensitive info.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001831 logger.error("Failing repos:\n%s", "\n".join(err_results))
Josip Sokcevic131fc962023-05-12 17:00:46 -07001832 # Not useful to log.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001833 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001834 'Try re-running with "-j1 --fail-fast" to exit at the first '
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001835 "error."
Gavin Makea2e3302023-03-11 06:46:20 +00001836 )
Jason Chang32b59562023-07-14 16:45:35 -07001837 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001838
1839 # Log the previous sync analysis state from the config.
1840 self.git_event_log.LogDataConfigEvents(
1841 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1842 )
1843
1844 # Update and log with the new sync analysis state.
1845 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1846 self.git_event_log.LogDataConfigEvents(
1847 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1848 )
1849
Gavin Makf0aeb222023-08-08 04:43:36 +00001850 self._local_sync_state.PruneRemovedProjects()
1851 if self._local_sync_state.IsPartiallySynced():
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001852 logger.warning(
Gavin Makf0aeb222023-08-08 04:43:36 +00001853 "warning: Partial syncs are not supported. For the best "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001854 "experience, sync the entire tree."
Gavin Makf0aeb222023-08-08 04:43:36 +00001855 )
1856
Gavin Makea2e3302023-03-11 06:46:20 +00001857 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001858 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001859
David Pursehouse819827a2020-02-12 15:20:19 +09001860
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001861def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001862 # Link the docs for the internal .repo/ layout for people.
1863 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1864 if not platform_utils.islink(link):
1865 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1866 try:
1867 platform_utils.symlink(target, link)
1868 except Exception:
1869 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001870
Gavin Makea2e3302023-03-11 06:46:20 +00001871 wrapper = Wrapper()
1872 if wrapper.NeedSetupGnuPG():
1873 wrapper.SetupGnuPG(quiet)
1874 for project in manifest.projects:
1875 if project.Exists:
1876 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001877
David Pursehouse819827a2020-02-12 15:20:19 +09001878
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001879def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001880 if rp.HasChanges:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +00001881 logger.warning("info: A new version of repo is available")
Gavin Makea2e3302023-03-11 06:46:20 +00001882 wrapper = Wrapper()
1883 try:
1884 rev = rp.bare_git.describe(rp.GetRevisionId())
1885 except GitError:
1886 rev = None
1887 _, new_rev = wrapper.check_repo_rev(
1888 rp.gitdir, rev, repo_verify=repo_verify
1889 )
1890 # See if we're held back due to missing signed tag.
1891 current_revid = rp.bare_git.rev_parse("HEAD")
1892 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1893 if current_revid != new_revid:
1894 # We want to switch to the new rev, but also not trash any
1895 # uncommitted changes. This helps with local testing/hacking.
1896 # If a local change has been made, we will throw that away.
1897 # We also have to make sure this will switch to an older commit if
1898 # that's the latest tag in order to support release rollback.
1899 try:
1900 rp.work_git.reset("--keep", new_rev)
1901 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001902 raise RepoUnhandledExceptionError(e)
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001903 print("info: Restarting repo with latest version")
Gavin Makea2e3302023-03-11 06:46:20 +00001904 raise RepoChangedException(["--repo-upgraded"])
1905 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001906 logger.warning("warning: Skipped upgrade to unverified version")
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001907 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001908 if verbose:
Aravind Vasudevance0ed792023-10-06 18:36:22 +00001909 print("repo version %s is current" % rp.work_git.describe(HEAD))
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001910
David Pursehouse819827a2020-02-12 15:20:19 +09001911
Mike Frysingerd4aee652023-10-19 05:13:32 -04001912class _FetchTimes:
Gavin Makea2e3302023-03-11 06:46:20 +00001913 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07001914
Gavin Makea2e3302023-03-11 06:46:20 +00001915 def __init__(self, manifest):
1916 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00001917 self._saved = None
1918 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001919
Gavin Makea2e3302023-03-11 06:46:20 +00001920 def Get(self, project):
1921 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00001922 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07001923
Gavin Makea2e3302023-03-11 06:46:20 +00001924 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00001925 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00001926
1927 # For shared projects, save the longest time.
1928 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07001929
Gavin Makea2e3302023-03-11 06:46:20 +00001930 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001931 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001932 try:
1933 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001934 self._saved = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451935 except (OSError, ValueError):
Gavin Makea2e3302023-03-11 06:46:20 +00001936 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00001937 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001938
Gavin Makea2e3302023-03-11 06:46:20 +00001939 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001940 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001941 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001942
Gavin Mak041f9772023-05-10 20:41:12 +00001943 for name, t in self._seen.items():
1944 # Keep a moving average across the previous/current sync runs.
1945 old = self._saved.get(name, t)
1946 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07001947
Gavin Makea2e3302023-03-11 06:46:20 +00001948 try:
1949 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001950 json.dump(self._seen, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451951 except (OSError, TypeError):
Gavin Makea2e3302023-03-11 06:46:20 +00001952 platform_utils.remove(self._path, missing_ok=True)
1953
Dan Willemsen0745bb22015-08-17 13:41:45 -07001954
Mike Frysingerd4aee652023-10-19 05:13:32 -04001955class LocalSyncState:
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001956 _LAST_FETCH = "last_fetch"
1957 _LAST_CHECKOUT = "last_checkout"
1958
1959 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00001960 self._manifest = manifest
1961 self._path = os.path.join(
1962 self._manifest.repodir, ".repo_localsyncstate.json"
1963 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001964 self._time = time.time()
1965 self._state = None
1966 self._Load()
1967
1968 def SetFetchTime(self, project):
1969 self._Set(project, self._LAST_FETCH)
1970
1971 def SetCheckoutTime(self, project):
1972 self._Set(project, self._LAST_CHECKOUT)
1973
1974 def GetFetchTime(self, project):
1975 return self._Get(project, self._LAST_FETCH)
1976
1977 def GetCheckoutTime(self, project):
1978 return self._Get(project, self._LAST_CHECKOUT)
1979
1980 def _Get(self, project, key):
1981 self._Load()
1982 p = project.relpath
1983 if p not in self._state:
1984 return
1985 return self._state[p].get(key)
1986
1987 def _Set(self, project, key):
1988 p = project.relpath
1989 if p not in self._state:
1990 self._state[p] = {}
1991 self._state[p][key] = self._time
1992
1993 def _Load(self):
1994 if self._state is None:
1995 try:
1996 with open(self._path) as f:
1997 self._state = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451998 except (OSError, ValueError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001999 platform_utils.remove(self._path, missing_ok=True)
2000 self._state = {}
2001
2002 def Save(self):
2003 if not self._state:
2004 return
2005 try:
2006 with open(self._path, "w") as f:
2007 json.dump(self._state, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452008 except (OSError, TypeError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002009 platform_utils.remove(self._path, missing_ok=True)
2010
Gavin Makf0aeb222023-08-08 04:43:36 +00002011 def PruneRemovedProjects(self):
2012 """Remove entries don't exist on disk and save."""
2013 if not self._state:
2014 return
2015 delete = set()
2016 for path in self._state:
2017 gitdir = os.path.join(self._manifest.topdir, path, ".git")
Matt Schulte0dd0a832023-11-30 11:00:16 -08002018 if not os.path.exists(gitdir) or os.path.islink(gitdir):
Gavin Makf0aeb222023-08-08 04:43:36 +00002019 delete.add(path)
2020 if not delete:
2021 return
2022 for path in delete:
2023 del self._state[path]
2024 self.Save()
2025
2026 def IsPartiallySynced(self):
2027 """Return whether a partial sync state is detected."""
2028 self._Load()
2029 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002030 for path, data in self._state.items():
2031 if path == self._manifest.repoProject.relpath:
2032 # The repo project isn't included in most syncs so we should
2033 # ignore it here.
2034 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002035 checkout_t = data.get(self._LAST_CHECKOUT)
2036 if not checkout_t:
2037 return True
2038 prev_checkout_t = prev_checkout_t or checkout_t
2039 if prev_checkout_t != checkout_t:
2040 return True
2041 return False
2042
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002043
Dan Willemsen0745bb22015-08-17 13:41:45 -07002044# This is a replacement for xmlrpc.client.Transport using urllib2
2045# and supporting persistent-http[s]. It cannot change hosts from
2046# request to request like the normal transport, the real url
2047# is passed during initialization.
2048class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002049 def __init__(self, orig_host):
Daniel Kutikb99272c2023-10-23 21:20:07 +02002050 super().__init__()
Gavin Makea2e3302023-03-11 06:46:20 +00002051 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002052
Gavin Makea2e3302023-03-11 06:46:20 +00002053 def request(self, host, handler, request_body, verbose=False):
2054 with GetUrlCookieFile(self.orig_host, not verbose) as (
2055 cookiefile,
2056 proxy,
2057 ):
2058 # Python doesn't understand cookies with the #HttpOnly_ prefix
2059 # Since we're only using them for HTTP, copy the file temporarily,
2060 # stripping those prefixes away.
2061 if cookiefile:
2062 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2063 tmpcookiefile.write("# HTTP Cookie File")
2064 try:
2065 with open(cookiefile) as f:
2066 for line in f:
2067 if line.startswith("#HttpOnly_"):
2068 line = line[len("#HttpOnly_") :]
2069 tmpcookiefile.write(line)
2070 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002071
Gavin Makea2e3302023-03-11 06:46:20 +00002072 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2073 try:
2074 cookiejar.load()
2075 except cookielib.LoadError:
2076 cookiejar = cookielib.CookieJar()
2077 finally:
2078 tmpcookiefile.close()
2079 else:
2080 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002081
Gavin Makea2e3302023-03-11 06:46:20 +00002082 proxyhandler = urllib.request.ProxyHandler
2083 if proxy:
2084 proxyhandler = urllib.request.ProxyHandler(
2085 {"http": proxy, "https": proxy}
2086 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002087
Gavin Makea2e3302023-03-11 06:46:20 +00002088 opener = urllib.request.build_opener(
2089 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2090 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002091
Gavin Makea2e3302023-03-11 06:46:20 +00002092 url = urllib.parse.urljoin(self.orig_host, handler)
2093 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002094
Gavin Makea2e3302023-03-11 06:46:20 +00002095 scheme = parse_results.scheme
2096 if scheme == "persistent-http":
2097 scheme = "http"
2098 if scheme == "persistent-https":
2099 # If we're proxying through persistent-https, use http. The
2100 # proxy itself will do the https.
2101 if proxy:
2102 scheme = "http"
2103 else:
2104 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002105
Gavin Makea2e3302023-03-11 06:46:20 +00002106 # Parse out any authentication information using the base class.
2107 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002108
Gavin Makea2e3302023-03-11 06:46:20 +00002109 url = urllib.parse.urlunparse(
2110 (
2111 scheme,
2112 host,
2113 parse_results.path,
2114 parse_results.params,
2115 parse_results.query,
2116 parse_results.fragment,
2117 )
2118 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002119
Gavin Makea2e3302023-03-11 06:46:20 +00002120 request = urllib.request.Request(url, request_body)
2121 if extra_headers is not None:
2122 for name, header in extra_headers:
2123 request.add_header(name, header)
2124 request.add_header("Content-Type", "text/xml")
2125 try:
2126 response = opener.open(request)
2127 except urllib.error.HTTPError as e:
2128 if e.code == 501:
2129 # We may have been redirected through a login process
2130 # but our POST turned into a GET. Retry.
2131 response = opener.open(request)
2132 else:
2133 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002134
Gavin Makea2e3302023-03-11 06:46:20 +00002135 p, u = xmlrpc.client.getparser()
2136 # Response should be fairly small, so read it all at once.
2137 # This way we can show it to the user in case of error (e.g. HTML).
2138 data = response.read()
2139 try:
2140 p.feed(data)
2141 except xml.parsers.expat.ExpatError as e:
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452142 raise OSError(
Gavin Makea2e3302023-03-11 06:46:20 +00002143 f"Parsing the manifest failed: {e}\n"
2144 f"Please report this to your manifest server admin.\n"
2145 f'Here is the full response:\n{data.decode("utf-8")}'
2146 )
2147 p.close()
2148 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002149
Gavin Makea2e3302023-03-11 06:46:20 +00002150 def close(self):
2151 pass