blob: bdb5616ca847c903e9dc9697852bb0f6d4bcd6b3 [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
LaMont Jonesbdcba7d2022-04-11 22:50:11 +000015import collections
Mike Frysingerebf04a42021-02-23 20:48:04 -050016import functools
Mike Frysingeracf63b22019-06-13 02:24:21 -040017import http.cookiejar as cookielib
Mike Frysinger7b586f22021-02-23 18:38:39 -050018import io
Anthony King85b24ac2014-05-06 15:57:48 +010019import json
Mike Frysingerebf04a42021-02-23 20:48:04 -050020import multiprocessing
David Pursehouse86d973d2012-08-24 10:21:02 +090021import netrc
Mike Frysinger06ddc8c2023-08-21 21:26:51 -040022import optparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import os
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070024import socket
Jason Changdaf2ad32023-08-31 17:06:36 -070025import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
Jason Changdaf2ad32023-08-31 17:06:36 -070028from typing import List, NamedTuple, Set, Union
Mike Frysingeracf63b22019-06-13 02:24:21 -040029import urllib.error
30import urllib.parse
31import urllib.request
Mike Frysinger5951e302022-05-20 23:34:44 -040032import xml.parsers.expat
Mike Frysingeracf63b22019-06-13 02:24:21 -040033import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Mike Frysinger64477332023-08-21 21:20:32 -040035
Roy Lee18afd7f2010-05-09 04:32:08 +080036try:
Gavin Makea2e3302023-03-11 06:46:20 +000037 import threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080038except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000039 import dummy_threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080040
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070041try:
Gavin Makea2e3302023-03-11 06:46:20 +000042 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090043
Gavin Makea2e3302023-03-11 06:46:20 +000044 def _rlimit_nofile():
45 return resource.getrlimit(resource.RLIMIT_NOFILE)
46
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070047except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000048
49 def _rlimit_nofile():
50 return (256, 256)
51
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070052
Mike Frysinger64477332023-08-21 21:20:32 -040053from command import Command
54from command import DEFAULT_LOCAL_JOBS
55from command import MirrorSafeCommand
56from command import WORKER_BATCH_SIZE
57from error import GitError
58from error import RepoChangedException
Jason Changdaf2ad32023-08-31 17:06:36 -070059from error import RepoError
Mike Frysinger64477332023-08-21 21:20:32 -040060from error import RepoExitError
61from error import RepoUnhandledExceptionError
62from error import SyncError
63from error import UpdateManifestError
David Rileye0684ad2017-04-05 00:02:59 -070064import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040065from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090066from git_config import GetUrlCookieFile
Mike Frysinger64477332023-08-21 21:20:32 -040067from git_refs import HEAD
68from git_refs import R_HEADS
Raman Tenneti6a872c92021-01-14 19:17:50 -080069import git_superproject
Mike Frysinger64477332023-08-21 21:20:32 -040070import platform_utils
71from progress import elapsed_str
72from progress import jobs_str
73from progress import Progress
74from project import DeleteWorktreeError
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070075from project import Project
76from project import RemoteSpec
Mike Frysinger64477332023-08-21 21:20:32 -040077from project import SyncBuffer
Aravind Vasudevane914ec22023-08-31 20:57:31 +000078from repo_logging import RepoLogger
Joanna Wanga6c52f52022-11-03 16:51:19 -040079from repo_trace import Trace
Mike Frysinger19e409c2021-05-05 19:44:35 -040080import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080081from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082
Mike Frysinger64477332023-08-21 21:20:32 -040083
Dave Borowitz67700e92012-10-23 15:00:54 -070084_ONE_DAY_S = 24 * 60 * 60
85
LaMont Jonesd7935532022-12-01 20:18:46 +000086# Env var to implicitly turn auto-gc back on. This was added to allow a user to
LaMont Jones100a2142022-12-02 22:54:11 +000087# revert a change in default behavior in v2.29.9. Remove after 2023-04-01.
Gavin Makea2e3302023-03-11 06:46:20 +000088_REPO_AUTO_GC = "REPO_AUTO_GC"
89_AUTO_GC = os.environ.get(_REPO_AUTO_GC) == "1"
LaMont Jones5ed8c632022-11-10 00:10:44 +000090
Jason Chang17833322023-05-23 13:06:55 -070091_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
92
Aravind Vasudevane914ec22023-08-31 20:57:31 +000093logger = RepoLogger(__file__)
94
David Pursehouse819827a2020-02-12 15:20:19 +090095
LaMont Jones1eddca82022-09-01 15:15:04 +000096class _FetchOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +000097 """_FetchOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +000098
Gavin Makea2e3302023-03-11 06:46:20 +000099 Attributes:
100 success (bool): True if successful.
101 project (Project): The fetched project.
102 start (float): The starting time.time().
103 finish (float): The ending time.time().
104 remote_fetched (bool): True if the remote was actually queried.
105 """
106
107 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700108 errors: List[Exception]
Gavin Makea2e3302023-03-11 06:46:20 +0000109 project: Project
110 start: float
111 finish: float
112 remote_fetched: bool
LaMont Jones1eddca82022-09-01 15:15:04 +0000113
114
115class _FetchResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000116 """_Fetch return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000117
Gavin Makea2e3302023-03-11 06:46:20 +0000118 Attributes:
119 success (bool): True if successful.
120 projects (Set[str]): The names of the git directories of fetched projects.
121 """
122
123 success: bool
124 projects: Set[str]
LaMont Jones1eddca82022-09-01 15:15:04 +0000125
126
127class _FetchMainResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000128 """_FetchMain return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000129
Gavin Makea2e3302023-03-11 06:46:20 +0000130 Attributes:
131 all_projects (List[Project]): The fetched projects.
132 """
133
134 all_projects: List[Project]
LaMont Jones1eddca82022-09-01 15:15:04 +0000135
136
137class _CheckoutOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000138 """_CheckoutOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000139
Gavin Makea2e3302023-03-11 06:46:20 +0000140 Attributes:
141 success (bool): True if successful.
142 project (Project): The project.
143 start (float): The starting time.time().
144 finish (float): The ending time.time().
145 """
146
147 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700148 errors: List[Exception]
Gavin Makea2e3302023-03-11 06:46:20 +0000149 project: Project
150 start: float
151 finish: float
LaMont Jones1eddca82022-09-01 15:15:04 +0000152
153
Jason Chang32b59562023-07-14 16:45:35 -0700154class SuperprojectError(SyncError):
155 """Superproject sync repo."""
156
157
158class SyncFailFastError(SyncError):
159 """Sync exit error when --fail-fast set."""
160
161
162class SmartSyncError(SyncError):
163 """Smart sync exit error."""
164
165
Jason Changdaf2ad32023-08-31 17:06:36 -0700166class ManifestInterruptError(RepoError):
167 """Aggregate Error to be logged when a user interrupts a manifest update."""
168
169 def __init__(self, output, **kwargs):
170 super().__init__(output, **kwargs)
171 self.output = output
172
173 def __str__(self):
174 error_type = type(self).__name__
175 return f"{error_type}:{self.output}"
176
177
178class TeeStringIO(io.StringIO):
179 """StringIO class that can write to an additional destination."""
180
181 def __init__(
182 self, io: Union[io.TextIOWrapper, None], *args, **kwargs
183 ) -> None:
184 super().__init__(*args, **kwargs)
185 self.io = io
186
187 def write(self, s: str) -> int:
188 """Write to additional destination."""
189 super().write(s)
190 if self.io is not None:
191 self.io.write(s)
192
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.
946 missing_set = set(p.name for p in missing)
947 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
Gavin Makea2e3302023-03-11 06:46:20 +0000959 def _CheckoutOne(self, detach_head, force_sync, project):
960 """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.
965 project: Project object for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +0800966
Gavin Makea2e3302023-03-11 06:46:20 +0000967 Returns:
968 Whether the fetch was successful.
969 """
970 start = time.time()
971 syncbuf = SyncBuffer(
972 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000973 )
Gavin Makea2e3302023-03-11 06:46:20 +0000974 success = False
Jason Chang32b59562023-07-14 16:45:35 -0700975 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +0900976 try:
Jason Chang32b59562023-07-14 16:45:35 -0700977 project.Sync_LocalHalf(
978 syncbuf, force_sync=force_sync, errors=errors
979 )
Gavin Makea2e3302023-03-11 06:46:20 +0000980 success = syncbuf.Finish()
981 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000982 logger.error(
983 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +0000984 )
Jason Chang32b59562023-07-14 16:45:35 -0700985 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000986 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000987 logger.error(
988 "error: Cannot checkout %s: %s: %s",
989 project.name,
990 type(e).__name__,
991 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000992 )
993 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700994
Gavin Makea2e3302023-03-11 06:46:20 +0000995 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000996 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000997 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700998 return _CheckoutOneResult(success, errors, project, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -0400999
Jason Chang32b59562023-07-14 16:45:35 -07001000 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001001 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001002
Gavin Makea2e3302023-03-11 06:46:20 +00001003 Args:
1004 all_projects: List of all projects that should be checked out.
1005 opt: Program options returned from optparse. See _Options().
1006 err_results: A list of strings, paths to git repos where checkout
1007 failed.
1008 """
1009 # Only checkout projects with worktrees.
1010 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001011
Gavin Makea2e3302023-03-11 06:46:20 +00001012 def _ProcessResults(pool, pm, results):
1013 ret = True
1014 for result in results:
1015 success = result.success
1016 project = result.project
1017 start = result.start
1018 finish = result.finish
1019 self.event_log.AddSync(
1020 project, event_log.TASK_SYNC_LOCAL, start, finish, success
1021 )
Jason Chang32b59562023-07-14 16:45:35 -07001022
1023 if result.errors:
1024 checkout_errors.extend(result.errors)
1025
Gavin Makea2e3302023-03-11 06:46:20 +00001026 # Check for any errors before running any more tasks.
1027 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001028 if success:
1029 self._local_sync_state.SetCheckoutTime(project)
1030 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001031 ret = False
1032 err_results.append(
1033 project.RelPath(local=opt.this_manifest_only)
1034 )
1035 if opt.fail_fast:
1036 if pool:
1037 pool.close()
1038 return ret
1039 pm.update(msg=project.name)
1040 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001041
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001042 proc_res = self.ExecuteInParallel(
1043 opt.jobs_checkout,
1044 functools.partial(
1045 self._CheckoutOne, opt.detach_head, opt.force_sync
1046 ),
1047 all_projects,
1048 callback=_ProcessResults,
1049 output=Progress("Checking out", len(all_projects), quiet=opt.quiet),
Gavin Makea2e3302023-03-11 06:46:20 +00001050 )
Simran Basib9a1b732015-08-20 12:19:28 -07001051
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001052 self._local_sync_state.Save()
1053 return proc_res and not err_results
1054
Gavin Makea2e3302023-03-11 06:46:20 +00001055 @staticmethod
1056 def _GetPreciousObjectsState(project: Project, opt):
1057 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001058
Gavin Makea2e3302023-03-11 06:46:20 +00001059 Args:
1060 project (Project): the project to examine, and possibly correct.
1061 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001062
Gavin Makea2e3302023-03-11 06:46:20 +00001063 Returns:
1064 Expected state of extensions.preciousObjects:
1065 False: Should be disabled. (not present)
1066 True: Should be enabled.
1067 """
1068 if project.use_git_worktrees:
1069 return False
1070 projects = project.manifest.GetProjectsWithName(
1071 project.name, all_manifests=True
1072 )
1073 if len(projects) == 1:
1074 return False
1075 if len(projects) > 1:
1076 # Objects are potentially shared with another project.
1077 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1078 # - When False, shared projects share (via symlink)
1079 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1080 # objects directory. All objects are precious, since there is no
1081 # project with a complete set of refs.
1082 # - When True, shared projects share (via info/alternates)
1083 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1084 # store, which is written only on the first clone of the project,
1085 # and is not written subsequently. (When Sync_NetworkHalf sees
1086 # that it exists, it makes sure that the alternates file points
1087 # there, and uses a project-local .git/objects directory for all
1088 # syncs going forward.
1089 # We do not support switching between the options. The environment
1090 # variable is present for testing and migration only.
1091 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001092
Gavin Makea2e3302023-03-11 06:46:20 +00001093 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001094
Gavin Makea2e3302023-03-11 06:46:20 +00001095 def _SetPreciousObjectsState(self, project: Project, opt):
1096 """Correct the preciousObjects state for the project.
1097
1098 Args:
1099 project: the project to examine, and possibly correct.
1100 opt: options given to sync.
1101 """
1102 expected = self._GetPreciousObjectsState(project, opt)
1103 actual = (
1104 project.config.GetBoolean("extensions.preciousObjects") or False
1105 )
1106 relpath = project.RelPath(local=opt.this_manifest_only)
1107
1108 if expected != actual:
1109 # If this is unexpected, log it and repair.
1110 Trace(
1111 f"{relpath} expected preciousObjects={expected}, got {actual}"
1112 )
1113 if expected:
1114 if not opt.quiet:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001115 logger.info(
1116 "%s: Shared project %s found, disabling pruning.",
1117 relpath,
1118 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:
1134 if not opt.quiet:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001135 logger.info("%s: not shared, disabling pruning.", relpath)
Gavin Makea2e3302023-03-11 06:46:20 +00001136 project.config.SetString("extensions.preciousObjects", None)
1137 project.config.SetString("gc.pruneExpire", None)
1138
1139 def _GCProjects(self, projects, opt, err_event):
1140 """Perform garbage collection.
1141
1142 If We are skipping garbage collection (opt.auto_gc not set), we still
1143 want to potentially mark objects precious, so that `git gc` does not
1144 discard shared objects.
1145 """
1146 if not opt.auto_gc:
1147 # Just repair preciousObjects state, and return.
1148 for project in projects:
1149 self._SetPreciousObjectsState(project, opt)
1150 return
1151
1152 pm = Progress(
1153 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1154 )
1155 pm.update(inc=0, msg="prescan")
1156
1157 tidy_dirs = {}
1158 for project in projects:
1159 self._SetPreciousObjectsState(project, opt)
1160
1161 project.config.SetString("gc.autoDetach", "false")
1162 # Only call git gc once per objdir, but call pack-refs for the
1163 # remainder.
1164 if project.objdir not in tidy_dirs:
1165 tidy_dirs[project.objdir] = (
1166 True, # Run a full gc.
1167 project.bare_git,
1168 )
1169 elif project.gitdir not in tidy_dirs:
1170 tidy_dirs[project.gitdir] = (
1171 False, # Do not run a full gc; just run pack-refs.
1172 project.bare_git,
1173 )
1174
1175 jobs = opt.jobs
1176
1177 if jobs < 2:
1178 for run_gc, bare_git in tidy_dirs.values():
1179 pm.update(msg=bare_git._project.name)
1180
1181 if run_gc:
1182 bare_git.gc("--auto")
1183 else:
1184 bare_git.pack_refs()
1185 pm.end()
1186 return
1187
1188 cpu_count = os.cpu_count()
1189 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1190
1191 threads = set()
1192 sem = _threading.Semaphore(jobs)
1193
1194 def tidy_up(run_gc, bare_git):
1195 pm.start(bare_git._project.name)
1196 try:
1197 try:
1198 if run_gc:
1199 bare_git.gc("--auto", config=config)
1200 else:
1201 bare_git.pack_refs(config=config)
1202 except GitError:
1203 err_event.set()
1204 except Exception:
1205 err_event.set()
1206 raise
1207 finally:
1208 pm.finish(bare_git._project.name)
1209 sem.release()
1210
1211 for run_gc, bare_git in tidy_dirs.values():
1212 if err_event.is_set() and opt.fail_fast:
1213 break
1214 sem.acquire()
1215 t = _threading.Thread(
1216 target=tidy_up,
1217 args=(
1218 run_gc,
1219 bare_git,
1220 ),
1221 )
1222 t.daemon = True
1223 threads.add(t)
1224 t.start()
1225
1226 for t in threads:
1227 t.join()
1228 pm.end()
1229
1230 def _ReloadManifest(self, manifest_name, manifest):
1231 """Reload the manfiest from the file specified by the |manifest_name|.
1232
1233 It unloads the manifest if |manifest_name| is None.
1234
1235 Args:
1236 manifest_name: Manifest file to be reloaded.
1237 manifest: The manifest to use.
1238 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001239 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001240 # Override calls Unload already.
1241 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001242 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001243 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001244
Gavin Makea2e3302023-03-11 06:46:20 +00001245 def UpdateProjectList(self, opt, manifest):
1246 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001247
Gavin Makea2e3302023-03-11 06:46:20 +00001248 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001249
Gavin Makea2e3302023-03-11 06:46:20 +00001250 Args:
1251 opt: Program options returned from optparse. See _Options().
1252 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001253
Gavin Makea2e3302023-03-11 06:46:20 +00001254 Returns:
1255 0: success
1256 1: failure
1257 """
1258 new_project_paths = []
1259 for project in self.GetProjects(
1260 None, missing_ok=True, manifest=manifest, all_manifests=False
1261 ):
1262 if project.relpath:
1263 new_project_paths.append(project.relpath)
1264 file_name = "project.list"
1265 file_path = os.path.join(manifest.subdir, file_name)
1266 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001267
Gavin Makea2e3302023-03-11 06:46:20 +00001268 if os.path.exists(file_path):
1269 with open(file_path, "r") as fd:
1270 old_project_paths = fd.read().split("\n")
1271 # In reversed order, so subfolders are deleted before parent folder.
1272 for path in sorted(old_project_paths, reverse=True):
1273 if not path:
1274 continue
1275 if path not in new_project_paths:
1276 # If the path has already been deleted, we don't need to do
1277 # it.
1278 gitdir = os.path.join(manifest.topdir, path, ".git")
1279 if os.path.exists(gitdir):
1280 project = Project(
1281 manifest=manifest,
1282 name=path,
1283 remote=RemoteSpec("origin"),
1284 gitdir=gitdir,
1285 objdir=gitdir,
1286 use_git_worktrees=os.path.isfile(gitdir),
1287 worktree=os.path.join(manifest.topdir, path),
1288 relpath=path,
1289 revisionExpr="HEAD",
1290 revisionId=None,
1291 groups=None,
1292 )
Jason Chang32b59562023-07-14 16:45:35 -07001293 project.DeleteWorktree(
Gavin Makea2e3302023-03-11 06:46:20 +00001294 quiet=opt.quiet, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001295 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001296
Gavin Makea2e3302023-03-11 06:46:20 +00001297 new_project_paths.sort()
1298 with open(file_path, "w") as fd:
1299 fd.write("\n".join(new_project_paths))
1300 fd.write("\n")
1301 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001302
Gavin Makea2e3302023-03-11 06:46:20 +00001303 def UpdateCopyLinkfileList(self, manifest):
1304 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001305
Gavin Makea2e3302023-03-11 06:46:20 +00001306 Returns:
1307 Whether update was successful.
1308 """
1309 new_paths = {}
1310 new_linkfile_paths = []
1311 new_copyfile_paths = []
1312 for project in self.GetProjects(
1313 None, missing_ok=True, manifest=manifest, all_manifests=False
1314 ):
1315 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1316 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001317
Gavin Makea2e3302023-03-11 06:46:20 +00001318 new_paths = {
1319 "linkfile": new_linkfile_paths,
1320 "copyfile": new_copyfile_paths,
1321 }
jiajia tanga590e642021-04-25 20:02:02 +08001322
Gavin Makea2e3302023-03-11 06:46:20 +00001323 copylinkfile_name = "copy-link-files.json"
1324 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1325 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001326
Gavin Makea2e3302023-03-11 06:46:20 +00001327 if os.path.exists(copylinkfile_path):
1328 with open(copylinkfile_path, "rb") as fp:
1329 try:
1330 old_copylinkfile_paths = json.load(fp)
1331 except Exception:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001332 logger.error(
1333 "error: %s is not a json formatted file.",
1334 copylinkfile_path,
Gavin Makea2e3302023-03-11 06:46:20 +00001335 )
1336 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001337 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001338
Gavin Makea2e3302023-03-11 06:46:20 +00001339 need_remove_files = []
1340 need_remove_files.extend(
1341 set(old_copylinkfile_paths.get("linkfile", []))
1342 - set(new_linkfile_paths)
1343 )
1344 need_remove_files.extend(
1345 set(old_copylinkfile_paths.get("copyfile", []))
1346 - set(new_copyfile_paths)
1347 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001348
Gavin Makea2e3302023-03-11 06:46:20 +00001349 for need_remove_file in need_remove_files:
1350 # Try to remove the updated copyfile or linkfile.
1351 # So, if the file is not exist, nothing need to do.
1352 platform_utils.remove(need_remove_file, missing_ok=True)
Raman Tenneti7954de12021-07-28 14:36:49 -07001353
Gavin Makea2e3302023-03-11 06:46:20 +00001354 # Create copy-link-files.json, save dest path of "copyfile" and
1355 # "linkfile".
1356 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1357 json.dump(new_paths, fp)
1358 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001359
Gavin Makea2e3302023-03-11 06:46:20 +00001360 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1361 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001362 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001363 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001364 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001365 )
Gavin Makea2e3302023-03-11 06:46:20 +00001366
1367 manifest_server = manifest.manifest_server
1368 if not opt.quiet:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001369 logger.info("Using manifest server %s", manifest_server)
Gavin Makea2e3302023-03-11 06:46:20 +00001370
1371 if "@" not in manifest_server:
1372 username = None
1373 password = None
1374 if opt.manifest_server_username and opt.manifest_server_password:
1375 username = opt.manifest_server_username
1376 password = opt.manifest_server_password
1377 else:
1378 try:
1379 info = netrc.netrc()
1380 except IOError:
1381 # .netrc file does not exist or could not be opened.
1382 pass
1383 else:
1384 try:
1385 parse_result = urllib.parse.urlparse(manifest_server)
1386 if parse_result.hostname:
1387 auth = info.authenticators(parse_result.hostname)
1388 if auth:
1389 username, _account, password = auth
1390 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001391 logger.error(
1392 "No credentials found for %s in .netrc",
1393 parse_result.hostname,
Gavin Makea2e3302023-03-11 06:46:20 +00001394 )
1395 except netrc.NetrcParseError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001396 logger.error("Error parsing .netrc file: %s", e)
Gavin Makea2e3302023-03-11 06:46:20 +00001397
1398 if username and password:
1399 manifest_server = manifest_server.replace(
1400 "://", "://%s:%s@" % (username, password), 1
1401 )
1402
1403 transport = PersistentTransport(manifest_server)
1404 if manifest_server.startswith("persistent-"):
1405 manifest_server = manifest_server[len("persistent-") :]
1406
1407 try:
1408 server = xmlrpc.client.Server(manifest_server, transport=transport)
1409 if opt.smart_sync:
1410 branch = self._GetBranch(manifest.manifestProject)
1411
1412 if "SYNC_TARGET" in os.environ:
1413 target = os.environ["SYNC_TARGET"]
1414 [success, manifest_str] = server.GetApprovedManifest(
1415 branch, target
1416 )
1417 elif (
1418 "TARGET_PRODUCT" in os.environ
1419 and "TARGET_BUILD_VARIANT" in os.environ
1420 ):
1421 target = "%s-%s" % (
1422 os.environ["TARGET_PRODUCT"],
1423 os.environ["TARGET_BUILD_VARIANT"],
1424 )
1425 [success, manifest_str] = server.GetApprovedManifest(
1426 branch, target
1427 )
1428 else:
1429 [success, manifest_str] = server.GetApprovedManifest(branch)
1430 else:
1431 assert opt.smart_tag
1432 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1433
1434 if success:
1435 manifest_name = os.path.basename(smart_sync_manifest_path)
1436 try:
1437 with open(smart_sync_manifest_path, "w") as f:
1438 f.write(manifest_str)
1439 except IOError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001440 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001441 "error: cannot write manifest to %s:\n%s"
1442 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001443 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001444 )
Gavin Makea2e3302023-03-11 06:46:20 +00001445 self._ReloadManifest(manifest_name, manifest)
1446 else:
Jason Chang32b59562023-07-14 16:45:35 -07001447 raise SmartSyncError(
1448 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001449 )
Gavin Makea2e3302023-03-11 06:46:20 +00001450 except (socket.error, IOError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001451 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001452 "error: cannot connect to manifest server %s:\n%s"
1453 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001454 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001455 )
Gavin Makea2e3302023-03-11 06:46:20 +00001456 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001457 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001458 "error: cannot connect to manifest server %s:\n%d %s"
1459 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001460 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001461 )
Gavin Makea2e3302023-03-11 06:46:20 +00001462
1463 return manifest_name
1464
Jason Changdaf2ad32023-08-31 17:06:36 -07001465 def _UpdateAllManifestProjects(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001466 """Fetch & update the local manifest project.
1467
1468 After syncing the manifest project, if the manifest has any sub
1469 manifests, those are recursively processed.
1470
1471 Args:
1472 opt: Program options returned from optparse. See _Options().
1473 mp: the manifestProject to query.
1474 manifest_name: Manifest file to be reloaded.
1475 """
1476 if not mp.standalone_manifest_url:
Jason Changdaf2ad32023-08-31 17:06:36 -07001477 self._UpdateManifestProject(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001478
1479 if mp.manifest.submanifests:
1480 for submanifest in mp.manifest.submanifests.values():
1481 child = submanifest.repo_client.manifest
1482 child.manifestProject.SyncWithPossibleInit(
1483 submanifest,
1484 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1485 verbose=opt.verbose,
1486 tags=opt.tags,
1487 git_event_log=self.git_event_log,
1488 )
1489 self._UpdateAllManifestProjects(
Jason Changdaf2ad32023-08-31 17:06:36 -07001490 opt, child.manifestProject, None, errors
Gavin Makea2e3302023-03-11 06:46:20 +00001491 )
1492
Jason Changdaf2ad32023-08-31 17:06:36 -07001493 def _UpdateManifestProject(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001494 """Fetch & update the local manifest project.
1495
1496 Args:
1497 opt: Program options returned from optparse. See _Options().
1498 mp: the manifestProject to query.
1499 manifest_name: Manifest file to be reloaded.
1500 """
1501 if not opt.local_only:
1502 start = time.time()
Jason Changdaf2ad32023-08-31 17:06:36 -07001503 buf = TeeStringIO(sys.stdout)
1504 try:
1505 result = mp.Sync_NetworkHalf(
1506 quiet=opt.quiet,
1507 output_redir=buf,
1508 verbose=opt.verbose,
1509 current_branch_only=self._GetCurrentBranchOnly(
1510 opt, mp.manifest
1511 ),
1512 force_sync=opt.force_sync,
1513 tags=opt.tags,
1514 optimized_fetch=opt.optimized_fetch,
1515 retry_fetches=opt.retry_fetches,
1516 submodules=mp.manifest.HasSubmodules,
1517 clone_filter=mp.manifest.CloneFilter,
1518 partial_clone_exclude=mp.manifest.PartialCloneExclude,
1519 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
1520 )
1521 if result.error:
1522 errors.append(result.error)
1523 except KeyboardInterrupt:
1524 errors.append(
1525 ManifestInterruptError(buf.getvalue(), project=mp.name)
1526 )
1527 raise
1528
Gavin Makea2e3302023-03-11 06:46:20 +00001529 finish = time.time()
1530 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001531 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001532 )
1533
1534 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001535 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001536 syncbuf = SyncBuffer(mp.config)
1537 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001538 mp.Sync_LocalHalf(
1539 syncbuf, submodules=mp.manifest.HasSubmodules, errors=errors
1540 )
Gavin Makea2e3302023-03-11 06:46:20 +00001541 clean = syncbuf.Finish()
1542 self.event_log.AddSync(
1543 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1544 )
1545 if not clean:
Jason Chang32b59562023-07-14 16:45:35 -07001546 raise UpdateManifestError(
1547 aggregate_errors=errors, project=mp.name
1548 )
Gavin Makea2e3302023-03-11 06:46:20 +00001549 self._ReloadManifest(manifest_name, mp.manifest)
1550
1551 def ValidateOptions(self, opt, args):
1552 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001553 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001554 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001555 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001556 )
1557 if opt.network_only and opt.detach_head:
1558 self.OptionParser.error("cannot combine -n and -d")
1559 if opt.network_only and opt.local_only:
1560 self.OptionParser.error("cannot combine -n and -l")
1561 if opt.manifest_name and opt.smart_sync:
1562 self.OptionParser.error("cannot combine -m and -s")
1563 if opt.manifest_name and opt.smart_tag:
1564 self.OptionParser.error("cannot combine -m and -t")
1565 if opt.manifest_server_username or opt.manifest_server_password:
1566 if not (opt.smart_sync or opt.smart_tag):
1567 self.OptionParser.error(
1568 "-u and -p may only be combined with -s or -t"
1569 )
1570 if None in [
1571 opt.manifest_server_username,
1572 opt.manifest_server_password,
1573 ]:
1574 self.OptionParser.error("both -u and -p must be given")
1575
1576 if opt.prune is None:
1577 opt.prune = True
1578
1579 if opt.auto_gc is None and _AUTO_GC:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001580 logger.error(
1581 "Will run `git gc --auto` because %s is set. %s is deprecated "
1582 "and will be removed in a future release. Use `--auto-gc` "
1583 "instead.",
1584 _REPO_AUTO_GC,
1585 _REPO_AUTO_GC,
Gavin Makea2e3302023-03-11 06:46:20 +00001586 )
1587 opt.auto_gc = True
1588
1589 def _ValidateOptionsWithManifest(self, opt, mp):
1590 """Like ValidateOptions, but after we've updated the manifest.
1591
1592 Needed to handle sync-xxx option defaults in the manifest.
1593
1594 Args:
1595 opt: The options to process.
1596 mp: The manifest project to pull defaults from.
1597 """
1598 if not opt.jobs:
1599 # If the user hasn't made a choice, use the manifest value.
1600 opt.jobs = mp.manifest.default.sync_j
1601 if opt.jobs:
1602 # If --jobs has a non-default value, propagate it as the default for
1603 # --jobs-xxx flags too.
1604 if not opt.jobs_network:
1605 opt.jobs_network = opt.jobs
1606 if not opt.jobs_checkout:
1607 opt.jobs_checkout = opt.jobs
1608 else:
1609 # Neither user nor manifest have made a choice, so setup defaults.
1610 if not opt.jobs_network:
1611 opt.jobs_network = 1
1612 if not opt.jobs_checkout:
1613 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1614 opt.jobs = os.cpu_count()
1615
1616 # Try to stay under user rlimit settings.
1617 #
1618 # Since each worker requires at 3 file descriptors to run `git fetch`,
1619 # use that to scale down the number of jobs. Unfortunately there isn't
1620 # an easy way to determine this reliably as systems change, but it was
1621 # last measured by hand in 2011.
1622 soft_limit, _ = _rlimit_nofile()
1623 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1624 opt.jobs = min(opt.jobs, jobs_soft_limit)
1625 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1626 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1627
1628 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001629 errors = []
1630 try:
1631 self._ExecuteHelper(opt, args, errors)
1632 except RepoExitError:
1633 raise
1634 except (KeyboardInterrupt, Exception) as e:
1635 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1636
1637 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001638 manifest = self.outer_manifest
1639 if not opt.outer_manifest:
1640 manifest = self.manifest
1641
1642 if opt.manifest_name:
1643 manifest.Override(opt.manifest_name)
1644
1645 manifest_name = opt.manifest_name
1646 smart_sync_manifest_path = os.path.join(
1647 manifest.manifestProject.worktree, "smart_sync_override.xml"
1648 )
1649
1650 if opt.clone_bundle is None:
1651 opt.clone_bundle = manifest.CloneBundle
1652
1653 if opt.smart_sync or opt.smart_tag:
1654 manifest_name = self._SmartSyncSetup(
1655 opt, smart_sync_manifest_path, manifest
1656 )
1657 else:
1658 if os.path.isfile(smart_sync_manifest_path):
1659 try:
1660 platform_utils.remove(smart_sync_manifest_path)
1661 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001662 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001663 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001664 "manifest: %s",
1665 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001666 )
1667
1668 err_event = multiprocessing.Event()
1669
1670 rp = manifest.repoProject
1671 rp.PreSync()
1672 cb = rp.CurrentBranch
1673 if cb:
1674 base = rp.GetBranch(cb).merge
1675 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001676 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001677 "warning: repo is not tracking a remote branch, so it will "
1678 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001679 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001680 )
1681
1682 for m in self.ManifestList(opt):
1683 if not m.manifestProject.standalone_manifest_url:
1684 m.manifestProject.PreSync()
1685
1686 if opt.repo_upgraded:
1687 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1688
1689 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001690
1691 if _REPO_ALLOW_SHALLOW is not None:
1692 if _REPO_ALLOW_SHALLOW == "1":
1693 mp.ConfigureCloneFilterForDepth(None)
1694 elif (
1695 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1696 ):
1697 mp.ConfigureCloneFilterForDepth("blob:none")
1698
Gavin Makea2e3302023-03-11 06:46:20 +00001699 if opt.mp_update:
Jason Changdaf2ad32023-08-31 17:06:36 -07001700 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001701 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001702 logger.info("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001703
1704 # Now that the manifests are up-to-date, setup options whose defaults
1705 # might be in the manifest.
1706 self._ValidateOptionsWithManifest(opt, mp)
1707
1708 superproject_logging_data = {}
1709 self._UpdateProjectsRevisionId(
1710 opt, args, superproject_logging_data, manifest
1711 )
1712
Gavin Makea2e3302023-03-11 06:46:20 +00001713 all_projects = self.GetProjects(
1714 args,
1715 missing_ok=True,
1716 submodules_ok=opt.fetch_submodules,
1717 manifest=manifest,
1718 all_manifests=not opt.this_manifest_only,
1719 )
1720
1721 err_network_sync = False
1722 err_update_projects = False
1723 err_update_linkfiles = False
1724
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001725 # Log the repo projects by existing and new.
1726 existing = [x for x in all_projects if x.Exists]
1727 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1728 mp.config.SetString(
1729 "repo.newprojectcount", str(len(all_projects) - len(existing))
1730 )
1731
Gavin Makea2e3302023-03-11 06:46:20 +00001732 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001733 self._local_sync_state = LocalSyncState(manifest)
Gavin Makea2e3302023-03-11 06:46:20 +00001734 if not opt.local_only:
1735 with multiprocessing.Manager() as manager:
1736 with ssh.ProxyManager(manager) as ssh_proxy:
1737 # Initialize the socket dir once in the parent.
1738 ssh_proxy.sock()
1739 result = self._FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -07001740 opt,
1741 args,
1742 all_projects,
1743 err_event,
1744 ssh_proxy,
1745 manifest,
1746 errors,
Gavin Makea2e3302023-03-11 06:46:20 +00001747 )
1748 all_projects = result.all_projects
1749
1750 if opt.network_only:
1751 return
1752
1753 # If we saw an error, exit with code 1 so that other scripts can
1754 # check.
1755 if err_event.is_set():
1756 err_network_sync = True
1757 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001758 logger.error(
1759 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001760 "Local checkouts *not* updated. Resolve network issues "
1761 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001762 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001763 )
Jason Chang32b59562023-07-14 16:45:35 -07001764 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001765
1766 for m in self.ManifestList(opt):
1767 if m.IsMirror or m.IsArchive:
1768 # Bail out now, we have no working tree.
1769 continue
1770
Jason Chang32b59562023-07-14 16:45:35 -07001771 try:
1772 self.UpdateProjectList(opt, m)
1773 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001774 err_event.set()
1775 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001776 errors.append(e)
1777 if isinstance(e, DeleteWorktreeError):
1778 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001779 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001780 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001781 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001782
Jason Chang32b59562023-07-14 16:45:35 -07001783 err_update_linkfiles = False
1784 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 Vasudevane914ec22023-08-31 20:57:31 +00001858 logger.info("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 Vasudevane914ec22023-08-31 20:57:31 +00001881 logger.info("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 Vasudevane914ec22023-08-31 20:57:31 +00001903 logger.info("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 Vasudevane914ec22023-08-31 20:57:31 +00001909 logger.info(
1910 "repo version %s is current", rp.work_git.describe(HEAD)
Gavin Makea2e3302023-03-11 06:46:20 +00001911 )
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001912
David Pursehouse819827a2020-02-12 15:20:19 +09001913
Dave Borowitz67700e92012-10-23 15:00:54 -07001914class _FetchTimes(object):
Gavin Makea2e3302023-03-11 06:46:20 +00001915 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07001916
Gavin Makea2e3302023-03-11 06:46:20 +00001917 def __init__(self, manifest):
1918 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00001919 self._saved = None
1920 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001921
Gavin Makea2e3302023-03-11 06:46:20 +00001922 def Get(self, project):
1923 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00001924 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07001925
Gavin Makea2e3302023-03-11 06:46:20 +00001926 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00001927 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00001928
1929 # For shared projects, save the longest time.
1930 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07001931
Gavin Makea2e3302023-03-11 06:46:20 +00001932 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001933 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001934 try:
1935 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001936 self._saved = json.load(f)
Gavin Makea2e3302023-03-11 06:46:20 +00001937 except (IOError, ValueError):
1938 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00001939 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001940
Gavin Makea2e3302023-03-11 06:46:20 +00001941 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001942 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001943 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001944
Gavin Mak041f9772023-05-10 20:41:12 +00001945 for name, t in self._seen.items():
1946 # Keep a moving average across the previous/current sync runs.
1947 old = self._saved.get(name, t)
1948 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07001949
Gavin Makea2e3302023-03-11 06:46:20 +00001950 try:
1951 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001952 json.dump(self._seen, f, indent=2)
Gavin Makea2e3302023-03-11 06:46:20 +00001953 except (IOError, TypeError):
1954 platform_utils.remove(self._path, missing_ok=True)
1955
Dan Willemsen0745bb22015-08-17 13:41:45 -07001956
Gavin Mak16109a62023-08-22 01:24:46 +00001957class LocalSyncState(object):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001958 _LAST_FETCH = "last_fetch"
1959 _LAST_CHECKOUT = "last_checkout"
1960
1961 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00001962 self._manifest = manifest
1963 self._path = os.path.join(
1964 self._manifest.repodir, ".repo_localsyncstate.json"
1965 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001966 self._time = time.time()
1967 self._state = None
1968 self._Load()
1969
1970 def SetFetchTime(self, project):
1971 self._Set(project, self._LAST_FETCH)
1972
1973 def SetCheckoutTime(self, project):
1974 self._Set(project, self._LAST_CHECKOUT)
1975
1976 def GetFetchTime(self, project):
1977 return self._Get(project, self._LAST_FETCH)
1978
1979 def GetCheckoutTime(self, project):
1980 return self._Get(project, self._LAST_CHECKOUT)
1981
1982 def _Get(self, project, key):
1983 self._Load()
1984 p = project.relpath
1985 if p not in self._state:
1986 return
1987 return self._state[p].get(key)
1988
1989 def _Set(self, project, key):
1990 p = project.relpath
1991 if p not in self._state:
1992 self._state[p] = {}
1993 self._state[p][key] = self._time
1994
1995 def _Load(self):
1996 if self._state is None:
1997 try:
1998 with open(self._path) as f:
1999 self._state = json.load(f)
2000 except (IOError, ValueError):
2001 platform_utils.remove(self._path, missing_ok=True)
2002 self._state = {}
2003
2004 def Save(self):
2005 if not self._state:
2006 return
2007 try:
2008 with open(self._path, "w") as f:
2009 json.dump(self._state, f, indent=2)
2010 except (IOError, TypeError):
2011 platform_utils.remove(self._path, missing_ok=True)
2012
Gavin Makf0aeb222023-08-08 04:43:36 +00002013 def PruneRemovedProjects(self):
2014 """Remove entries don't exist on disk and save."""
2015 if not self._state:
2016 return
2017 delete = set()
2018 for path in self._state:
2019 gitdir = os.path.join(self._manifest.topdir, path, ".git")
2020 if not os.path.exists(gitdir):
2021 delete.add(path)
2022 if not delete:
2023 return
2024 for path in delete:
2025 del self._state[path]
2026 self.Save()
2027
2028 def IsPartiallySynced(self):
2029 """Return whether a partial sync state is detected."""
2030 self._Load()
2031 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002032 for path, data in self._state.items():
2033 if path == self._manifest.repoProject.relpath:
2034 # The repo project isn't included in most syncs so we should
2035 # ignore it here.
2036 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002037 checkout_t = data.get(self._LAST_CHECKOUT)
2038 if not checkout_t:
2039 return True
2040 prev_checkout_t = prev_checkout_t or checkout_t
2041 if prev_checkout_t != checkout_t:
2042 return True
2043 return False
2044
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002045
Dan Willemsen0745bb22015-08-17 13:41:45 -07002046# This is a replacement for xmlrpc.client.Transport using urllib2
2047# and supporting persistent-http[s]. It cannot change hosts from
2048# request to request like the normal transport, the real url
2049# is passed during initialization.
2050class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002051 def __init__(self, orig_host):
2052 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002053
Gavin Makea2e3302023-03-11 06:46:20 +00002054 def request(self, host, handler, request_body, verbose=False):
2055 with GetUrlCookieFile(self.orig_host, not verbose) as (
2056 cookiefile,
2057 proxy,
2058 ):
2059 # Python doesn't understand cookies with the #HttpOnly_ prefix
2060 # Since we're only using them for HTTP, copy the file temporarily,
2061 # stripping those prefixes away.
2062 if cookiefile:
2063 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2064 tmpcookiefile.write("# HTTP Cookie File")
2065 try:
2066 with open(cookiefile) as f:
2067 for line in f:
2068 if line.startswith("#HttpOnly_"):
2069 line = line[len("#HttpOnly_") :]
2070 tmpcookiefile.write(line)
2071 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002072
Gavin Makea2e3302023-03-11 06:46:20 +00002073 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2074 try:
2075 cookiejar.load()
2076 except cookielib.LoadError:
2077 cookiejar = cookielib.CookieJar()
2078 finally:
2079 tmpcookiefile.close()
2080 else:
2081 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002082
Gavin Makea2e3302023-03-11 06:46:20 +00002083 proxyhandler = urllib.request.ProxyHandler
2084 if proxy:
2085 proxyhandler = urllib.request.ProxyHandler(
2086 {"http": proxy, "https": proxy}
2087 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002088
Gavin Makea2e3302023-03-11 06:46:20 +00002089 opener = urllib.request.build_opener(
2090 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2091 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002092
Gavin Makea2e3302023-03-11 06:46:20 +00002093 url = urllib.parse.urljoin(self.orig_host, handler)
2094 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002095
Gavin Makea2e3302023-03-11 06:46:20 +00002096 scheme = parse_results.scheme
2097 if scheme == "persistent-http":
2098 scheme = "http"
2099 if scheme == "persistent-https":
2100 # If we're proxying through persistent-https, use http. The
2101 # proxy itself will do the https.
2102 if proxy:
2103 scheme = "http"
2104 else:
2105 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002106
Gavin Makea2e3302023-03-11 06:46:20 +00002107 # Parse out any authentication information using the base class.
2108 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002109
Gavin Makea2e3302023-03-11 06:46:20 +00002110 url = urllib.parse.urlunparse(
2111 (
2112 scheme,
2113 host,
2114 parse_results.path,
2115 parse_results.params,
2116 parse_results.query,
2117 parse_results.fragment,
2118 )
2119 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002120
Gavin Makea2e3302023-03-11 06:46:20 +00002121 request = urllib.request.Request(url, request_body)
2122 if extra_headers is not None:
2123 for name, header in extra_headers:
2124 request.add_header(name, header)
2125 request.add_header("Content-Type", "text/xml")
2126 try:
2127 response = opener.open(request)
2128 except urllib.error.HTTPError as e:
2129 if e.code == 501:
2130 # We may have been redirected through a login process
2131 # but our POST turned into a GET. Retry.
2132 response = opener.open(request)
2133 else:
2134 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002135
Gavin Makea2e3302023-03-11 06:46:20 +00002136 p, u = xmlrpc.client.getparser()
2137 # Response should be fairly small, so read it all at once.
2138 # This way we can show it to the user in case of error (e.g. HTML).
2139 data = response.read()
2140 try:
2141 p.feed(data)
2142 except xml.parsers.expat.ExpatError as e:
2143 raise IOError(
2144 f"Parsing the manifest failed: {e}\n"
2145 f"Please report this to your manifest server admin.\n"
2146 f'Here is the full response:\n{data.decode("utf-8")}'
2147 )
2148 p.close()
2149 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002150
Gavin Makea2e3302023-03-11 06:46:20 +00002151 def close(self):
2152 pass