blob: decf559b44f7adf1109eca26adaa68f1646c18a9 [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
Fredrik de Grootebdf0402024-10-22 14:14:59 +0200961 # Call self update, unless requested not to
962 if os.environ.get("REPO_SKIP_SELF_UPDATE", "0") == "0":
963 _PostRepoFetch(rp, opt.repo_verify)
Gavin Makea2e3302023-03-11 06:46:20 +0000964 if opt.network_only:
965 # Bail out now; the rest touches the working tree.
966 if err_event.is_set():
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000967 e = SyncError(
Jason Chang32b59562023-07-14 16:45:35 -0700968 "error: Exited sync due to fetch errors.",
969 aggregate_errors=errors,
970 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000971
972 logger.error(e)
973 raise e
Jason Changdaf2ad32023-08-31 17:06:36 -0700974 return _FetchMainResult([])
Dave Borowitz18857212012-10-23 17:02:59 -0700975
Gavin Makea2e3302023-03-11 06:46:20 +0000976 # Iteratively fetch missing and/or nested unregistered submodules.
977 previously_missing_set = set()
978 while True:
979 self._ReloadManifest(None, manifest)
980 all_projects = self.GetProjects(
981 args,
982 missing_ok=True,
983 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000984 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000985 all_manifests=not opt.this_manifest_only,
986 )
987 missing = []
988 for project in all_projects:
989 if project.gitdir not in fetched:
990 missing.append(project)
991 if not missing:
992 break
993 # Stop us from non-stopped fetching actually-missing repos: If set
994 # of missing repos has not been changed from last fetch, we break.
Jason R. Coombs0bcffd82023-10-20 23:29:42 +0545995 missing_set = {p.name for p in missing}
Gavin Makea2e3302023-03-11 06:46:20 +0000996 if previously_missing_set == missing_set:
997 break
998 previously_missing_set = missing_set
Jason Changdaf2ad32023-08-31 17:06:36 -0700999 result = self._Fetch(missing, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001000 success = result.success
1001 new_fetched = result.projects
1002 if not success:
1003 err_event.set()
1004 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001005
Jason Changdaf2ad32023-08-31 17:06:36 -07001006 return _FetchMainResult(all_projects)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001007
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001008 @classmethod
Josip Sokcevicedadb252024-02-29 09:48:37 -08001009 def _CheckoutOne(
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001010 cls,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001011 detach_head,
1012 force_sync,
1013 force_checkout,
1014 force_rebase,
1015 verbose,
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001016 project_idx,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001017 ):
Gavin Makea2e3302023-03-11 06:46:20 +00001018 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +08001019
Gavin Makea2e3302023-03-11 06:46:20 +00001020 Args:
1021 detach_head: Whether to leave a detached HEAD.
Josip Sokcevicedadb252024-02-29 09:48:37 -08001022 force_sync: Force checking out of .git directory (e.g. overwrite
1023 existing git directory that was previously linked to a different
1024 object directory).
1025 force_checkout: Force checking out of the repo content.
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001026 force_rebase: Force rebase.
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001027 verbose: Whether to show verbose messages.
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001028 project_idx: Project index for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +08001029
Gavin Makea2e3302023-03-11 06:46:20 +00001030 Returns:
1031 Whether the fetch was successful.
1032 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001033 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +00001034 start = time.time()
1035 syncbuf = SyncBuffer(
1036 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001037 )
Gavin Makea2e3302023-03-11 06:46:20 +00001038 success = False
Jason Chang32b59562023-07-14 16:45:35 -07001039 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +09001040 try:
Jason Chang32b59562023-07-14 16:45:35 -07001041 project.Sync_LocalHalf(
Josip Sokcevicedadb252024-02-29 09:48:37 -08001042 syncbuf,
1043 force_sync=force_sync,
1044 force_checkout=force_checkout,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001045 force_rebase=force_rebase,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001046 errors=errors,
1047 verbose=verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001048 )
Gavin Makea2e3302023-03-11 06:46:20 +00001049 success = syncbuf.Finish()
1050 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001051 logger.error(
1052 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +00001053 )
Jason Chang32b59562023-07-14 16:45:35 -07001054 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001055 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001056 logger.error(
1057 "error: Cannot checkout %s: %s: %s",
1058 project.name,
1059 type(e).__name__,
1060 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001061 )
1062 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -07001063
Gavin Makea2e3302023-03-11 06:46:20 +00001064 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001065 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001066 finish = time.time()
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001067 return _CheckoutOneResult(success, errors, project_idx, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -04001068
Jason Chang32b59562023-07-14 16:45:35 -07001069 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001070 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001071
Gavin Makea2e3302023-03-11 06:46:20 +00001072 Args:
1073 all_projects: List of all projects that should be checked out.
1074 opt: Program options returned from optparse. See _Options().
1075 err_results: A list of strings, paths to git repos where checkout
1076 failed.
1077 """
1078 # Only checkout projects with worktrees.
1079 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001080
Gavin Makea2e3302023-03-11 06:46:20 +00001081 def _ProcessResults(pool, pm, results):
1082 ret = True
1083 for result in results:
1084 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001085 project = self.get_parallel_context()["projects"][
1086 result.project_idx
1087 ]
Gavin Makea2e3302023-03-11 06:46:20 +00001088 start = result.start
1089 finish = result.finish
1090 self.event_log.AddSync(
1091 project, event_log.TASK_SYNC_LOCAL, start, finish, success
1092 )
Jason Chang32b59562023-07-14 16:45:35 -07001093
1094 if result.errors:
1095 checkout_errors.extend(result.errors)
1096
Gavin Makea2e3302023-03-11 06:46:20 +00001097 # Check for any errors before running any more tasks.
1098 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001099 if success:
1100 self._local_sync_state.SetCheckoutTime(project)
1101 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001102 ret = False
1103 err_results.append(
1104 project.RelPath(local=opt.this_manifest_only)
1105 )
1106 if opt.fail_fast:
1107 if pool:
1108 pool.close()
1109 return ret
1110 pm.update(msg=project.name)
1111 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001112
Josip Sokcevic55545722024-02-22 16:38:00 -08001113 for projects in _SafeCheckoutOrder(all_projects):
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001114 with self.ParallelContext():
1115 self.get_parallel_context()["projects"] = projects
1116 proc_res = self.ExecuteInParallel(
1117 opt.jobs_checkout,
1118 functools.partial(
1119 self._CheckoutOne,
1120 opt.detach_head,
1121 opt.force_sync,
1122 opt.force_checkout,
1123 opt.rebase,
1124 opt.verbose,
1125 ),
1126 range(len(projects)),
1127 callback=_ProcessResults,
1128 output=Progress(
1129 "Checking out", len(all_projects), quiet=opt.quiet
1130 ),
1131 # Use chunksize=1 to avoid the chance that some workers are
1132 # idle while other workers still have more than one job in
1133 # their chunk queue.
1134 chunksize=1,
1135 )
Simran Basib9a1b732015-08-20 12:19:28 -07001136
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001137 self._local_sync_state.Save()
1138 return proc_res and not err_results
1139
Gavin Makea2e3302023-03-11 06:46:20 +00001140 @staticmethod
1141 def _GetPreciousObjectsState(project: Project, opt):
1142 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001143
Gavin Makea2e3302023-03-11 06:46:20 +00001144 Args:
1145 project (Project): the project to examine, and possibly correct.
1146 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001147
Gavin Makea2e3302023-03-11 06:46:20 +00001148 Returns:
1149 Expected state of extensions.preciousObjects:
1150 False: Should be disabled. (not present)
1151 True: Should be enabled.
1152 """
1153 if project.use_git_worktrees:
1154 return False
1155 projects = project.manifest.GetProjectsWithName(
1156 project.name, all_manifests=True
1157 )
1158 if len(projects) == 1:
1159 return False
1160 if len(projects) > 1:
1161 # Objects are potentially shared with another project.
1162 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1163 # - When False, shared projects share (via symlink)
1164 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1165 # objects directory. All objects are precious, since there is no
1166 # project with a complete set of refs.
1167 # - When True, shared projects share (via info/alternates)
1168 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1169 # store, which is written only on the first clone of the project,
1170 # and is not written subsequently. (When Sync_NetworkHalf sees
1171 # that it exists, it makes sure that the alternates file points
1172 # there, and uses a project-local .git/objects directory for all
1173 # syncs going forward.
1174 # We do not support switching between the options. The environment
1175 # variable is present for testing and migration only.
1176 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001177
Gavin Makea2e3302023-03-11 06:46:20 +00001178 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001179
Gavin Makea2e3302023-03-11 06:46:20 +00001180 def _SetPreciousObjectsState(self, project: Project, opt):
1181 """Correct the preciousObjects state for the project.
1182
1183 Args:
1184 project: the project to examine, and possibly correct.
1185 opt: options given to sync.
1186 """
1187 expected = self._GetPreciousObjectsState(project, opt)
1188 actual = (
1189 project.config.GetBoolean("extensions.preciousObjects") or False
1190 )
1191 relpath = project.RelPath(local=opt.this_manifest_only)
1192
1193 if expected != actual:
1194 # If this is unexpected, log it and repair.
1195 Trace(
1196 f"{relpath} expected preciousObjects={expected}, got {actual}"
1197 )
1198 if expected:
1199 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001200 print(
1201 "\r%s: Shared project %s found, disabling pruning."
1202 % (relpath, project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001203 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001204
Gavin Makea2e3302023-03-11 06:46:20 +00001205 if git_require((2, 7, 0)):
1206 project.EnableRepositoryExtension("preciousObjects")
1207 else:
1208 # This isn't perfect, but it's the best we can do with old
1209 # git.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001210 logger.warning(
1211 "%s: WARNING: shared projects are unreliable when "
Gavin Makea2e3302023-03-11 06:46:20 +00001212 "using old versions of git; please upgrade to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001213 "git-2.7.0+.",
1214 relpath,
Gavin Makea2e3302023-03-11 06:46:20 +00001215 )
1216 project.config.SetString("gc.pruneExpire", "never")
1217 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001218 project.config.SetString("extensions.preciousObjects", None)
1219 project.config.SetString("gc.pruneExpire", None)
1220
1221 def _GCProjects(self, projects, opt, err_event):
1222 """Perform garbage collection.
1223
1224 If We are skipping garbage collection (opt.auto_gc not set), we still
1225 want to potentially mark objects precious, so that `git gc` does not
1226 discard shared objects.
1227 """
1228 if not opt.auto_gc:
1229 # Just repair preciousObjects state, and return.
1230 for project in projects:
1231 self._SetPreciousObjectsState(project, opt)
1232 return
1233
1234 pm = Progress(
1235 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1236 )
1237 pm.update(inc=0, msg="prescan")
1238
1239 tidy_dirs = {}
1240 for project in projects:
1241 self._SetPreciousObjectsState(project, opt)
1242
1243 project.config.SetString("gc.autoDetach", "false")
1244 # Only call git gc once per objdir, but call pack-refs for the
1245 # remainder.
1246 if project.objdir not in tidy_dirs:
1247 tidy_dirs[project.objdir] = (
1248 True, # Run a full gc.
1249 project.bare_git,
1250 )
1251 elif project.gitdir not in tidy_dirs:
1252 tidy_dirs[project.gitdir] = (
1253 False, # Do not run a full gc; just run pack-refs.
1254 project.bare_git,
1255 )
1256
1257 jobs = opt.jobs
1258
1259 if jobs < 2:
1260 for run_gc, bare_git in tidy_dirs.values():
1261 pm.update(msg=bare_git._project.name)
1262
1263 if run_gc:
1264 bare_git.gc("--auto")
1265 else:
1266 bare_git.pack_refs()
1267 pm.end()
1268 return
1269
1270 cpu_count = os.cpu_count()
1271 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1272
1273 threads = set()
1274 sem = _threading.Semaphore(jobs)
1275
1276 def tidy_up(run_gc, bare_git):
1277 pm.start(bare_git._project.name)
1278 try:
1279 try:
1280 if run_gc:
1281 bare_git.gc("--auto", config=config)
1282 else:
1283 bare_git.pack_refs(config=config)
1284 except GitError:
1285 err_event.set()
1286 except Exception:
1287 err_event.set()
1288 raise
1289 finally:
1290 pm.finish(bare_git._project.name)
1291 sem.release()
1292
1293 for run_gc, bare_git in tidy_dirs.values():
1294 if err_event.is_set() and opt.fail_fast:
1295 break
1296 sem.acquire()
1297 t = _threading.Thread(
1298 target=tidy_up,
1299 args=(
1300 run_gc,
1301 bare_git,
1302 ),
1303 )
1304 t.daemon = True
1305 threads.add(t)
1306 t.start()
1307
1308 for t in threads:
1309 t.join()
1310 pm.end()
1311
1312 def _ReloadManifest(self, manifest_name, manifest):
1313 """Reload the manfiest from the file specified by the |manifest_name|.
1314
1315 It unloads the manifest if |manifest_name| is None.
1316
1317 Args:
1318 manifest_name: Manifest file to be reloaded.
1319 manifest: The manifest to use.
1320 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001321 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001322 # Override calls Unload already.
1323 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001324 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001325 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001326
Gavin Makea2e3302023-03-11 06:46:20 +00001327 def UpdateProjectList(self, opt, manifest):
1328 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001329
Gavin Makea2e3302023-03-11 06:46:20 +00001330 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001331
Gavin Makea2e3302023-03-11 06:46:20 +00001332 Args:
1333 opt: Program options returned from optparse. See _Options().
1334 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001335
Gavin Makea2e3302023-03-11 06:46:20 +00001336 Returns:
1337 0: success
1338 1: failure
1339 """
1340 new_project_paths = []
1341 for project in self.GetProjects(
1342 None, missing_ok=True, manifest=manifest, all_manifests=False
1343 ):
1344 if project.relpath:
1345 new_project_paths.append(project.relpath)
1346 file_name = "project.list"
1347 file_path = os.path.join(manifest.subdir, file_name)
1348 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001349
Gavin Makea2e3302023-03-11 06:46:20 +00001350 if os.path.exists(file_path):
Jason R. Coombs034950b2023-10-20 23:32:02 +05451351 with open(file_path) as fd:
Gavin Makea2e3302023-03-11 06:46:20 +00001352 old_project_paths = fd.read().split("\n")
1353 # In reversed order, so subfolders are deleted before parent folder.
1354 for path in sorted(old_project_paths, reverse=True):
1355 if not path:
1356 continue
1357 if path not in new_project_paths:
1358 # If the path has already been deleted, we don't need to do
1359 # it.
1360 gitdir = os.path.join(manifest.topdir, path, ".git")
1361 if os.path.exists(gitdir):
1362 project = Project(
1363 manifest=manifest,
1364 name=path,
1365 remote=RemoteSpec("origin"),
1366 gitdir=gitdir,
1367 objdir=gitdir,
1368 use_git_worktrees=os.path.isfile(gitdir),
1369 worktree=os.path.join(manifest.topdir, path),
1370 relpath=path,
1371 revisionExpr="HEAD",
1372 revisionId=None,
1373 groups=None,
1374 )
Jason Chang32b59562023-07-14 16:45:35 -07001375 project.DeleteWorktree(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001376 verbose=opt.verbose, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001377 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001378
Gavin Makea2e3302023-03-11 06:46:20 +00001379 new_project_paths.sort()
1380 with open(file_path, "w") as fd:
1381 fd.write("\n".join(new_project_paths))
1382 fd.write("\n")
1383 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001384
Gavin Makea2e3302023-03-11 06:46:20 +00001385 def UpdateCopyLinkfileList(self, manifest):
1386 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001387
Gavin Makea2e3302023-03-11 06:46:20 +00001388 Returns:
1389 Whether update was successful.
1390 """
1391 new_paths = {}
1392 new_linkfile_paths = []
1393 new_copyfile_paths = []
1394 for project in self.GetProjects(
1395 None, missing_ok=True, manifest=manifest, all_manifests=False
1396 ):
1397 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1398 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001399
Gavin Makea2e3302023-03-11 06:46:20 +00001400 new_paths = {
1401 "linkfile": new_linkfile_paths,
1402 "copyfile": new_copyfile_paths,
1403 }
jiajia tanga590e642021-04-25 20:02:02 +08001404
Gavin Makea2e3302023-03-11 06:46:20 +00001405 copylinkfile_name = "copy-link-files.json"
1406 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1407 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001408
Gavin Makea2e3302023-03-11 06:46:20 +00001409 if os.path.exists(copylinkfile_path):
1410 with open(copylinkfile_path, "rb") as fp:
1411 try:
1412 old_copylinkfile_paths = json.load(fp)
1413 except Exception:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001414 logger.error(
1415 "error: %s is not a json formatted file.",
1416 copylinkfile_path,
Gavin Makea2e3302023-03-11 06:46:20 +00001417 )
1418 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001419 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001420
Gavin Makea2e3302023-03-11 06:46:20 +00001421 need_remove_files = []
1422 need_remove_files.extend(
1423 set(old_copylinkfile_paths.get("linkfile", []))
1424 - set(new_linkfile_paths)
1425 )
1426 need_remove_files.extend(
1427 set(old_copylinkfile_paths.get("copyfile", []))
1428 - set(new_copyfile_paths)
1429 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001430
Gavin Makea2e3302023-03-11 06:46:20 +00001431 for need_remove_file in need_remove_files:
1432 # Try to remove the updated copyfile or linkfile.
1433 # So, if the file is not exist, nothing need to do.
1434 platform_utils.remove(need_remove_file, missing_ok=True)
Raman Tenneti7954de12021-07-28 14:36:49 -07001435
Gavin Makea2e3302023-03-11 06:46:20 +00001436 # Create copy-link-files.json, save dest path of "copyfile" and
1437 # "linkfile".
1438 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1439 json.dump(new_paths, fp)
1440 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001441
Gavin Makea2e3302023-03-11 06:46:20 +00001442 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1443 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001444 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001445 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001446 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001447 )
Gavin Makea2e3302023-03-11 06:46:20 +00001448
1449 manifest_server = manifest.manifest_server
1450 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001451 print("Using manifest server %s" % manifest_server)
Gavin Makea2e3302023-03-11 06:46:20 +00001452
1453 if "@" not in manifest_server:
1454 username = None
1455 password = None
1456 if opt.manifest_server_username and opt.manifest_server_password:
1457 username = opt.manifest_server_username
1458 password = opt.manifest_server_password
1459 else:
1460 try:
1461 info = netrc.netrc()
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451462 except OSError:
Gavin Makea2e3302023-03-11 06:46:20 +00001463 # .netrc file does not exist or could not be opened.
1464 pass
1465 else:
1466 try:
1467 parse_result = urllib.parse.urlparse(manifest_server)
1468 if parse_result.hostname:
1469 auth = info.authenticators(parse_result.hostname)
1470 if auth:
1471 username, _account, password = auth
1472 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001473 logger.error(
1474 "No credentials found for %s in .netrc",
1475 parse_result.hostname,
Gavin Makea2e3302023-03-11 06:46:20 +00001476 )
1477 except netrc.NetrcParseError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001478 logger.error("Error parsing .netrc file: %s", e)
Gavin Makea2e3302023-03-11 06:46:20 +00001479
1480 if username and password:
1481 manifest_server = manifest_server.replace(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001482 "://", f"://{username}:{password}@", 1
Gavin Makea2e3302023-03-11 06:46:20 +00001483 )
1484
1485 transport = PersistentTransport(manifest_server)
1486 if manifest_server.startswith("persistent-"):
1487 manifest_server = manifest_server[len("persistent-") :]
1488
1489 try:
1490 server = xmlrpc.client.Server(manifest_server, transport=transport)
1491 if opt.smart_sync:
1492 branch = self._GetBranch(manifest.manifestProject)
1493
1494 if "SYNC_TARGET" in os.environ:
1495 target = os.environ["SYNC_TARGET"]
1496 [success, manifest_str] = server.GetApprovedManifest(
1497 branch, target
1498 )
1499 elif (
1500 "TARGET_PRODUCT" in os.environ
1501 and "TARGET_BUILD_VARIANT" in os.environ
Navil1e19f7d2024-09-11 16:49:49 +00001502 and "TARGET_RELEASE" in os.environ
1503 ):
1504 target = "%s-%s-%s" % (
1505 os.environ["TARGET_PRODUCT"],
1506 os.environ["TARGET_RELEASE"],
1507 os.environ["TARGET_BUILD_VARIANT"],
1508 )
1509 [success, manifest_str] = server.GetApprovedManifest(
1510 branch, target
1511 )
1512 elif (
1513 "TARGET_PRODUCT" in os.environ
1514 and "TARGET_BUILD_VARIANT" in os.environ
Gavin Makea2e3302023-03-11 06:46:20 +00001515 ):
1516 target = "%s-%s" % (
1517 os.environ["TARGET_PRODUCT"],
1518 os.environ["TARGET_BUILD_VARIANT"],
1519 )
1520 [success, manifest_str] = server.GetApprovedManifest(
1521 branch, target
1522 )
1523 else:
1524 [success, manifest_str] = server.GetApprovedManifest(branch)
1525 else:
1526 assert opt.smart_tag
1527 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1528
1529 if success:
1530 manifest_name = os.path.basename(smart_sync_manifest_path)
1531 try:
1532 with open(smart_sync_manifest_path, "w") as f:
1533 f.write(manifest_str)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451534 except OSError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001535 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001536 "error: cannot write manifest to %s:\n%s"
1537 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001538 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001539 )
Gavin Makea2e3302023-03-11 06:46:20 +00001540 self._ReloadManifest(manifest_name, manifest)
1541 else:
Jason Chang32b59562023-07-14 16:45:35 -07001542 raise SmartSyncError(
1543 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001544 )
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451545 except (OSError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001546 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001547 "error: cannot connect to manifest server %s:\n%s"
1548 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001549 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001550 )
Gavin Makea2e3302023-03-11 06:46:20 +00001551 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001552 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001553 "error: cannot connect to manifest server %s:\n%d %s"
1554 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001555 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001556 )
Gavin Makea2e3302023-03-11 06:46:20 +00001557
1558 return manifest_name
1559
Jason Changdaf2ad32023-08-31 17:06:36 -07001560 def _UpdateAllManifestProjects(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001561 """Fetch & update the local manifest project.
1562
1563 After syncing the manifest project, if the manifest has any sub
1564 manifests, those are recursively processed.
1565
1566 Args:
1567 opt: Program options returned from optparse. See _Options().
1568 mp: the manifestProject to query.
1569 manifest_name: Manifest file to be reloaded.
1570 """
1571 if not mp.standalone_manifest_url:
Jason Changdaf2ad32023-08-31 17:06:36 -07001572 self._UpdateManifestProject(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001573
1574 if mp.manifest.submanifests:
1575 for submanifest in mp.manifest.submanifests.values():
1576 child = submanifest.repo_client.manifest
1577 child.manifestProject.SyncWithPossibleInit(
1578 submanifest,
1579 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1580 verbose=opt.verbose,
1581 tags=opt.tags,
1582 git_event_log=self.git_event_log,
1583 )
1584 self._UpdateAllManifestProjects(
Jason Changdaf2ad32023-08-31 17:06:36 -07001585 opt, child.manifestProject, None, errors
Gavin Makea2e3302023-03-11 06:46:20 +00001586 )
1587
Jason Changdaf2ad32023-08-31 17:06:36 -07001588 def _UpdateManifestProject(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001589 """Fetch & update the local manifest project.
1590
1591 Args:
1592 opt: Program options returned from optparse. See _Options().
1593 mp: the manifestProject to query.
1594 manifest_name: Manifest file to be reloaded.
1595 """
1596 if not opt.local_only:
1597 start = time.time()
Jason Changdaf2ad32023-08-31 17:06:36 -07001598 buf = TeeStringIO(sys.stdout)
1599 try:
1600 result = mp.Sync_NetworkHalf(
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -08001601 quiet=not opt.verbose,
Jason Changdaf2ad32023-08-31 17:06:36 -07001602 output_redir=buf,
1603 verbose=opt.verbose,
1604 current_branch_only=self._GetCurrentBranchOnly(
1605 opt, mp.manifest
1606 ),
1607 force_sync=opt.force_sync,
1608 tags=opt.tags,
1609 optimized_fetch=opt.optimized_fetch,
1610 retry_fetches=opt.retry_fetches,
1611 submodules=mp.manifest.HasSubmodules,
1612 clone_filter=mp.manifest.CloneFilter,
1613 partial_clone_exclude=mp.manifest.PartialCloneExclude,
1614 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
1615 )
1616 if result.error:
1617 errors.append(result.error)
1618 except KeyboardInterrupt:
1619 errors.append(
1620 ManifestInterruptError(buf.getvalue(), project=mp.name)
1621 )
1622 raise
1623
Gavin Makea2e3302023-03-11 06:46:20 +00001624 finish = time.time()
1625 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001626 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001627 )
1628
1629 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001630 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001631 syncbuf = SyncBuffer(mp.config)
1632 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001633 mp.Sync_LocalHalf(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001634 syncbuf,
1635 submodules=mp.manifest.HasSubmodules,
1636 errors=errors,
1637 verbose=opt.verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001638 )
Gavin Makea2e3302023-03-11 06:46:20 +00001639 clean = syncbuf.Finish()
1640 self.event_log.AddSync(
1641 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1642 )
1643 if not clean:
Yiwei Zhangd379e772023-12-20 20:39:59 +00001644 raise UpdateManifestError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001645 self._ReloadManifest(manifest_name, mp.manifest)
1646
1647 def ValidateOptions(self, opt, args):
1648 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001649 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001650 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001651 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001652 )
1653 if opt.network_only and opt.detach_head:
1654 self.OptionParser.error("cannot combine -n and -d")
1655 if opt.network_only and opt.local_only:
1656 self.OptionParser.error("cannot combine -n and -l")
1657 if opt.manifest_name and opt.smart_sync:
1658 self.OptionParser.error("cannot combine -m and -s")
1659 if opt.manifest_name and opt.smart_tag:
1660 self.OptionParser.error("cannot combine -m and -t")
1661 if opt.manifest_server_username or opt.manifest_server_password:
1662 if not (opt.smart_sync or opt.smart_tag):
1663 self.OptionParser.error(
1664 "-u and -p may only be combined with -s or -t"
1665 )
1666 if None in [
1667 opt.manifest_server_username,
1668 opt.manifest_server_password,
1669 ]:
1670 self.OptionParser.error("both -u and -p must be given")
1671
1672 if opt.prune is None:
1673 opt.prune = True
1674
Gavin Makea2e3302023-03-11 06:46:20 +00001675 def _ValidateOptionsWithManifest(self, opt, mp):
1676 """Like ValidateOptions, but after we've updated the manifest.
1677
1678 Needed to handle sync-xxx option defaults in the manifest.
1679
1680 Args:
1681 opt: The options to process.
1682 mp: The manifest project to pull defaults from.
1683 """
1684 if not opt.jobs:
1685 # If the user hasn't made a choice, use the manifest value.
1686 opt.jobs = mp.manifest.default.sync_j
1687 if opt.jobs:
1688 # If --jobs has a non-default value, propagate it as the default for
1689 # --jobs-xxx flags too.
1690 if not opt.jobs_network:
1691 opt.jobs_network = opt.jobs
1692 if not opt.jobs_checkout:
1693 opt.jobs_checkout = opt.jobs
1694 else:
1695 # Neither user nor manifest have made a choice, so setup defaults.
1696 if not opt.jobs_network:
1697 opt.jobs_network = 1
1698 if not opt.jobs_checkout:
1699 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1700 opt.jobs = os.cpu_count()
1701
1702 # Try to stay under user rlimit settings.
1703 #
1704 # Since each worker requires at 3 file descriptors to run `git fetch`,
1705 # use that to scale down the number of jobs. Unfortunately there isn't
1706 # an easy way to determine this reliably as systems change, but it was
1707 # last measured by hand in 2011.
1708 soft_limit, _ = _rlimit_nofile()
1709 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1710 opt.jobs = min(opt.jobs, jobs_soft_limit)
1711 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1712 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1713
1714 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001715 errors = []
1716 try:
1717 self._ExecuteHelper(opt, args, errors)
Jason Chang26fa3182024-02-05 15:15:20 -08001718 except (RepoExitError, RepoChangedException):
Jason Chang32b59562023-07-14 16:45:35 -07001719 raise
1720 except (KeyboardInterrupt, Exception) as e:
1721 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1722
1723 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001724 manifest = self.outer_manifest
1725 if not opt.outer_manifest:
1726 manifest = self.manifest
1727
1728 if opt.manifest_name:
1729 manifest.Override(opt.manifest_name)
1730
1731 manifest_name = opt.manifest_name
1732 smart_sync_manifest_path = os.path.join(
1733 manifest.manifestProject.worktree, "smart_sync_override.xml"
1734 )
1735
1736 if opt.clone_bundle is None:
1737 opt.clone_bundle = manifest.CloneBundle
1738
1739 if opt.smart_sync or opt.smart_tag:
1740 manifest_name = self._SmartSyncSetup(
1741 opt, smart_sync_manifest_path, manifest
1742 )
1743 else:
1744 if os.path.isfile(smart_sync_manifest_path):
1745 try:
1746 platform_utils.remove(smart_sync_manifest_path)
1747 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001748 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001749 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001750 "manifest: %s",
1751 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001752 )
1753
1754 err_event = multiprocessing.Event()
1755
1756 rp = manifest.repoProject
1757 rp.PreSync()
1758 cb = rp.CurrentBranch
1759 if cb:
1760 base = rp.GetBranch(cb).merge
1761 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001762 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001763 "warning: repo is not tracking a remote branch, so it will "
1764 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001765 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001766 )
1767
1768 for m in self.ManifestList(opt):
1769 if not m.manifestProject.standalone_manifest_url:
1770 m.manifestProject.PreSync()
1771
1772 if opt.repo_upgraded:
1773 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1774
1775 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001776
1777 if _REPO_ALLOW_SHALLOW is not None:
1778 if _REPO_ALLOW_SHALLOW == "1":
1779 mp.ConfigureCloneFilterForDepth(None)
1780 elif (
1781 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1782 ):
1783 mp.ConfigureCloneFilterForDepth("blob:none")
1784
Gavin Makea2e3302023-03-11 06:46:20 +00001785 if opt.mp_update:
Jason Changdaf2ad32023-08-31 17:06:36 -07001786 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001787 else:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001788 print("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001789
1790 # Now that the manifests are up-to-date, setup options whose defaults
1791 # might be in the manifest.
1792 self._ValidateOptionsWithManifest(opt, mp)
1793
1794 superproject_logging_data = {}
1795 self._UpdateProjectsRevisionId(
1796 opt, args, superproject_logging_data, manifest
1797 )
1798
Gavin Makea2e3302023-03-11 06:46:20 +00001799 all_projects = self.GetProjects(
1800 args,
1801 missing_ok=True,
1802 submodules_ok=opt.fetch_submodules,
1803 manifest=manifest,
1804 all_manifests=not opt.this_manifest_only,
1805 )
1806
1807 err_network_sync = False
1808 err_update_projects = False
1809 err_update_linkfiles = False
1810
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001811 # Log the repo projects by existing and new.
1812 existing = [x for x in all_projects if x.Exists]
1813 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1814 mp.config.SetString(
1815 "repo.newprojectcount", str(len(all_projects) - len(existing))
1816 )
1817
Gavin Makea2e3302023-03-11 06:46:20 +00001818 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001819 self._local_sync_state = LocalSyncState(manifest)
Gavin Makea2e3302023-03-11 06:46:20 +00001820 if not opt.local_only:
1821 with multiprocessing.Manager() as manager:
1822 with ssh.ProxyManager(manager) as ssh_proxy:
1823 # Initialize the socket dir once in the parent.
1824 ssh_proxy.sock()
1825 result = self._FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -07001826 opt,
1827 args,
1828 all_projects,
1829 err_event,
1830 ssh_proxy,
1831 manifest,
1832 errors,
Gavin Makea2e3302023-03-11 06:46:20 +00001833 )
1834 all_projects = result.all_projects
1835
1836 if opt.network_only:
1837 return
1838
1839 # If we saw an error, exit with code 1 so that other scripts can
1840 # check.
1841 if err_event.is_set():
1842 err_network_sync = True
1843 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001844 logger.error(
1845 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001846 "Local checkouts *not* updated. Resolve network issues "
1847 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001848 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001849 )
Jason Chang32b59562023-07-14 16:45:35 -07001850 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001851
1852 for m in self.ManifestList(opt):
1853 if m.IsMirror or m.IsArchive:
1854 # Bail out now, we have no working tree.
1855 continue
1856
Jason Chang32b59562023-07-14 16:45:35 -07001857 try:
1858 self.UpdateProjectList(opt, m)
1859 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001860 err_event.set()
1861 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001862 errors.append(e)
1863 if isinstance(e, DeleteWorktreeError):
1864 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001865 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001866 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001867 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001868
Jason Chang32b59562023-07-14 16:45:35 -07001869 try:
1870 self.UpdateCopyLinkfileList(m)
1871 except Exception as e:
1872 err_update_linkfiles = True
1873 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001874 err_event.set()
1875 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001876 logger.error(
1877 "error: Local update copyfile or linkfile failed."
Gavin Makea2e3302023-03-11 06:46:20 +00001878 )
Jason Chang32b59562023-07-14 16:45:35 -07001879 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001880
1881 err_results = []
1882 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001883 err_checkout = not self._Checkout(
1884 all_projects, opt, err_results, errors
1885 )
Gavin Makea2e3302023-03-11 06:46:20 +00001886 if err_checkout:
1887 err_event.set()
1888
1889 printed_notices = set()
1890 # If there's a notice that's supposed to print at the end of the sync,
1891 # print it now... But avoid printing duplicate messages, and preserve
1892 # order.
1893 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1894 if m.notice and m.notice not in printed_notices:
1895 print(m.notice)
1896 printed_notices.add(m.notice)
1897
1898 # If we saw an error, exit with code 1 so that other scripts can check.
1899 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001900
1901 def print_and_log(err_msg):
1902 self.git_event_log.ErrorEvent(err_msg)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001903 logger.error("%s", err_msg)
Josip Sokcevic131fc962023-05-12 17:00:46 -07001904
1905 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001906 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001907 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001908 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001909 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001910 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001911 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001912 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001913 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001914 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001915 # Don't log repositories, as it may contain sensitive info.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001916 logger.error("Failing repos:\n%s", "\n".join(err_results))
Josip Sokcevic131fc962023-05-12 17:00:46 -07001917 # Not useful to log.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001918 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001919 'Try re-running with "-j1 --fail-fast" to exit at the first '
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001920 "error."
Gavin Makea2e3302023-03-11 06:46:20 +00001921 )
Jason Chang32b59562023-07-14 16:45:35 -07001922 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001923
1924 # Log the previous sync analysis state from the config.
1925 self.git_event_log.LogDataConfigEvents(
1926 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1927 )
1928
1929 # Update and log with the new sync analysis state.
1930 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1931 self.git_event_log.LogDataConfigEvents(
1932 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1933 )
1934
Gavin Makf0aeb222023-08-08 04:43:36 +00001935 self._local_sync_state.PruneRemovedProjects()
1936 if self._local_sync_state.IsPartiallySynced():
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001937 logger.warning(
Gavin Makf0aeb222023-08-08 04:43:36 +00001938 "warning: Partial syncs are not supported. For the best "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001939 "experience, sync the entire tree."
Gavin Makf0aeb222023-08-08 04:43:36 +00001940 )
1941
Gavin Makea2e3302023-03-11 06:46:20 +00001942 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001943 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001944
David Pursehouse819827a2020-02-12 15:20:19 +09001945
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001946def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001947 # Link the docs for the internal .repo/ layout for people.
1948 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1949 if not platform_utils.islink(link):
1950 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1951 try:
1952 platform_utils.symlink(target, link)
1953 except Exception:
1954 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001955
Gavin Makea2e3302023-03-11 06:46:20 +00001956 wrapper = Wrapper()
1957 if wrapper.NeedSetupGnuPG():
1958 wrapper.SetupGnuPG(quiet)
1959 for project in manifest.projects:
1960 if project.Exists:
1961 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001962
David Pursehouse819827a2020-02-12 15:20:19 +09001963
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001964def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001965 if rp.HasChanges:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +00001966 logger.warning("info: A new version of repo is available")
Gavin Makea2e3302023-03-11 06:46:20 +00001967 wrapper = Wrapper()
1968 try:
1969 rev = rp.bare_git.describe(rp.GetRevisionId())
1970 except GitError:
1971 rev = None
1972 _, new_rev = wrapper.check_repo_rev(
1973 rp.gitdir, rev, repo_verify=repo_verify
1974 )
1975 # See if we're held back due to missing signed tag.
1976 current_revid = rp.bare_git.rev_parse("HEAD")
1977 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1978 if current_revid != new_revid:
1979 # We want to switch to the new rev, but also not trash any
1980 # uncommitted changes. This helps with local testing/hacking.
1981 # If a local change has been made, we will throw that away.
1982 # We also have to make sure this will switch to an older commit if
1983 # that's the latest tag in order to support release rollback.
1984 try:
1985 rp.work_git.reset("--keep", new_rev)
1986 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001987 raise RepoUnhandledExceptionError(e)
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001988 print("info: Restarting repo with latest version")
Gavin Makea2e3302023-03-11 06:46:20 +00001989 raise RepoChangedException(["--repo-upgraded"])
1990 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001991 logger.warning("warning: Skipped upgrade to unverified version")
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001992 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001993 if verbose:
Aravind Vasudevance0ed792023-10-06 18:36:22 +00001994 print("repo version %s is current" % rp.work_git.describe(HEAD))
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001995
David Pursehouse819827a2020-02-12 15:20:19 +09001996
Mike Frysingerd4aee652023-10-19 05:13:32 -04001997class _FetchTimes:
Gavin Makea2e3302023-03-11 06:46:20 +00001998 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07001999
Gavin Makea2e3302023-03-11 06:46:20 +00002000 def __init__(self, manifest):
2001 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00002002 self._saved = None
2003 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002004
Gavin Makea2e3302023-03-11 06:46:20 +00002005 def Get(self, project):
2006 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00002007 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07002008
Gavin Makea2e3302023-03-11 06:46:20 +00002009 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00002010 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00002011
2012 # For shared projects, save the longest time.
2013 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07002014
Gavin Makea2e3302023-03-11 06:46:20 +00002015 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002016 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002017 try:
2018 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002019 self._saved = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452020 except (OSError, ValueError):
Gavin Makea2e3302023-03-11 06:46:20 +00002021 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00002022 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002023
Gavin Makea2e3302023-03-11 06:46:20 +00002024 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002025 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002026 return
Dave Borowitzd9478582012-10-23 16:35:39 -07002027
Gavin Mak041f9772023-05-10 20:41:12 +00002028 for name, t in self._seen.items():
2029 # Keep a moving average across the previous/current sync runs.
2030 old = self._saved.get(name, t)
2031 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07002032
Gavin Makea2e3302023-03-11 06:46:20 +00002033 try:
2034 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002035 json.dump(self._seen, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452036 except (OSError, TypeError):
Gavin Makea2e3302023-03-11 06:46:20 +00002037 platform_utils.remove(self._path, missing_ok=True)
2038
Dan Willemsen0745bb22015-08-17 13:41:45 -07002039
Mike Frysingerd4aee652023-10-19 05:13:32 -04002040class LocalSyncState:
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002041 _LAST_FETCH = "last_fetch"
2042 _LAST_CHECKOUT = "last_checkout"
2043
2044 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00002045 self._manifest = manifest
2046 self._path = os.path.join(
2047 self._manifest.repodir, ".repo_localsyncstate.json"
2048 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002049 self._time = time.time()
2050 self._state = None
2051 self._Load()
2052
2053 def SetFetchTime(self, project):
2054 self._Set(project, self._LAST_FETCH)
2055
2056 def SetCheckoutTime(self, project):
2057 self._Set(project, self._LAST_CHECKOUT)
2058
2059 def GetFetchTime(self, project):
2060 return self._Get(project, self._LAST_FETCH)
2061
2062 def GetCheckoutTime(self, project):
2063 return self._Get(project, self._LAST_CHECKOUT)
2064
2065 def _Get(self, project, key):
2066 self._Load()
2067 p = project.relpath
2068 if p not in self._state:
2069 return
2070 return self._state[p].get(key)
2071
2072 def _Set(self, project, key):
2073 p = project.relpath
2074 if p not in self._state:
2075 self._state[p] = {}
2076 self._state[p][key] = self._time
2077
2078 def _Load(self):
2079 if self._state is None:
2080 try:
2081 with open(self._path) as f:
2082 self._state = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452083 except (OSError, ValueError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002084 platform_utils.remove(self._path, missing_ok=True)
2085 self._state = {}
2086
2087 def Save(self):
2088 if not self._state:
2089 return
2090 try:
2091 with open(self._path, "w") as f:
2092 json.dump(self._state, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452093 except (OSError, TypeError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002094 platform_utils.remove(self._path, missing_ok=True)
2095
Gavin Makf0aeb222023-08-08 04:43:36 +00002096 def PruneRemovedProjects(self):
2097 """Remove entries don't exist on disk and save."""
2098 if not self._state:
2099 return
2100 delete = set()
2101 for path in self._state:
2102 gitdir = os.path.join(self._manifest.topdir, path, ".git")
Matt Schulte0dd0a832023-11-30 11:00:16 -08002103 if not os.path.exists(gitdir) or os.path.islink(gitdir):
Gavin Makf0aeb222023-08-08 04:43:36 +00002104 delete.add(path)
2105 if not delete:
2106 return
2107 for path in delete:
2108 del self._state[path]
2109 self.Save()
2110
2111 def IsPartiallySynced(self):
2112 """Return whether a partial sync state is detected."""
2113 self._Load()
2114 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002115 for path, data in self._state.items():
2116 if path == self._manifest.repoProject.relpath:
2117 # The repo project isn't included in most syncs so we should
2118 # ignore it here.
2119 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002120 checkout_t = data.get(self._LAST_CHECKOUT)
2121 if not checkout_t:
2122 return True
2123 prev_checkout_t = prev_checkout_t or checkout_t
2124 if prev_checkout_t != checkout_t:
2125 return True
2126 return False
2127
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002128
Dan Willemsen0745bb22015-08-17 13:41:45 -07002129# This is a replacement for xmlrpc.client.Transport using urllib2
2130# and supporting persistent-http[s]. It cannot change hosts from
2131# request to request like the normal transport, the real url
2132# is passed during initialization.
2133class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002134 def __init__(self, orig_host):
Daniel Kutikb99272c2023-10-23 21:20:07 +02002135 super().__init__()
Gavin Makea2e3302023-03-11 06:46:20 +00002136 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002137
Gavin Makea2e3302023-03-11 06:46:20 +00002138 def request(self, host, handler, request_body, verbose=False):
2139 with GetUrlCookieFile(self.orig_host, not verbose) as (
2140 cookiefile,
2141 proxy,
2142 ):
2143 # Python doesn't understand cookies with the #HttpOnly_ prefix
2144 # Since we're only using them for HTTP, copy the file temporarily,
2145 # stripping those prefixes away.
2146 if cookiefile:
2147 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2148 tmpcookiefile.write("# HTTP Cookie File")
2149 try:
2150 with open(cookiefile) as f:
2151 for line in f:
2152 if line.startswith("#HttpOnly_"):
2153 line = line[len("#HttpOnly_") :]
2154 tmpcookiefile.write(line)
2155 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002156
Gavin Makea2e3302023-03-11 06:46:20 +00002157 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2158 try:
2159 cookiejar.load()
2160 except cookielib.LoadError:
2161 cookiejar = cookielib.CookieJar()
2162 finally:
2163 tmpcookiefile.close()
2164 else:
2165 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002166
Gavin Makea2e3302023-03-11 06:46:20 +00002167 proxyhandler = urllib.request.ProxyHandler
2168 if proxy:
2169 proxyhandler = urllib.request.ProxyHandler(
2170 {"http": proxy, "https": proxy}
2171 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002172
Gavin Makea2e3302023-03-11 06:46:20 +00002173 opener = urllib.request.build_opener(
2174 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2175 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002176
Gavin Makea2e3302023-03-11 06:46:20 +00002177 url = urllib.parse.urljoin(self.orig_host, handler)
2178 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002179
Gavin Makea2e3302023-03-11 06:46:20 +00002180 scheme = parse_results.scheme
2181 if scheme == "persistent-http":
2182 scheme = "http"
2183 if scheme == "persistent-https":
2184 # If we're proxying through persistent-https, use http. The
2185 # proxy itself will do the https.
2186 if proxy:
2187 scheme = "http"
2188 else:
2189 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002190
Gavin Makea2e3302023-03-11 06:46:20 +00002191 # Parse out any authentication information using the base class.
2192 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002193
Gavin Makea2e3302023-03-11 06:46:20 +00002194 url = urllib.parse.urlunparse(
2195 (
2196 scheme,
2197 host,
2198 parse_results.path,
2199 parse_results.params,
2200 parse_results.query,
2201 parse_results.fragment,
2202 )
2203 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002204
Gavin Makea2e3302023-03-11 06:46:20 +00002205 request = urllib.request.Request(url, request_body)
2206 if extra_headers is not None:
2207 for name, header in extra_headers:
2208 request.add_header(name, header)
2209 request.add_header("Content-Type", "text/xml")
2210 try:
2211 response = opener.open(request)
2212 except urllib.error.HTTPError as e:
2213 if e.code == 501:
2214 # We may have been redirected through a login process
2215 # but our POST turned into a GET. Retry.
2216 response = opener.open(request)
2217 else:
2218 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002219
Gavin Makea2e3302023-03-11 06:46:20 +00002220 p, u = xmlrpc.client.getparser()
2221 # Response should be fairly small, so read it all at once.
2222 # This way we can show it to the user in case of error (e.g. HTML).
2223 data = response.read()
2224 try:
2225 p.feed(data)
2226 except xml.parsers.expat.ExpatError as e:
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452227 raise OSError(
Gavin Makea2e3302023-03-11 06:46:20 +00002228 f"Parsing the manifest failed: {e}\n"
2229 f"Please report this to your manifest server admin.\n"
2230 f'Here is the full response:\n{data.decode("utf-8")}'
2231 )
2232 p.close()
2233 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002234
Gavin Makea2e3302023-03-11 06:46:20 +00002235 def close(self):
2236 pass