blob: 00fee7763bd1dc5e3743668bfa4f1da7b230f14a [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
LaMont Jonesbdcba7d2022-04-11 22:50:11 +000015import collections
Mike Frysingerebf04a42021-02-23 20:48:04 -050016import functools
Mike Frysingeracf63b22019-06-13 02:24:21 -040017import http.cookiejar as cookielib
Mike Frysinger7b586f22021-02-23 18:38:39 -050018import io
Anthony King85b24ac2014-05-06 15:57:48 +010019import json
Mike Frysingerebf04a42021-02-23 20:48:04 -050020import multiprocessing
David Pursehouse86d973d2012-08-24 10:21:02 +090021import netrc
Mike Frysinger06ddc8c2023-08-21 21:26:51 -040022import optparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import os
Josip Sokcevic55545722024-02-22 16:38:00 -080024from pathlib import Path
Jason Changdaf2ad32023-08-31 17:06:36 -070025import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
Jason Changdaf2ad32023-08-31 17:06:36 -070028from typing import List, NamedTuple, Set, Union
Mike Frysingeracf63b22019-06-13 02:24:21 -040029import urllib.error
30import urllib.parse
31import urllib.request
Mike Frysinger5951e302022-05-20 23:34:44 -040032import xml.parsers.expat
Mike Frysingeracf63b22019-06-13 02:24:21 -040033import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Mike Frysinger64477332023-08-21 21:20:32 -040035
Roy Lee18afd7f2010-05-09 04:32:08 +080036try:
Gavin Makea2e3302023-03-11 06:46:20 +000037 import threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080038except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000039 import dummy_threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080040
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070041try:
Gavin Makea2e3302023-03-11 06:46:20 +000042 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090043
Gavin Makea2e3302023-03-11 06:46:20 +000044 def _rlimit_nofile():
45 return resource.getrlimit(resource.RLIMIT_NOFILE)
46
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070047except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000048
49 def _rlimit_nofile():
50 return (256, 256)
51
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070052
Mike Frysinger64477332023-08-21 21:20:32 -040053from command import Command
54from command import DEFAULT_LOCAL_JOBS
55from command import MirrorSafeCommand
56from command import WORKER_BATCH_SIZE
57from error import GitError
58from error import RepoChangedException
Jason Changdaf2ad32023-08-31 17:06:36 -070059from error import RepoError
Mike Frysinger64477332023-08-21 21:20:32 -040060from error import RepoExitError
61from error import RepoUnhandledExceptionError
62from error import SyncError
63from error import UpdateManifestError
David Rileye0684ad2017-04-05 00:02:59 -070064import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040065from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090066from git_config import GetUrlCookieFile
Mike Frysinger64477332023-08-21 21:20:32 -040067from git_refs import HEAD
68from git_refs import R_HEADS
Raman Tenneti6a872c92021-01-14 19:17:50 -080069import git_superproject
Mike Frysinger64477332023-08-21 21:20:32 -040070import platform_utils
71from progress import elapsed_str
72from progress import jobs_str
73from progress import Progress
74from project import DeleteWorktreeError
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070075from project import Project
76from project import RemoteSpec
Mike Frysinger64477332023-08-21 21:20:32 -040077from project import SyncBuffer
Aravind Vasudevane914ec22023-08-31 20:57:31 +000078from repo_logging import RepoLogger
Joanna Wanga6c52f52022-11-03 16:51:19 -040079from repo_trace import Trace
Mike Frysinger19e409c2021-05-05 19:44:35 -040080import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080081from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082
Mike Frysinger64477332023-08-21 21:20:32 -040083
Dave Borowitz67700e92012-10-23 15:00:54 -070084_ONE_DAY_S = 24 * 60 * 60
85
Jason Chang17833322023-05-23 13:06:55 -070086_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
87
Aravind Vasudevane914ec22023-08-31 20:57:31 +000088logger = RepoLogger(__file__)
89
David Pursehouse819827a2020-02-12 15:20:19 +090090
Josip Sokcevic55545722024-02-22 16:38:00 -080091def _SafeCheckoutOrder(checkouts: List[Project]) -> List[List[Project]]:
92 """Generate a sequence of checkouts that is safe to perform. The client
93 should checkout everything from n-th index before moving to n+1.
94
95 This is only useful if manifest contains nested projects.
96
97 E.g. if foo, foo/bar and foo/bar/baz are project paths, then foo needs to
98 finish before foo/bar can proceed, and foo/bar needs to finish before
99 foo/bar/baz."""
100 res = [[]]
101 current = res[0]
102
103 # depth_stack contains a current stack of parent paths.
104 depth_stack = []
Josip Sokcevic46790222024-03-07 22:18:58 +0000105 # Checkouts are iterated in the hierarchical order. That way, it can easily
106 # be determined if the previous checkout is parent of the current checkout.
107 # We are splitting by the path separator so the final result is
108 # hierarchical, and not just lexicographical. For example, if the projects
109 # are: foo, foo/bar, foo-bar, lexicographical order produces foo, foo-bar
110 # and foo/bar, which doesn't work.
111 for checkout in sorted(checkouts, key=lambda x: x.relpath.split("/")):
Josip Sokcevic55545722024-02-22 16:38:00 -0800112 checkout_path = Path(checkout.relpath)
113 while depth_stack:
114 try:
115 checkout_path.relative_to(depth_stack[-1])
116 except ValueError:
117 # Path.relative_to returns ValueError if paths are not relative.
118 # TODO(sokcevic): Switch to is_relative_to once min supported
119 # version is py3.9.
120 depth_stack.pop()
121 else:
122 if len(depth_stack) >= len(res):
123 # Another depth created.
124 res.append([])
125 break
126
127 current = res[len(depth_stack)]
128 current.append(checkout)
129 depth_stack.append(checkout_path)
130
131 return res
132
133
Josip Sokcevic454fdaf2024-10-07 17:33:38 +0000134def _chunksize(projects: int, jobs: int) -> int:
135 """Calculate chunk size for the given number of projects and jobs."""
136 return min(max(1, projects // jobs), WORKER_BATCH_SIZE)
137
138
LaMont Jones1eddca82022-09-01 15:15:04 +0000139class _FetchOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000140 """_FetchOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000141
Gavin Makea2e3302023-03-11 06:46:20 +0000142 Attributes:
143 success (bool): True if successful.
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800144 project_idx (int): The fetched project index.
Gavin Makea2e3302023-03-11 06:46:20 +0000145 start (float): The starting time.time().
146 finish (float): The ending time.time().
147 remote_fetched (bool): True if the remote was actually queried.
148 """
149
150 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700151 errors: List[Exception]
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800152 project_idx: int
Gavin Makea2e3302023-03-11 06:46:20 +0000153 start: float
154 finish: float
155 remote_fetched: bool
LaMont Jones1eddca82022-09-01 15:15:04 +0000156
157
158class _FetchResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000159 """_Fetch return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000160
Gavin Makea2e3302023-03-11 06:46:20 +0000161 Attributes:
162 success (bool): True if successful.
163 projects (Set[str]): The names of the git directories of fetched projects.
164 """
165
166 success: bool
167 projects: Set[str]
LaMont Jones1eddca82022-09-01 15:15:04 +0000168
169
170class _FetchMainResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000171 """_FetchMain return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000172
Gavin Makea2e3302023-03-11 06:46:20 +0000173 Attributes:
174 all_projects (List[Project]): The fetched projects.
175 """
176
177 all_projects: List[Project]
LaMont Jones1eddca82022-09-01 15:15:04 +0000178
179
180class _CheckoutOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000181 """_CheckoutOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000182
Gavin Makea2e3302023-03-11 06:46:20 +0000183 Attributes:
184 success (bool): True if successful.
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800185 project_idx (int): The project index.
Gavin Makea2e3302023-03-11 06:46:20 +0000186 start (float): The starting time.time().
187 finish (float): The ending time.time().
188 """
189
190 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700191 errors: List[Exception]
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800192 project_idx: int
Gavin Makea2e3302023-03-11 06:46:20 +0000193 start: float
194 finish: float
LaMont Jones1eddca82022-09-01 15:15:04 +0000195
196
Jason Chang32b59562023-07-14 16:45:35 -0700197class SuperprojectError(SyncError):
198 """Superproject sync repo."""
199
200
201class SyncFailFastError(SyncError):
202 """Sync exit error when --fail-fast set."""
203
204
205class SmartSyncError(SyncError):
206 """Smart sync exit error."""
207
208
Jason Changdaf2ad32023-08-31 17:06:36 -0700209class ManifestInterruptError(RepoError):
210 """Aggregate Error to be logged when a user interrupts a manifest update."""
211
212 def __init__(self, output, **kwargs):
213 super().__init__(output, **kwargs)
214 self.output = output
215
216 def __str__(self):
217 error_type = type(self).__name__
218 return f"{error_type}:{self.output}"
219
220
221class TeeStringIO(io.StringIO):
222 """StringIO class that can write to an additional destination."""
223
224 def __init__(
225 self, io: Union[io.TextIOWrapper, None], *args, **kwargs
226 ) -> None:
227 super().__init__(*args, **kwargs)
228 self.io = io
229
230 def write(self, s: str) -> int:
231 """Write to additional destination."""
Daniel Kutikb0430b52023-10-23 21:16:04 +0200232 ret = super().write(s)
Jason Changdaf2ad32023-08-31 17:06:36 -0700233 if self.io is not None:
234 self.io.write(s)
Daniel Kutikb0430b52023-10-23 21:16:04 +0200235 return ret
Jason Changdaf2ad32023-08-31 17:06:36 -0700236
237
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800238class Sync(Command, MirrorSafeCommand):
Gavin Makea2e3302023-03-11 06:46:20 +0000239 COMMON = True
240 MULTI_MANIFEST_SUPPORT = True
241 helpSummary = "Update working tree to the latest revision"
242 helpUsage = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243%prog [...]
244"""
Gavin Makea2e3302023-03-11 06:46:20 +0000245 helpDescription = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246The '%prog' command synchronizes local project directories
247with the remote repositories specified in the manifest. If a local
248project does not yet exist, it will clone a new local directory from
249the remote repository and set up tracking branches as specified in
250the manifest. If the local project already exists, '%prog'
251will update the remote branches and rebase any new local changes
252on top of the new remote changes.
253
254'%prog' will synchronize all projects listed at the command
255line. Projects can be specified either by name, or by a relative
256or absolute path to the project's local directory. If no projects
257are specified, '%prog' will synchronize all projects listed in
258the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700259
260The -d/--detach option can be used to switch specified projects
261back to the manifest revision. This option is especially helpful
262if the project is currently on a topic branch, but the manifest
263revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700264
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700265The -s/--smart-sync option can be used to sync to a known good
266build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200267manifest. The -t/--smart-tag option is similar and allows you to
268specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700269
David Pursehousecf76b1b2012-09-14 10:31:42 +0900270The -u/--manifest-server-username and -p/--manifest-server-password
271options can be used to specify a username and password to authenticate
272with the manifest server when using the -s or -t option.
273
274If -u and -p are not specified when using the -s or -t option, '%prog'
275will attempt to read authentication credentials for the manifest server
276from the user's .netrc file.
277
278'%prog' will not use authentication credentials from -u/-p or .netrc
279if the manifest server specified in the manifest file already includes
280credentials.
281
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400282By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400283to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500284
Kevin Degiabaa7f32014-11-12 11:27:45 -0700285The --force-sync option can be used to overwrite existing git
286directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900287object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700288refs may be removed when overwriting.
289
Josip Sokcevicedadb252024-02-29 09:48:37 -0800290The --force-checkout option can be used to force git to switch revs even if the
291index or the working tree differs from HEAD, and if there are untracked files.
292WARNING: This may cause data to be lost since uncommitted changes may be
293removed.
294
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500295The --force-remove-dirty option can be used to remove previously used
296projects with uncommitted changes. WARNING: This may cause data to be
297lost since uncommitted changes may be removed with projects that no longer
298exist in the manifest.
299
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700300The --no-clone-bundle option disables any attempt to use
301$URL/clone.bundle to bootstrap a new Git repository from a
302resumeable bundle file on a content delivery network. This
303may be necessary if there are problems with the local Python
304HTTP client or proxy configuration, but the Git binary works.
305
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800306The --fetch-submodules option enables fetching Git submodules
307of a project from server.
308
David Pursehousef2fad612015-01-29 14:36:28 +0900309The -c/--current-branch option can be used to only fetch objects that
310are on the branch specified by a project's revision.
311
David Pursehouseb1553542014-09-04 21:28:09 +0900312The --optimized-fetch option can be used to only fetch projects that
313are fixed to a sha1 revision if the sha1 revision does not already
314exist locally.
315
David Pursehouse74cfd272015-10-14 10:50:15 +0900316The --prune option can be used to remove any refs that no longer
317exist on the remote.
318
LaMont Jones7efab532022-09-01 15:41:12 +0000319The --auto-gc option can be used to trigger garbage collection on all
320projects. By default, repo does not run garbage collection.
321
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400322# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700323
324If at least one project remote URL uses an SSH connection (ssh://,
325git+ssh://, or user@host:path syntax) repo will automatically
326enable the SSH ControlMaster option when connecting to that host.
327This feature permits other projects in the same '%prog' session to
328reuse the same SSH tunnel, saving connection setup overheads.
329
330To disable this behavior on UNIX platforms, set the GIT_SSH
331environment variable to 'ssh'. For example:
332
333 export GIT_SSH=ssh
334 %prog
335
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400336# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700337
338This feature is automatically disabled on Windows, due to the lack
339of UNIX domain socket support.
340
341This feature is not compatible with url.insteadof rewrites in the
342user's ~/.gitconfig. '%prog' is currently not able to perform the
343rewrite early enough to establish the ControlMaster tunnel.
344
345If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
346later is required to fix a server side protocol bug.
347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348"""
Gavin Makea2e3302023-03-11 06:46:20 +0000349 # A value of 0 means we want parallel jobs, but we'll determine the default
350 # value later on.
351 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700352
Gavin Makea2e3302023-03-11 06:46:20 +0000353 def _Options(self, p, show_smart=True):
354 p.add_option(
355 "--jobs-network",
356 default=None,
357 type=int,
358 metavar="JOBS",
359 help="number of network jobs to run in parallel (defaults to "
360 "--jobs or 1)",
361 )
362 p.add_option(
363 "--jobs-checkout",
364 default=None,
365 type=int,
366 metavar="JOBS",
367 help="number of local checkout jobs to run in parallel (defaults "
368 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
369 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400370
Gavin Makea2e3302023-03-11 06:46:20 +0000371 p.add_option(
372 "-f",
373 "--force-broken",
374 dest="force_broken",
375 action="store_true",
376 help="obsolete option (to be deleted in the future)",
377 )
378 p.add_option(
379 "--fail-fast",
380 dest="fail_fast",
381 action="store_true",
382 help="stop syncing after first error is hit",
383 )
384 p.add_option(
385 "--force-sync",
386 dest="force_sync",
387 action="store_true",
388 help="overwrite an existing git directory if it needs to "
389 "point to a different object directory. WARNING: this "
390 "may cause loss of data",
391 )
392 p.add_option(
Josip Sokcevicedadb252024-02-29 09:48:37 -0800393 "--force-checkout",
394 dest="force_checkout",
395 action="store_true",
396 help="force checkout even if it results in throwing away "
397 "uncommitted modifications. "
398 "WARNING: this may cause loss of data",
399 )
400 p.add_option(
Gavin Makea2e3302023-03-11 06:46:20 +0000401 "--force-remove-dirty",
402 dest="force_remove_dirty",
403 action="store_true",
404 help="force remove projects with uncommitted modifications if "
405 "projects no longer exist in the manifest. "
406 "WARNING: this may cause loss of data",
407 )
408 p.add_option(
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +0200409 "--rebase",
410 dest="rebase",
411 action="store_true",
412 help="rebase local commits regardless of whether they are "
413 "published",
414 )
415 p.add_option(
Gavin Makea2e3302023-03-11 06:46:20 +0000416 "-l",
417 "--local-only",
418 dest="local_only",
419 action="store_true",
420 help="only update working tree, don't fetch",
421 )
422 p.add_option(
423 "--no-manifest-update",
424 "--nmu",
425 dest="mp_update",
426 action="store_false",
427 default="true",
428 help="use the existing manifest checkout as-is. "
429 "(do not update to the latest revision)",
430 )
431 p.add_option(
432 "-n",
433 "--network-only",
434 dest="network_only",
435 action="store_true",
436 help="fetch only, don't update working tree",
437 )
438 p.add_option(
439 "-d",
440 "--detach",
441 dest="detach_head",
442 action="store_true",
443 help="detach projects back to manifest revision",
444 )
445 p.add_option(
446 "-c",
447 "--current-branch",
448 dest="current_branch_only",
449 action="store_true",
450 help="fetch only current branch from server",
451 )
452 p.add_option(
453 "--no-current-branch",
454 dest="current_branch_only",
455 action="store_false",
456 help="fetch all branches from server",
457 )
458 p.add_option(
459 "-m",
460 "--manifest-name",
461 dest="manifest_name",
462 help="temporary manifest to use for this sync",
463 metavar="NAME.xml",
464 )
465 p.add_option(
466 "--clone-bundle",
467 action="store_true",
468 help="enable use of /clone.bundle on HTTP/HTTPS",
469 )
470 p.add_option(
471 "--no-clone-bundle",
472 dest="clone_bundle",
473 action="store_false",
474 help="disable use of /clone.bundle on HTTP/HTTPS",
475 )
476 p.add_option(
477 "-u",
478 "--manifest-server-username",
479 action="store",
480 dest="manifest_server_username",
481 help="username to authenticate with the manifest server",
482 )
483 p.add_option(
484 "-p",
485 "--manifest-server-password",
486 action="store",
487 dest="manifest_server_password",
488 help="password to authenticate with the manifest server",
489 )
490 p.add_option(
491 "--fetch-submodules",
492 dest="fetch_submodules",
493 action="store_true",
494 help="fetch submodules from server",
495 )
496 p.add_option(
497 "--use-superproject",
498 action="store_true",
499 help="use the manifest superproject to sync projects; implies -c",
500 )
501 p.add_option(
502 "--no-use-superproject",
503 action="store_false",
504 dest="use_superproject",
505 help="disable use of manifest superprojects",
506 )
507 p.add_option("--tags", action="store_true", help="fetch tags")
508 p.add_option(
509 "--no-tags",
510 dest="tags",
511 action="store_false",
512 help="don't fetch tags (default)",
513 )
514 p.add_option(
515 "--optimized-fetch",
516 dest="optimized_fetch",
517 action="store_true",
518 help="only fetch projects fixed to sha1 if revision does not exist "
519 "locally",
520 )
521 p.add_option(
522 "--retry-fetches",
523 default=0,
524 action="store",
525 type="int",
526 help="number of times to retry fetches on transient errors",
527 )
528 p.add_option(
529 "--prune",
530 action="store_true",
531 help="delete refs that no longer exist on the remote (default)",
532 )
533 p.add_option(
534 "--no-prune",
535 dest="prune",
536 action="store_false",
537 help="do not delete refs that no longer exist on the remote",
538 )
539 p.add_option(
540 "--auto-gc",
541 action="store_true",
542 default=None,
543 help="run garbage collection on all synced projects",
544 )
545 p.add_option(
546 "--no-auto-gc",
547 dest="auto_gc",
548 action="store_false",
549 help="do not run garbage collection on any projects (default)",
550 )
551 if show_smart:
552 p.add_option(
553 "-s",
554 "--smart-sync",
555 dest="smart_sync",
556 action="store_true",
557 help="smart sync using manifest from the latest known good "
558 "build",
559 )
560 p.add_option(
561 "-t",
562 "--smart-tag",
563 dest="smart_tag",
564 action="store",
565 help="smart sync using manifest from a known tag",
566 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700567
Gavin Makea2e3302023-03-11 06:46:20 +0000568 g = p.add_option_group("repo Version options")
569 g.add_option(
570 "--no-repo-verify",
571 dest="repo_verify",
572 default=True,
573 action="store_false",
574 help="do not verify repo source code",
575 )
576 g.add_option(
577 "--repo-upgraded",
578 dest="repo_upgraded",
579 action="store_true",
Mike Frysinger06ddc8c2023-08-21 21:26:51 -0400580 help=optparse.SUPPRESS_HELP,
Gavin Makea2e3302023-03-11 06:46:20 +0000581 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582
Gavin Makea2e3302023-03-11 06:46:20 +0000583 def _GetBranch(self, manifest_project):
584 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000585
Gavin Makea2e3302023-03-11 06:46:20 +0000586 Args:
587 manifest_project: The manifestProject to query.
588 """
589 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
590 branch = b.merge
591 if branch.startswith(R_HEADS):
592 branch = branch[len(R_HEADS) :]
593 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800594
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800595 @classmethod
596 def _GetCurrentBranchOnly(cls, opt, manifest):
Gavin Makea2e3302023-03-11 06:46:20 +0000597 """Returns whether current-branch or use-superproject options are
598 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200599
Gavin Makea2e3302023-03-11 06:46:20 +0000600 Args:
601 opt: Program options returned from optparse. See _Options().
602 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000603
Gavin Makea2e3302023-03-11 06:46:20 +0000604 Returns:
605 True if a superproject is requested, otherwise the value of the
606 current_branch option (True, False or None).
607 """
608 return (
609 git_superproject.UseSuperproject(opt.use_superproject, manifest)
610 or opt.current_branch_only
611 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700612
Gavin Makea2e3302023-03-11 06:46:20 +0000613 def _UpdateProjectsRevisionId(
614 self, opt, args, superproject_logging_data, manifest
615 ):
616 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800617
Gavin Makea2e3302023-03-11 06:46:20 +0000618 This function updates each project's revisionId with the commit hash
619 from the superproject. It writes the updated manifest into a file and
620 reloads the manifest from it. When appropriate, sub manifests are also
621 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800622
Gavin Makea2e3302023-03-11 06:46:20 +0000623 Args:
624 opt: Program options returned from optparse. See _Options().
625 args: Arguments to pass to GetProjects. See the GetProjects
626 docstring for details.
627 superproject_logging_data: A dictionary of superproject data to log.
628 manifest: The manifest to use.
629 """
630 have_superproject = manifest.superproject or any(
631 m.superproject for m in manifest.all_children
632 )
633 if not have_superproject:
634 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000635
Gavin Makea2e3302023-03-11 06:46:20 +0000636 if opt.local_only and manifest.superproject:
637 manifest_path = manifest.superproject.manifest_path
638 if manifest_path:
639 self._ReloadManifest(manifest_path, manifest)
640 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700641
Gavin Makea2e3302023-03-11 06:46:20 +0000642 all_projects = self.GetProjects(
643 args,
644 missing_ok=True,
645 submodules_ok=opt.fetch_submodules,
646 manifest=manifest,
647 all_manifests=not opt.this_manifest_only,
648 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000649
Gavin Makea2e3302023-03-11 06:46:20 +0000650 per_manifest = collections.defaultdict(list)
651 if opt.this_manifest_only:
652 per_manifest[manifest.path_prefix] = all_projects
653 else:
654 for p in all_projects:
655 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000656
Gavin Makea2e3302023-03-11 06:46:20 +0000657 superproject_logging_data = {}
658 need_unload = False
659 for m in self.ManifestList(opt):
660 if m.path_prefix not in per_manifest:
661 continue
662 use_super = git_superproject.UseSuperproject(
663 opt.use_superproject, m
664 )
665 if superproject_logging_data:
666 superproject_logging_data["multimanifest"] = True
667 superproject_logging_data.update(
668 superproject=use_super,
669 haslocalmanifests=bool(m.HasLocalManifests),
670 hassuperprojecttag=bool(m.superproject),
671 )
672 if use_super and (m.IsMirror or m.IsArchive):
673 # Don't use superproject, because we have no working tree.
674 use_super = False
675 superproject_logging_data["superproject"] = False
676 superproject_logging_data["noworktree"] = True
677 if opt.use_superproject is not False:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000678 logger.warning(
679 "%s: not using superproject because there is no "
680 "working tree.",
681 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000682 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000683
Gavin Makea2e3302023-03-11 06:46:20 +0000684 if not use_super:
685 continue
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -0800686 m.superproject.SetQuiet(not opt.verbose)
Gavin Makea2e3302023-03-11 06:46:20 +0000687 print_messages = git_superproject.PrintMessages(
688 opt.use_superproject, m
689 )
690 m.superproject.SetPrintMessages(print_messages)
691 update_result = m.superproject.UpdateProjectsRevisionId(
692 per_manifest[m.path_prefix], git_event_log=self.git_event_log
693 )
694 manifest_path = update_result.manifest_path
695 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
696 if manifest_path:
697 m.SetManifestOverride(manifest_path)
698 need_unload = True
699 else:
700 if print_messages:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000701 logger.warning(
702 "%s: warning: Update of revisionId from superproject "
703 "has failed, repo sync will not use superproject to "
704 "fetch the source. Please resync with the "
705 "--no-use-superproject option to avoid this repo "
706 "warning.",
707 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000708 )
709 if update_result.fatal and opt.use_superproject is not None:
Jason Chang32b59562023-07-14 16:45:35 -0700710 raise SuperprojectError()
Gavin Makea2e3302023-03-11 06:46:20 +0000711 if need_unload:
712 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800713
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800714 @classmethod
715 def _FetchProjectList(cls, opt, projects):
Gavin Makea2e3302023-03-11 06:46:20 +0000716 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500717
Gavin Makea2e3302023-03-11 06:46:20 +0000718 The projects we're given share the same underlying git object store, so
719 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800720
Gavin Mak551285f2023-05-04 04:48:43 +0000721 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700722
Gavin Makea2e3302023-03-11 06:46:20 +0000723 Args:
724 opt: Program options returned from optparse. See _Options().
725 projects: Projects to fetch.
726 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800727 return [cls._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700728
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800729 @classmethod
730 def _FetchOne(cls, opt, project_idx):
Gavin Makea2e3302023-03-11 06:46:20 +0000731 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700732
Gavin Makea2e3302023-03-11 06:46:20 +0000733 Args:
734 opt: Program options returned from optparse. See _Options().
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800735 project_idx: Project index for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700736
Gavin Makea2e3302023-03-11 06:46:20 +0000737 Returns:
738 Whether the fetch was successful.
739 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800740 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +0000741 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000742 k = f"{project.name} @ {project.relpath}"
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800743 cls.get_parallel_context()["sync_dict"][k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000744 success = False
745 remote_fetched = False
Jason Chang32b59562023-07-14 16:45:35 -0700746 errors = []
Jason Changdaf2ad32023-08-31 17:06:36 -0700747 buf = TeeStringIO(sys.stdout if opt.verbose else None)
Gavin Makea2e3302023-03-11 06:46:20 +0000748 try:
749 sync_result = project.Sync_NetworkHalf(
750 quiet=opt.quiet,
751 verbose=opt.verbose,
752 output_redir=buf,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800753 current_branch_only=cls._GetCurrentBranchOnly(
Gavin Makea2e3302023-03-11 06:46:20 +0000754 opt, project.manifest
755 ),
756 force_sync=opt.force_sync,
757 clone_bundle=opt.clone_bundle,
758 tags=opt.tags,
759 archive=project.manifest.IsArchive,
760 optimized_fetch=opt.optimized_fetch,
761 retry_fetches=opt.retry_fetches,
762 prune=opt.prune,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800763 ssh_proxy=cls.get_parallel_context()["ssh_proxy"],
Gavin Makea2e3302023-03-11 06:46:20 +0000764 clone_filter=project.manifest.CloneFilter,
765 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700766 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000767 )
768 success = sync_result.success
769 remote_fetched = sync_result.remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700770 if sync_result.error:
771 errors.append(sync_result.error)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700772
Gavin Makea2e3302023-03-11 06:46:20 +0000773 output = buf.getvalue()
Jason Changdaf2ad32023-08-31 17:06:36 -0700774 if output and buf.io is None and not success:
Gavin Makea2e3302023-03-11 06:46:20 +0000775 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700776
Gavin Makea2e3302023-03-11 06:46:20 +0000777 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000778 logger.error(
779 "error: Cannot fetch %s from %s",
780 project.name,
781 project.remote.url,
Gavin Makea2e3302023-03-11 06:46:20 +0000782 )
783 except KeyboardInterrupt:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000784 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000785 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000786 logger.error("error.GitError: Cannot fetch %s", e)
Jason Chang32b59562023-07-14 16:45:35 -0700787 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000788 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000789 logger.error(
790 "error: Cannot fetch %s (%s: %s)",
791 project.name,
792 type(e).__name__,
793 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000794 )
Jason Chang32b59562023-07-14 16:45:35 -0700795 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000796 raise
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800797 finally:
798 del cls.get_parallel_context()["sync_dict"][k]
Mike Frysinger7b586f22021-02-23 18:38:39 -0500799
Gavin Makea2e3302023-03-11 06:46:20 +0000800 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700801 return _FetchOneResult(
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800802 success, errors, project_idx, start, finish, remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700803 )
David James8d201162013-10-11 17:03:19 -0700804
Gavin Mak04cba4a2023-05-24 21:28:28 +0000805 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000806 earliest_time = float("inf")
807 earliest_proj = None
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800808 items = self.get_parallel_context()["sync_dict"].items()
Gavin Mak945c0062023-05-30 20:04:07 +0000809 for project, t in items:
Gavin Mak551285f2023-05-04 04:48:43 +0000810 if t < earliest_time:
811 earliest_time = t
812 earliest_proj = project
813
Josip Sokcevic71122f92023-05-26 02:44:37 +0000814 if not earliest_proj:
Gavin Mak945c0062023-05-30 20:04:07 +0000815 # This function is called when sync is still running but in some
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800816 # cases (by chance), sync_dict can contain no entries. Return some
Gavin Mak945c0062023-05-30 20:04:07 +0000817 # text to indicate that sync is still working.
818 return "..working.."
Josip Sokcevic71122f92023-05-26 02:44:37 +0000819
Gavin Mak551285f2023-05-04 04:48:43 +0000820 elapsed = time.time() - earliest_time
Gavin Mak945c0062023-05-30 20:04:07 +0000821 jobs = jobs_str(len(items))
Gavin Mak04cba4a2023-05-24 21:28:28 +0000822 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000823
Jason Changdaf2ad32023-08-31 17:06:36 -0700824 def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
Gavin Makea2e3302023-03-11 06:46:20 +0000825 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500826
Gavin Makea2e3302023-03-11 06:46:20 +0000827 fetched = set()
828 remote_fetched = set()
Gavin Makedcaa942023-04-27 05:58:57 +0000829 pm = Progress(
830 "Fetching",
831 len(projects),
832 delay=False,
833 quiet=opt.quiet,
834 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000835 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000836 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800837
Gavin Mak551285f2023-05-04 04:48:43 +0000838 sync_event = _threading.Event()
839
840 def _MonitorSyncLoop():
841 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000842 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000843 if sync_event.wait(timeout=1):
844 return
845
846 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
847 sync_progress_thread.daemon = True
Gavin Mak551285f2023-05-04 04:48:43 +0000848
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800849 def _ProcessResults(pool, pm, results_sets):
Gavin Makea2e3302023-03-11 06:46:20 +0000850 ret = True
851 for results in results_sets:
852 for result in results:
853 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800854 project = projects[result.project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +0000855 start = result.start
856 finish = result.finish
857 self._fetch_times.Set(project, finish - start)
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000858 self._local_sync_state.SetFetchTime(project)
Gavin Makea2e3302023-03-11 06:46:20 +0000859 self.event_log.AddSync(
860 project,
861 event_log.TASK_SYNC_NETWORK,
862 start,
863 finish,
864 success,
865 )
Jason Chang32b59562023-07-14 16:45:35 -0700866 if result.errors:
867 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000868 if result.remote_fetched:
869 remote_fetched.add(project)
870 # Check for any errors before running any more tasks.
871 # ...we'll let existing jobs finish, though.
872 if not success:
873 ret = False
874 else:
875 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000876 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000877 if not ret and opt.fail_fast:
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800878 if pool:
879 pool.close()
Gavin Makea2e3302023-03-11 06:46:20 +0000880 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500881 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700882
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800883 with self.ParallelContext():
884 self.get_parallel_context()["projects"] = projects
885 self.get_parallel_context()[
886 "sync_dict"
887 ] = multiprocessing.Manager().dict()
Mike Frysingerebf04a42021-02-23 20:48:04 -0500888
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800889 objdir_project_map = dict()
890 for index, project in enumerate(projects):
891 objdir_project_map.setdefault(project.objdir, []).append(index)
892 projects_list = list(objdir_project_map.values())
893
894 jobs = min(opt.jobs_network, len(projects_list))
895
896 # We pass the ssh proxy settings via the class. This allows
897 # multiprocessing to pickle it up when spawning children. We can't
898 # pass it as an argument to _FetchProjectList below as
899 # multiprocessing is unable to pickle those.
900 self.get_parallel_context()["ssh_proxy"] = ssh_proxy
901
902 sync_progress_thread.start()
Josip Sokcevic454fdaf2024-10-07 17:33:38 +0000903 if not opt.quiet:
Gavin Makea2e3302023-03-11 06:46:20 +0000904 pm.update(inc=0, msg="warming up")
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800905 try:
906 ret = self.ExecuteInParallel(
907 jobs,
Gavin Makea2e3302023-03-11 06:46:20 +0000908 functools.partial(self._FetchProjectList, opt),
909 projects_list,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800910 callback=_ProcessResults,
911 output=pm,
912 # Use chunksize=1 to avoid the chance that some workers are
913 # idle while other workers still have more than one job in
914 # their chunk queue.
915 chunksize=1,
Gavin Makea2e3302023-03-11 06:46:20 +0000916 )
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800917 finally:
918 sync_event.set()
919 sync_progress_thread.join()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000920
Gavin Makea2e3302023-03-11 06:46:20 +0000921 self._fetch_times.Save()
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000922 self._local_sync_state.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000923
Gavin Makea2e3302023-03-11 06:46:20 +0000924 if not self.outer_client.manifest.IsArchive:
925 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400926
Jason Changdaf2ad32023-08-31 17:06:36 -0700927 return _FetchResult(ret, fetched)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000928
Gavin Makea2e3302023-03-11 06:46:20 +0000929 def _FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -0700930 self, opt, args, all_projects, err_event, ssh_proxy, manifest, errors
Gavin Makea2e3302023-03-11 06:46:20 +0000931 ):
932 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400933
Gavin Makea2e3302023-03-11 06:46:20 +0000934 Args:
935 opt: Program options returned from optparse. See _Options().
936 args: Command line args used to filter out projects.
937 all_projects: List of all projects that should be fetched.
938 err_event: Whether an error was hit while processing.
939 ssh_proxy: SSH manager for clients & masters.
940 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700941
Gavin Makea2e3302023-03-11 06:46:20 +0000942 Returns:
943 List of all projects that should be checked out.
944 """
945 rp = manifest.repoProject
LaMont Jones891e8f72022-09-08 20:17:58 +0000946
Gavin Makea2e3302023-03-11 06:46:20 +0000947 to_fetch = []
948 now = time.time()
949 if _ONE_DAY_S <= (now - rp.LastFetch):
950 to_fetch.append(rp)
951 to_fetch.extend(all_projects)
952 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700953
Jason Changdaf2ad32023-08-31 17:06:36 -0700954 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000955 success = result.success
956 fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700957
Gavin Makea2e3302023-03-11 06:46:20 +0000958 if not success:
959 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700960
Gavin Makea2e3302023-03-11 06:46:20 +0000961 _PostRepoFetch(rp, opt.repo_verify)
962 if opt.network_only:
963 # Bail out now; the rest touches the working tree.
964 if err_event.is_set():
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000965 e = SyncError(
Jason Chang32b59562023-07-14 16:45:35 -0700966 "error: Exited sync due to fetch errors.",
967 aggregate_errors=errors,
968 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000969
970 logger.error(e)
971 raise e
Jason Changdaf2ad32023-08-31 17:06:36 -0700972 return _FetchMainResult([])
Dave Borowitz18857212012-10-23 17:02:59 -0700973
Gavin Makea2e3302023-03-11 06:46:20 +0000974 # Iteratively fetch missing and/or nested unregistered submodules.
975 previously_missing_set = set()
976 while True:
977 self._ReloadManifest(None, manifest)
978 all_projects = self.GetProjects(
979 args,
980 missing_ok=True,
981 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000982 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000983 all_manifests=not opt.this_manifest_only,
984 )
985 missing = []
986 for project in all_projects:
987 if project.gitdir not in fetched:
988 missing.append(project)
989 if not missing:
990 break
991 # Stop us from non-stopped fetching actually-missing repos: If set
992 # of missing repos has not been changed from last fetch, we break.
Jason R. Coombs0bcffd82023-10-20 23:29:42 +0545993 missing_set = {p.name for p in missing}
Gavin Makea2e3302023-03-11 06:46:20 +0000994 if previously_missing_set == missing_set:
995 break
996 previously_missing_set = missing_set
Jason Changdaf2ad32023-08-31 17:06:36 -0700997 result = self._Fetch(missing, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000998 success = result.success
999 new_fetched = result.projects
1000 if not success:
1001 err_event.set()
1002 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001003
Jason Changdaf2ad32023-08-31 17:06:36 -07001004 return _FetchMainResult(all_projects)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001005
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001006 @classmethod
Josip Sokcevicedadb252024-02-29 09:48:37 -08001007 def _CheckoutOne(
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001008 cls,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001009 detach_head,
1010 force_sync,
1011 force_checkout,
1012 force_rebase,
1013 verbose,
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001014 project_idx,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001015 ):
Gavin Makea2e3302023-03-11 06:46:20 +00001016 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +08001017
Gavin Makea2e3302023-03-11 06:46:20 +00001018 Args:
1019 detach_head: Whether to leave a detached HEAD.
Josip Sokcevicedadb252024-02-29 09:48:37 -08001020 force_sync: Force checking out of .git directory (e.g. overwrite
1021 existing git directory that was previously linked to a different
1022 object directory).
1023 force_checkout: Force checking out of the repo content.
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001024 force_rebase: Force rebase.
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001025 verbose: Whether to show verbose messages.
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001026 project_idx: Project index for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +08001027
Gavin Makea2e3302023-03-11 06:46:20 +00001028 Returns:
1029 Whether the fetch was successful.
1030 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001031 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +00001032 start = time.time()
1033 syncbuf = SyncBuffer(
1034 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001035 )
Gavin Makea2e3302023-03-11 06:46:20 +00001036 success = False
Jason Chang32b59562023-07-14 16:45:35 -07001037 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +09001038 try:
Jason Chang32b59562023-07-14 16:45:35 -07001039 project.Sync_LocalHalf(
Josip Sokcevicedadb252024-02-29 09:48:37 -08001040 syncbuf,
1041 force_sync=force_sync,
1042 force_checkout=force_checkout,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001043 force_rebase=force_rebase,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001044 errors=errors,
1045 verbose=verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001046 )
Gavin Makea2e3302023-03-11 06:46:20 +00001047 success = syncbuf.Finish()
1048 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001049 logger.error(
1050 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +00001051 )
Jason Chang32b59562023-07-14 16:45:35 -07001052 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001053 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001054 logger.error(
1055 "error: Cannot checkout %s: %s: %s",
1056 project.name,
1057 type(e).__name__,
1058 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001059 )
1060 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -07001061
Gavin Makea2e3302023-03-11 06:46:20 +00001062 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001063 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001064 finish = time.time()
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001065 return _CheckoutOneResult(success, errors, project_idx, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -04001066
Jason Chang32b59562023-07-14 16:45:35 -07001067 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001068 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001069
Gavin Makea2e3302023-03-11 06:46:20 +00001070 Args:
1071 all_projects: List of all projects that should be checked out.
1072 opt: Program options returned from optparse. See _Options().
1073 err_results: A list of strings, paths to git repos where checkout
1074 failed.
1075 """
1076 # Only checkout projects with worktrees.
1077 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001078
Gavin Makea2e3302023-03-11 06:46:20 +00001079 def _ProcessResults(pool, pm, results):
1080 ret = True
1081 for result in results:
1082 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001083 project = self.get_parallel_context()["projects"][
1084 result.project_idx
1085 ]
Gavin Makea2e3302023-03-11 06:46:20 +00001086 start = result.start
1087 finish = result.finish
1088 self.event_log.AddSync(
1089 project, event_log.TASK_SYNC_LOCAL, start, finish, success
1090 )
Jason Chang32b59562023-07-14 16:45:35 -07001091
1092 if result.errors:
1093 checkout_errors.extend(result.errors)
1094
Gavin Makea2e3302023-03-11 06:46:20 +00001095 # Check for any errors before running any more tasks.
1096 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001097 if success:
1098 self._local_sync_state.SetCheckoutTime(project)
1099 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001100 ret = False
1101 err_results.append(
1102 project.RelPath(local=opt.this_manifest_only)
1103 )
1104 if opt.fail_fast:
1105 if pool:
1106 pool.close()
1107 return ret
1108 pm.update(msg=project.name)
1109 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001110
Josip Sokcevic55545722024-02-22 16:38:00 -08001111 for projects in _SafeCheckoutOrder(all_projects):
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001112 with self.ParallelContext():
1113 self.get_parallel_context()["projects"] = projects
1114 proc_res = self.ExecuteInParallel(
1115 opt.jobs_checkout,
1116 functools.partial(
1117 self._CheckoutOne,
1118 opt.detach_head,
1119 opt.force_sync,
1120 opt.force_checkout,
1121 opt.rebase,
1122 opt.verbose,
1123 ),
1124 range(len(projects)),
1125 callback=_ProcessResults,
1126 output=Progress(
1127 "Checking out", len(all_projects), quiet=opt.quiet
1128 ),
1129 # Use chunksize=1 to avoid the chance that some workers are
1130 # idle while other workers still have more than one job in
1131 # their chunk queue.
1132 chunksize=1,
1133 )
Simran Basib9a1b732015-08-20 12:19:28 -07001134
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001135 self._local_sync_state.Save()
1136 return proc_res and not err_results
1137
Gavin Makea2e3302023-03-11 06:46:20 +00001138 @staticmethod
1139 def _GetPreciousObjectsState(project: Project, opt):
1140 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001141
Gavin Makea2e3302023-03-11 06:46:20 +00001142 Args:
1143 project (Project): the project to examine, and possibly correct.
1144 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001145
Gavin Makea2e3302023-03-11 06:46:20 +00001146 Returns:
1147 Expected state of extensions.preciousObjects:
1148 False: Should be disabled. (not present)
1149 True: Should be enabled.
1150 """
1151 if project.use_git_worktrees:
1152 return False
1153 projects = project.manifest.GetProjectsWithName(
1154 project.name, all_manifests=True
1155 )
1156 if len(projects) == 1:
1157 return False
1158 if len(projects) > 1:
1159 # Objects are potentially shared with another project.
1160 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1161 # - When False, shared projects share (via symlink)
1162 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1163 # objects directory. All objects are precious, since there is no
1164 # project with a complete set of refs.
1165 # - When True, shared projects share (via info/alternates)
1166 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1167 # store, which is written only on the first clone of the project,
1168 # and is not written subsequently. (When Sync_NetworkHalf sees
1169 # that it exists, it makes sure that the alternates file points
1170 # there, and uses a project-local .git/objects directory for all
1171 # syncs going forward.
1172 # We do not support switching between the options. The environment
1173 # variable is present for testing and migration only.
1174 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001175
Gavin Makea2e3302023-03-11 06:46:20 +00001176 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001177
Gavin Makea2e3302023-03-11 06:46:20 +00001178 def _SetPreciousObjectsState(self, project: Project, opt):
1179 """Correct the preciousObjects state for the project.
1180
1181 Args:
1182 project: the project to examine, and possibly correct.
1183 opt: options given to sync.
1184 """
1185 expected = self._GetPreciousObjectsState(project, opt)
1186 actual = (
1187 project.config.GetBoolean("extensions.preciousObjects") or False
1188 )
1189 relpath = project.RelPath(local=opt.this_manifest_only)
1190
1191 if expected != actual:
1192 # If this is unexpected, log it and repair.
1193 Trace(
1194 f"{relpath} expected preciousObjects={expected}, got {actual}"
1195 )
1196 if expected:
1197 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001198 print(
1199 "\r%s: Shared project %s found, disabling pruning."
1200 % (relpath, project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001201 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001202
Gavin Makea2e3302023-03-11 06:46:20 +00001203 if git_require((2, 7, 0)):
1204 project.EnableRepositoryExtension("preciousObjects")
1205 else:
1206 # This isn't perfect, but it's the best we can do with old
1207 # git.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001208 logger.warning(
1209 "%s: WARNING: shared projects are unreliable when "
Gavin Makea2e3302023-03-11 06:46:20 +00001210 "using old versions of git; please upgrade to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001211 "git-2.7.0+.",
1212 relpath,
Gavin Makea2e3302023-03-11 06:46:20 +00001213 )
1214 project.config.SetString("gc.pruneExpire", "never")
1215 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001216 project.config.SetString("extensions.preciousObjects", None)
1217 project.config.SetString("gc.pruneExpire", None)
1218
1219 def _GCProjects(self, projects, opt, err_event):
1220 """Perform garbage collection.
1221
1222 If We are skipping garbage collection (opt.auto_gc not set), we still
1223 want to potentially mark objects precious, so that `git gc` does not
1224 discard shared objects.
1225 """
1226 if not opt.auto_gc:
1227 # Just repair preciousObjects state, and return.
1228 for project in projects:
1229 self._SetPreciousObjectsState(project, opt)
1230 return
1231
1232 pm = Progress(
1233 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1234 )
1235 pm.update(inc=0, msg="prescan")
1236
1237 tidy_dirs = {}
1238 for project in projects:
1239 self._SetPreciousObjectsState(project, opt)
1240
1241 project.config.SetString("gc.autoDetach", "false")
1242 # Only call git gc once per objdir, but call pack-refs for the
1243 # remainder.
1244 if project.objdir not in tidy_dirs:
1245 tidy_dirs[project.objdir] = (
1246 True, # Run a full gc.
1247 project.bare_git,
1248 )
1249 elif project.gitdir not in tidy_dirs:
1250 tidy_dirs[project.gitdir] = (
1251 False, # Do not run a full gc; just run pack-refs.
1252 project.bare_git,
1253 )
1254
1255 jobs = opt.jobs
1256
1257 if jobs < 2:
1258 for run_gc, bare_git in tidy_dirs.values():
1259 pm.update(msg=bare_git._project.name)
1260
1261 if run_gc:
1262 bare_git.gc("--auto")
1263 else:
1264 bare_git.pack_refs()
1265 pm.end()
1266 return
1267
1268 cpu_count = os.cpu_count()
1269 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1270
1271 threads = set()
1272 sem = _threading.Semaphore(jobs)
1273
1274 def tidy_up(run_gc, bare_git):
1275 pm.start(bare_git._project.name)
1276 try:
1277 try:
1278 if run_gc:
1279 bare_git.gc("--auto", config=config)
1280 else:
1281 bare_git.pack_refs(config=config)
1282 except GitError:
1283 err_event.set()
1284 except Exception:
1285 err_event.set()
1286 raise
1287 finally:
1288 pm.finish(bare_git._project.name)
1289 sem.release()
1290
1291 for run_gc, bare_git in tidy_dirs.values():
1292 if err_event.is_set() and opt.fail_fast:
1293 break
1294 sem.acquire()
1295 t = _threading.Thread(
1296 target=tidy_up,
1297 args=(
1298 run_gc,
1299 bare_git,
1300 ),
1301 )
1302 t.daemon = True
1303 threads.add(t)
1304 t.start()
1305
1306 for t in threads:
1307 t.join()
1308 pm.end()
1309
1310 def _ReloadManifest(self, manifest_name, manifest):
1311 """Reload the manfiest from the file specified by the |manifest_name|.
1312
1313 It unloads the manifest if |manifest_name| is None.
1314
1315 Args:
1316 manifest_name: Manifest file to be reloaded.
1317 manifest: The manifest to use.
1318 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001319 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001320 # Override calls Unload already.
1321 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001322 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001323 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001324
Gavin Makea2e3302023-03-11 06:46:20 +00001325 def UpdateProjectList(self, opt, manifest):
1326 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001327
Gavin Makea2e3302023-03-11 06:46:20 +00001328 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001329
Gavin Makea2e3302023-03-11 06:46:20 +00001330 Args:
1331 opt: Program options returned from optparse. See _Options().
1332 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001333
Gavin Makea2e3302023-03-11 06:46:20 +00001334 Returns:
1335 0: success
1336 1: failure
1337 """
1338 new_project_paths = []
1339 for project in self.GetProjects(
1340 None, missing_ok=True, manifest=manifest, all_manifests=False
1341 ):
1342 if project.relpath:
1343 new_project_paths.append(project.relpath)
1344 file_name = "project.list"
1345 file_path = os.path.join(manifest.subdir, file_name)
1346 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001347
Gavin Makea2e3302023-03-11 06:46:20 +00001348 if os.path.exists(file_path):
Jason R. Coombs034950b2023-10-20 23:32:02 +05451349 with open(file_path) as fd:
Gavin Makea2e3302023-03-11 06:46:20 +00001350 old_project_paths = fd.read().split("\n")
1351 # In reversed order, so subfolders are deleted before parent folder.
1352 for path in sorted(old_project_paths, reverse=True):
1353 if not path:
1354 continue
1355 if path not in new_project_paths:
1356 # If the path has already been deleted, we don't need to do
1357 # it.
1358 gitdir = os.path.join(manifest.topdir, path, ".git")
1359 if os.path.exists(gitdir):
1360 project = Project(
1361 manifest=manifest,
1362 name=path,
1363 remote=RemoteSpec("origin"),
1364 gitdir=gitdir,
1365 objdir=gitdir,
1366 use_git_worktrees=os.path.isfile(gitdir),
1367 worktree=os.path.join(manifest.topdir, path),
1368 relpath=path,
1369 revisionExpr="HEAD",
1370 revisionId=None,
1371 groups=None,
1372 )
Jason Chang32b59562023-07-14 16:45:35 -07001373 project.DeleteWorktree(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001374 verbose=opt.verbose, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001375 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001376
Gavin Makea2e3302023-03-11 06:46:20 +00001377 new_project_paths.sort()
1378 with open(file_path, "w") as fd:
1379 fd.write("\n".join(new_project_paths))
1380 fd.write("\n")
1381 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001382
Gavin Makea2e3302023-03-11 06:46:20 +00001383 def UpdateCopyLinkfileList(self, manifest):
1384 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001385
Gavin Makea2e3302023-03-11 06:46:20 +00001386 Returns:
1387 Whether update was successful.
1388 """
1389 new_paths = {}
1390 new_linkfile_paths = []
1391 new_copyfile_paths = []
1392 for project in self.GetProjects(
1393 None, missing_ok=True, manifest=manifest, all_manifests=False
1394 ):
1395 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1396 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001397
Gavin Makea2e3302023-03-11 06:46:20 +00001398 new_paths = {
1399 "linkfile": new_linkfile_paths,
1400 "copyfile": new_copyfile_paths,
1401 }
jiajia tanga590e642021-04-25 20:02:02 +08001402
Gavin Makea2e3302023-03-11 06:46:20 +00001403 copylinkfile_name = "copy-link-files.json"
1404 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1405 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001406
Gavin Makea2e3302023-03-11 06:46:20 +00001407 if os.path.exists(copylinkfile_path):
1408 with open(copylinkfile_path, "rb") as fp:
1409 try:
1410 old_copylinkfile_paths = json.load(fp)
1411 except Exception:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001412 logger.error(
1413 "error: %s is not a json formatted file.",
1414 copylinkfile_path,
Gavin Makea2e3302023-03-11 06:46:20 +00001415 )
1416 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001417 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001418
Gavin Makea2e3302023-03-11 06:46:20 +00001419 need_remove_files = []
1420 need_remove_files.extend(
1421 set(old_copylinkfile_paths.get("linkfile", []))
1422 - set(new_linkfile_paths)
1423 )
1424 need_remove_files.extend(
1425 set(old_copylinkfile_paths.get("copyfile", []))
1426 - set(new_copyfile_paths)
1427 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001428
Gavin Makea2e3302023-03-11 06:46:20 +00001429 for need_remove_file in need_remove_files:
1430 # Try to remove the updated copyfile or linkfile.
1431 # So, if the file is not exist, nothing need to do.
1432 platform_utils.remove(need_remove_file, missing_ok=True)
Raman Tenneti7954de12021-07-28 14:36:49 -07001433
Gavin Makea2e3302023-03-11 06:46:20 +00001434 # Create copy-link-files.json, save dest path of "copyfile" and
1435 # "linkfile".
1436 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1437 json.dump(new_paths, fp)
1438 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001439
Gavin Makea2e3302023-03-11 06:46:20 +00001440 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1441 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001442 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001443 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001444 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001445 )
Gavin Makea2e3302023-03-11 06:46:20 +00001446
1447 manifest_server = manifest.manifest_server
1448 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001449 print("Using manifest server %s" % manifest_server)
Gavin Makea2e3302023-03-11 06:46:20 +00001450
1451 if "@" not in manifest_server:
1452 username = None
1453 password = None
1454 if opt.manifest_server_username and opt.manifest_server_password:
1455 username = opt.manifest_server_username
1456 password = opt.manifest_server_password
1457 else:
1458 try:
1459 info = netrc.netrc()
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451460 except OSError:
Gavin Makea2e3302023-03-11 06:46:20 +00001461 # .netrc file does not exist or could not be opened.
1462 pass
1463 else:
1464 try:
1465 parse_result = urllib.parse.urlparse(manifest_server)
1466 if parse_result.hostname:
1467 auth = info.authenticators(parse_result.hostname)
1468 if auth:
1469 username, _account, password = auth
1470 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001471 logger.error(
1472 "No credentials found for %s in .netrc",
1473 parse_result.hostname,
Gavin Makea2e3302023-03-11 06:46:20 +00001474 )
1475 except netrc.NetrcParseError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001476 logger.error("Error parsing .netrc file: %s", e)
Gavin Makea2e3302023-03-11 06:46:20 +00001477
1478 if username and password:
1479 manifest_server = manifest_server.replace(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001480 "://", f"://{username}:{password}@", 1
Gavin Makea2e3302023-03-11 06:46:20 +00001481 )
1482
1483 transport = PersistentTransport(manifest_server)
1484 if manifest_server.startswith("persistent-"):
1485 manifest_server = manifest_server[len("persistent-") :]
1486
1487 try:
1488 server = xmlrpc.client.Server(manifest_server, transport=transport)
1489 if opt.smart_sync:
1490 branch = self._GetBranch(manifest.manifestProject)
1491
1492 if "SYNC_TARGET" in os.environ:
1493 target = os.environ["SYNC_TARGET"]
1494 [success, manifest_str] = server.GetApprovedManifest(
1495 branch, target
1496 )
1497 elif (
1498 "TARGET_PRODUCT" in os.environ
1499 and "TARGET_BUILD_VARIANT" in os.environ
Navil1e19f7d2024-09-11 16:49:49 +00001500 and "TARGET_RELEASE" in os.environ
1501 ):
1502 target = "%s-%s-%s" % (
1503 os.environ["TARGET_PRODUCT"],
1504 os.environ["TARGET_RELEASE"],
1505 os.environ["TARGET_BUILD_VARIANT"],
1506 )
1507 [success, manifest_str] = server.GetApprovedManifest(
1508 branch, target
1509 )
1510 elif (
1511 "TARGET_PRODUCT" in os.environ
1512 and "TARGET_BUILD_VARIANT" in os.environ
Gavin Makea2e3302023-03-11 06:46:20 +00001513 ):
1514 target = "%s-%s" % (
1515 os.environ["TARGET_PRODUCT"],
1516 os.environ["TARGET_BUILD_VARIANT"],
1517 )
1518 [success, manifest_str] = server.GetApprovedManifest(
1519 branch, target
1520 )
1521 else:
1522 [success, manifest_str] = server.GetApprovedManifest(branch)
1523 else:
1524 assert opt.smart_tag
1525 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1526
1527 if success:
1528 manifest_name = os.path.basename(smart_sync_manifest_path)
1529 try:
1530 with open(smart_sync_manifest_path, "w") as f:
1531 f.write(manifest_str)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451532 except OSError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001533 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001534 "error: cannot write manifest to %s:\n%s"
1535 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001536 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001537 )
Gavin Makea2e3302023-03-11 06:46:20 +00001538 self._ReloadManifest(manifest_name, manifest)
1539 else:
Jason Chang32b59562023-07-14 16:45:35 -07001540 raise SmartSyncError(
1541 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001542 )
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451543 except (OSError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001544 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001545 "error: cannot connect to manifest server %s:\n%s"
1546 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001547 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001548 )
Gavin Makea2e3302023-03-11 06:46:20 +00001549 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001550 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001551 "error: cannot connect to manifest server %s:\n%d %s"
1552 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001553 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001554 )
Gavin Makea2e3302023-03-11 06:46:20 +00001555
1556 return manifest_name
1557
Jason Changdaf2ad32023-08-31 17:06:36 -07001558 def _UpdateAllManifestProjects(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001559 """Fetch & update the local manifest project.
1560
1561 After syncing the manifest project, if the manifest has any sub
1562 manifests, those are recursively processed.
1563
1564 Args:
1565 opt: Program options returned from optparse. See _Options().
1566 mp: the manifestProject to query.
1567 manifest_name: Manifest file to be reloaded.
1568 """
1569 if not mp.standalone_manifest_url:
Jason Changdaf2ad32023-08-31 17:06:36 -07001570 self._UpdateManifestProject(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001571
1572 if mp.manifest.submanifests:
1573 for submanifest in mp.manifest.submanifests.values():
1574 child = submanifest.repo_client.manifest
1575 child.manifestProject.SyncWithPossibleInit(
1576 submanifest,
1577 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1578 verbose=opt.verbose,
1579 tags=opt.tags,
1580 git_event_log=self.git_event_log,
1581 )
1582 self._UpdateAllManifestProjects(
Jason Changdaf2ad32023-08-31 17:06:36 -07001583 opt, child.manifestProject, None, errors
Gavin Makea2e3302023-03-11 06:46:20 +00001584 )
1585
Jason Changdaf2ad32023-08-31 17:06:36 -07001586 def _UpdateManifestProject(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001587 """Fetch & update the local manifest project.
1588
1589 Args:
1590 opt: Program options returned from optparse. See _Options().
1591 mp: the manifestProject to query.
1592 manifest_name: Manifest file to be reloaded.
1593 """
1594 if not opt.local_only:
1595 start = time.time()
Jason Changdaf2ad32023-08-31 17:06:36 -07001596 buf = TeeStringIO(sys.stdout)
1597 try:
1598 result = mp.Sync_NetworkHalf(
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -08001599 quiet=not opt.verbose,
Jason Changdaf2ad32023-08-31 17:06:36 -07001600 output_redir=buf,
1601 verbose=opt.verbose,
1602 current_branch_only=self._GetCurrentBranchOnly(
1603 opt, mp.manifest
1604 ),
1605 force_sync=opt.force_sync,
1606 tags=opt.tags,
1607 optimized_fetch=opt.optimized_fetch,
1608 retry_fetches=opt.retry_fetches,
1609 submodules=mp.manifest.HasSubmodules,
1610 clone_filter=mp.manifest.CloneFilter,
1611 partial_clone_exclude=mp.manifest.PartialCloneExclude,
1612 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
1613 )
1614 if result.error:
1615 errors.append(result.error)
1616 except KeyboardInterrupt:
1617 errors.append(
1618 ManifestInterruptError(buf.getvalue(), project=mp.name)
1619 )
1620 raise
1621
Gavin Makea2e3302023-03-11 06:46:20 +00001622 finish = time.time()
1623 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001624 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001625 )
1626
1627 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001628 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001629 syncbuf = SyncBuffer(mp.config)
1630 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001631 mp.Sync_LocalHalf(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001632 syncbuf,
1633 submodules=mp.manifest.HasSubmodules,
1634 errors=errors,
1635 verbose=opt.verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001636 )
Gavin Makea2e3302023-03-11 06:46:20 +00001637 clean = syncbuf.Finish()
1638 self.event_log.AddSync(
1639 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1640 )
1641 if not clean:
Yiwei Zhangd379e772023-12-20 20:39:59 +00001642 raise UpdateManifestError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001643 self._ReloadManifest(manifest_name, mp.manifest)
1644
1645 def ValidateOptions(self, opt, args):
1646 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001647 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001648 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001649 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001650 )
1651 if opt.network_only and opt.detach_head:
1652 self.OptionParser.error("cannot combine -n and -d")
1653 if opt.network_only and opt.local_only:
1654 self.OptionParser.error("cannot combine -n and -l")
1655 if opt.manifest_name and opt.smart_sync:
1656 self.OptionParser.error("cannot combine -m and -s")
1657 if opt.manifest_name and opt.smart_tag:
1658 self.OptionParser.error("cannot combine -m and -t")
1659 if opt.manifest_server_username or opt.manifest_server_password:
1660 if not (opt.smart_sync or opt.smart_tag):
1661 self.OptionParser.error(
1662 "-u and -p may only be combined with -s or -t"
1663 )
1664 if None in [
1665 opt.manifest_server_username,
1666 opt.manifest_server_password,
1667 ]:
1668 self.OptionParser.error("both -u and -p must be given")
1669
1670 if opt.prune is None:
1671 opt.prune = True
1672
Gavin Makea2e3302023-03-11 06:46:20 +00001673 def _ValidateOptionsWithManifest(self, opt, mp):
1674 """Like ValidateOptions, but after we've updated the manifest.
1675
1676 Needed to handle sync-xxx option defaults in the manifest.
1677
1678 Args:
1679 opt: The options to process.
1680 mp: The manifest project to pull defaults from.
1681 """
1682 if not opt.jobs:
1683 # If the user hasn't made a choice, use the manifest value.
1684 opt.jobs = mp.manifest.default.sync_j
1685 if opt.jobs:
1686 # If --jobs has a non-default value, propagate it as the default for
1687 # --jobs-xxx flags too.
1688 if not opt.jobs_network:
1689 opt.jobs_network = opt.jobs
1690 if not opt.jobs_checkout:
1691 opt.jobs_checkout = opt.jobs
1692 else:
1693 # Neither user nor manifest have made a choice, so setup defaults.
1694 if not opt.jobs_network:
1695 opt.jobs_network = 1
1696 if not opt.jobs_checkout:
1697 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1698 opt.jobs = os.cpu_count()
1699
1700 # Try to stay under user rlimit settings.
1701 #
1702 # Since each worker requires at 3 file descriptors to run `git fetch`,
1703 # use that to scale down the number of jobs. Unfortunately there isn't
1704 # an easy way to determine this reliably as systems change, but it was
1705 # last measured by hand in 2011.
1706 soft_limit, _ = _rlimit_nofile()
1707 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1708 opt.jobs = min(opt.jobs, jobs_soft_limit)
1709 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1710 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1711
1712 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001713 errors = []
1714 try:
1715 self._ExecuteHelper(opt, args, errors)
Jason Chang26fa3182024-02-05 15:15:20 -08001716 except (RepoExitError, RepoChangedException):
Jason Chang32b59562023-07-14 16:45:35 -07001717 raise
1718 except (KeyboardInterrupt, Exception) as e:
1719 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1720
1721 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001722 manifest = self.outer_manifest
1723 if not opt.outer_manifest:
1724 manifest = self.manifest
1725
1726 if opt.manifest_name:
1727 manifest.Override(opt.manifest_name)
1728
1729 manifest_name = opt.manifest_name
1730 smart_sync_manifest_path = os.path.join(
1731 manifest.manifestProject.worktree, "smart_sync_override.xml"
1732 )
1733
1734 if opt.clone_bundle is None:
1735 opt.clone_bundle = manifest.CloneBundle
1736
1737 if opt.smart_sync or opt.smart_tag:
1738 manifest_name = self._SmartSyncSetup(
1739 opt, smart_sync_manifest_path, manifest
1740 )
1741 else:
1742 if os.path.isfile(smart_sync_manifest_path):
1743 try:
1744 platform_utils.remove(smart_sync_manifest_path)
1745 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001746 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001747 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001748 "manifest: %s",
1749 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001750 )
1751
1752 err_event = multiprocessing.Event()
1753
1754 rp = manifest.repoProject
1755 rp.PreSync()
1756 cb = rp.CurrentBranch
1757 if cb:
1758 base = rp.GetBranch(cb).merge
1759 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001760 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001761 "warning: repo is not tracking a remote branch, so it will "
1762 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001763 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001764 )
1765
1766 for m in self.ManifestList(opt):
1767 if not m.manifestProject.standalone_manifest_url:
1768 m.manifestProject.PreSync()
1769
1770 if opt.repo_upgraded:
1771 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1772
1773 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001774
1775 if _REPO_ALLOW_SHALLOW is not None:
1776 if _REPO_ALLOW_SHALLOW == "1":
1777 mp.ConfigureCloneFilterForDepth(None)
1778 elif (
1779 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1780 ):
1781 mp.ConfigureCloneFilterForDepth("blob:none")
1782
Gavin Makea2e3302023-03-11 06:46:20 +00001783 if opt.mp_update:
Jason Changdaf2ad32023-08-31 17:06:36 -07001784 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001785 else:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001786 print("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001787
1788 # Now that the manifests are up-to-date, setup options whose defaults
1789 # might be in the manifest.
1790 self._ValidateOptionsWithManifest(opt, mp)
1791
1792 superproject_logging_data = {}
1793 self._UpdateProjectsRevisionId(
1794 opt, args, superproject_logging_data, manifest
1795 )
1796
Gavin Makea2e3302023-03-11 06:46:20 +00001797 all_projects = self.GetProjects(
1798 args,
1799 missing_ok=True,
1800 submodules_ok=opt.fetch_submodules,
1801 manifest=manifest,
1802 all_manifests=not opt.this_manifest_only,
1803 )
1804
1805 err_network_sync = False
1806 err_update_projects = False
1807 err_update_linkfiles = False
1808
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001809 # Log the repo projects by existing and new.
1810 existing = [x for x in all_projects if x.Exists]
1811 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1812 mp.config.SetString(
1813 "repo.newprojectcount", str(len(all_projects) - len(existing))
1814 )
1815
Gavin Makea2e3302023-03-11 06:46:20 +00001816 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001817 self._local_sync_state = LocalSyncState(manifest)
Gavin Makea2e3302023-03-11 06:46:20 +00001818 if not opt.local_only:
1819 with multiprocessing.Manager() as manager:
1820 with ssh.ProxyManager(manager) as ssh_proxy:
1821 # Initialize the socket dir once in the parent.
1822 ssh_proxy.sock()
1823 result = self._FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -07001824 opt,
1825 args,
1826 all_projects,
1827 err_event,
1828 ssh_proxy,
1829 manifest,
1830 errors,
Gavin Makea2e3302023-03-11 06:46:20 +00001831 )
1832 all_projects = result.all_projects
1833
1834 if opt.network_only:
1835 return
1836
1837 # If we saw an error, exit with code 1 so that other scripts can
1838 # check.
1839 if err_event.is_set():
1840 err_network_sync = True
1841 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001842 logger.error(
1843 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001844 "Local checkouts *not* updated. Resolve network issues "
1845 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001846 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001847 )
Jason Chang32b59562023-07-14 16:45:35 -07001848 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001849
1850 for m in self.ManifestList(opt):
1851 if m.IsMirror or m.IsArchive:
1852 # Bail out now, we have no working tree.
1853 continue
1854
Jason Chang32b59562023-07-14 16:45:35 -07001855 try:
1856 self.UpdateProjectList(opt, m)
1857 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001858 err_event.set()
1859 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001860 errors.append(e)
1861 if isinstance(e, DeleteWorktreeError):
1862 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001863 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001864 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001865 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001866
Jason Chang32b59562023-07-14 16:45:35 -07001867 try:
1868 self.UpdateCopyLinkfileList(m)
1869 except Exception as e:
1870 err_update_linkfiles = True
1871 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001872 err_event.set()
1873 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001874 logger.error(
1875 "error: Local update copyfile or linkfile failed."
Gavin Makea2e3302023-03-11 06:46:20 +00001876 )
Jason Chang32b59562023-07-14 16:45:35 -07001877 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001878
1879 err_results = []
1880 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001881 err_checkout = not self._Checkout(
1882 all_projects, opt, err_results, errors
1883 )
Gavin Makea2e3302023-03-11 06:46:20 +00001884 if err_checkout:
1885 err_event.set()
1886
1887 printed_notices = set()
1888 # If there's a notice that's supposed to print at the end of the sync,
1889 # print it now... But avoid printing duplicate messages, and preserve
1890 # order.
1891 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1892 if m.notice and m.notice not in printed_notices:
1893 print(m.notice)
1894 printed_notices.add(m.notice)
1895
1896 # If we saw an error, exit with code 1 so that other scripts can check.
1897 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001898
1899 def print_and_log(err_msg):
1900 self.git_event_log.ErrorEvent(err_msg)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001901 logger.error("%s", err_msg)
Josip Sokcevic131fc962023-05-12 17:00:46 -07001902
1903 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001904 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001905 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001906 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001907 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001908 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001909 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001910 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001911 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001912 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001913 # Don't log repositories, as it may contain sensitive info.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001914 logger.error("Failing repos:\n%s", "\n".join(err_results))
Josip Sokcevic131fc962023-05-12 17:00:46 -07001915 # Not useful to log.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001916 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001917 'Try re-running with "-j1 --fail-fast" to exit at the first '
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001918 "error."
Gavin Makea2e3302023-03-11 06:46:20 +00001919 )
Jason Chang32b59562023-07-14 16:45:35 -07001920 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001921
1922 # Log the previous sync analysis state from the config.
1923 self.git_event_log.LogDataConfigEvents(
1924 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1925 )
1926
1927 # Update and log with the new sync analysis state.
1928 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1929 self.git_event_log.LogDataConfigEvents(
1930 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1931 )
1932
Gavin Makf0aeb222023-08-08 04:43:36 +00001933 self._local_sync_state.PruneRemovedProjects()
1934 if self._local_sync_state.IsPartiallySynced():
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001935 logger.warning(
Gavin Makf0aeb222023-08-08 04:43:36 +00001936 "warning: Partial syncs are not supported. For the best "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001937 "experience, sync the entire tree."
Gavin Makf0aeb222023-08-08 04:43:36 +00001938 )
1939
Gavin Makea2e3302023-03-11 06:46:20 +00001940 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001941 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001942
David Pursehouse819827a2020-02-12 15:20:19 +09001943
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001944def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001945 # Link the docs for the internal .repo/ layout for people.
1946 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1947 if not platform_utils.islink(link):
1948 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1949 try:
1950 platform_utils.symlink(target, link)
1951 except Exception:
1952 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001953
Gavin Makea2e3302023-03-11 06:46:20 +00001954 wrapper = Wrapper()
1955 if wrapper.NeedSetupGnuPG():
1956 wrapper.SetupGnuPG(quiet)
1957 for project in manifest.projects:
1958 if project.Exists:
1959 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001960
David Pursehouse819827a2020-02-12 15:20:19 +09001961
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001962def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001963 if rp.HasChanges:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +00001964 logger.warning("info: A new version of repo is available")
Gavin Makea2e3302023-03-11 06:46:20 +00001965 wrapper = Wrapper()
1966 try:
1967 rev = rp.bare_git.describe(rp.GetRevisionId())
1968 except GitError:
1969 rev = None
1970 _, new_rev = wrapper.check_repo_rev(
1971 rp.gitdir, rev, repo_verify=repo_verify
1972 )
1973 # See if we're held back due to missing signed tag.
1974 current_revid = rp.bare_git.rev_parse("HEAD")
1975 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1976 if current_revid != new_revid:
1977 # We want to switch to the new rev, but also not trash any
1978 # uncommitted changes. This helps with local testing/hacking.
1979 # If a local change has been made, we will throw that away.
1980 # We also have to make sure this will switch to an older commit if
1981 # that's the latest tag in order to support release rollback.
1982 try:
1983 rp.work_git.reset("--keep", new_rev)
1984 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001985 raise RepoUnhandledExceptionError(e)
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001986 print("info: Restarting repo with latest version")
Gavin Makea2e3302023-03-11 06:46:20 +00001987 raise RepoChangedException(["--repo-upgraded"])
1988 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001989 logger.warning("warning: Skipped upgrade to unverified version")
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001990 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001991 if verbose:
Aravind Vasudevance0ed792023-10-06 18:36:22 +00001992 print("repo version %s is current" % rp.work_git.describe(HEAD))
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001993
David Pursehouse819827a2020-02-12 15:20:19 +09001994
Mike Frysingerd4aee652023-10-19 05:13:32 -04001995class _FetchTimes:
Gavin Makea2e3302023-03-11 06:46:20 +00001996 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07001997
Gavin Makea2e3302023-03-11 06:46:20 +00001998 def __init__(self, manifest):
1999 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00002000 self._saved = None
2001 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002002
Gavin Makea2e3302023-03-11 06:46:20 +00002003 def Get(self, project):
2004 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00002005 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07002006
Gavin Makea2e3302023-03-11 06:46:20 +00002007 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00002008 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00002009
2010 # For shared projects, save the longest time.
2011 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07002012
Gavin Makea2e3302023-03-11 06:46:20 +00002013 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002014 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002015 try:
2016 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002017 self._saved = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452018 except (OSError, ValueError):
Gavin Makea2e3302023-03-11 06:46:20 +00002019 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00002020 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002021
Gavin Makea2e3302023-03-11 06:46:20 +00002022 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002023 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002024 return
Dave Borowitzd9478582012-10-23 16:35:39 -07002025
Gavin Mak041f9772023-05-10 20:41:12 +00002026 for name, t in self._seen.items():
2027 # Keep a moving average across the previous/current sync runs.
2028 old = self._saved.get(name, t)
2029 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07002030
Gavin Makea2e3302023-03-11 06:46:20 +00002031 try:
2032 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002033 json.dump(self._seen, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452034 except (OSError, TypeError):
Gavin Makea2e3302023-03-11 06:46:20 +00002035 platform_utils.remove(self._path, missing_ok=True)
2036
Dan Willemsen0745bb22015-08-17 13:41:45 -07002037
Mike Frysingerd4aee652023-10-19 05:13:32 -04002038class LocalSyncState:
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002039 _LAST_FETCH = "last_fetch"
2040 _LAST_CHECKOUT = "last_checkout"
2041
2042 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00002043 self._manifest = manifest
2044 self._path = os.path.join(
2045 self._manifest.repodir, ".repo_localsyncstate.json"
2046 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002047 self._time = time.time()
2048 self._state = None
2049 self._Load()
2050
2051 def SetFetchTime(self, project):
2052 self._Set(project, self._LAST_FETCH)
2053
2054 def SetCheckoutTime(self, project):
2055 self._Set(project, self._LAST_CHECKOUT)
2056
2057 def GetFetchTime(self, project):
2058 return self._Get(project, self._LAST_FETCH)
2059
2060 def GetCheckoutTime(self, project):
2061 return self._Get(project, self._LAST_CHECKOUT)
2062
2063 def _Get(self, project, key):
2064 self._Load()
2065 p = project.relpath
2066 if p not in self._state:
2067 return
2068 return self._state[p].get(key)
2069
2070 def _Set(self, project, key):
2071 p = project.relpath
2072 if p not in self._state:
2073 self._state[p] = {}
2074 self._state[p][key] = self._time
2075
2076 def _Load(self):
2077 if self._state is None:
2078 try:
2079 with open(self._path) as f:
2080 self._state = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452081 except (OSError, ValueError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002082 platform_utils.remove(self._path, missing_ok=True)
2083 self._state = {}
2084
2085 def Save(self):
2086 if not self._state:
2087 return
2088 try:
2089 with open(self._path, "w") as f:
2090 json.dump(self._state, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452091 except (OSError, TypeError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002092 platform_utils.remove(self._path, missing_ok=True)
2093
Gavin Makf0aeb222023-08-08 04:43:36 +00002094 def PruneRemovedProjects(self):
2095 """Remove entries don't exist on disk and save."""
2096 if not self._state:
2097 return
2098 delete = set()
2099 for path in self._state:
2100 gitdir = os.path.join(self._manifest.topdir, path, ".git")
Matt Schulte0dd0a832023-11-30 11:00:16 -08002101 if not os.path.exists(gitdir) or os.path.islink(gitdir):
Gavin Makf0aeb222023-08-08 04:43:36 +00002102 delete.add(path)
2103 if not delete:
2104 return
2105 for path in delete:
2106 del self._state[path]
2107 self.Save()
2108
2109 def IsPartiallySynced(self):
2110 """Return whether a partial sync state is detected."""
2111 self._Load()
2112 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002113 for path, data in self._state.items():
2114 if path == self._manifest.repoProject.relpath:
2115 # The repo project isn't included in most syncs so we should
2116 # ignore it here.
2117 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002118 checkout_t = data.get(self._LAST_CHECKOUT)
2119 if not checkout_t:
2120 return True
2121 prev_checkout_t = prev_checkout_t or checkout_t
2122 if prev_checkout_t != checkout_t:
2123 return True
2124 return False
2125
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002126
Dan Willemsen0745bb22015-08-17 13:41:45 -07002127# This is a replacement for xmlrpc.client.Transport using urllib2
2128# and supporting persistent-http[s]. It cannot change hosts from
2129# request to request like the normal transport, the real url
2130# is passed during initialization.
2131class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002132 def __init__(self, orig_host):
Daniel Kutikb99272c2023-10-23 21:20:07 +02002133 super().__init__()
Gavin Makea2e3302023-03-11 06:46:20 +00002134 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002135
Gavin Makea2e3302023-03-11 06:46:20 +00002136 def request(self, host, handler, request_body, verbose=False):
2137 with GetUrlCookieFile(self.orig_host, not verbose) as (
2138 cookiefile,
2139 proxy,
2140 ):
2141 # Python doesn't understand cookies with the #HttpOnly_ prefix
2142 # Since we're only using them for HTTP, copy the file temporarily,
2143 # stripping those prefixes away.
2144 if cookiefile:
2145 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2146 tmpcookiefile.write("# HTTP Cookie File")
2147 try:
2148 with open(cookiefile) as f:
2149 for line in f:
2150 if line.startswith("#HttpOnly_"):
2151 line = line[len("#HttpOnly_") :]
2152 tmpcookiefile.write(line)
2153 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002154
Gavin Makea2e3302023-03-11 06:46:20 +00002155 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2156 try:
2157 cookiejar.load()
2158 except cookielib.LoadError:
2159 cookiejar = cookielib.CookieJar()
2160 finally:
2161 tmpcookiefile.close()
2162 else:
2163 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002164
Gavin Makea2e3302023-03-11 06:46:20 +00002165 proxyhandler = urllib.request.ProxyHandler
2166 if proxy:
2167 proxyhandler = urllib.request.ProxyHandler(
2168 {"http": proxy, "https": proxy}
2169 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002170
Gavin Makea2e3302023-03-11 06:46:20 +00002171 opener = urllib.request.build_opener(
2172 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2173 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002174
Gavin Makea2e3302023-03-11 06:46:20 +00002175 url = urllib.parse.urljoin(self.orig_host, handler)
2176 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002177
Gavin Makea2e3302023-03-11 06:46:20 +00002178 scheme = parse_results.scheme
2179 if scheme == "persistent-http":
2180 scheme = "http"
2181 if scheme == "persistent-https":
2182 # If we're proxying through persistent-https, use http. The
2183 # proxy itself will do the https.
2184 if proxy:
2185 scheme = "http"
2186 else:
2187 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002188
Gavin Makea2e3302023-03-11 06:46:20 +00002189 # Parse out any authentication information using the base class.
2190 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002191
Gavin Makea2e3302023-03-11 06:46:20 +00002192 url = urllib.parse.urlunparse(
2193 (
2194 scheme,
2195 host,
2196 parse_results.path,
2197 parse_results.params,
2198 parse_results.query,
2199 parse_results.fragment,
2200 )
2201 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002202
Gavin Makea2e3302023-03-11 06:46:20 +00002203 request = urllib.request.Request(url, request_body)
2204 if extra_headers is not None:
2205 for name, header in extra_headers:
2206 request.add_header(name, header)
2207 request.add_header("Content-Type", "text/xml")
2208 try:
2209 response = opener.open(request)
2210 except urllib.error.HTTPError as e:
2211 if e.code == 501:
2212 # We may have been redirected through a login process
2213 # but our POST turned into a GET. Retry.
2214 response = opener.open(request)
2215 else:
2216 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002217
Gavin Makea2e3302023-03-11 06:46:20 +00002218 p, u = xmlrpc.client.getparser()
2219 # Response should be fairly small, so read it all at once.
2220 # This way we can show it to the user in case of error (e.g. HTML).
2221 data = response.read()
2222 try:
2223 p.feed(data)
2224 except xml.parsers.expat.ExpatError as e:
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452225 raise OSError(
Gavin Makea2e3302023-03-11 06:46:20 +00002226 f"Parsing the manifest failed: {e}\n"
2227 f"Please report this to your manifest server admin.\n"
2228 f'Here is the full response:\n{data.decode("utf-8")}'
2229 )
2230 p.close()
2231 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002232
Gavin Makea2e3302023-03-11 06:46:20 +00002233 def close(self):
2234 pass