blob: f7ed49e4f8b483a09b73ed62389f73b497acd8dc [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
Josip Sokcevic55545722024-02-22 16:38:00 -080024from pathlib import Path
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
Jason Chang17833322023-05-23 13:06:55 -070086_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
87
Aravind Vasudevane914ec22023-08-31 20:57:31 +000088logger = RepoLogger(__file__)
89
David Pursehouse819827a2020-02-12 15:20:19 +090090
Josip Sokcevic55545722024-02-22 16:38:00 -080091def _SafeCheckoutOrder(checkouts: List[Project]) -> List[List[Project]]:
92 """Generate a sequence of checkouts that is safe to perform. The client
93 should checkout everything from n-th index before moving to n+1.
94
95 This is only useful if manifest contains nested projects.
96
97 E.g. if foo, foo/bar and foo/bar/baz are project paths, then foo needs to
98 finish before foo/bar can proceed, and foo/bar needs to finish before
99 foo/bar/baz."""
100 res = [[]]
101 current = res[0]
102
103 # depth_stack contains a current stack of parent paths.
104 depth_stack = []
Josip Sokcevic46790222024-03-07 22:18:58 +0000105 # Checkouts are iterated in the hierarchical order. That way, it can easily
106 # be determined if the previous checkout is parent of the current checkout.
107 # We are splitting by the path separator so the final result is
108 # hierarchical, and not just lexicographical. For example, if the projects
109 # are: foo, foo/bar, foo-bar, lexicographical order produces foo, foo-bar
110 # and foo/bar, which doesn't work.
111 for checkout in sorted(checkouts, key=lambda x: x.relpath.split("/")):
Josip Sokcevic55545722024-02-22 16:38:00 -0800112 checkout_path = Path(checkout.relpath)
113 while depth_stack:
114 try:
115 checkout_path.relative_to(depth_stack[-1])
116 except ValueError:
117 # Path.relative_to returns ValueError if paths are not relative.
118 # TODO(sokcevic): Switch to is_relative_to once min supported
119 # version is py3.9.
120 depth_stack.pop()
121 else:
122 if len(depth_stack) >= len(res):
123 # Another depth created.
124 res.append([])
125 break
126
127 current = res[len(depth_stack)]
128 current.append(checkout)
129 depth_stack.append(checkout_path)
130
131 return res
132
133
Josip Sokcevic454fdaf2024-10-07 17:33:38 +0000134def _chunksize(projects: int, jobs: int) -> int:
135 """Calculate chunk size for the given number of projects and jobs."""
136 return min(max(1, projects // jobs), WORKER_BATCH_SIZE)
137
138
LaMont Jones1eddca82022-09-01 15:15:04 +0000139class _FetchOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000140 """_FetchOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000141
Gavin Makea2e3302023-03-11 06:46:20 +0000142 Attributes:
143 success (bool): True if successful.
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800144 project_idx (int): The fetched project index.
Gavin Makea2e3302023-03-11 06:46:20 +0000145 start (float): The starting time.time().
146 finish (float): The ending time.time().
147 remote_fetched (bool): True if the remote was actually queried.
148 """
149
150 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700151 errors: List[Exception]
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800152 project_idx: int
Gavin Makea2e3302023-03-11 06:46:20 +0000153 start: float
154 finish: float
155 remote_fetched: bool
LaMont Jones1eddca82022-09-01 15:15:04 +0000156
157
158class _FetchResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000159 """_Fetch return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000160
Gavin Makea2e3302023-03-11 06:46:20 +0000161 Attributes:
162 success (bool): True if successful.
163 projects (Set[str]): The names of the git directories of fetched projects.
164 """
165
166 success: bool
167 projects: Set[str]
LaMont Jones1eddca82022-09-01 15:15:04 +0000168
169
170class _FetchMainResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000171 """_FetchMain return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000172
Gavin Makea2e3302023-03-11 06:46:20 +0000173 Attributes:
174 all_projects (List[Project]): The fetched projects.
175 """
176
177 all_projects: List[Project]
LaMont Jones1eddca82022-09-01 15:15:04 +0000178
179
180class _CheckoutOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000181 """_CheckoutOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000182
Gavin Makea2e3302023-03-11 06:46:20 +0000183 Attributes:
184 success (bool): True if successful.
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800185 project_idx (int): The project index.
Gavin Makea2e3302023-03-11 06:46:20 +0000186 start (float): The starting time.time().
187 finish (float): The ending time.time().
188 """
189
190 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700191 errors: List[Exception]
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800192 project_idx: int
Gavin Makea2e3302023-03-11 06:46:20 +0000193 start: float
194 finish: float
LaMont Jones1eddca82022-09-01 15:15:04 +0000195
196
Jason Chang32b59562023-07-14 16:45:35 -0700197class SuperprojectError(SyncError):
198 """Superproject sync repo."""
199
200
201class SyncFailFastError(SyncError):
202 """Sync exit error when --fail-fast set."""
203
204
205class SmartSyncError(SyncError):
206 """Smart sync exit error."""
207
208
Jason Changdaf2ad32023-08-31 17:06:36 -0700209class ManifestInterruptError(RepoError):
210 """Aggregate Error to be logged when a user interrupts a manifest update."""
211
212 def __init__(self, output, **kwargs):
213 super().__init__(output, **kwargs)
214 self.output = output
215
216 def __str__(self):
217 error_type = type(self).__name__
218 return f"{error_type}:{self.output}"
219
220
221class TeeStringIO(io.StringIO):
222 """StringIO class that can write to an additional destination."""
223
224 def __init__(
225 self, io: Union[io.TextIOWrapper, None], *args, **kwargs
226 ) -> None:
227 super().__init__(*args, **kwargs)
228 self.io = io
229
230 def write(self, s: str) -> int:
231 """Write to additional destination."""
Daniel Kutikb0430b52023-10-23 21:16:04 +0200232 ret = super().write(s)
Jason Changdaf2ad32023-08-31 17:06:36 -0700233 if self.io is not None:
234 self.io.write(s)
Daniel Kutikb0430b52023-10-23 21:16:04 +0200235 return ret
Jason Changdaf2ad32023-08-31 17:06:36 -0700236
237
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800238class Sync(Command, MirrorSafeCommand):
Gavin Makea2e3302023-03-11 06:46:20 +0000239 COMMON = True
240 MULTI_MANIFEST_SUPPORT = True
241 helpSummary = "Update working tree to the latest revision"
242 helpUsage = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243%prog [...]
244"""
Gavin Makea2e3302023-03-11 06:46:20 +0000245 helpDescription = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246The '%prog' command synchronizes local project directories
247with the remote repositories specified in the manifest. If a local
248project does not yet exist, it will clone a new local directory from
249the remote repository and set up tracking branches as specified in
250the manifest. If the local project already exists, '%prog'
251will update the remote branches and rebase any new local changes
252on top of the new remote changes.
253
254'%prog' will synchronize all projects listed at the command
255line. Projects can be specified either by name, or by a relative
256or absolute path to the project's local directory. If no projects
257are specified, '%prog' will synchronize all projects listed in
258the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700259
260The -d/--detach option can be used to switch specified projects
261back to the manifest revision. This option is especially helpful
262if the project is currently on a topic branch, but the manifest
263revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700264
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700265The -s/--smart-sync option can be used to sync to a known good
266build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200267manifest. The -t/--smart-tag option is similar and allows you to
268specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700269
David Pursehousecf76b1b2012-09-14 10:31:42 +0900270The -u/--manifest-server-username and -p/--manifest-server-password
271options can be used to specify a username and password to authenticate
272with the manifest server when using the -s or -t option.
273
274If -u and -p are not specified when using the -s or -t option, '%prog'
275will attempt to read authentication credentials for the manifest server
276from the user's .netrc file.
277
278'%prog' will not use authentication credentials from -u/-p or .netrc
279if the manifest server specified in the manifest file already includes
280credentials.
281
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400282By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400283to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500284
Kevin Degiabaa7f32014-11-12 11:27:45 -0700285The --force-sync option can be used to overwrite existing git
286directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900287object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700288refs may be removed when overwriting.
289
Josip Sokcevicedadb252024-02-29 09:48:37 -0800290The --force-checkout option can be used to force git to switch revs even if the
291index or the working tree differs from HEAD, and if there are untracked files.
292WARNING: This may cause data to be lost since uncommitted changes may be
293removed.
294
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500295The --force-remove-dirty option can be used to remove previously used
296projects with uncommitted changes. WARNING: This may cause data to be
297lost since uncommitted changes may be removed with projects that no longer
298exist in the manifest.
299
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700300The --no-clone-bundle option disables any attempt to use
301$URL/clone.bundle to bootstrap a new Git repository from a
302resumeable bundle file on a content delivery network. This
303may be necessary if there are problems with the local Python
304HTTP client or proxy configuration, but the Git binary works.
305
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800306The --fetch-submodules option enables fetching Git submodules
307of a project from server.
308
David Pursehousef2fad612015-01-29 14:36:28 +0900309The -c/--current-branch option can be used to only fetch objects that
310are on the branch specified by a project's revision.
311
David Pursehouseb1553542014-09-04 21:28:09 +0900312The --optimized-fetch option can be used to only fetch projects that
313are fixed to a sha1 revision if the sha1 revision does not already
314exist locally.
315
David Pursehouse74cfd272015-10-14 10:50:15 +0900316The --prune option can be used to remove any refs that no longer
317exist on the remote.
318
LaMont Jones7efab532022-09-01 15:41:12 +0000319The --auto-gc option can be used to trigger garbage collection on all
320projects. By default, repo does not run garbage collection.
321
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400322# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700323
324If at least one project remote URL uses an SSH connection (ssh://,
325git+ssh://, or user@host:path syntax) repo will automatically
326enable the SSH ControlMaster option when connecting to that host.
327This feature permits other projects in the same '%prog' session to
328reuse the same SSH tunnel, saving connection setup overheads.
329
330To disable this behavior on UNIX platforms, set the GIT_SSH
331environment variable to 'ssh'. For example:
332
333 export GIT_SSH=ssh
334 %prog
335
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400336# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700337
338This feature is automatically disabled on Windows, due to the lack
339of UNIX domain socket support.
340
341This feature is not compatible with url.insteadof rewrites in the
342user's ~/.gitconfig. '%prog' is currently not able to perform the
343rewrite early enough to establish the ControlMaster tunnel.
344
345If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
346later is required to fix a server side protocol bug.
347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348"""
Gavin Makea2e3302023-03-11 06:46:20 +0000349 # A value of 0 means we want parallel jobs, but we'll determine the default
350 # value later on.
351 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700352
Gavin Makea2e3302023-03-11 06:46:20 +0000353 def _Options(self, p, show_smart=True):
354 p.add_option(
355 "--jobs-network",
356 default=None,
357 type=int,
358 metavar="JOBS",
359 help="number of network jobs to run in parallel (defaults to "
360 "--jobs or 1)",
361 )
362 p.add_option(
363 "--jobs-checkout",
364 default=None,
365 type=int,
366 metavar="JOBS",
367 help="number of local checkout jobs to run in parallel (defaults "
368 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
369 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400370
Gavin Makea2e3302023-03-11 06:46:20 +0000371 p.add_option(
372 "-f",
373 "--force-broken",
374 dest="force_broken",
375 action="store_true",
376 help="obsolete option (to be deleted in the future)",
377 )
378 p.add_option(
379 "--fail-fast",
380 dest="fail_fast",
381 action="store_true",
382 help="stop syncing after first error is hit",
383 )
384 p.add_option(
385 "--force-sync",
386 dest="force_sync",
387 action="store_true",
388 help="overwrite an existing git directory if it needs to "
389 "point to a different object directory. WARNING: this "
390 "may cause loss of data",
391 )
392 p.add_option(
Josip Sokcevicedadb252024-02-29 09:48:37 -0800393 "--force-checkout",
394 dest="force_checkout",
395 action="store_true",
396 help="force checkout even if it results in throwing away "
397 "uncommitted modifications. "
398 "WARNING: this may cause loss of data",
399 )
400 p.add_option(
Gavin Makea2e3302023-03-11 06:46:20 +0000401 "--force-remove-dirty",
402 dest="force_remove_dirty",
403 action="store_true",
404 help="force remove projects with uncommitted modifications if "
405 "projects no longer exist in the manifest. "
406 "WARNING: this may cause loss of data",
407 )
408 p.add_option(
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +0200409 "--rebase",
410 dest="rebase",
411 action="store_true",
412 help="rebase local commits regardless of whether they are "
413 "published",
414 )
415 p.add_option(
Gavin Makea2e3302023-03-11 06:46:20 +0000416 "-l",
417 "--local-only",
418 dest="local_only",
419 action="store_true",
420 help="only update working tree, don't fetch",
421 )
422 p.add_option(
423 "--no-manifest-update",
424 "--nmu",
425 dest="mp_update",
426 action="store_false",
427 default="true",
428 help="use the existing manifest checkout as-is. "
429 "(do not update to the latest revision)",
430 )
431 p.add_option(
432 "-n",
433 "--network-only",
434 dest="network_only",
435 action="store_true",
436 help="fetch only, don't update working tree",
437 )
438 p.add_option(
439 "-d",
440 "--detach",
441 dest="detach_head",
442 action="store_true",
443 help="detach projects back to manifest revision",
444 )
445 p.add_option(
446 "-c",
447 "--current-branch",
448 dest="current_branch_only",
449 action="store_true",
450 help="fetch only current branch from server",
451 )
452 p.add_option(
453 "--no-current-branch",
454 dest="current_branch_only",
455 action="store_false",
456 help="fetch all branches from server",
457 )
458 p.add_option(
459 "-m",
460 "--manifest-name",
461 dest="manifest_name",
462 help="temporary manifest to use for this sync",
463 metavar="NAME.xml",
464 )
465 p.add_option(
466 "--clone-bundle",
467 action="store_true",
468 help="enable use of /clone.bundle on HTTP/HTTPS",
469 )
470 p.add_option(
471 "--no-clone-bundle",
472 dest="clone_bundle",
473 action="store_false",
474 help="disable use of /clone.bundle on HTTP/HTTPS",
475 )
476 p.add_option(
477 "-u",
478 "--manifest-server-username",
479 action="store",
480 dest="manifest_server_username",
481 help="username to authenticate with the manifest server",
482 )
483 p.add_option(
484 "-p",
485 "--manifest-server-password",
486 action="store",
487 dest="manifest_server_password",
488 help="password to authenticate with the manifest server",
489 )
490 p.add_option(
491 "--fetch-submodules",
492 dest="fetch_submodules",
493 action="store_true",
494 help="fetch submodules from server",
495 )
496 p.add_option(
497 "--use-superproject",
498 action="store_true",
499 help="use the manifest superproject to sync projects; implies -c",
500 )
501 p.add_option(
502 "--no-use-superproject",
503 action="store_false",
504 dest="use_superproject",
505 help="disable use of manifest superprojects",
506 )
507 p.add_option("--tags", action="store_true", help="fetch tags")
508 p.add_option(
509 "--no-tags",
510 dest="tags",
511 action="store_false",
512 help="don't fetch tags (default)",
513 )
514 p.add_option(
515 "--optimized-fetch",
516 dest="optimized_fetch",
517 action="store_true",
518 help="only fetch projects fixed to sha1 if revision does not exist "
519 "locally",
520 )
521 p.add_option(
522 "--retry-fetches",
523 default=0,
524 action="store",
525 type="int",
526 help="number of times to retry fetches on transient errors",
527 )
528 p.add_option(
529 "--prune",
530 action="store_true",
531 help="delete refs that no longer exist on the remote (default)",
532 )
533 p.add_option(
534 "--no-prune",
535 dest="prune",
536 action="store_false",
537 help="do not delete refs that no longer exist on the remote",
538 )
539 p.add_option(
540 "--auto-gc",
541 action="store_true",
542 default=None,
543 help="run garbage collection on all synced projects",
544 )
545 p.add_option(
546 "--no-auto-gc",
547 dest="auto_gc",
548 action="store_false",
549 help="do not run garbage collection on any projects (default)",
550 )
551 if show_smart:
552 p.add_option(
553 "-s",
554 "--smart-sync",
555 dest="smart_sync",
556 action="store_true",
557 help="smart sync using manifest from the latest known good "
558 "build",
559 )
560 p.add_option(
561 "-t",
562 "--smart-tag",
563 dest="smart_tag",
564 action="store",
565 help="smart sync using manifest from a known tag",
566 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700567
Gavin Makea2e3302023-03-11 06:46:20 +0000568 g = p.add_option_group("repo Version options")
569 g.add_option(
570 "--no-repo-verify",
571 dest="repo_verify",
572 default=True,
573 action="store_false",
574 help="do not verify repo source code",
575 )
576 g.add_option(
577 "--repo-upgraded",
578 dest="repo_upgraded",
579 action="store_true",
Mike Frysinger06ddc8c2023-08-21 21:26:51 -0400580 help=optparse.SUPPRESS_HELP,
Gavin Makea2e3302023-03-11 06:46:20 +0000581 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582
Gavin Makea2e3302023-03-11 06:46:20 +0000583 def _GetBranch(self, manifest_project):
584 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000585
Gavin Makea2e3302023-03-11 06:46:20 +0000586 Args:
587 manifest_project: The manifestProject to query.
588 """
589 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
590 branch = b.merge
591 if branch.startswith(R_HEADS):
592 branch = branch[len(R_HEADS) :]
593 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800594
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800595 @classmethod
596 def _GetCurrentBranchOnly(cls, opt, manifest):
Gavin Makea2e3302023-03-11 06:46:20 +0000597 """Returns whether current-branch or use-superproject options are
598 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200599
Gavin Makea2e3302023-03-11 06:46:20 +0000600 Args:
601 opt: Program options returned from optparse. See _Options().
602 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000603
Gavin Makea2e3302023-03-11 06:46:20 +0000604 Returns:
605 True if a superproject is requested, otherwise the value of the
606 current_branch option (True, False or None).
607 """
608 return (
609 git_superproject.UseSuperproject(opt.use_superproject, manifest)
610 or opt.current_branch_only
611 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700612
Gavin Makea2e3302023-03-11 06:46:20 +0000613 def _UpdateProjectsRevisionId(
614 self, opt, args, superproject_logging_data, manifest
615 ):
616 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800617
Gavin Makea2e3302023-03-11 06:46:20 +0000618 This function updates each project's revisionId with the commit hash
619 from the superproject. It writes the updated manifest into a file and
620 reloads the manifest from it. When appropriate, sub manifests are also
621 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800622
Gavin Makea2e3302023-03-11 06:46:20 +0000623 Args:
624 opt: Program options returned from optparse. See _Options().
625 args: Arguments to pass to GetProjects. See the GetProjects
626 docstring for details.
627 superproject_logging_data: A dictionary of superproject data to log.
628 manifest: The manifest to use.
629 """
630 have_superproject = manifest.superproject or any(
631 m.superproject for m in manifest.all_children
632 )
633 if not have_superproject:
634 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000635
Gavin Makea2e3302023-03-11 06:46:20 +0000636 if opt.local_only and manifest.superproject:
637 manifest_path = manifest.superproject.manifest_path
638 if manifest_path:
639 self._ReloadManifest(manifest_path, manifest)
640 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700641
Gavin Makea2e3302023-03-11 06:46:20 +0000642 all_projects = self.GetProjects(
643 args,
644 missing_ok=True,
645 submodules_ok=opt.fetch_submodules,
646 manifest=manifest,
647 all_manifests=not opt.this_manifest_only,
648 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000649
Gavin Makea2e3302023-03-11 06:46:20 +0000650 per_manifest = collections.defaultdict(list)
651 if opt.this_manifest_only:
652 per_manifest[manifest.path_prefix] = all_projects
653 else:
654 for p in all_projects:
655 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000656
Gavin Makea2e3302023-03-11 06:46:20 +0000657 superproject_logging_data = {}
658 need_unload = False
659 for m in self.ManifestList(opt):
660 if m.path_prefix not in per_manifest:
661 continue
662 use_super = git_superproject.UseSuperproject(
663 opt.use_superproject, m
664 )
665 if superproject_logging_data:
666 superproject_logging_data["multimanifest"] = True
667 superproject_logging_data.update(
668 superproject=use_super,
669 haslocalmanifests=bool(m.HasLocalManifests),
670 hassuperprojecttag=bool(m.superproject),
671 )
672 if use_super and (m.IsMirror or m.IsArchive):
673 # Don't use superproject, because we have no working tree.
674 use_super = False
675 superproject_logging_data["superproject"] = False
676 superproject_logging_data["noworktree"] = True
677 if opt.use_superproject is not False:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000678 logger.warning(
679 "%s: not using superproject because there is no "
680 "working tree.",
681 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000682 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000683
Gavin Makea2e3302023-03-11 06:46:20 +0000684 if not use_super:
685 continue
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -0800686 m.superproject.SetQuiet(not opt.verbose)
Gavin Makea2e3302023-03-11 06:46:20 +0000687 print_messages = git_superproject.PrintMessages(
688 opt.use_superproject, m
689 )
690 m.superproject.SetPrintMessages(print_messages)
691 update_result = m.superproject.UpdateProjectsRevisionId(
692 per_manifest[m.path_prefix], git_event_log=self.git_event_log
693 )
694 manifest_path = update_result.manifest_path
695 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
696 if manifest_path:
697 m.SetManifestOverride(manifest_path)
698 need_unload = True
699 else:
700 if print_messages:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000701 logger.warning(
702 "%s: warning: Update of revisionId from superproject "
703 "has failed, repo sync will not use superproject to "
704 "fetch the source. Please resync with the "
705 "--no-use-superproject option to avoid this repo "
706 "warning.",
707 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000708 )
709 if update_result.fatal and opt.use_superproject is not None:
Jason Chang32b59562023-07-14 16:45:35 -0700710 raise SuperprojectError()
Gavin Makea2e3302023-03-11 06:46:20 +0000711 if need_unload:
712 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800713
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800714 @classmethod
715 def _FetchProjectList(cls, opt, projects):
Gavin Makea2e3302023-03-11 06:46:20 +0000716 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500717
Gavin Makea2e3302023-03-11 06:46:20 +0000718 The projects we're given share the same underlying git object store, so
719 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800720
Gavin Mak551285f2023-05-04 04:48:43 +0000721 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700722
Gavin Makea2e3302023-03-11 06:46:20 +0000723 Args:
724 opt: Program options returned from optparse. See _Options().
725 projects: Projects to fetch.
726 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800727 return [cls._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700728
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800729 @classmethod
730 def _FetchOne(cls, opt, project_idx):
Gavin Makea2e3302023-03-11 06:46:20 +0000731 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700732
Gavin Makea2e3302023-03-11 06:46:20 +0000733 Args:
734 opt: Program options returned from optparse. See _Options().
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800735 project_idx: Project index for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700736
Gavin Makea2e3302023-03-11 06:46:20 +0000737 Returns:
738 Whether the fetch was successful.
739 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800740 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +0000741 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000742 k = f"{project.name} @ {project.relpath}"
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800743 cls.get_parallel_context()["sync_dict"][k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000744 success = False
745 remote_fetched = False
Jason Chang32b59562023-07-14 16:45:35 -0700746 errors = []
Jason Changdaf2ad32023-08-31 17:06:36 -0700747 buf = TeeStringIO(sys.stdout if opt.verbose else None)
Gavin Makea2e3302023-03-11 06:46:20 +0000748 try:
749 sync_result = project.Sync_NetworkHalf(
750 quiet=opt.quiet,
751 verbose=opt.verbose,
752 output_redir=buf,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800753 current_branch_only=cls._GetCurrentBranchOnly(
Gavin Makea2e3302023-03-11 06:46:20 +0000754 opt, project.manifest
755 ),
756 force_sync=opt.force_sync,
757 clone_bundle=opt.clone_bundle,
758 tags=opt.tags,
759 archive=project.manifest.IsArchive,
760 optimized_fetch=opt.optimized_fetch,
761 retry_fetches=opt.retry_fetches,
762 prune=opt.prune,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800763 ssh_proxy=cls.get_parallel_context()["ssh_proxy"],
Gavin Makea2e3302023-03-11 06:46:20 +0000764 clone_filter=project.manifest.CloneFilter,
765 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700766 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000767 )
768 success = sync_result.success
769 remote_fetched = sync_result.remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700770 if sync_result.error:
771 errors.append(sync_result.error)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700772
Gavin Makea2e3302023-03-11 06:46:20 +0000773 output = buf.getvalue()
Jason Changdaf2ad32023-08-31 17:06:36 -0700774 if output and buf.io is None and not success:
Gavin Makea2e3302023-03-11 06:46:20 +0000775 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700776
Gavin Makea2e3302023-03-11 06:46:20 +0000777 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000778 logger.error(
779 "error: Cannot fetch %s from %s",
780 project.name,
781 project.remote.url,
Gavin Makea2e3302023-03-11 06:46:20 +0000782 )
783 except KeyboardInterrupt:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000784 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000785 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000786 logger.error("error.GitError: Cannot fetch %s", e)
Jason Chang32b59562023-07-14 16:45:35 -0700787 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000788 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000789 logger.error(
790 "error: Cannot fetch %s (%s: %s)",
791 project.name,
792 type(e).__name__,
793 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000794 )
Jason Chang32b59562023-07-14 16:45:35 -0700795 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000796 raise
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800797 finally:
798 del cls.get_parallel_context()["sync_dict"][k]
Mike Frysinger7b586f22021-02-23 18:38:39 -0500799
Gavin Makea2e3302023-03-11 06:46:20 +0000800 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700801 return _FetchOneResult(
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800802 success, errors, project_idx, start, finish, remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700803 )
David James8d201162013-10-11 17:03:19 -0700804
Gavin Mak04cba4a2023-05-24 21:28:28 +0000805 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000806 earliest_time = float("inf")
807 earliest_proj = None
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800808 items = self.get_parallel_context()["sync_dict"].items()
Gavin Mak945c0062023-05-30 20:04:07 +0000809 for project, t in items:
Gavin Mak551285f2023-05-04 04:48:43 +0000810 if t < earliest_time:
811 earliest_time = t
812 earliest_proj = project
813
Josip Sokcevic71122f92023-05-26 02:44:37 +0000814 if not earliest_proj:
Gavin Mak945c0062023-05-30 20:04:07 +0000815 # This function is called when sync is still running but in some
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800816 # cases (by chance), sync_dict can contain no entries. Return some
Gavin Mak945c0062023-05-30 20:04:07 +0000817 # text to indicate that sync is still working.
818 return "..working.."
Josip Sokcevic71122f92023-05-26 02:44:37 +0000819
Gavin Mak551285f2023-05-04 04:48:43 +0000820 elapsed = time.time() - earliest_time
Gavin Mak945c0062023-05-30 20:04:07 +0000821 jobs = jobs_str(len(items))
Gavin Mak04cba4a2023-05-24 21:28:28 +0000822 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000823
Kuang-che Wuab2d3212024-11-06 13:03:42 +0800824 @classmethod
825 def InitWorker(cls):
826 # Force connect to the manager server now.
827 # This is good because workers are initialized one by one. Without this,
828 # multiple workers may connect to the manager when handling the first
829 # job at the same time. Then the connection may fail if too many
830 # connections are pending and execeeded the socket listening backlog,
831 # especially on MacOS.
832 len(cls.get_parallel_context()["sync_dict"])
833
Jason Changdaf2ad32023-08-31 17:06:36 -0700834 def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
Gavin Makea2e3302023-03-11 06:46:20 +0000835 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500836
Gavin Makea2e3302023-03-11 06:46:20 +0000837 fetched = set()
838 remote_fetched = set()
Gavin Makedcaa942023-04-27 05:58:57 +0000839 pm = Progress(
840 "Fetching",
841 len(projects),
842 delay=False,
843 quiet=opt.quiet,
844 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000845 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000846 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800847
Gavin Mak551285f2023-05-04 04:48:43 +0000848 sync_event = _threading.Event()
849
850 def _MonitorSyncLoop():
851 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000852 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000853 if sync_event.wait(timeout=1):
854 return
855
856 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
857 sync_progress_thread.daemon = True
Gavin Mak551285f2023-05-04 04:48:43 +0000858
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800859 def _ProcessResults(pool, pm, results_sets):
Gavin Makea2e3302023-03-11 06:46:20 +0000860 ret = True
861 for results in results_sets:
862 for result in results:
863 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800864 project = projects[result.project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +0000865 start = result.start
866 finish = result.finish
867 self._fetch_times.Set(project, finish - start)
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000868 self._local_sync_state.SetFetchTime(project)
Gavin Makea2e3302023-03-11 06:46:20 +0000869 self.event_log.AddSync(
870 project,
871 event_log.TASK_SYNC_NETWORK,
872 start,
873 finish,
874 success,
875 )
Jason Chang32b59562023-07-14 16:45:35 -0700876 if result.errors:
877 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000878 if result.remote_fetched:
879 remote_fetched.add(project)
880 # Check for any errors before running any more tasks.
881 # ...we'll let existing jobs finish, though.
882 if not success:
883 ret = False
884 else:
885 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000886 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000887 if not ret and opt.fail_fast:
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800888 if pool:
889 pool.close()
Gavin Makea2e3302023-03-11 06:46:20 +0000890 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500891 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700892
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800893 with self.ParallelContext():
894 self.get_parallel_context()["projects"] = projects
895 self.get_parallel_context()[
896 "sync_dict"
897 ] = multiprocessing.Manager().dict()
Mike Frysingerebf04a42021-02-23 20:48:04 -0500898
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800899 objdir_project_map = dict()
900 for index, project in enumerate(projects):
901 objdir_project_map.setdefault(project.objdir, []).append(index)
902 projects_list = list(objdir_project_map.values())
903
Peter Kjellerstedt616e3142024-11-20 21:10:29 +0100904 jobs = max(1, min(opt.jobs_network, len(projects_list)))
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800905
906 # We pass the ssh proxy settings via the class. This allows
907 # multiprocessing to pickle it up when spawning children. We can't
908 # pass it as an argument to _FetchProjectList below as
909 # multiprocessing is unable to pickle those.
910 self.get_parallel_context()["ssh_proxy"] = ssh_proxy
911
912 sync_progress_thread.start()
Josip Sokcevic454fdaf2024-10-07 17:33:38 +0000913 if not opt.quiet:
Gavin Makea2e3302023-03-11 06:46:20 +0000914 pm.update(inc=0, msg="warming up")
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800915 try:
916 ret = self.ExecuteInParallel(
917 jobs,
Gavin Makea2e3302023-03-11 06:46:20 +0000918 functools.partial(self._FetchProjectList, opt),
919 projects_list,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800920 callback=_ProcessResults,
921 output=pm,
922 # Use chunksize=1 to avoid the chance that some workers are
923 # idle while other workers still have more than one job in
924 # their chunk queue.
925 chunksize=1,
Kuang-che Wuab2d3212024-11-06 13:03:42 +0800926 initializer=self.InitWorker,
Gavin Makea2e3302023-03-11 06:46:20 +0000927 )
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800928 finally:
929 sync_event.set()
930 sync_progress_thread.join()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000931
Gavin Makea2e3302023-03-11 06:46:20 +0000932 self._fetch_times.Save()
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000933 self._local_sync_state.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000934
Gavin Makea2e3302023-03-11 06:46:20 +0000935 if not self.outer_client.manifest.IsArchive:
936 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400937
Jason Changdaf2ad32023-08-31 17:06:36 -0700938 return _FetchResult(ret, fetched)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000939
Gavin Makea2e3302023-03-11 06:46:20 +0000940 def _FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -0700941 self, opt, args, all_projects, err_event, ssh_proxy, manifest, errors
Gavin Makea2e3302023-03-11 06:46:20 +0000942 ):
943 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400944
Gavin Makea2e3302023-03-11 06:46:20 +0000945 Args:
946 opt: Program options returned from optparse. See _Options().
947 args: Command line args used to filter out projects.
948 all_projects: List of all projects that should be fetched.
949 err_event: Whether an error was hit while processing.
950 ssh_proxy: SSH manager for clients & masters.
951 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700952
Gavin Makea2e3302023-03-11 06:46:20 +0000953 Returns:
954 List of all projects that should be checked out.
955 """
956 rp = manifest.repoProject
LaMont Jones891e8f72022-09-08 20:17:58 +0000957
Gavin Makea2e3302023-03-11 06:46:20 +0000958 to_fetch = []
959 now = time.time()
960 if _ONE_DAY_S <= (now - rp.LastFetch):
961 to_fetch.append(rp)
962 to_fetch.extend(all_projects)
963 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700964
Jason Changdaf2ad32023-08-31 17:06:36 -0700965 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000966 success = result.success
967 fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700968
Gavin Makea2e3302023-03-11 06:46:20 +0000969 if not success:
970 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700971
Fredrik de Grootebdf0402024-10-22 14:14:59 +0200972 # Call self update, unless requested not to
973 if os.environ.get("REPO_SKIP_SELF_UPDATE", "0") == "0":
974 _PostRepoFetch(rp, opt.repo_verify)
Gavin Makea2e3302023-03-11 06:46:20 +0000975 if opt.network_only:
976 # Bail out now; the rest touches the working tree.
977 if err_event.is_set():
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000978 e = SyncError(
Jason Chang32b59562023-07-14 16:45:35 -0700979 "error: Exited sync due to fetch errors.",
980 aggregate_errors=errors,
981 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000982
983 logger.error(e)
984 raise e
Jason Changdaf2ad32023-08-31 17:06:36 -0700985 return _FetchMainResult([])
Dave Borowitz18857212012-10-23 17:02:59 -0700986
Gavin Makea2e3302023-03-11 06:46:20 +0000987 # Iteratively fetch missing and/or nested unregistered submodules.
988 previously_missing_set = set()
989 while True:
990 self._ReloadManifest(None, manifest)
991 all_projects = self.GetProjects(
992 args,
993 missing_ok=True,
994 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000995 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000996 all_manifests=not opt.this_manifest_only,
997 )
998 missing = []
999 for project in all_projects:
1000 if project.gitdir not in fetched:
1001 missing.append(project)
1002 if not missing:
1003 break
1004 # Stop us from non-stopped fetching actually-missing repos: If set
1005 # of missing repos has not been changed from last fetch, we break.
Jason R. Coombs0bcffd82023-10-20 23:29:42 +05451006 missing_set = {p.name for p in missing}
Gavin Makea2e3302023-03-11 06:46:20 +00001007 if previously_missing_set == missing_set:
1008 break
1009 previously_missing_set = missing_set
Jason Changdaf2ad32023-08-31 17:06:36 -07001010 result = self._Fetch(missing, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001011 success = result.success
1012 new_fetched = result.projects
1013 if not success:
1014 err_event.set()
1015 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001016
Jason Changdaf2ad32023-08-31 17:06:36 -07001017 return _FetchMainResult(all_projects)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001018
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001019 @classmethod
Josip Sokcevicedadb252024-02-29 09:48:37 -08001020 def _CheckoutOne(
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001021 cls,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001022 detach_head,
1023 force_sync,
1024 force_checkout,
1025 force_rebase,
1026 verbose,
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001027 project_idx,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001028 ):
Gavin Makea2e3302023-03-11 06:46:20 +00001029 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +08001030
Gavin Makea2e3302023-03-11 06:46:20 +00001031 Args:
1032 detach_head: Whether to leave a detached HEAD.
Josip Sokcevicedadb252024-02-29 09:48:37 -08001033 force_sync: Force checking out of .git directory (e.g. overwrite
1034 existing git directory that was previously linked to a different
1035 object directory).
1036 force_checkout: Force checking out of the repo content.
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001037 force_rebase: Force rebase.
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001038 verbose: Whether to show verbose messages.
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001039 project_idx: Project index for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +08001040
Gavin Makea2e3302023-03-11 06:46:20 +00001041 Returns:
1042 Whether the fetch was successful.
1043 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001044 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +00001045 start = time.time()
1046 syncbuf = SyncBuffer(
1047 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001048 )
Gavin Makea2e3302023-03-11 06:46:20 +00001049 success = False
Jason Chang32b59562023-07-14 16:45:35 -07001050 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +09001051 try:
Jason Chang32b59562023-07-14 16:45:35 -07001052 project.Sync_LocalHalf(
Josip Sokcevicedadb252024-02-29 09:48:37 -08001053 syncbuf,
1054 force_sync=force_sync,
1055 force_checkout=force_checkout,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001056 force_rebase=force_rebase,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001057 errors=errors,
1058 verbose=verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001059 )
Gavin Makea2e3302023-03-11 06:46:20 +00001060 success = syncbuf.Finish()
Josip Sokcevicd93fe602025-01-08 18:31:46 +00001061 except KeyboardInterrupt:
1062 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001063 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001064 logger.error(
1065 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +00001066 )
Jason Chang32b59562023-07-14 16:45:35 -07001067 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001068 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001069 logger.error(
1070 "error: Cannot checkout %s: %s: %s",
1071 project.name,
1072 type(e).__name__,
1073 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001074 )
1075 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -07001076
Gavin Makea2e3302023-03-11 06:46:20 +00001077 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001078 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001079 finish = time.time()
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001080 return _CheckoutOneResult(success, errors, project_idx, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -04001081
Jason Chang32b59562023-07-14 16:45:35 -07001082 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001083 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001084
Gavin Makea2e3302023-03-11 06:46:20 +00001085 Args:
1086 all_projects: List of all projects that should be checked out.
1087 opt: Program options returned from optparse. See _Options().
1088 err_results: A list of strings, paths to git repos where checkout
1089 failed.
1090 """
1091 # Only checkout projects with worktrees.
1092 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001093
Gavin Makea2e3302023-03-11 06:46:20 +00001094 def _ProcessResults(pool, pm, results):
1095 ret = True
1096 for result in results:
1097 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001098 project = self.get_parallel_context()["projects"][
1099 result.project_idx
1100 ]
Gavin Makea2e3302023-03-11 06:46:20 +00001101 start = result.start
1102 finish = result.finish
1103 self.event_log.AddSync(
1104 project, event_log.TASK_SYNC_LOCAL, start, finish, success
1105 )
Jason Chang32b59562023-07-14 16:45:35 -07001106
1107 if result.errors:
1108 checkout_errors.extend(result.errors)
1109
Gavin Makea2e3302023-03-11 06:46:20 +00001110 # Check for any errors before running any more tasks.
1111 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001112 if success:
1113 self._local_sync_state.SetCheckoutTime(project)
1114 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001115 ret = False
1116 err_results.append(
1117 project.RelPath(local=opt.this_manifest_only)
1118 )
1119 if opt.fail_fast:
1120 if pool:
1121 pool.close()
1122 return ret
1123 pm.update(msg=project.name)
1124 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001125
Josip Sokcevic55545722024-02-22 16:38:00 -08001126 for projects in _SafeCheckoutOrder(all_projects):
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001127 with self.ParallelContext():
1128 self.get_parallel_context()["projects"] = projects
1129 proc_res = self.ExecuteInParallel(
1130 opt.jobs_checkout,
1131 functools.partial(
1132 self._CheckoutOne,
1133 opt.detach_head,
1134 opt.force_sync,
1135 opt.force_checkout,
1136 opt.rebase,
1137 opt.verbose,
1138 ),
1139 range(len(projects)),
1140 callback=_ProcessResults,
1141 output=Progress(
1142 "Checking out", len(all_projects), quiet=opt.quiet
1143 ),
1144 # Use chunksize=1 to avoid the chance that some workers are
1145 # idle while other workers still have more than one job in
1146 # their chunk queue.
1147 chunksize=1,
1148 )
Simran Basib9a1b732015-08-20 12:19:28 -07001149
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001150 self._local_sync_state.Save()
1151 return proc_res and not err_results
1152
Gavin Makea2e3302023-03-11 06:46:20 +00001153 @staticmethod
1154 def _GetPreciousObjectsState(project: Project, opt):
1155 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001156
Gavin Makea2e3302023-03-11 06:46:20 +00001157 Args:
1158 project (Project): the project to examine, and possibly correct.
1159 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001160
Gavin Makea2e3302023-03-11 06:46:20 +00001161 Returns:
1162 Expected state of extensions.preciousObjects:
1163 False: Should be disabled. (not present)
1164 True: Should be enabled.
1165 """
1166 if project.use_git_worktrees:
1167 return False
1168 projects = project.manifest.GetProjectsWithName(
1169 project.name, all_manifests=True
1170 )
1171 if len(projects) == 1:
1172 return False
1173 if len(projects) > 1:
1174 # Objects are potentially shared with another project.
1175 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1176 # - When False, shared projects share (via symlink)
1177 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1178 # objects directory. All objects are precious, since there is no
1179 # project with a complete set of refs.
1180 # - When True, shared projects share (via info/alternates)
1181 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1182 # store, which is written only on the first clone of the project,
1183 # and is not written subsequently. (When Sync_NetworkHalf sees
1184 # that it exists, it makes sure that the alternates file points
1185 # there, and uses a project-local .git/objects directory for all
1186 # syncs going forward.
1187 # We do not support switching between the options. The environment
1188 # variable is present for testing and migration only.
1189 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001190
Gavin Makea2e3302023-03-11 06:46:20 +00001191 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001192
Gavin Makea2e3302023-03-11 06:46:20 +00001193 def _SetPreciousObjectsState(self, project: Project, opt):
1194 """Correct the preciousObjects state for the project.
1195
1196 Args:
1197 project: the project to examine, and possibly correct.
1198 opt: options given to sync.
1199 """
1200 expected = self._GetPreciousObjectsState(project, opt)
1201 actual = (
1202 project.config.GetBoolean("extensions.preciousObjects") or False
1203 )
1204 relpath = project.RelPath(local=opt.this_manifest_only)
1205
1206 if expected != actual:
1207 # If this is unexpected, log it and repair.
1208 Trace(
1209 f"{relpath} expected preciousObjects={expected}, got {actual}"
1210 )
1211 if expected:
1212 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001213 print(
1214 "\r%s: Shared project %s found, disabling pruning."
1215 % (relpath, project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001216 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001217
Gavin Makea2e3302023-03-11 06:46:20 +00001218 if git_require((2, 7, 0)):
1219 project.EnableRepositoryExtension("preciousObjects")
1220 else:
1221 # This isn't perfect, but it's the best we can do with old
1222 # git.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001223 logger.warning(
1224 "%s: WARNING: shared projects are unreliable when "
Gavin Makea2e3302023-03-11 06:46:20 +00001225 "using old versions of git; please upgrade to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001226 "git-2.7.0+.",
1227 relpath,
Gavin Makea2e3302023-03-11 06:46:20 +00001228 )
1229 project.config.SetString("gc.pruneExpire", "never")
1230 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001231 project.config.SetString("extensions.preciousObjects", None)
1232 project.config.SetString("gc.pruneExpire", None)
1233
1234 def _GCProjects(self, projects, opt, err_event):
1235 """Perform garbage collection.
1236
1237 If We are skipping garbage collection (opt.auto_gc not set), we still
1238 want to potentially mark objects precious, so that `git gc` does not
1239 discard shared objects.
1240 """
1241 if not opt.auto_gc:
1242 # Just repair preciousObjects state, and return.
1243 for project in projects:
1244 self._SetPreciousObjectsState(project, opt)
1245 return
1246
1247 pm = Progress(
1248 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1249 )
1250 pm.update(inc=0, msg="prescan")
1251
1252 tidy_dirs = {}
1253 for project in projects:
1254 self._SetPreciousObjectsState(project, opt)
1255
1256 project.config.SetString("gc.autoDetach", "false")
1257 # Only call git gc once per objdir, but call pack-refs for the
1258 # remainder.
1259 if project.objdir not in tidy_dirs:
1260 tidy_dirs[project.objdir] = (
1261 True, # Run a full gc.
1262 project.bare_git,
1263 )
1264 elif project.gitdir not in tidy_dirs:
1265 tidy_dirs[project.gitdir] = (
1266 False, # Do not run a full gc; just run pack-refs.
1267 project.bare_git,
1268 )
1269
1270 jobs = opt.jobs
1271
1272 if jobs < 2:
1273 for run_gc, bare_git in tidy_dirs.values():
1274 pm.update(msg=bare_git._project.name)
1275
1276 if run_gc:
1277 bare_git.gc("--auto")
1278 else:
1279 bare_git.pack_refs()
1280 pm.end()
1281 return
1282
1283 cpu_count = os.cpu_count()
1284 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1285
1286 threads = set()
1287 sem = _threading.Semaphore(jobs)
1288
1289 def tidy_up(run_gc, bare_git):
1290 pm.start(bare_git._project.name)
1291 try:
1292 try:
1293 if run_gc:
1294 bare_git.gc("--auto", config=config)
1295 else:
1296 bare_git.pack_refs(config=config)
1297 except GitError:
1298 err_event.set()
1299 except Exception:
1300 err_event.set()
1301 raise
1302 finally:
1303 pm.finish(bare_git._project.name)
1304 sem.release()
1305
1306 for run_gc, bare_git in tidy_dirs.values():
1307 if err_event.is_set() and opt.fail_fast:
1308 break
1309 sem.acquire()
1310 t = _threading.Thread(
1311 target=tidy_up,
1312 args=(
1313 run_gc,
1314 bare_git,
1315 ),
1316 )
1317 t.daemon = True
1318 threads.add(t)
1319 t.start()
1320
1321 for t in threads:
1322 t.join()
1323 pm.end()
1324
1325 def _ReloadManifest(self, manifest_name, manifest):
1326 """Reload the manfiest from the file specified by the |manifest_name|.
1327
1328 It unloads the manifest if |manifest_name| is None.
1329
1330 Args:
1331 manifest_name: Manifest file to be reloaded.
1332 manifest: The manifest to use.
1333 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001334 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001335 # Override calls Unload already.
1336 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001337 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001338 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001339
Gavin Makea2e3302023-03-11 06:46:20 +00001340 def UpdateProjectList(self, opt, manifest):
1341 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001342
Gavin Makea2e3302023-03-11 06:46:20 +00001343 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001344
Gavin Makea2e3302023-03-11 06:46:20 +00001345 Args:
1346 opt: Program options returned from optparse. See _Options().
1347 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001348
Gavin Makea2e3302023-03-11 06:46:20 +00001349 Returns:
1350 0: success
1351 1: failure
1352 """
1353 new_project_paths = []
1354 for project in self.GetProjects(
1355 None, missing_ok=True, manifest=manifest, all_manifests=False
1356 ):
1357 if project.relpath:
1358 new_project_paths.append(project.relpath)
1359 file_name = "project.list"
1360 file_path = os.path.join(manifest.subdir, file_name)
1361 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001362
Gavin Makea2e3302023-03-11 06:46:20 +00001363 if os.path.exists(file_path):
Jason R. Coombs034950b2023-10-20 23:32:02 +05451364 with open(file_path) as fd:
Gavin Makea2e3302023-03-11 06:46:20 +00001365 old_project_paths = fd.read().split("\n")
1366 # In reversed order, so subfolders are deleted before parent folder.
1367 for path in sorted(old_project_paths, reverse=True):
1368 if not path:
1369 continue
1370 if path not in new_project_paths:
1371 # If the path has already been deleted, we don't need to do
1372 # it.
1373 gitdir = os.path.join(manifest.topdir, path, ".git")
1374 if os.path.exists(gitdir):
1375 project = Project(
1376 manifest=manifest,
1377 name=path,
1378 remote=RemoteSpec("origin"),
1379 gitdir=gitdir,
1380 objdir=gitdir,
1381 use_git_worktrees=os.path.isfile(gitdir),
1382 worktree=os.path.join(manifest.topdir, path),
1383 relpath=path,
1384 revisionExpr="HEAD",
1385 revisionId=None,
1386 groups=None,
1387 )
Jason Chang32b59562023-07-14 16:45:35 -07001388 project.DeleteWorktree(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001389 verbose=opt.verbose, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001390 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001391
Gavin Makea2e3302023-03-11 06:46:20 +00001392 new_project_paths.sort()
1393 with open(file_path, "w") as fd:
1394 fd.write("\n".join(new_project_paths))
1395 fd.write("\n")
1396 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001397
Gavin Makea2e3302023-03-11 06:46:20 +00001398 def UpdateCopyLinkfileList(self, manifest):
1399 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001400
Gavin Makea2e3302023-03-11 06:46:20 +00001401 Returns:
1402 Whether update was successful.
1403 """
1404 new_paths = {}
1405 new_linkfile_paths = []
1406 new_copyfile_paths = []
1407 for project in self.GetProjects(
1408 None, missing_ok=True, manifest=manifest, all_manifests=False
1409 ):
1410 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1411 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001412
Gavin Makea2e3302023-03-11 06:46:20 +00001413 new_paths = {
1414 "linkfile": new_linkfile_paths,
1415 "copyfile": new_copyfile_paths,
1416 }
jiajia tanga590e642021-04-25 20:02:02 +08001417
Gavin Makea2e3302023-03-11 06:46:20 +00001418 copylinkfile_name = "copy-link-files.json"
1419 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1420 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001421
Gavin Makea2e3302023-03-11 06:46:20 +00001422 if os.path.exists(copylinkfile_path):
1423 with open(copylinkfile_path, "rb") as fp:
1424 try:
1425 old_copylinkfile_paths = json.load(fp)
1426 except Exception:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001427 logger.error(
1428 "error: %s is not a json formatted file.",
1429 copylinkfile_path,
Gavin Makea2e3302023-03-11 06:46:20 +00001430 )
1431 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001432 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001433
Gavin Makea2e3302023-03-11 06:46:20 +00001434 need_remove_files = []
1435 need_remove_files.extend(
1436 set(old_copylinkfile_paths.get("linkfile", []))
1437 - set(new_linkfile_paths)
1438 )
1439 need_remove_files.extend(
1440 set(old_copylinkfile_paths.get("copyfile", []))
1441 - set(new_copyfile_paths)
1442 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001443
Gavin Makea2e3302023-03-11 06:46:20 +00001444 for need_remove_file in need_remove_files:
1445 # Try to remove the updated copyfile or linkfile.
1446 # So, if the file is not exist, nothing need to do.
Josip Sokcevic9500aca2024-12-13 18:24:20 +00001447 platform_utils.remove(
1448 os.path.join(self.client.topdir, need_remove_file),
1449 missing_ok=True,
1450 )
Raman Tenneti7954de12021-07-28 14:36:49 -07001451
Gavin Makea2e3302023-03-11 06:46:20 +00001452 # Create copy-link-files.json, save dest path of "copyfile" and
1453 # "linkfile".
1454 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1455 json.dump(new_paths, fp)
1456 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001457
Gavin Makea2e3302023-03-11 06:46:20 +00001458 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1459 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001460 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001461 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001462 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001463 )
Gavin Makea2e3302023-03-11 06:46:20 +00001464
1465 manifest_server = manifest.manifest_server
1466 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001467 print("Using manifest server %s" % manifest_server)
Gavin Makea2e3302023-03-11 06:46:20 +00001468
1469 if "@" not in manifest_server:
1470 username = None
1471 password = None
1472 if opt.manifest_server_username and opt.manifest_server_password:
1473 username = opt.manifest_server_username
1474 password = opt.manifest_server_password
1475 else:
1476 try:
1477 info = netrc.netrc()
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451478 except OSError:
Gavin Makea2e3302023-03-11 06:46:20 +00001479 # .netrc file does not exist or could not be opened.
1480 pass
1481 else:
1482 try:
1483 parse_result = urllib.parse.urlparse(manifest_server)
1484 if parse_result.hostname:
1485 auth = info.authenticators(parse_result.hostname)
1486 if auth:
1487 username, _account, password = auth
1488 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001489 logger.error(
1490 "No credentials found for %s in .netrc",
1491 parse_result.hostname,
Gavin Makea2e3302023-03-11 06:46:20 +00001492 )
1493 except netrc.NetrcParseError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001494 logger.error("Error parsing .netrc file: %s", e)
Gavin Makea2e3302023-03-11 06:46:20 +00001495
1496 if username and password:
1497 manifest_server = manifest_server.replace(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001498 "://", f"://{username}:{password}@", 1
Gavin Makea2e3302023-03-11 06:46:20 +00001499 )
1500
1501 transport = PersistentTransport(manifest_server)
1502 if manifest_server.startswith("persistent-"):
1503 manifest_server = manifest_server[len("persistent-") :]
1504
Mike Frysingerdfdf5772025-01-30 19:11:36 -05001505 # Changes in behavior should update docs/smart-sync.md accordingly.
Gavin Makea2e3302023-03-11 06:46:20 +00001506 try:
1507 server = xmlrpc.client.Server(manifest_server, transport=transport)
1508 if opt.smart_sync:
1509 branch = self._GetBranch(manifest.manifestProject)
1510
1511 if "SYNC_TARGET" in os.environ:
1512 target = os.environ["SYNC_TARGET"]
1513 [success, manifest_str] = server.GetApprovedManifest(
1514 branch, target
1515 )
1516 elif (
1517 "TARGET_PRODUCT" in os.environ
1518 and "TARGET_BUILD_VARIANT" in os.environ
Navil1e19f7d2024-09-11 16:49:49 +00001519 and "TARGET_RELEASE" in os.environ
1520 ):
1521 target = "%s-%s-%s" % (
1522 os.environ["TARGET_PRODUCT"],
1523 os.environ["TARGET_RELEASE"],
1524 os.environ["TARGET_BUILD_VARIANT"],
1525 )
1526 [success, manifest_str] = server.GetApprovedManifest(
1527 branch, target
1528 )
1529 elif (
1530 "TARGET_PRODUCT" in os.environ
1531 and "TARGET_BUILD_VARIANT" in os.environ
Gavin Makea2e3302023-03-11 06:46:20 +00001532 ):
1533 target = "%s-%s" % (
1534 os.environ["TARGET_PRODUCT"],
1535 os.environ["TARGET_BUILD_VARIANT"],
1536 )
1537 [success, manifest_str] = server.GetApprovedManifest(
1538 branch, target
1539 )
1540 else:
1541 [success, manifest_str] = server.GetApprovedManifest(branch)
1542 else:
1543 assert opt.smart_tag
1544 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1545
1546 if success:
1547 manifest_name = os.path.basename(smart_sync_manifest_path)
1548 try:
1549 with open(smart_sync_manifest_path, "w") as f:
1550 f.write(manifest_str)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451551 except OSError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001552 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001553 "error: cannot write manifest to %s:\n%s"
1554 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001555 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001556 )
Gavin Makea2e3302023-03-11 06:46:20 +00001557 self._ReloadManifest(manifest_name, manifest)
1558 else:
Jason Chang32b59562023-07-14 16:45:35 -07001559 raise SmartSyncError(
1560 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001561 )
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451562 except (OSError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001563 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001564 "error: cannot connect to manifest server %s:\n%s"
1565 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001566 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001567 )
Gavin Makea2e3302023-03-11 06:46:20 +00001568 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001569 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001570 "error: cannot connect to manifest server %s:\n%d %s"
1571 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001572 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001573 )
Gavin Makea2e3302023-03-11 06:46:20 +00001574
1575 return manifest_name
1576
Jason Changdaf2ad32023-08-31 17:06:36 -07001577 def _UpdateAllManifestProjects(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001578 """Fetch & update the local manifest project.
1579
1580 After syncing the manifest project, if the manifest has any sub
1581 manifests, those are recursively processed.
1582
1583 Args:
1584 opt: Program options returned from optparse. See _Options().
1585 mp: the manifestProject to query.
1586 manifest_name: Manifest file to be reloaded.
1587 """
1588 if not mp.standalone_manifest_url:
Jason Changdaf2ad32023-08-31 17:06:36 -07001589 self._UpdateManifestProject(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001590
1591 if mp.manifest.submanifests:
1592 for submanifest in mp.manifest.submanifests.values():
1593 child = submanifest.repo_client.manifest
1594 child.manifestProject.SyncWithPossibleInit(
1595 submanifest,
1596 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1597 verbose=opt.verbose,
1598 tags=opt.tags,
1599 git_event_log=self.git_event_log,
1600 )
1601 self._UpdateAllManifestProjects(
Jason Changdaf2ad32023-08-31 17:06:36 -07001602 opt, child.manifestProject, None, errors
Gavin Makea2e3302023-03-11 06:46:20 +00001603 )
1604
Jason Changdaf2ad32023-08-31 17:06:36 -07001605 def _UpdateManifestProject(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001606 """Fetch & update the local manifest project.
1607
1608 Args:
1609 opt: Program options returned from optparse. See _Options().
1610 mp: the manifestProject to query.
1611 manifest_name: Manifest file to be reloaded.
1612 """
1613 if not opt.local_only:
1614 start = time.time()
Jason Changdaf2ad32023-08-31 17:06:36 -07001615 buf = TeeStringIO(sys.stdout)
1616 try:
1617 result = mp.Sync_NetworkHalf(
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -08001618 quiet=not opt.verbose,
Jason Changdaf2ad32023-08-31 17:06:36 -07001619 output_redir=buf,
1620 verbose=opt.verbose,
1621 current_branch_only=self._GetCurrentBranchOnly(
1622 opt, mp.manifest
1623 ),
1624 force_sync=opt.force_sync,
1625 tags=opt.tags,
1626 optimized_fetch=opt.optimized_fetch,
1627 retry_fetches=opt.retry_fetches,
1628 submodules=mp.manifest.HasSubmodules,
1629 clone_filter=mp.manifest.CloneFilter,
1630 partial_clone_exclude=mp.manifest.PartialCloneExclude,
1631 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
1632 )
1633 if result.error:
1634 errors.append(result.error)
1635 except KeyboardInterrupt:
1636 errors.append(
1637 ManifestInterruptError(buf.getvalue(), project=mp.name)
1638 )
1639 raise
1640
Gavin Makea2e3302023-03-11 06:46:20 +00001641 finish = time.time()
1642 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001643 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001644 )
1645
1646 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001647 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001648 syncbuf = SyncBuffer(mp.config)
1649 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001650 mp.Sync_LocalHalf(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001651 syncbuf,
1652 submodules=mp.manifest.HasSubmodules,
1653 errors=errors,
1654 verbose=opt.verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001655 )
Gavin Makea2e3302023-03-11 06:46:20 +00001656 clean = syncbuf.Finish()
1657 self.event_log.AddSync(
1658 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1659 )
1660 if not clean:
Yiwei Zhangd379e772023-12-20 20:39:59 +00001661 raise UpdateManifestError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001662 self._ReloadManifest(manifest_name, mp.manifest)
1663
1664 def ValidateOptions(self, opt, args):
1665 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001666 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001667 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001668 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001669 )
1670 if opt.network_only and opt.detach_head:
1671 self.OptionParser.error("cannot combine -n and -d")
1672 if opt.network_only and opt.local_only:
1673 self.OptionParser.error("cannot combine -n and -l")
1674 if opt.manifest_name and opt.smart_sync:
1675 self.OptionParser.error("cannot combine -m and -s")
1676 if opt.manifest_name and opt.smart_tag:
1677 self.OptionParser.error("cannot combine -m and -t")
1678 if opt.manifest_server_username or opt.manifest_server_password:
1679 if not (opt.smart_sync or opt.smart_tag):
1680 self.OptionParser.error(
1681 "-u and -p may only be combined with -s or -t"
1682 )
1683 if None in [
1684 opt.manifest_server_username,
1685 opt.manifest_server_password,
1686 ]:
1687 self.OptionParser.error("both -u and -p must be given")
1688
1689 if opt.prune is None:
1690 opt.prune = True
1691
Gavin Makea2e3302023-03-11 06:46:20 +00001692 def _ValidateOptionsWithManifest(self, opt, mp):
1693 """Like ValidateOptions, but after we've updated the manifest.
1694
1695 Needed to handle sync-xxx option defaults in the manifest.
1696
1697 Args:
1698 opt: The options to process.
1699 mp: The manifest project to pull defaults from.
1700 """
1701 if not opt.jobs:
1702 # If the user hasn't made a choice, use the manifest value.
1703 opt.jobs = mp.manifest.default.sync_j
1704 if opt.jobs:
1705 # If --jobs has a non-default value, propagate it as the default for
1706 # --jobs-xxx flags too.
1707 if not opt.jobs_network:
1708 opt.jobs_network = opt.jobs
1709 if not opt.jobs_checkout:
1710 opt.jobs_checkout = opt.jobs
1711 else:
1712 # Neither user nor manifest have made a choice, so setup defaults.
1713 if not opt.jobs_network:
1714 opt.jobs_network = 1
1715 if not opt.jobs_checkout:
1716 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1717 opt.jobs = os.cpu_count()
1718
1719 # Try to stay under user rlimit settings.
1720 #
1721 # Since each worker requires at 3 file descriptors to run `git fetch`,
1722 # use that to scale down the number of jobs. Unfortunately there isn't
1723 # an easy way to determine this reliably as systems change, but it was
1724 # last measured by hand in 2011.
1725 soft_limit, _ = _rlimit_nofile()
1726 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1727 opt.jobs = min(opt.jobs, jobs_soft_limit)
1728 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1729 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1730
1731 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001732 errors = []
1733 try:
1734 self._ExecuteHelper(opt, args, errors)
Jason Chang26fa3182024-02-05 15:15:20 -08001735 except (RepoExitError, RepoChangedException):
Jason Chang32b59562023-07-14 16:45:35 -07001736 raise
1737 except (KeyboardInterrupt, Exception) as e:
1738 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1739
1740 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001741 manifest = self.outer_manifest
1742 if not opt.outer_manifest:
1743 manifest = self.manifest
1744
1745 if opt.manifest_name:
1746 manifest.Override(opt.manifest_name)
1747
1748 manifest_name = opt.manifest_name
1749 smart_sync_manifest_path = os.path.join(
1750 manifest.manifestProject.worktree, "smart_sync_override.xml"
1751 )
1752
1753 if opt.clone_bundle is None:
1754 opt.clone_bundle = manifest.CloneBundle
1755
1756 if opt.smart_sync or opt.smart_tag:
1757 manifest_name = self._SmartSyncSetup(
1758 opt, smart_sync_manifest_path, manifest
1759 )
1760 else:
1761 if os.path.isfile(smart_sync_manifest_path):
1762 try:
1763 platform_utils.remove(smart_sync_manifest_path)
1764 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001765 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001766 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001767 "manifest: %s",
1768 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001769 )
1770
1771 err_event = multiprocessing.Event()
1772
1773 rp = manifest.repoProject
1774 rp.PreSync()
1775 cb = rp.CurrentBranch
1776 if cb:
1777 base = rp.GetBranch(cb).merge
1778 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001779 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001780 "warning: repo is not tracking a remote branch, so it will "
1781 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001782 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001783 )
1784
1785 for m in self.ManifestList(opt):
1786 if not m.manifestProject.standalone_manifest_url:
1787 m.manifestProject.PreSync()
1788
1789 if opt.repo_upgraded:
1790 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1791
1792 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001793
1794 if _REPO_ALLOW_SHALLOW is not None:
1795 if _REPO_ALLOW_SHALLOW == "1":
1796 mp.ConfigureCloneFilterForDepth(None)
1797 elif (
1798 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1799 ):
1800 mp.ConfigureCloneFilterForDepth("blob:none")
1801
Gavin Makea2e3302023-03-11 06:46:20 +00001802 if opt.mp_update:
Jason Changdaf2ad32023-08-31 17:06:36 -07001803 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001804 else:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001805 print("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001806
1807 # Now that the manifests are up-to-date, setup options whose defaults
1808 # might be in the manifest.
1809 self._ValidateOptionsWithManifest(opt, mp)
1810
1811 superproject_logging_data = {}
1812 self._UpdateProjectsRevisionId(
1813 opt, args, superproject_logging_data, manifest
1814 )
1815
Gavin Makea2e3302023-03-11 06:46:20 +00001816 all_projects = self.GetProjects(
1817 args,
1818 missing_ok=True,
1819 submodules_ok=opt.fetch_submodules,
1820 manifest=manifest,
1821 all_manifests=not opt.this_manifest_only,
1822 )
1823
1824 err_network_sync = False
1825 err_update_projects = False
1826 err_update_linkfiles = False
1827
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001828 # Log the repo projects by existing and new.
1829 existing = [x for x in all_projects if x.Exists]
1830 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1831 mp.config.SetString(
1832 "repo.newprojectcount", str(len(all_projects) - len(existing))
1833 )
1834
Gavin Makea2e3302023-03-11 06:46:20 +00001835 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001836 self._local_sync_state = LocalSyncState(manifest)
Josip Sokcevic61224d02024-12-18 18:37:41 +00001837 if not opt.local_only and not opt.repo_upgraded:
Gavin Makea2e3302023-03-11 06:46:20 +00001838 with multiprocessing.Manager() as manager:
1839 with ssh.ProxyManager(manager) as ssh_proxy:
1840 # Initialize the socket dir once in the parent.
1841 ssh_proxy.sock()
1842 result = self._FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -07001843 opt,
1844 args,
1845 all_projects,
1846 err_event,
1847 ssh_proxy,
1848 manifest,
1849 errors,
Gavin Makea2e3302023-03-11 06:46:20 +00001850 )
1851 all_projects = result.all_projects
1852
1853 if opt.network_only:
1854 return
1855
1856 # If we saw an error, exit with code 1 so that other scripts can
1857 # check.
1858 if err_event.is_set():
1859 err_network_sync = True
1860 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001861 logger.error(
1862 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001863 "Local checkouts *not* updated. Resolve network issues "
1864 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001865 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001866 )
Jason Chang32b59562023-07-14 16:45:35 -07001867 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001868
1869 for m in self.ManifestList(opt):
1870 if m.IsMirror or m.IsArchive:
1871 # Bail out now, we have no working tree.
1872 continue
1873
Jason Chang32b59562023-07-14 16:45:35 -07001874 try:
1875 self.UpdateProjectList(opt, m)
1876 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001877 err_event.set()
1878 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001879 errors.append(e)
1880 if isinstance(e, DeleteWorktreeError):
1881 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001882 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001883 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001884 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001885
Jason Chang32b59562023-07-14 16:45:35 -07001886 try:
1887 self.UpdateCopyLinkfileList(m)
1888 except Exception as e:
1889 err_update_linkfiles = True
1890 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001891 err_event.set()
1892 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001893 logger.error(
1894 "error: Local update copyfile or linkfile failed."
Gavin Makea2e3302023-03-11 06:46:20 +00001895 )
Jason Chang32b59562023-07-14 16:45:35 -07001896 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001897
1898 err_results = []
1899 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001900 err_checkout = not self._Checkout(
1901 all_projects, opt, err_results, errors
1902 )
Gavin Makea2e3302023-03-11 06:46:20 +00001903 if err_checkout:
1904 err_event.set()
1905
1906 printed_notices = set()
1907 # If there's a notice that's supposed to print at the end of the sync,
1908 # print it now... But avoid printing duplicate messages, and preserve
1909 # order.
1910 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1911 if m.notice and m.notice not in printed_notices:
1912 print(m.notice)
1913 printed_notices.add(m.notice)
1914
1915 # If we saw an error, exit with code 1 so that other scripts can check.
1916 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001917
1918 def print_and_log(err_msg):
1919 self.git_event_log.ErrorEvent(err_msg)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001920 logger.error("%s", err_msg)
Josip Sokcevic131fc962023-05-12 17:00:46 -07001921
1922 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001923 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001924 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001925 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001926 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001927 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001928 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001929 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001930 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001931 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001932 # Don't log repositories, as it may contain sensitive info.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001933 logger.error("Failing repos:\n%s", "\n".join(err_results))
Josip Sokcevic131fc962023-05-12 17:00:46 -07001934 # Not useful to log.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001935 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001936 'Try re-running with "-j1 --fail-fast" to exit at the first '
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001937 "error."
Gavin Makea2e3302023-03-11 06:46:20 +00001938 )
Jason Chang32b59562023-07-14 16:45:35 -07001939 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001940
1941 # Log the previous sync analysis state from the config.
1942 self.git_event_log.LogDataConfigEvents(
1943 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1944 )
1945
1946 # Update and log with the new sync analysis state.
1947 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1948 self.git_event_log.LogDataConfigEvents(
1949 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1950 )
1951
Gavin Makf0aeb222023-08-08 04:43:36 +00001952 self._local_sync_state.PruneRemovedProjects()
1953 if self._local_sync_state.IsPartiallySynced():
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001954 logger.warning(
Gavin Makf0aeb222023-08-08 04:43:36 +00001955 "warning: Partial syncs are not supported. For the best "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001956 "experience, sync the entire tree."
Gavin Makf0aeb222023-08-08 04:43:36 +00001957 )
1958
Gavin Makea2e3302023-03-11 06:46:20 +00001959 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001960 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001961
David Pursehouse819827a2020-02-12 15:20:19 +09001962
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001963def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001964 # Link the docs for the internal .repo/ layout for people.
1965 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1966 if not platform_utils.islink(link):
1967 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1968 try:
1969 platform_utils.symlink(target, link)
1970 except Exception:
1971 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001972
Gavin Makea2e3302023-03-11 06:46:20 +00001973 wrapper = Wrapper()
1974 if wrapper.NeedSetupGnuPG():
1975 wrapper.SetupGnuPG(quiet)
1976 for project in manifest.projects:
1977 if project.Exists:
1978 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001979
David Pursehouse819827a2020-02-12 15:20:19 +09001980
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001981def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001982 if rp.HasChanges:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +00001983 logger.warning("info: A new version of repo is available")
Gavin Makea2e3302023-03-11 06:46:20 +00001984 wrapper = Wrapper()
1985 try:
1986 rev = rp.bare_git.describe(rp.GetRevisionId())
1987 except GitError:
1988 rev = None
1989 _, new_rev = wrapper.check_repo_rev(
1990 rp.gitdir, rev, repo_verify=repo_verify
1991 )
1992 # See if we're held back due to missing signed tag.
1993 current_revid = rp.bare_git.rev_parse("HEAD")
1994 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1995 if current_revid != new_revid:
1996 # We want to switch to the new rev, but also not trash any
1997 # uncommitted changes. This helps with local testing/hacking.
1998 # If a local change has been made, we will throw that away.
1999 # We also have to make sure this will switch to an older commit if
2000 # that's the latest tag in order to support release rollback.
2001 try:
2002 rp.work_git.reset("--keep", new_rev)
2003 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07002004 raise RepoUnhandledExceptionError(e)
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00002005 print("info: Restarting repo with latest version")
Gavin Makea2e3302023-03-11 06:46:20 +00002006 raise RepoChangedException(["--repo-upgraded"])
2007 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00002008 logger.warning("warning: Skipped upgrade to unverified version")
Shawn O. Pearcee756c412009-04-13 11:51:15 -07002009 else:
Gavin Makea2e3302023-03-11 06:46:20 +00002010 if verbose:
Aravind Vasudevance0ed792023-10-06 18:36:22 +00002011 print("repo version %s is current" % rp.work_git.describe(HEAD))
Shawn O. Pearcee756c412009-04-13 11:51:15 -07002012
David Pursehouse819827a2020-02-12 15:20:19 +09002013
Mike Frysingerd4aee652023-10-19 05:13:32 -04002014class _FetchTimes:
Gavin Makea2e3302023-03-11 06:46:20 +00002015 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07002016
Gavin Makea2e3302023-03-11 06:46:20 +00002017 def __init__(self, manifest):
2018 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00002019 self._saved = None
2020 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002021
Gavin Makea2e3302023-03-11 06:46:20 +00002022 def Get(self, project):
2023 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00002024 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07002025
Gavin Makea2e3302023-03-11 06:46:20 +00002026 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00002027 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00002028
2029 # For shared projects, save the longest time.
2030 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07002031
Gavin Makea2e3302023-03-11 06:46:20 +00002032 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002033 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002034 try:
2035 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002036 self._saved = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452037 except (OSError, ValueError):
Gavin Makea2e3302023-03-11 06:46:20 +00002038 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00002039 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002040
Gavin Makea2e3302023-03-11 06:46:20 +00002041 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002042 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002043 return
Dave Borowitzd9478582012-10-23 16:35:39 -07002044
Gavin Mak041f9772023-05-10 20:41:12 +00002045 for name, t in self._seen.items():
2046 # Keep a moving average across the previous/current sync runs.
2047 old = self._saved.get(name, t)
2048 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07002049
Gavin Makea2e3302023-03-11 06:46:20 +00002050 try:
2051 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002052 json.dump(self._seen, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452053 except (OSError, TypeError):
Gavin Makea2e3302023-03-11 06:46:20 +00002054 platform_utils.remove(self._path, missing_ok=True)
2055
Dan Willemsen0745bb22015-08-17 13:41:45 -07002056
Mike Frysingerd4aee652023-10-19 05:13:32 -04002057class LocalSyncState:
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002058 _LAST_FETCH = "last_fetch"
2059 _LAST_CHECKOUT = "last_checkout"
2060
2061 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00002062 self._manifest = manifest
2063 self._path = os.path.join(
2064 self._manifest.repodir, ".repo_localsyncstate.json"
2065 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002066 self._time = time.time()
2067 self._state = None
2068 self._Load()
2069
2070 def SetFetchTime(self, project):
2071 self._Set(project, self._LAST_FETCH)
2072
2073 def SetCheckoutTime(self, project):
2074 self._Set(project, self._LAST_CHECKOUT)
2075
2076 def GetFetchTime(self, project):
2077 return self._Get(project, self._LAST_FETCH)
2078
2079 def GetCheckoutTime(self, project):
2080 return self._Get(project, self._LAST_CHECKOUT)
2081
2082 def _Get(self, project, key):
2083 self._Load()
2084 p = project.relpath
2085 if p not in self._state:
2086 return
2087 return self._state[p].get(key)
2088
2089 def _Set(self, project, key):
2090 p = project.relpath
2091 if p not in self._state:
2092 self._state[p] = {}
2093 self._state[p][key] = self._time
2094
2095 def _Load(self):
2096 if self._state is None:
2097 try:
2098 with open(self._path) as f:
2099 self._state = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452100 except (OSError, ValueError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002101 platform_utils.remove(self._path, missing_ok=True)
2102 self._state = {}
2103
2104 def Save(self):
2105 if not self._state:
2106 return
2107 try:
2108 with open(self._path, "w") as f:
2109 json.dump(self._state, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452110 except (OSError, TypeError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002111 platform_utils.remove(self._path, missing_ok=True)
2112
Gavin Makf0aeb222023-08-08 04:43:36 +00002113 def PruneRemovedProjects(self):
2114 """Remove entries don't exist on disk and save."""
2115 if not self._state:
2116 return
2117 delete = set()
2118 for path in self._state:
2119 gitdir = os.path.join(self._manifest.topdir, path, ".git")
Matt Schulte0dd0a832023-11-30 11:00:16 -08002120 if not os.path.exists(gitdir) or os.path.islink(gitdir):
Gavin Makf0aeb222023-08-08 04:43:36 +00002121 delete.add(path)
2122 if not delete:
2123 return
2124 for path in delete:
2125 del self._state[path]
2126 self.Save()
2127
2128 def IsPartiallySynced(self):
2129 """Return whether a partial sync state is detected."""
2130 self._Load()
2131 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002132 for path, data in self._state.items():
2133 if path == self._manifest.repoProject.relpath:
2134 # The repo project isn't included in most syncs so we should
2135 # ignore it here.
2136 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002137 checkout_t = data.get(self._LAST_CHECKOUT)
2138 if not checkout_t:
2139 return True
2140 prev_checkout_t = prev_checkout_t or checkout_t
2141 if prev_checkout_t != checkout_t:
2142 return True
2143 return False
2144
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002145
Dan Willemsen0745bb22015-08-17 13:41:45 -07002146# This is a replacement for xmlrpc.client.Transport using urllib2
2147# and supporting persistent-http[s]. It cannot change hosts from
2148# request to request like the normal transport, the real url
2149# is passed during initialization.
2150class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002151 def __init__(self, orig_host):
Daniel Kutikb99272c2023-10-23 21:20:07 +02002152 super().__init__()
Gavin Makea2e3302023-03-11 06:46:20 +00002153 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002154
Gavin Makea2e3302023-03-11 06:46:20 +00002155 def request(self, host, handler, request_body, verbose=False):
2156 with GetUrlCookieFile(self.orig_host, not verbose) as (
2157 cookiefile,
2158 proxy,
2159 ):
2160 # Python doesn't understand cookies with the #HttpOnly_ prefix
2161 # Since we're only using them for HTTP, copy the file temporarily,
2162 # stripping those prefixes away.
2163 if cookiefile:
2164 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2165 tmpcookiefile.write("# HTTP Cookie File")
2166 try:
2167 with open(cookiefile) as f:
2168 for line in f:
2169 if line.startswith("#HttpOnly_"):
2170 line = line[len("#HttpOnly_") :]
2171 tmpcookiefile.write(line)
2172 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002173
Gavin Makea2e3302023-03-11 06:46:20 +00002174 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2175 try:
2176 cookiejar.load()
2177 except cookielib.LoadError:
2178 cookiejar = cookielib.CookieJar()
2179 finally:
2180 tmpcookiefile.close()
2181 else:
2182 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002183
Gavin Makea2e3302023-03-11 06:46:20 +00002184 proxyhandler = urllib.request.ProxyHandler
2185 if proxy:
2186 proxyhandler = urllib.request.ProxyHandler(
2187 {"http": proxy, "https": proxy}
2188 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002189
Gavin Makea2e3302023-03-11 06:46:20 +00002190 opener = urllib.request.build_opener(
2191 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2192 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002193
Gavin Makea2e3302023-03-11 06:46:20 +00002194 url = urllib.parse.urljoin(self.orig_host, handler)
2195 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002196
Gavin Makea2e3302023-03-11 06:46:20 +00002197 scheme = parse_results.scheme
2198 if scheme == "persistent-http":
2199 scheme = "http"
2200 if scheme == "persistent-https":
2201 # If we're proxying through persistent-https, use http. The
2202 # proxy itself will do the https.
2203 if proxy:
2204 scheme = "http"
2205 else:
2206 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002207
Gavin Makea2e3302023-03-11 06:46:20 +00002208 # Parse out any authentication information using the base class.
2209 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002210
Gavin Makea2e3302023-03-11 06:46:20 +00002211 url = urllib.parse.urlunparse(
2212 (
2213 scheme,
2214 host,
2215 parse_results.path,
2216 parse_results.params,
2217 parse_results.query,
2218 parse_results.fragment,
2219 )
2220 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002221
Gavin Makea2e3302023-03-11 06:46:20 +00002222 request = urllib.request.Request(url, request_body)
2223 if extra_headers is not None:
2224 for name, header in extra_headers:
2225 request.add_header(name, header)
2226 request.add_header("Content-Type", "text/xml")
2227 try:
2228 response = opener.open(request)
2229 except urllib.error.HTTPError as e:
2230 if e.code == 501:
2231 # We may have been redirected through a login process
2232 # but our POST turned into a GET. Retry.
2233 response = opener.open(request)
2234 else:
2235 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002236
Gavin Makea2e3302023-03-11 06:46:20 +00002237 p, u = xmlrpc.client.getparser()
2238 # Response should be fairly small, so read it all at once.
2239 # This way we can show it to the user in case of error (e.g. HTML).
2240 data = response.read()
2241 try:
2242 p.feed(data)
2243 except xml.parsers.expat.ExpatError as e:
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452244 raise OSError(
Gavin Makea2e3302023-03-11 06:46:20 +00002245 f"Parsing the manifest failed: {e}\n"
2246 f"Please report this to your manifest server admin.\n"
2247 f'Here is the full response:\n{data.decode("utf-8")}'
2248 )
2249 p.close()
2250 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002251
Gavin Makea2e3302023-03-11 06:46:20 +00002252 def close(self):
2253 pass