blob: 3a4151dfe1d48c781ba6b546c201e30c2ae3fbc4 [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 Makdaebd6c2025-04-09 13:59:27 -0700353 _JOBS_WARN_THRESHOLD = 100
354
Gavin Makea2e3302023-03-11 06:46:20 +0000355 def _Options(self, p, show_smart=True):
356 p.add_option(
357 "--jobs-network",
358 default=None,
359 type=int,
360 metavar="JOBS",
361 help="number of network jobs to run in parallel (defaults to "
362 "--jobs or 1)",
363 )
364 p.add_option(
365 "--jobs-checkout",
366 default=None,
367 type=int,
368 metavar="JOBS",
369 help="number of local checkout jobs to run in parallel (defaults "
370 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
371 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400372
Gavin Makea2e3302023-03-11 06:46:20 +0000373 p.add_option(
374 "-f",
375 "--force-broken",
Gavin Makea2e3302023-03-11 06:46:20 +0000376 action="store_true",
377 help="obsolete option (to be deleted in the future)",
378 )
379 p.add_option(
380 "--fail-fast",
Gavin Makea2e3302023-03-11 06:46:20 +0000381 action="store_true",
382 help="stop syncing after first error is hit",
383 )
384 p.add_option(
385 "--force-sync",
Gavin Makea2e3302023-03-11 06:46:20 +0000386 action="store_true",
387 help="overwrite an existing git directory if it needs to "
388 "point to a different object directory. WARNING: this "
389 "may cause loss of data",
390 )
391 p.add_option(
Josip Sokcevicedadb252024-02-29 09:48:37 -0800392 "--force-checkout",
Josip Sokcevicedadb252024-02-29 09:48:37 -0800393 action="store_true",
394 help="force checkout even if it results in throwing away "
395 "uncommitted modifications. "
396 "WARNING: this may cause loss of data",
397 )
398 p.add_option(
Gavin Makea2e3302023-03-11 06:46:20 +0000399 "--force-remove-dirty",
Gavin Makea2e3302023-03-11 06:46:20 +0000400 action="store_true",
401 help="force remove projects with uncommitted modifications if "
402 "projects no longer exist in the manifest. "
403 "WARNING: this may cause loss of data",
404 )
405 p.add_option(
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +0200406 "--rebase",
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +0200407 action="store_true",
408 help="rebase local commits regardless of whether they are "
409 "published",
410 )
411 p.add_option(
Gavin Makea2e3302023-03-11 06:46:20 +0000412 "-l",
413 "--local-only",
Gavin Makea2e3302023-03-11 06:46:20 +0000414 action="store_true",
415 help="only update working tree, don't fetch",
416 )
417 p.add_option(
418 "--no-manifest-update",
419 "--nmu",
420 dest="mp_update",
421 action="store_false",
422 default="true",
423 help="use the existing manifest checkout as-is. "
424 "(do not update to the latest revision)",
425 )
426 p.add_option(
427 "-n",
428 "--network-only",
Gavin Makea2e3302023-03-11 06:46:20 +0000429 action="store_true",
430 help="fetch only, don't update working tree",
431 )
432 p.add_option(
433 "-d",
434 "--detach",
435 dest="detach_head",
436 action="store_true",
437 help="detach projects back to manifest revision",
438 )
439 p.add_option(
440 "-c",
441 "--current-branch",
442 dest="current_branch_only",
443 action="store_true",
444 help="fetch only current branch from server",
445 )
446 p.add_option(
447 "--no-current-branch",
448 dest="current_branch_only",
449 action="store_false",
450 help="fetch all branches from server",
451 )
452 p.add_option(
453 "-m",
454 "--manifest-name",
Gavin Makea2e3302023-03-11 06:46:20 +0000455 help="temporary manifest to use for this sync",
456 metavar="NAME.xml",
457 )
458 p.add_option(
459 "--clone-bundle",
460 action="store_true",
461 help="enable use of /clone.bundle on HTTP/HTTPS",
462 )
463 p.add_option(
464 "--no-clone-bundle",
465 dest="clone_bundle",
466 action="store_false",
467 help="disable use of /clone.bundle on HTTP/HTTPS",
468 )
469 p.add_option(
470 "-u",
471 "--manifest-server-username",
472 action="store",
Gavin Makea2e3302023-03-11 06:46:20 +0000473 help="username to authenticate with the manifest server",
474 )
475 p.add_option(
476 "-p",
477 "--manifest-server-password",
478 action="store",
Gavin Makea2e3302023-03-11 06:46:20 +0000479 help="password to authenticate with the manifest server",
480 )
481 p.add_option(
482 "--fetch-submodules",
Gavin Makea2e3302023-03-11 06:46:20 +0000483 action="store_true",
484 help="fetch submodules from server",
485 )
486 p.add_option(
487 "--use-superproject",
488 action="store_true",
489 help="use the manifest superproject to sync projects; implies -c",
490 )
491 p.add_option(
492 "--no-use-superproject",
493 action="store_false",
494 dest="use_superproject",
495 help="disable use of manifest superprojects",
496 )
497 p.add_option("--tags", action="store_true", help="fetch tags")
498 p.add_option(
499 "--no-tags",
500 dest="tags",
501 action="store_false",
502 help="don't fetch tags (default)",
503 )
504 p.add_option(
505 "--optimized-fetch",
Gavin Makea2e3302023-03-11 06:46:20 +0000506 action="store_true",
507 help="only fetch projects fixed to sha1 if revision does not exist "
508 "locally",
509 )
510 p.add_option(
511 "--retry-fetches",
512 default=0,
513 action="store",
514 type="int",
515 help="number of times to retry fetches on transient errors",
516 )
517 p.add_option(
518 "--prune",
519 action="store_true",
520 help="delete refs that no longer exist on the remote (default)",
521 )
522 p.add_option(
523 "--no-prune",
524 dest="prune",
525 action="store_false",
526 help="do not delete refs that no longer exist on the remote",
527 )
528 p.add_option(
529 "--auto-gc",
530 action="store_true",
531 default=None,
532 help="run garbage collection on all synced projects",
533 )
534 p.add_option(
535 "--no-auto-gc",
536 dest="auto_gc",
537 action="store_false",
538 help="do not run garbage collection on any projects (default)",
539 )
540 if show_smart:
541 p.add_option(
542 "-s",
543 "--smart-sync",
Gavin Makea2e3302023-03-11 06:46:20 +0000544 action="store_true",
545 help="smart sync using manifest from the latest known good "
546 "build",
547 )
548 p.add_option(
549 "-t",
550 "--smart-tag",
Gavin Makea2e3302023-03-11 06:46:20 +0000551 action="store",
552 help="smart sync using manifest from a known tag",
553 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700554
Gavin Makea2e3302023-03-11 06:46:20 +0000555 g = p.add_option_group("repo Version options")
556 g.add_option(
557 "--no-repo-verify",
558 dest="repo_verify",
559 default=True,
560 action="store_false",
561 help="do not verify repo source code",
562 )
563 g.add_option(
564 "--repo-upgraded",
Gavin Makea2e3302023-03-11 06:46:20 +0000565 action="store_true",
Mike Frysinger06ddc8c2023-08-21 21:26:51 -0400566 help=optparse.SUPPRESS_HELP,
Gavin Makea2e3302023-03-11 06:46:20 +0000567 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700568
Gavin Makea2e3302023-03-11 06:46:20 +0000569 def _GetBranch(self, manifest_project):
570 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000571
Gavin Makea2e3302023-03-11 06:46:20 +0000572 Args:
573 manifest_project: The manifestProject to query.
574 """
575 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
576 branch = b.merge
577 if branch.startswith(R_HEADS):
578 branch = branch[len(R_HEADS) :]
579 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800580
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800581 @classmethod
582 def _GetCurrentBranchOnly(cls, opt, manifest):
Gavin Makea2e3302023-03-11 06:46:20 +0000583 """Returns whether current-branch or use-superproject options are
584 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200585
Gavin Makea2e3302023-03-11 06:46:20 +0000586 Args:
587 opt: Program options returned from optparse. See _Options().
588 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000589
Gavin Makea2e3302023-03-11 06:46:20 +0000590 Returns:
591 True if a superproject is requested, otherwise the value of the
592 current_branch option (True, False or None).
593 """
594 return (
595 git_superproject.UseSuperproject(opt.use_superproject, manifest)
596 or opt.current_branch_only
597 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700598
Gavin Makea2e3302023-03-11 06:46:20 +0000599 def _UpdateProjectsRevisionId(
600 self, opt, args, superproject_logging_data, manifest
601 ):
602 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800603
Gavin Makea2e3302023-03-11 06:46:20 +0000604 This function updates each project's revisionId with the commit hash
605 from the superproject. It writes the updated manifest into a file and
606 reloads the manifest from it. When appropriate, sub manifests are also
607 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800608
Gavin Makea2e3302023-03-11 06:46:20 +0000609 Args:
610 opt: Program options returned from optparse. See _Options().
611 args: Arguments to pass to GetProjects. See the GetProjects
612 docstring for details.
613 superproject_logging_data: A dictionary of superproject data to log.
614 manifest: The manifest to use.
615 """
616 have_superproject = manifest.superproject or any(
617 m.superproject for m in manifest.all_children
618 )
619 if not have_superproject:
620 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000621
Gavin Makea2e3302023-03-11 06:46:20 +0000622 if opt.local_only and manifest.superproject:
623 manifest_path = manifest.superproject.manifest_path
624 if manifest_path:
625 self._ReloadManifest(manifest_path, manifest)
626 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700627
Gavin Makea2e3302023-03-11 06:46:20 +0000628 all_projects = self.GetProjects(
629 args,
630 missing_ok=True,
631 submodules_ok=opt.fetch_submodules,
632 manifest=manifest,
633 all_manifests=not opt.this_manifest_only,
634 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000635
Gavin Makea2e3302023-03-11 06:46:20 +0000636 per_manifest = collections.defaultdict(list)
637 if opt.this_manifest_only:
638 per_manifest[manifest.path_prefix] = all_projects
639 else:
640 for p in all_projects:
641 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000642
Gavin Makea2e3302023-03-11 06:46:20 +0000643 superproject_logging_data = {}
644 need_unload = False
645 for m in self.ManifestList(opt):
646 if m.path_prefix not in per_manifest:
647 continue
648 use_super = git_superproject.UseSuperproject(
649 opt.use_superproject, m
650 )
651 if superproject_logging_data:
652 superproject_logging_data["multimanifest"] = True
653 superproject_logging_data.update(
654 superproject=use_super,
655 haslocalmanifests=bool(m.HasLocalManifests),
656 hassuperprojecttag=bool(m.superproject),
657 )
658 if use_super and (m.IsMirror or m.IsArchive):
659 # Don't use superproject, because we have no working tree.
660 use_super = False
661 superproject_logging_data["superproject"] = False
662 superproject_logging_data["noworktree"] = True
663 if opt.use_superproject is not False:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000664 logger.warning(
665 "%s: not using superproject because there is no "
666 "working tree.",
667 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000668 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000669
Gavin Makea2e3302023-03-11 06:46:20 +0000670 if not use_super:
671 continue
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -0800672 m.superproject.SetQuiet(not opt.verbose)
Gavin Makea2e3302023-03-11 06:46:20 +0000673 print_messages = git_superproject.PrintMessages(
674 opt.use_superproject, m
675 )
676 m.superproject.SetPrintMessages(print_messages)
677 update_result = m.superproject.UpdateProjectsRevisionId(
678 per_manifest[m.path_prefix], git_event_log=self.git_event_log
679 )
680 manifest_path = update_result.manifest_path
681 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
682 if manifest_path:
683 m.SetManifestOverride(manifest_path)
684 need_unload = True
685 else:
686 if print_messages:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000687 logger.warning(
688 "%s: warning: Update of revisionId from superproject "
689 "has failed, repo sync will not use superproject to "
690 "fetch the source. Please resync with the "
691 "--no-use-superproject option to avoid this repo "
692 "warning.",
693 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000694 )
695 if update_result.fatal and opt.use_superproject is not None:
Jason Chang32b59562023-07-14 16:45:35 -0700696 raise SuperprojectError()
Gavin Makea2e3302023-03-11 06:46:20 +0000697 if need_unload:
698 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800699
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800700 @classmethod
701 def _FetchProjectList(cls, opt, projects):
Gavin Makea2e3302023-03-11 06:46:20 +0000702 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500703
Gavin Makea2e3302023-03-11 06:46:20 +0000704 The projects we're given share the same underlying git object store, so
705 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800706
Gavin Mak551285f2023-05-04 04:48:43 +0000707 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700708
Gavin Makea2e3302023-03-11 06:46:20 +0000709 Args:
710 opt: Program options returned from optparse. See _Options().
711 projects: Projects to fetch.
712 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800713 return [cls._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700714
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800715 @classmethod
716 def _FetchOne(cls, opt, project_idx):
Gavin Makea2e3302023-03-11 06:46:20 +0000717 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700718
Gavin Makea2e3302023-03-11 06:46:20 +0000719 Args:
720 opt: Program options returned from optparse. See _Options().
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800721 project_idx: Project index for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700722
Gavin Makea2e3302023-03-11 06:46:20 +0000723 Returns:
724 Whether the fetch was successful.
725 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800726 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +0000727 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000728 k = f"{project.name} @ {project.relpath}"
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800729 cls.get_parallel_context()["sync_dict"][k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000730 success = False
731 remote_fetched = False
Jason Chang32b59562023-07-14 16:45:35 -0700732 errors = []
Jason Changdaf2ad32023-08-31 17:06:36 -0700733 buf = TeeStringIO(sys.stdout if opt.verbose else None)
Gavin Makea2e3302023-03-11 06:46:20 +0000734 try:
735 sync_result = project.Sync_NetworkHalf(
736 quiet=opt.quiet,
737 verbose=opt.verbose,
738 output_redir=buf,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800739 current_branch_only=cls._GetCurrentBranchOnly(
Gavin Makea2e3302023-03-11 06:46:20 +0000740 opt, project.manifest
741 ),
742 force_sync=opt.force_sync,
743 clone_bundle=opt.clone_bundle,
744 tags=opt.tags,
745 archive=project.manifest.IsArchive,
746 optimized_fetch=opt.optimized_fetch,
747 retry_fetches=opt.retry_fetches,
748 prune=opt.prune,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800749 ssh_proxy=cls.get_parallel_context()["ssh_proxy"],
Gavin Makea2e3302023-03-11 06:46:20 +0000750 clone_filter=project.manifest.CloneFilter,
751 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700752 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000753 )
754 success = sync_result.success
755 remote_fetched = sync_result.remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700756 if sync_result.error:
757 errors.append(sync_result.error)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700758
Gavin Makea2e3302023-03-11 06:46:20 +0000759 output = buf.getvalue()
Jason Changdaf2ad32023-08-31 17:06:36 -0700760 if output and buf.io is None and not success:
Gavin Makea2e3302023-03-11 06:46:20 +0000761 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700762
Gavin Makea2e3302023-03-11 06:46:20 +0000763 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000764 logger.error(
765 "error: Cannot fetch %s from %s",
766 project.name,
767 project.remote.url,
Gavin Makea2e3302023-03-11 06:46:20 +0000768 )
769 except KeyboardInterrupt:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000770 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000771 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000772 logger.error("error.GitError: Cannot fetch %s", e)
Jason Chang32b59562023-07-14 16:45:35 -0700773 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000774 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000775 logger.error(
776 "error: Cannot fetch %s (%s: %s)",
777 project.name,
778 type(e).__name__,
779 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000780 )
Jason Chang32b59562023-07-14 16:45:35 -0700781 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000782 raise
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800783 finally:
784 del cls.get_parallel_context()["sync_dict"][k]
Mike Frysinger7b586f22021-02-23 18:38:39 -0500785
Gavin Makea2e3302023-03-11 06:46:20 +0000786 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700787 return _FetchOneResult(
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800788 success, errors, project_idx, start, finish, remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700789 )
David James8d201162013-10-11 17:03:19 -0700790
Gavin Mak04cba4a2023-05-24 21:28:28 +0000791 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000792 earliest_time = float("inf")
793 earliest_proj = None
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800794 items = self.get_parallel_context()["sync_dict"].items()
Gavin Mak945c0062023-05-30 20:04:07 +0000795 for project, t in items:
Gavin Mak551285f2023-05-04 04:48:43 +0000796 if t < earliest_time:
797 earliest_time = t
798 earliest_proj = project
799
Josip Sokcevic71122f92023-05-26 02:44:37 +0000800 if not earliest_proj:
Gavin Mak945c0062023-05-30 20:04:07 +0000801 # This function is called when sync is still running but in some
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800802 # cases (by chance), sync_dict can contain no entries. Return some
Gavin Mak945c0062023-05-30 20:04:07 +0000803 # text to indicate that sync is still working.
804 return "..working.."
Josip Sokcevic71122f92023-05-26 02:44:37 +0000805
Gavin Mak551285f2023-05-04 04:48:43 +0000806 elapsed = time.time() - earliest_time
Gavin Mak945c0062023-05-30 20:04:07 +0000807 jobs = jobs_str(len(items))
Gavin Mak04cba4a2023-05-24 21:28:28 +0000808 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000809
Kuang-che Wuab2d3212024-11-06 13:03:42 +0800810 @classmethod
811 def InitWorker(cls):
812 # Force connect to the manager server now.
813 # This is good because workers are initialized one by one. Without this,
814 # multiple workers may connect to the manager when handling the first
815 # job at the same time. Then the connection may fail if too many
816 # connections are pending and execeeded the socket listening backlog,
817 # especially on MacOS.
818 len(cls.get_parallel_context()["sync_dict"])
819
Jason Changdaf2ad32023-08-31 17:06:36 -0700820 def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
Gavin Makea2e3302023-03-11 06:46:20 +0000821 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500822
Gavin Makea2e3302023-03-11 06:46:20 +0000823 fetched = set()
824 remote_fetched = set()
Gavin Makedcaa942023-04-27 05:58:57 +0000825 pm = Progress(
826 "Fetching",
827 len(projects),
828 delay=False,
829 quiet=opt.quiet,
830 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000831 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000832 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800833
Gavin Mak551285f2023-05-04 04:48:43 +0000834 sync_event = _threading.Event()
835
836 def _MonitorSyncLoop():
837 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000838 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000839 if sync_event.wait(timeout=1):
840 return
841
842 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
843 sync_progress_thread.daemon = True
Gavin Mak551285f2023-05-04 04:48:43 +0000844
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800845 def _ProcessResults(pool, pm, results_sets):
Gavin Makea2e3302023-03-11 06:46:20 +0000846 ret = True
847 for results in results_sets:
848 for result in results:
849 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800850 project = projects[result.project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +0000851 start = result.start
852 finish = result.finish
853 self._fetch_times.Set(project, finish - start)
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000854 self._local_sync_state.SetFetchTime(project)
Gavin Makea2e3302023-03-11 06:46:20 +0000855 self.event_log.AddSync(
856 project,
857 event_log.TASK_SYNC_NETWORK,
858 start,
859 finish,
860 success,
861 )
Jason Chang32b59562023-07-14 16:45:35 -0700862 if result.errors:
863 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000864 if result.remote_fetched:
865 remote_fetched.add(project)
866 # Check for any errors before running any more tasks.
867 # ...we'll let existing jobs finish, though.
868 if not success:
869 ret = False
870 else:
871 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000872 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000873 if not ret and opt.fail_fast:
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800874 if pool:
875 pool.close()
Gavin Makea2e3302023-03-11 06:46:20 +0000876 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500877 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700878
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800879 with self.ParallelContext():
880 self.get_parallel_context()["projects"] = projects
881 self.get_parallel_context()[
882 "sync_dict"
883 ] = multiprocessing.Manager().dict()
Mike Frysingerebf04a42021-02-23 20:48:04 -0500884
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800885 objdir_project_map = dict()
886 for index, project in enumerate(projects):
887 objdir_project_map.setdefault(project.objdir, []).append(index)
888 projects_list = list(objdir_project_map.values())
889
Peter Kjellerstedt616e3142024-11-20 21:10:29 +0100890 jobs = max(1, min(opt.jobs_network, len(projects_list)))
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800891
892 # We pass the ssh proxy settings via the class. This allows
893 # multiprocessing to pickle it up when spawning children. We can't
894 # pass it as an argument to _FetchProjectList below as
895 # multiprocessing is unable to pickle those.
896 self.get_parallel_context()["ssh_proxy"] = ssh_proxy
897
898 sync_progress_thread.start()
Josip Sokcevic454fdaf2024-10-07 17:33:38 +0000899 if not opt.quiet:
Gavin Makea2e3302023-03-11 06:46:20 +0000900 pm.update(inc=0, msg="warming up")
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800901 try:
902 ret = self.ExecuteInParallel(
903 jobs,
Gavin Makea2e3302023-03-11 06:46:20 +0000904 functools.partial(self._FetchProjectList, opt),
905 projects_list,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800906 callback=_ProcessResults,
907 output=pm,
908 # Use chunksize=1 to avoid the chance that some workers are
909 # idle while other workers still have more than one job in
910 # their chunk queue.
911 chunksize=1,
Kuang-che Wuab2d3212024-11-06 13:03:42 +0800912 initializer=self.InitWorker,
Gavin Makea2e3302023-03-11 06:46:20 +0000913 )
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800914 finally:
915 sync_event.set()
916 sync_progress_thread.join()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000917
Gavin Makea2e3302023-03-11 06:46:20 +0000918 self._fetch_times.Save()
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000919 self._local_sync_state.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000920
Gavin Makea2e3302023-03-11 06:46:20 +0000921 if not self.outer_client.manifest.IsArchive:
922 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400923
Jason Changdaf2ad32023-08-31 17:06:36 -0700924 return _FetchResult(ret, fetched)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000925
Gavin Makea2e3302023-03-11 06:46:20 +0000926 def _FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -0700927 self, opt, args, all_projects, err_event, ssh_proxy, manifest, errors
Gavin Makea2e3302023-03-11 06:46:20 +0000928 ):
929 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400930
Gavin Makea2e3302023-03-11 06:46:20 +0000931 Args:
932 opt: Program options returned from optparse. See _Options().
933 args: Command line args used to filter out projects.
934 all_projects: List of all projects that should be fetched.
935 err_event: Whether an error was hit while processing.
936 ssh_proxy: SSH manager for clients & masters.
937 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700938
Gavin Makea2e3302023-03-11 06:46:20 +0000939 Returns:
940 List of all projects that should be checked out.
941 """
942 rp = manifest.repoProject
LaMont Jones891e8f72022-09-08 20:17:58 +0000943
Gavin Makea2e3302023-03-11 06:46:20 +0000944 to_fetch = []
945 now = time.time()
946 if _ONE_DAY_S <= (now - rp.LastFetch):
947 to_fetch.append(rp)
948 to_fetch.extend(all_projects)
949 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700950
Jason Changdaf2ad32023-08-31 17:06:36 -0700951 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000952 success = result.success
953 fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700954
Gavin Makea2e3302023-03-11 06:46:20 +0000955 if not success:
956 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700957
Fredrik de Grootebdf0402024-10-22 14:14:59 +0200958 # Call self update, unless requested not to
959 if os.environ.get("REPO_SKIP_SELF_UPDATE", "0") == "0":
960 _PostRepoFetch(rp, opt.repo_verify)
Gavin Makea2e3302023-03-11 06:46:20 +0000961 if opt.network_only:
962 # Bail out now; the rest touches the working tree.
963 if err_event.is_set():
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000964 e = SyncError(
Jason Chang32b59562023-07-14 16:45:35 -0700965 "error: Exited sync due to fetch errors.",
966 aggregate_errors=errors,
967 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000968
969 logger.error(e)
970 raise e
Jason Changdaf2ad32023-08-31 17:06:36 -0700971 return _FetchMainResult([])
Dave Borowitz18857212012-10-23 17:02:59 -0700972
Gavin Makea2e3302023-03-11 06:46:20 +0000973 # Iteratively fetch missing and/or nested unregistered submodules.
974 previously_missing_set = set()
975 while True:
976 self._ReloadManifest(None, manifest)
977 all_projects = self.GetProjects(
978 args,
979 missing_ok=True,
980 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000981 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000982 all_manifests=not opt.this_manifest_only,
983 )
984 missing = []
985 for project in all_projects:
986 if project.gitdir not in fetched:
987 missing.append(project)
988 if not missing:
989 break
990 # Stop us from non-stopped fetching actually-missing repos: If set
991 # of missing repos has not been changed from last fetch, we break.
Jason R. Coombs0bcffd82023-10-20 23:29:42 +0545992 missing_set = {p.name for p in missing}
Gavin Makea2e3302023-03-11 06:46:20 +0000993 if previously_missing_set == missing_set:
994 break
995 previously_missing_set = missing_set
Jason Changdaf2ad32023-08-31 17:06:36 -0700996 result = self._Fetch(missing, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000997 success = result.success
998 new_fetched = result.projects
999 if not success:
1000 err_event.set()
1001 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001002
Jason Changdaf2ad32023-08-31 17:06:36 -07001003 return _FetchMainResult(all_projects)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001004
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001005 @classmethod
Josip Sokcevicedadb252024-02-29 09:48:37 -08001006 def _CheckoutOne(
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001007 cls,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001008 detach_head,
1009 force_sync,
1010 force_checkout,
1011 force_rebase,
1012 verbose,
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001013 project_idx,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001014 ):
Gavin Makea2e3302023-03-11 06:46:20 +00001015 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +08001016
Gavin Makea2e3302023-03-11 06:46:20 +00001017 Args:
1018 detach_head: Whether to leave a detached HEAD.
Josip Sokcevicedadb252024-02-29 09:48:37 -08001019 force_sync: Force checking out of .git directory (e.g. overwrite
1020 existing git directory that was previously linked to a different
1021 object directory).
1022 force_checkout: Force checking out of the repo content.
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001023 force_rebase: Force rebase.
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001024 verbose: Whether to show verbose messages.
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001025 project_idx: Project index for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +08001026
Gavin Makea2e3302023-03-11 06:46:20 +00001027 Returns:
1028 Whether the fetch was successful.
1029 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001030 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +00001031 start = time.time()
1032 syncbuf = SyncBuffer(
1033 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001034 )
Gavin Makea2e3302023-03-11 06:46:20 +00001035 success = False
Jason Chang32b59562023-07-14 16:45:35 -07001036 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +09001037 try:
Jason Chang32b59562023-07-14 16:45:35 -07001038 project.Sync_LocalHalf(
Josip Sokcevicedadb252024-02-29 09:48:37 -08001039 syncbuf,
1040 force_sync=force_sync,
1041 force_checkout=force_checkout,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001042 force_rebase=force_rebase,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001043 errors=errors,
1044 verbose=verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001045 )
Gavin Makea2e3302023-03-11 06:46:20 +00001046 success = syncbuf.Finish()
Josip Sokcevicd93fe602025-01-08 18:31:46 +00001047 except KeyboardInterrupt:
1048 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001049 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001050 logger.error(
1051 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +00001052 )
Jason Chang32b59562023-07-14 16:45:35 -07001053 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001054 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001055 logger.error(
1056 "error: Cannot checkout %s: %s: %s",
1057 project.name,
1058 type(e).__name__,
1059 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001060 )
1061 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -07001062
Gavin Makea2e3302023-03-11 06:46:20 +00001063 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001064 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001065 finish = time.time()
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001066 return _CheckoutOneResult(success, errors, project_idx, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -04001067
Jason Chang32b59562023-07-14 16:45:35 -07001068 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001069 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001070
Gavin Makea2e3302023-03-11 06:46:20 +00001071 Args:
1072 all_projects: List of all projects that should be checked out.
1073 opt: Program options returned from optparse. See _Options().
1074 err_results: A list of strings, paths to git repos where checkout
1075 failed.
1076 """
1077 # Only checkout projects with worktrees.
1078 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001079
Gavin Makea2e3302023-03-11 06:46:20 +00001080 def _ProcessResults(pool, pm, results):
1081 ret = True
1082 for result in results:
1083 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001084 project = self.get_parallel_context()["projects"][
1085 result.project_idx
1086 ]
Gavin Makea2e3302023-03-11 06:46:20 +00001087 start = result.start
1088 finish = result.finish
1089 self.event_log.AddSync(
1090 project, event_log.TASK_SYNC_LOCAL, start, finish, success
1091 )
Jason Chang32b59562023-07-14 16:45:35 -07001092
1093 if result.errors:
1094 checkout_errors.extend(result.errors)
1095
Gavin Makea2e3302023-03-11 06:46:20 +00001096 # Check for any errors before running any more tasks.
1097 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001098 if success:
1099 self._local_sync_state.SetCheckoutTime(project)
1100 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001101 ret = False
1102 err_results.append(
1103 project.RelPath(local=opt.this_manifest_only)
1104 )
1105 if opt.fail_fast:
1106 if pool:
1107 pool.close()
1108 return ret
1109 pm.update(msg=project.name)
1110 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001111
Josip Sokcevic55545722024-02-22 16:38:00 -08001112 for projects in _SafeCheckoutOrder(all_projects):
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001113 with self.ParallelContext():
1114 self.get_parallel_context()["projects"] = projects
1115 proc_res = self.ExecuteInParallel(
1116 opt.jobs_checkout,
1117 functools.partial(
1118 self._CheckoutOne,
1119 opt.detach_head,
1120 opt.force_sync,
1121 opt.force_checkout,
1122 opt.rebase,
1123 opt.verbose,
1124 ),
1125 range(len(projects)),
1126 callback=_ProcessResults,
1127 output=Progress(
1128 "Checking out", len(all_projects), quiet=opt.quiet
1129 ),
1130 # Use chunksize=1 to avoid the chance that some workers are
1131 # idle while other workers still have more than one job in
1132 # their chunk queue.
1133 chunksize=1,
1134 )
Simran Basib9a1b732015-08-20 12:19:28 -07001135
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001136 self._local_sync_state.Save()
1137 return proc_res and not err_results
1138
Gavin Makea2e3302023-03-11 06:46:20 +00001139 @staticmethod
1140 def _GetPreciousObjectsState(project: Project, opt):
1141 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001142
Gavin Makea2e3302023-03-11 06:46:20 +00001143 Args:
1144 project (Project): the project to examine, and possibly correct.
1145 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001146
Gavin Makea2e3302023-03-11 06:46:20 +00001147 Returns:
1148 Expected state of extensions.preciousObjects:
1149 False: Should be disabled. (not present)
1150 True: Should be enabled.
1151 """
1152 if project.use_git_worktrees:
1153 return False
1154 projects = project.manifest.GetProjectsWithName(
1155 project.name, all_manifests=True
1156 )
1157 if len(projects) == 1:
1158 return False
1159 if len(projects) > 1:
1160 # Objects are potentially shared with another project.
1161 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1162 # - When False, shared projects share (via symlink)
1163 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1164 # objects directory. All objects are precious, since there is no
1165 # project with a complete set of refs.
1166 # - When True, shared projects share (via info/alternates)
1167 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1168 # store, which is written only on the first clone of the project,
1169 # and is not written subsequently. (When Sync_NetworkHalf sees
1170 # that it exists, it makes sure that the alternates file points
1171 # there, and uses a project-local .git/objects directory for all
1172 # syncs going forward.
1173 # We do not support switching between the options. The environment
1174 # variable is present for testing and migration only.
1175 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001176
Gavin Makea2e3302023-03-11 06:46:20 +00001177 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001178
Gavin Makea2e3302023-03-11 06:46:20 +00001179 def _SetPreciousObjectsState(self, project: Project, opt):
1180 """Correct the preciousObjects state for the project.
1181
1182 Args:
1183 project: the project to examine, and possibly correct.
1184 opt: options given to sync.
1185 """
1186 expected = self._GetPreciousObjectsState(project, opt)
1187 actual = (
1188 project.config.GetBoolean("extensions.preciousObjects") or False
1189 )
1190 relpath = project.RelPath(local=opt.this_manifest_only)
1191
1192 if expected != actual:
1193 # If this is unexpected, log it and repair.
1194 Trace(
1195 f"{relpath} expected preciousObjects={expected}, got {actual}"
1196 )
1197 if expected:
1198 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001199 print(
1200 "\r%s: Shared project %s found, disabling pruning."
1201 % (relpath, project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001202 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001203
Gavin Makea2e3302023-03-11 06:46:20 +00001204 if git_require((2, 7, 0)):
1205 project.EnableRepositoryExtension("preciousObjects")
1206 else:
1207 # This isn't perfect, but it's the best we can do with old
1208 # git.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001209 logger.warning(
1210 "%s: WARNING: shared projects are unreliable when "
Gavin Makea2e3302023-03-11 06:46:20 +00001211 "using old versions of git; please upgrade to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001212 "git-2.7.0+.",
1213 relpath,
Gavin Makea2e3302023-03-11 06:46:20 +00001214 )
1215 project.config.SetString("gc.pruneExpire", "never")
1216 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001217 project.config.SetString("extensions.preciousObjects", None)
1218 project.config.SetString("gc.pruneExpire", None)
1219
1220 def _GCProjects(self, projects, opt, err_event):
1221 """Perform garbage collection.
1222
1223 If We are skipping garbage collection (opt.auto_gc not set), we still
1224 want to potentially mark objects precious, so that `git gc` does not
1225 discard shared objects.
1226 """
1227 if not opt.auto_gc:
1228 # Just repair preciousObjects state, and return.
1229 for project in projects:
1230 self._SetPreciousObjectsState(project, opt)
1231 return
1232
1233 pm = Progress(
1234 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1235 )
1236 pm.update(inc=0, msg="prescan")
1237
1238 tidy_dirs = {}
1239 for project in projects:
1240 self._SetPreciousObjectsState(project, opt)
1241
1242 project.config.SetString("gc.autoDetach", "false")
1243 # Only call git gc once per objdir, but call pack-refs for the
1244 # remainder.
1245 if project.objdir not in tidy_dirs:
1246 tidy_dirs[project.objdir] = (
1247 True, # Run a full gc.
1248 project.bare_git,
1249 )
1250 elif project.gitdir not in tidy_dirs:
1251 tidy_dirs[project.gitdir] = (
1252 False, # Do not run a full gc; just run pack-refs.
1253 project.bare_git,
1254 )
1255
1256 jobs = opt.jobs
1257
1258 if jobs < 2:
1259 for run_gc, bare_git in tidy_dirs.values():
1260 pm.update(msg=bare_git._project.name)
1261
1262 if run_gc:
1263 bare_git.gc("--auto")
1264 else:
1265 bare_git.pack_refs()
1266 pm.end()
1267 return
1268
1269 cpu_count = os.cpu_count()
1270 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1271
1272 threads = set()
1273 sem = _threading.Semaphore(jobs)
1274
1275 def tidy_up(run_gc, bare_git):
1276 pm.start(bare_git._project.name)
1277 try:
1278 try:
1279 if run_gc:
1280 bare_git.gc("--auto", config=config)
1281 else:
1282 bare_git.pack_refs(config=config)
1283 except GitError:
1284 err_event.set()
1285 except Exception:
1286 err_event.set()
1287 raise
1288 finally:
1289 pm.finish(bare_git._project.name)
1290 sem.release()
1291
1292 for run_gc, bare_git in tidy_dirs.values():
1293 if err_event.is_set() and opt.fail_fast:
1294 break
1295 sem.acquire()
1296 t = _threading.Thread(
1297 target=tidy_up,
1298 args=(
1299 run_gc,
1300 bare_git,
1301 ),
1302 )
1303 t.daemon = True
1304 threads.add(t)
1305 t.start()
1306
1307 for t in threads:
1308 t.join()
1309 pm.end()
1310
1311 def _ReloadManifest(self, manifest_name, manifest):
1312 """Reload the manfiest from the file specified by the |manifest_name|.
1313
1314 It unloads the manifest if |manifest_name| is None.
1315
1316 Args:
1317 manifest_name: Manifest file to be reloaded.
1318 manifest: The manifest to use.
1319 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001320 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001321 # Override calls Unload already.
1322 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001323 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001324 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001325
Gavin Makea2e3302023-03-11 06:46:20 +00001326 def UpdateProjectList(self, opt, manifest):
1327 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001328
Gavin Makea2e3302023-03-11 06:46:20 +00001329 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001330
Gavin Makea2e3302023-03-11 06:46:20 +00001331 Args:
1332 opt: Program options returned from optparse. See _Options().
1333 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001334
Gavin Makea2e3302023-03-11 06:46:20 +00001335 Returns:
1336 0: success
1337 1: failure
1338 """
1339 new_project_paths = []
1340 for project in self.GetProjects(
1341 None, missing_ok=True, manifest=manifest, all_manifests=False
1342 ):
1343 if project.relpath:
1344 new_project_paths.append(project.relpath)
1345 file_name = "project.list"
1346 file_path = os.path.join(manifest.subdir, file_name)
1347 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001348
Gavin Makea2e3302023-03-11 06:46:20 +00001349 if os.path.exists(file_path):
Jason R. Coombs034950b2023-10-20 23:32:02 +05451350 with open(file_path) as fd:
Gavin Makea2e3302023-03-11 06:46:20 +00001351 old_project_paths = fd.read().split("\n")
1352 # In reversed order, so subfolders are deleted before parent folder.
1353 for path in sorted(old_project_paths, reverse=True):
1354 if not path:
1355 continue
1356 if path not in new_project_paths:
1357 # If the path has already been deleted, we don't need to do
1358 # it.
1359 gitdir = os.path.join(manifest.topdir, path, ".git")
1360 if os.path.exists(gitdir):
1361 project = Project(
1362 manifest=manifest,
1363 name=path,
1364 remote=RemoteSpec("origin"),
1365 gitdir=gitdir,
1366 objdir=gitdir,
1367 use_git_worktrees=os.path.isfile(gitdir),
1368 worktree=os.path.join(manifest.topdir, path),
1369 relpath=path,
1370 revisionExpr="HEAD",
1371 revisionId=None,
1372 groups=None,
1373 )
Jason Chang32b59562023-07-14 16:45:35 -07001374 project.DeleteWorktree(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001375 verbose=opt.verbose, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001376 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001377
Gavin Makea2e3302023-03-11 06:46:20 +00001378 new_project_paths.sort()
1379 with open(file_path, "w") as fd:
1380 fd.write("\n".join(new_project_paths))
1381 fd.write("\n")
1382 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001383
Gavin Makea2e3302023-03-11 06:46:20 +00001384 def UpdateCopyLinkfileList(self, manifest):
1385 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001386
Gavin Makea2e3302023-03-11 06:46:20 +00001387 Returns:
1388 Whether update was successful.
1389 """
1390 new_paths = {}
1391 new_linkfile_paths = []
1392 new_copyfile_paths = []
1393 for project in self.GetProjects(
1394 None, missing_ok=True, manifest=manifest, all_manifests=False
1395 ):
1396 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1397 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001398
Gavin Makea2e3302023-03-11 06:46:20 +00001399 new_paths = {
1400 "linkfile": new_linkfile_paths,
1401 "copyfile": new_copyfile_paths,
1402 }
jiajia tanga590e642021-04-25 20:02:02 +08001403
Gavin Makea2e3302023-03-11 06:46:20 +00001404 copylinkfile_name = "copy-link-files.json"
1405 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1406 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001407
Gavin Makea2e3302023-03-11 06:46:20 +00001408 if os.path.exists(copylinkfile_path):
1409 with open(copylinkfile_path, "rb") as fp:
1410 try:
1411 old_copylinkfile_paths = json.load(fp)
1412 except Exception:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001413 logger.error(
1414 "error: %s is not a json formatted file.",
1415 copylinkfile_path,
Gavin Makea2e3302023-03-11 06:46:20 +00001416 )
1417 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001418 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001419
Gavin Makea2e3302023-03-11 06:46:20 +00001420 need_remove_files = []
1421 need_remove_files.extend(
1422 set(old_copylinkfile_paths.get("linkfile", []))
1423 - set(new_linkfile_paths)
1424 )
1425 need_remove_files.extend(
1426 set(old_copylinkfile_paths.get("copyfile", []))
1427 - set(new_copyfile_paths)
1428 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001429
Gavin Makea2e3302023-03-11 06:46:20 +00001430 for need_remove_file in need_remove_files:
1431 # Try to remove the updated copyfile or linkfile.
1432 # So, if the file is not exist, nothing need to do.
Josip Sokcevic9500aca2024-12-13 18:24:20 +00001433 platform_utils.remove(
1434 os.path.join(self.client.topdir, need_remove_file),
1435 missing_ok=True,
1436 )
Raman Tenneti7954de12021-07-28 14:36:49 -07001437
Gavin Makea2e3302023-03-11 06:46:20 +00001438 # Create copy-link-files.json, save dest path of "copyfile" and
1439 # "linkfile".
1440 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1441 json.dump(new_paths, fp)
1442 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001443
Gavin Makea2e3302023-03-11 06:46:20 +00001444 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1445 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001446 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001447 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001448 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001449 )
Gavin Makea2e3302023-03-11 06:46:20 +00001450
1451 manifest_server = manifest.manifest_server
1452 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001453 print("Using manifest server %s" % manifest_server)
Gavin Makea2e3302023-03-11 06:46:20 +00001454
1455 if "@" not in manifest_server:
1456 username = None
1457 password = None
1458 if opt.manifest_server_username and opt.manifest_server_password:
1459 username = opt.manifest_server_username
1460 password = opt.manifest_server_password
1461 else:
1462 try:
1463 info = netrc.netrc()
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451464 except OSError:
Gavin Makea2e3302023-03-11 06:46:20 +00001465 # .netrc file does not exist or could not be opened.
1466 pass
1467 else:
1468 try:
1469 parse_result = urllib.parse.urlparse(manifest_server)
1470 if parse_result.hostname:
1471 auth = info.authenticators(parse_result.hostname)
1472 if auth:
1473 username, _account, password = auth
1474 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001475 logger.error(
1476 "No credentials found for %s in .netrc",
1477 parse_result.hostname,
Gavin Makea2e3302023-03-11 06:46:20 +00001478 )
1479 except netrc.NetrcParseError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001480 logger.error("Error parsing .netrc file: %s", e)
Gavin Makea2e3302023-03-11 06:46:20 +00001481
1482 if username and password:
1483 manifest_server = manifest_server.replace(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001484 "://", f"://{username}:{password}@", 1
Gavin Makea2e3302023-03-11 06:46:20 +00001485 )
1486
1487 transport = PersistentTransport(manifest_server)
1488 if manifest_server.startswith("persistent-"):
1489 manifest_server = manifest_server[len("persistent-") :]
1490
Mike Frysingerdfdf5772025-01-30 19:11:36 -05001491 # Changes in behavior should update docs/smart-sync.md accordingly.
Gavin Makea2e3302023-03-11 06:46:20 +00001492 try:
1493 server = xmlrpc.client.Server(manifest_server, transport=transport)
1494 if opt.smart_sync:
1495 branch = self._GetBranch(manifest.manifestProject)
1496
1497 if "SYNC_TARGET" in os.environ:
1498 target = os.environ["SYNC_TARGET"]
1499 [success, manifest_str] = server.GetApprovedManifest(
1500 branch, target
1501 )
1502 elif (
1503 "TARGET_PRODUCT" in os.environ
1504 and "TARGET_BUILD_VARIANT" in os.environ
Navil1e19f7d2024-09-11 16:49:49 +00001505 and "TARGET_RELEASE" in os.environ
1506 ):
1507 target = "%s-%s-%s" % (
1508 os.environ["TARGET_PRODUCT"],
1509 os.environ["TARGET_RELEASE"],
1510 os.environ["TARGET_BUILD_VARIANT"],
1511 )
1512 [success, manifest_str] = server.GetApprovedManifest(
1513 branch, target
1514 )
1515 elif (
1516 "TARGET_PRODUCT" in os.environ
1517 and "TARGET_BUILD_VARIANT" in os.environ
Gavin Makea2e3302023-03-11 06:46:20 +00001518 ):
1519 target = "%s-%s" % (
1520 os.environ["TARGET_PRODUCT"],
1521 os.environ["TARGET_BUILD_VARIANT"],
1522 )
1523 [success, manifest_str] = server.GetApprovedManifest(
1524 branch, target
1525 )
1526 else:
1527 [success, manifest_str] = server.GetApprovedManifest(branch)
1528 else:
1529 assert opt.smart_tag
1530 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1531
1532 if success:
1533 manifest_name = os.path.basename(smart_sync_manifest_path)
1534 try:
1535 with open(smart_sync_manifest_path, "w") as f:
1536 f.write(manifest_str)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451537 except OSError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001538 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001539 "error: cannot write manifest to %s:\n%s"
1540 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001541 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001542 )
Gavin Makea2e3302023-03-11 06:46:20 +00001543 self._ReloadManifest(manifest_name, manifest)
1544 else:
Jason Chang32b59562023-07-14 16:45:35 -07001545 raise SmartSyncError(
1546 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001547 )
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451548 except (OSError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001549 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001550 "error: cannot connect to manifest server %s:\n%s"
1551 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001552 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001553 )
Gavin Makea2e3302023-03-11 06:46:20 +00001554 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001555 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001556 "error: cannot connect to manifest server %s:\n%d %s"
1557 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001558 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001559 )
Gavin Makea2e3302023-03-11 06:46:20 +00001560
1561 return manifest_name
1562
Jason Changdaf2ad32023-08-31 17:06:36 -07001563 def _UpdateAllManifestProjects(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001564 """Fetch & update the local manifest project.
1565
1566 After syncing the manifest project, if the manifest has any sub
1567 manifests, those are recursively processed.
1568
1569 Args:
1570 opt: Program options returned from optparse. See _Options().
1571 mp: the manifestProject to query.
1572 manifest_name: Manifest file to be reloaded.
1573 """
1574 if not mp.standalone_manifest_url:
Jason Changdaf2ad32023-08-31 17:06:36 -07001575 self._UpdateManifestProject(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001576
1577 if mp.manifest.submanifests:
1578 for submanifest in mp.manifest.submanifests.values():
1579 child = submanifest.repo_client.manifest
1580 child.manifestProject.SyncWithPossibleInit(
1581 submanifest,
1582 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1583 verbose=opt.verbose,
1584 tags=opt.tags,
1585 git_event_log=self.git_event_log,
1586 )
1587 self._UpdateAllManifestProjects(
Jason Changdaf2ad32023-08-31 17:06:36 -07001588 opt, child.manifestProject, None, errors
Gavin Makea2e3302023-03-11 06:46:20 +00001589 )
1590
Jason Changdaf2ad32023-08-31 17:06:36 -07001591 def _UpdateManifestProject(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001592 """Fetch & update the local manifest project.
1593
1594 Args:
1595 opt: Program options returned from optparse. See _Options().
1596 mp: the manifestProject to query.
1597 manifest_name: Manifest file to be reloaded.
1598 """
1599 if not opt.local_only:
1600 start = time.time()
Jason Changdaf2ad32023-08-31 17:06:36 -07001601 buf = TeeStringIO(sys.stdout)
1602 try:
1603 result = mp.Sync_NetworkHalf(
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -08001604 quiet=not opt.verbose,
Jason Changdaf2ad32023-08-31 17:06:36 -07001605 output_redir=buf,
1606 verbose=opt.verbose,
1607 current_branch_only=self._GetCurrentBranchOnly(
1608 opt, mp.manifest
1609 ),
1610 force_sync=opt.force_sync,
1611 tags=opt.tags,
1612 optimized_fetch=opt.optimized_fetch,
1613 retry_fetches=opt.retry_fetches,
1614 submodules=mp.manifest.HasSubmodules,
1615 clone_filter=mp.manifest.CloneFilter,
1616 partial_clone_exclude=mp.manifest.PartialCloneExclude,
1617 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
1618 )
1619 if result.error:
1620 errors.append(result.error)
1621 except KeyboardInterrupt:
1622 errors.append(
1623 ManifestInterruptError(buf.getvalue(), project=mp.name)
1624 )
1625 raise
1626
Gavin Makea2e3302023-03-11 06:46:20 +00001627 finish = time.time()
1628 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001629 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001630 )
1631
1632 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001633 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001634 syncbuf = SyncBuffer(mp.config)
1635 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001636 mp.Sync_LocalHalf(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001637 syncbuf,
1638 submodules=mp.manifest.HasSubmodules,
1639 errors=errors,
1640 verbose=opt.verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001641 )
Gavin Makea2e3302023-03-11 06:46:20 +00001642 clean = syncbuf.Finish()
1643 self.event_log.AddSync(
1644 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1645 )
1646 if not clean:
Yiwei Zhangd379e772023-12-20 20:39:59 +00001647 raise UpdateManifestError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001648 self._ReloadManifest(manifest_name, mp.manifest)
1649
1650 def ValidateOptions(self, opt, args):
1651 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001652 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001653 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001654 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001655 )
1656 if opt.network_only and opt.detach_head:
1657 self.OptionParser.error("cannot combine -n and -d")
1658 if opt.network_only and opt.local_only:
1659 self.OptionParser.error("cannot combine -n and -l")
1660 if opt.manifest_name and opt.smart_sync:
1661 self.OptionParser.error("cannot combine -m and -s")
1662 if opt.manifest_name and opt.smart_tag:
1663 self.OptionParser.error("cannot combine -m and -t")
1664 if opt.manifest_server_username or opt.manifest_server_password:
1665 if not (opt.smart_sync or opt.smart_tag):
1666 self.OptionParser.error(
1667 "-u and -p may only be combined with -s or -t"
1668 )
1669 if None in [
1670 opt.manifest_server_username,
1671 opt.manifest_server_password,
1672 ]:
1673 self.OptionParser.error("both -u and -p must be given")
1674
1675 if opt.prune is None:
1676 opt.prune = True
1677
Gavin Makea2e3302023-03-11 06:46:20 +00001678 def _ValidateOptionsWithManifest(self, opt, mp):
1679 """Like ValidateOptions, but after we've updated the manifest.
1680
1681 Needed to handle sync-xxx option defaults in the manifest.
1682
1683 Args:
1684 opt: The options to process.
1685 mp: The manifest project to pull defaults from.
1686 """
1687 if not opt.jobs:
1688 # If the user hasn't made a choice, use the manifest value.
1689 opt.jobs = mp.manifest.default.sync_j
1690 if opt.jobs:
1691 # If --jobs has a non-default value, propagate it as the default for
1692 # --jobs-xxx flags too.
1693 if not opt.jobs_network:
1694 opt.jobs_network = opt.jobs
1695 if not opt.jobs_checkout:
1696 opt.jobs_checkout = opt.jobs
1697 else:
1698 # Neither user nor manifest have made a choice, so setup defaults.
1699 if not opt.jobs_network:
1700 opt.jobs_network = 1
1701 if not opt.jobs_checkout:
1702 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1703 opt.jobs = os.cpu_count()
1704
1705 # Try to stay under user rlimit settings.
1706 #
1707 # Since each worker requires at 3 file descriptors to run `git fetch`,
1708 # use that to scale down the number of jobs. Unfortunately there isn't
1709 # an easy way to determine this reliably as systems change, but it was
1710 # last measured by hand in 2011.
1711 soft_limit, _ = _rlimit_nofile()
1712 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1713 opt.jobs = min(opt.jobs, jobs_soft_limit)
1714 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1715 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1716
Gavin Makdaebd6c2025-04-09 13:59:27 -07001717 # Warn once if effective job counts seem excessively high.
1718 # Prioritize --jobs, then --jobs-network, then --jobs-checkout.
1719 job_options_to_check = (
1720 ("--jobs", opt.jobs),
1721 ("--jobs-network", opt.jobs_network),
1722 ("--jobs-checkout", opt.jobs_checkout),
1723 )
1724 for name, value in job_options_to_check:
1725 if value > self._JOBS_WARN_THRESHOLD:
1726 logger.warning(
1727 "High job count (%d > %d) specified for %s; this may "
1728 "lead to excessive resource usage or diminishing returns.",
1729 value,
1730 self._JOBS_WARN_THRESHOLD,
1731 name,
1732 )
1733 break
1734
Gavin Makea2e3302023-03-11 06:46:20 +00001735 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001736 errors = []
1737 try:
1738 self._ExecuteHelper(opt, args, errors)
Jason Chang26fa3182024-02-05 15:15:20 -08001739 except (RepoExitError, RepoChangedException):
Jason Chang32b59562023-07-14 16:45:35 -07001740 raise
1741 except (KeyboardInterrupt, Exception) as e:
1742 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1743
1744 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001745 manifest = self.outer_manifest
1746 if not opt.outer_manifest:
1747 manifest = self.manifest
1748
1749 if opt.manifest_name:
1750 manifest.Override(opt.manifest_name)
1751
1752 manifest_name = opt.manifest_name
1753 smart_sync_manifest_path = os.path.join(
1754 manifest.manifestProject.worktree, "smart_sync_override.xml"
1755 )
1756
1757 if opt.clone_bundle is None:
1758 opt.clone_bundle = manifest.CloneBundle
1759
1760 if opt.smart_sync or opt.smart_tag:
1761 manifest_name = self._SmartSyncSetup(
1762 opt, smart_sync_manifest_path, manifest
1763 )
1764 else:
1765 if os.path.isfile(smart_sync_manifest_path):
1766 try:
1767 platform_utils.remove(smart_sync_manifest_path)
1768 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001769 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001770 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001771 "manifest: %s",
1772 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001773 )
1774
1775 err_event = multiprocessing.Event()
1776
1777 rp = manifest.repoProject
1778 rp.PreSync()
1779 cb = rp.CurrentBranch
1780 if cb:
1781 base = rp.GetBranch(cb).merge
1782 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001783 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001784 "warning: repo is not tracking a remote branch, so it will "
1785 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001786 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001787 )
1788
1789 for m in self.ManifestList(opt):
1790 if not m.manifestProject.standalone_manifest_url:
1791 m.manifestProject.PreSync()
1792
1793 if opt.repo_upgraded:
1794 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1795
1796 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001797
1798 if _REPO_ALLOW_SHALLOW is not None:
1799 if _REPO_ALLOW_SHALLOW == "1":
1800 mp.ConfigureCloneFilterForDepth(None)
1801 elif (
1802 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1803 ):
1804 mp.ConfigureCloneFilterForDepth("blob:none")
1805
Gavin Makea2e3302023-03-11 06:46:20 +00001806 if opt.mp_update:
Jason Changdaf2ad32023-08-31 17:06:36 -07001807 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001808 else:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001809 print("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001810
1811 # Now that the manifests are up-to-date, setup options whose defaults
1812 # might be in the manifest.
1813 self._ValidateOptionsWithManifest(opt, mp)
1814
1815 superproject_logging_data = {}
1816 self._UpdateProjectsRevisionId(
1817 opt, args, superproject_logging_data, manifest
1818 )
1819
Gavin Makea2e3302023-03-11 06:46:20 +00001820 all_projects = self.GetProjects(
1821 args,
1822 missing_ok=True,
1823 submodules_ok=opt.fetch_submodules,
1824 manifest=manifest,
1825 all_manifests=not opt.this_manifest_only,
1826 )
1827
1828 err_network_sync = False
1829 err_update_projects = False
1830 err_update_linkfiles = False
1831
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001832 # Log the repo projects by existing and new.
1833 existing = [x for x in all_projects if x.Exists]
1834 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1835 mp.config.SetString(
1836 "repo.newprojectcount", str(len(all_projects) - len(existing))
1837 )
1838
Gavin Makea2e3302023-03-11 06:46:20 +00001839 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001840 self._local_sync_state = LocalSyncState(manifest)
Josip Sokcevic5ae82922025-01-31 12:00:52 -08001841 if not opt.local_only:
Gavin Makea2e3302023-03-11 06:46:20 +00001842 with multiprocessing.Manager() as manager:
1843 with ssh.ProxyManager(manager) as ssh_proxy:
1844 # Initialize the socket dir once in the parent.
1845 ssh_proxy.sock()
1846 result = self._FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -07001847 opt,
1848 args,
1849 all_projects,
1850 err_event,
1851 ssh_proxy,
1852 manifest,
1853 errors,
Gavin Makea2e3302023-03-11 06:46:20 +00001854 )
1855 all_projects = result.all_projects
1856
1857 if opt.network_only:
1858 return
1859
1860 # If we saw an error, exit with code 1 so that other scripts can
1861 # check.
1862 if err_event.is_set():
1863 err_network_sync = True
1864 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001865 logger.error(
1866 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001867 "Local checkouts *not* updated. Resolve network issues "
1868 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001869 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001870 )
Jason Chang32b59562023-07-14 16:45:35 -07001871 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001872
1873 for m in self.ManifestList(opt):
1874 if m.IsMirror or m.IsArchive:
1875 # Bail out now, we have no working tree.
1876 continue
1877
Jason Chang32b59562023-07-14 16:45:35 -07001878 try:
1879 self.UpdateProjectList(opt, m)
1880 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001881 err_event.set()
1882 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001883 errors.append(e)
1884 if isinstance(e, DeleteWorktreeError):
1885 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001886 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001887 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001888 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001889
Jason Chang32b59562023-07-14 16:45:35 -07001890 try:
1891 self.UpdateCopyLinkfileList(m)
1892 except Exception as e:
1893 err_update_linkfiles = True
1894 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001895 err_event.set()
1896 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001897 logger.error(
1898 "error: Local update copyfile or linkfile failed."
Gavin Makea2e3302023-03-11 06:46:20 +00001899 )
Jason Chang32b59562023-07-14 16:45:35 -07001900 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001901
1902 err_results = []
1903 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001904 err_checkout = not self._Checkout(
1905 all_projects, opt, err_results, errors
1906 )
Gavin Makea2e3302023-03-11 06:46:20 +00001907 if err_checkout:
1908 err_event.set()
1909
1910 printed_notices = set()
1911 # If there's a notice that's supposed to print at the end of the sync,
1912 # print it now... But avoid printing duplicate messages, and preserve
1913 # order.
1914 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1915 if m.notice and m.notice not in printed_notices:
1916 print(m.notice)
1917 printed_notices.add(m.notice)
1918
1919 # If we saw an error, exit with code 1 so that other scripts can check.
1920 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001921
1922 def print_and_log(err_msg):
1923 self.git_event_log.ErrorEvent(err_msg)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001924 logger.error("%s", err_msg)
Josip Sokcevic131fc962023-05-12 17:00:46 -07001925
1926 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001927 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001928 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001929 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001930 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001931 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001932 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001933 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001934 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001935 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001936 # Don't log repositories, as it may contain sensitive info.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001937 logger.error("Failing repos:\n%s", "\n".join(err_results))
Josip Sokcevic131fc962023-05-12 17:00:46 -07001938 # Not useful to log.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001939 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001940 'Try re-running with "-j1 --fail-fast" to exit at the first '
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001941 "error."
Gavin Makea2e3302023-03-11 06:46:20 +00001942 )
Jason Chang32b59562023-07-14 16:45:35 -07001943 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001944
1945 # Log the previous sync analysis state from the config.
1946 self.git_event_log.LogDataConfigEvents(
1947 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1948 )
1949
1950 # Update and log with the new sync analysis state.
1951 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1952 self.git_event_log.LogDataConfigEvents(
1953 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1954 )
1955
Gavin Makf0aeb222023-08-08 04:43:36 +00001956 self._local_sync_state.PruneRemovedProjects()
1957 if self._local_sync_state.IsPartiallySynced():
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001958 logger.warning(
Gavin Makf0aeb222023-08-08 04:43:36 +00001959 "warning: Partial syncs are not supported. For the best "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001960 "experience, sync the entire tree."
Gavin Makf0aeb222023-08-08 04:43:36 +00001961 )
1962
Gavin Makea2e3302023-03-11 06:46:20 +00001963 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001964 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001965
David Pursehouse819827a2020-02-12 15:20:19 +09001966
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001967def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001968 # Link the docs for the internal .repo/ layout for people.
1969 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1970 if not platform_utils.islink(link):
1971 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1972 try:
1973 platform_utils.symlink(target, link)
1974 except Exception:
1975 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001976
Gavin Makea2e3302023-03-11 06:46:20 +00001977 wrapper = Wrapper()
1978 if wrapper.NeedSetupGnuPG():
1979 wrapper.SetupGnuPG(quiet)
1980 for project in manifest.projects:
1981 if project.Exists:
1982 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001983
David Pursehouse819827a2020-02-12 15:20:19 +09001984
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001985def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001986 if rp.HasChanges:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +00001987 logger.warning("info: A new version of repo is available")
Gavin Makea2e3302023-03-11 06:46:20 +00001988 wrapper = Wrapper()
1989 try:
1990 rev = rp.bare_git.describe(rp.GetRevisionId())
1991 except GitError:
1992 rev = None
1993 _, new_rev = wrapper.check_repo_rev(
1994 rp.gitdir, rev, repo_verify=repo_verify
1995 )
1996 # See if we're held back due to missing signed tag.
1997 current_revid = rp.bare_git.rev_parse("HEAD")
1998 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1999 if current_revid != new_revid:
2000 # We want to switch to the new rev, but also not trash any
2001 # uncommitted changes. This helps with local testing/hacking.
2002 # If a local change has been made, we will throw that away.
2003 # We also have to make sure this will switch to an older commit if
2004 # that's the latest tag in order to support release rollback.
2005 try:
Josip Sokcevicfc901b92025-03-12 20:40:49 +00002006 # Refresh index since reset --keep won't do it.
2007 rp.work_git.update_index("-q", "--refresh")
Gavin Makea2e3302023-03-11 06:46:20 +00002008 rp.work_git.reset("--keep", new_rev)
2009 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07002010 raise RepoUnhandledExceptionError(e)
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00002011 print("info: Restarting repo with latest version")
Gavin Makea2e3302023-03-11 06:46:20 +00002012 raise RepoChangedException(["--repo-upgraded"])
2013 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00002014 logger.warning("warning: Skipped upgrade to unverified version")
Shawn O. Pearcee756c412009-04-13 11:51:15 -07002015 else:
Gavin Makea2e3302023-03-11 06:46:20 +00002016 if verbose:
Aravind Vasudevance0ed792023-10-06 18:36:22 +00002017 print("repo version %s is current" % rp.work_git.describe(HEAD))
Shawn O. Pearcee756c412009-04-13 11:51:15 -07002018
David Pursehouse819827a2020-02-12 15:20:19 +09002019
Mike Frysingerd4aee652023-10-19 05:13:32 -04002020class _FetchTimes:
Gavin Makea2e3302023-03-11 06:46:20 +00002021 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07002022
Gavin Makea2e3302023-03-11 06:46:20 +00002023 def __init__(self, manifest):
2024 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00002025 self._saved = None
2026 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002027
Gavin Makea2e3302023-03-11 06:46:20 +00002028 def Get(self, project):
2029 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00002030 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07002031
Gavin Makea2e3302023-03-11 06:46:20 +00002032 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00002033 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00002034
2035 # For shared projects, save the longest time.
2036 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07002037
Gavin Makea2e3302023-03-11 06:46:20 +00002038 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002039 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002040 try:
2041 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002042 self._saved = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452043 except (OSError, ValueError):
Gavin Makea2e3302023-03-11 06:46:20 +00002044 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00002045 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002046
Gavin Makea2e3302023-03-11 06:46:20 +00002047 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002048 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002049 return
Dave Borowitzd9478582012-10-23 16:35:39 -07002050
Gavin Mak041f9772023-05-10 20:41:12 +00002051 for name, t in self._seen.items():
2052 # Keep a moving average across the previous/current sync runs.
2053 old = self._saved.get(name, t)
2054 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07002055
Gavin Makea2e3302023-03-11 06:46:20 +00002056 try:
2057 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002058 json.dump(self._seen, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452059 except (OSError, TypeError):
Gavin Makea2e3302023-03-11 06:46:20 +00002060 platform_utils.remove(self._path, missing_ok=True)
2061
Dan Willemsen0745bb22015-08-17 13:41:45 -07002062
Mike Frysingerd4aee652023-10-19 05:13:32 -04002063class LocalSyncState:
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002064 _LAST_FETCH = "last_fetch"
2065 _LAST_CHECKOUT = "last_checkout"
2066
2067 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00002068 self._manifest = manifest
2069 self._path = os.path.join(
2070 self._manifest.repodir, ".repo_localsyncstate.json"
2071 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002072 self._time = time.time()
2073 self._state = None
2074 self._Load()
2075
2076 def SetFetchTime(self, project):
2077 self._Set(project, self._LAST_FETCH)
2078
2079 def SetCheckoutTime(self, project):
2080 self._Set(project, self._LAST_CHECKOUT)
2081
2082 def GetFetchTime(self, project):
2083 return self._Get(project, self._LAST_FETCH)
2084
2085 def GetCheckoutTime(self, project):
2086 return self._Get(project, self._LAST_CHECKOUT)
2087
2088 def _Get(self, project, key):
2089 self._Load()
2090 p = project.relpath
2091 if p not in self._state:
2092 return
2093 return self._state[p].get(key)
2094
2095 def _Set(self, project, key):
2096 p = project.relpath
2097 if p not in self._state:
2098 self._state[p] = {}
2099 self._state[p][key] = self._time
2100
2101 def _Load(self):
2102 if self._state is None:
2103 try:
2104 with open(self._path) as f:
2105 self._state = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452106 except (OSError, ValueError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002107 platform_utils.remove(self._path, missing_ok=True)
2108 self._state = {}
2109
2110 def Save(self):
2111 if not self._state:
2112 return
2113 try:
2114 with open(self._path, "w") as f:
2115 json.dump(self._state, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452116 except (OSError, TypeError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002117 platform_utils.remove(self._path, missing_ok=True)
2118
Gavin Makf0aeb222023-08-08 04:43:36 +00002119 def PruneRemovedProjects(self):
2120 """Remove entries don't exist on disk and save."""
2121 if not self._state:
2122 return
2123 delete = set()
2124 for path in self._state:
2125 gitdir = os.path.join(self._manifest.topdir, path, ".git")
Matt Schulte0dd0a832023-11-30 11:00:16 -08002126 if not os.path.exists(gitdir) or os.path.islink(gitdir):
Gavin Makf0aeb222023-08-08 04:43:36 +00002127 delete.add(path)
2128 if not delete:
2129 return
2130 for path in delete:
2131 del self._state[path]
2132 self.Save()
2133
2134 def IsPartiallySynced(self):
2135 """Return whether a partial sync state is detected."""
2136 self._Load()
2137 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002138 for path, data in self._state.items():
2139 if path == self._manifest.repoProject.relpath:
2140 # The repo project isn't included in most syncs so we should
2141 # ignore it here.
2142 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002143 checkout_t = data.get(self._LAST_CHECKOUT)
2144 if not checkout_t:
2145 return True
2146 prev_checkout_t = prev_checkout_t or checkout_t
2147 if prev_checkout_t != checkout_t:
2148 return True
2149 return False
2150
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002151
Dan Willemsen0745bb22015-08-17 13:41:45 -07002152# This is a replacement for xmlrpc.client.Transport using urllib2
2153# and supporting persistent-http[s]. It cannot change hosts from
2154# request to request like the normal transport, the real url
2155# is passed during initialization.
2156class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002157 def __init__(self, orig_host):
Daniel Kutikb99272c2023-10-23 21:20:07 +02002158 super().__init__()
Gavin Makea2e3302023-03-11 06:46:20 +00002159 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002160
Gavin Makea2e3302023-03-11 06:46:20 +00002161 def request(self, host, handler, request_body, verbose=False):
2162 with GetUrlCookieFile(self.orig_host, not verbose) as (
2163 cookiefile,
2164 proxy,
2165 ):
2166 # Python doesn't understand cookies with the #HttpOnly_ prefix
2167 # Since we're only using them for HTTP, copy the file temporarily,
2168 # stripping those prefixes away.
2169 if cookiefile:
2170 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2171 tmpcookiefile.write("# HTTP Cookie File")
2172 try:
2173 with open(cookiefile) as f:
2174 for line in f:
2175 if line.startswith("#HttpOnly_"):
2176 line = line[len("#HttpOnly_") :]
2177 tmpcookiefile.write(line)
2178 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002179
Gavin Makea2e3302023-03-11 06:46:20 +00002180 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2181 try:
2182 cookiejar.load()
2183 except cookielib.LoadError:
2184 cookiejar = cookielib.CookieJar()
2185 finally:
2186 tmpcookiefile.close()
2187 else:
2188 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002189
Gavin Makea2e3302023-03-11 06:46:20 +00002190 proxyhandler = urllib.request.ProxyHandler
2191 if proxy:
2192 proxyhandler = urllib.request.ProxyHandler(
2193 {"http": proxy, "https": proxy}
2194 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002195
Gavin Makea2e3302023-03-11 06:46:20 +00002196 opener = urllib.request.build_opener(
2197 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2198 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002199
Gavin Makea2e3302023-03-11 06:46:20 +00002200 url = urllib.parse.urljoin(self.orig_host, handler)
2201 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002202
Gavin Makea2e3302023-03-11 06:46:20 +00002203 scheme = parse_results.scheme
2204 if scheme == "persistent-http":
2205 scheme = "http"
2206 if scheme == "persistent-https":
2207 # If we're proxying through persistent-https, use http. The
2208 # proxy itself will do the https.
2209 if proxy:
2210 scheme = "http"
2211 else:
2212 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002213
Gavin Makea2e3302023-03-11 06:46:20 +00002214 # Parse out any authentication information using the base class.
2215 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002216
Gavin Makea2e3302023-03-11 06:46:20 +00002217 url = urllib.parse.urlunparse(
2218 (
2219 scheme,
2220 host,
2221 parse_results.path,
2222 parse_results.params,
2223 parse_results.query,
2224 parse_results.fragment,
2225 )
2226 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002227
Gavin Makea2e3302023-03-11 06:46:20 +00002228 request = urllib.request.Request(url, request_body)
2229 if extra_headers is not None:
2230 for name, header in extra_headers:
2231 request.add_header(name, header)
2232 request.add_header("Content-Type", "text/xml")
2233 try:
2234 response = opener.open(request)
2235 except urllib.error.HTTPError as e:
2236 if e.code == 501:
2237 # We may have been redirected through a login process
2238 # but our POST turned into a GET. Retry.
2239 response = opener.open(request)
2240 else:
2241 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002242
Gavin Makea2e3302023-03-11 06:46:20 +00002243 p, u = xmlrpc.client.getparser()
2244 # Response should be fairly small, so read it all at once.
2245 # This way we can show it to the user in case of error (e.g. HTML).
2246 data = response.read()
2247 try:
2248 p.feed(data)
2249 except xml.parsers.expat.ExpatError as e:
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452250 raise OSError(
Gavin Makea2e3302023-03-11 06:46:20 +00002251 f"Parsing the manifest failed: {e}\n"
2252 f"Please report this to your manifest server admin.\n"
2253 f'Here is the full response:\n{data.decode("utf-8")}'
2254 )
2255 p.close()
2256 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002257
Gavin Makea2e3302023-03-11 06:46:20 +00002258 def close(self):
2259 pass