blob: 7c4c468a755c982fe8f112cce0faaa4cb9a6579c [file] [log] [blame]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001# Copyright (C) 2008 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
LaMont Jonesbdcba7d2022-04-11 22:50:11 +000015import collections
Mike Frysingerebf04a42021-02-23 20:48:04 -050016import functools
Mike Frysingeracf63b22019-06-13 02:24:21 -040017import http.cookiejar as cookielib
Mike Frysinger7b586f22021-02-23 18:38:39 -050018import io
Anthony King85b24ac2014-05-06 15:57:48 +010019import json
Mike Frysingerebf04a42021-02-23 20:48:04 -050020import multiprocessing
David Pursehouse86d973d2012-08-24 10:21:02 +090021import netrc
Mike Frysinger06ddc8c2023-08-21 21:26:51 -040022import optparse
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import os
Josip Sokcevic55545722024-02-22 16:38:00 -080024from pathlib import Path
Jason Changdaf2ad32023-08-31 17:06:36 -070025import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
Jason Changdaf2ad32023-08-31 17:06:36 -070028from typing import List, NamedTuple, Set, Union
Mike Frysingeracf63b22019-06-13 02:24:21 -040029import urllib.error
30import urllib.parse
31import urllib.request
Mike Frysinger5951e302022-05-20 23:34:44 -040032import xml.parsers.expat
Mike Frysingeracf63b22019-06-13 02:24:21 -040033import xmlrpc.client
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070034
Mike Frysinger64477332023-08-21 21:20:32 -040035
Roy Lee18afd7f2010-05-09 04:32:08 +080036try:
Gavin Makea2e3302023-03-11 06:46:20 +000037 import threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080038except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000039 import dummy_threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080040
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070041try:
Gavin Makea2e3302023-03-11 06:46:20 +000042 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090043
Gavin Makea2e3302023-03-11 06:46:20 +000044 def _rlimit_nofile():
45 return resource.getrlimit(resource.RLIMIT_NOFILE)
46
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070047except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000048
49 def _rlimit_nofile():
50 return (256, 256)
51
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070052
Mike Frysinger64477332023-08-21 21:20:32 -040053from command import Command
54from command import DEFAULT_LOCAL_JOBS
55from command import MirrorSafeCommand
56from command import WORKER_BATCH_SIZE
57from error import GitError
58from error import RepoChangedException
Jason Changdaf2ad32023-08-31 17:06:36 -070059from error import RepoError
Mike Frysinger64477332023-08-21 21:20:32 -040060from error import RepoExitError
61from error import RepoUnhandledExceptionError
62from error import SyncError
63from error import UpdateManifestError
David Rileye0684ad2017-04-05 00:02:59 -070064import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040065from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090066from git_config import GetUrlCookieFile
Mike Frysinger64477332023-08-21 21:20:32 -040067from git_refs import HEAD
68from git_refs import R_HEADS
Raman Tenneti6a872c92021-01-14 19:17:50 -080069import git_superproject
Mike Frysinger64477332023-08-21 21:20:32 -040070import platform_utils
71from progress import elapsed_str
72from progress import jobs_str
73from progress import Progress
74from project import DeleteWorktreeError
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070075from project import Project
76from project import RemoteSpec
Mike Frysinger64477332023-08-21 21:20:32 -040077from project import SyncBuffer
Aravind Vasudevane914ec22023-08-31 20:57:31 +000078from repo_logging import RepoLogger
Joanna Wanga6c52f52022-11-03 16:51:19 -040079from repo_trace import Trace
Mike Frysinger19e409c2021-05-05 19:44:35 -040080import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080081from wrapper import Wrapper
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070082
Mike Frysinger64477332023-08-21 21:20:32 -040083
Dave Borowitz67700e92012-10-23 15:00:54 -070084_ONE_DAY_S = 24 * 60 * 60
85
Jason Chang17833322023-05-23 13:06:55 -070086_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
87
Aravind Vasudevane914ec22023-08-31 20:57:31 +000088logger = RepoLogger(__file__)
89
David Pursehouse819827a2020-02-12 15:20:19 +090090
Josip Sokcevic55545722024-02-22 16:38:00 -080091def _SafeCheckoutOrder(checkouts: List[Project]) -> List[List[Project]]:
92 """Generate a sequence of checkouts that is safe to perform. The client
93 should checkout everything from n-th index before moving to n+1.
94
95 This is only useful if manifest contains nested projects.
96
97 E.g. if foo, foo/bar and foo/bar/baz are project paths, then foo needs to
98 finish before foo/bar can proceed, and foo/bar needs to finish before
99 foo/bar/baz."""
100 res = [[]]
101 current = res[0]
102
103 # depth_stack contains a current stack of parent paths.
104 depth_stack = []
Josip Sokcevic46790222024-03-07 22:18:58 +0000105 # Checkouts are iterated in the hierarchical order. That way, it can easily
106 # be determined if the previous checkout is parent of the current checkout.
107 # We are splitting by the path separator so the final result is
108 # hierarchical, and not just lexicographical. For example, if the projects
109 # are: foo, foo/bar, foo-bar, lexicographical order produces foo, foo-bar
110 # and foo/bar, which doesn't work.
111 for checkout in sorted(checkouts, key=lambda x: x.relpath.split("/")):
Josip Sokcevic55545722024-02-22 16:38:00 -0800112 checkout_path = Path(checkout.relpath)
113 while depth_stack:
114 try:
115 checkout_path.relative_to(depth_stack[-1])
116 except ValueError:
117 # Path.relative_to returns ValueError if paths are not relative.
118 # TODO(sokcevic): Switch to is_relative_to once min supported
119 # version is py3.9.
120 depth_stack.pop()
121 else:
122 if len(depth_stack) >= len(res):
123 # Another depth created.
124 res.append([])
125 break
126
127 current = res[len(depth_stack)]
128 current.append(checkout)
129 depth_stack.append(checkout_path)
130
131 return res
132
133
Josip Sokcevic454fdaf2024-10-07 17:33:38 +0000134def _chunksize(projects: int, jobs: int) -> int:
135 """Calculate chunk size for the given number of projects and jobs."""
136 return min(max(1, projects // jobs), WORKER_BATCH_SIZE)
137
138
LaMont Jones1eddca82022-09-01 15:15:04 +0000139class _FetchOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000140 """_FetchOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000141
Gavin Makea2e3302023-03-11 06:46:20 +0000142 Attributes:
143 success (bool): True if successful.
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800144 project_idx (int): The fetched project index.
Gavin Makea2e3302023-03-11 06:46:20 +0000145 start (float): The starting time.time().
146 finish (float): The ending time.time().
147 remote_fetched (bool): True if the remote was actually queried.
148 """
149
150 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700151 errors: List[Exception]
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800152 project_idx: int
Gavin Makea2e3302023-03-11 06:46:20 +0000153 start: float
154 finish: float
155 remote_fetched: bool
LaMont Jones1eddca82022-09-01 15:15:04 +0000156
157
158class _FetchResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000159 """_Fetch return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000160
Gavin Makea2e3302023-03-11 06:46:20 +0000161 Attributes:
162 success (bool): True if successful.
163 projects (Set[str]): The names of the git directories of fetched projects.
164 """
165
166 success: bool
167 projects: Set[str]
LaMont Jones1eddca82022-09-01 15:15:04 +0000168
169
170class _FetchMainResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000171 """_FetchMain return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000172
Gavin Makea2e3302023-03-11 06:46:20 +0000173 Attributes:
174 all_projects (List[Project]): The fetched projects.
175 """
176
177 all_projects: List[Project]
LaMont Jones1eddca82022-09-01 15:15:04 +0000178
179
180class _CheckoutOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000181 """_CheckoutOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000182
Gavin Makea2e3302023-03-11 06:46:20 +0000183 Attributes:
184 success (bool): True if successful.
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800185 project_idx (int): The project index.
Gavin Makea2e3302023-03-11 06:46:20 +0000186 start (float): The starting time.time().
187 finish (float): The ending time.time().
188 """
189
190 success: bool
Jason Chang32b59562023-07-14 16:45:35 -0700191 errors: List[Exception]
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800192 project_idx: int
Gavin Makea2e3302023-03-11 06:46:20 +0000193 start: float
194 finish: float
LaMont Jones1eddca82022-09-01 15:15:04 +0000195
196
Jason Chang32b59562023-07-14 16:45:35 -0700197class SuperprojectError(SyncError):
198 """Superproject sync repo."""
199
200
201class SyncFailFastError(SyncError):
202 """Sync exit error when --fail-fast set."""
203
204
205class SmartSyncError(SyncError):
206 """Smart sync exit error."""
207
208
Jason Changdaf2ad32023-08-31 17:06:36 -0700209class ManifestInterruptError(RepoError):
210 """Aggregate Error to be logged when a user interrupts a manifest update."""
211
212 def __init__(self, output, **kwargs):
213 super().__init__(output, **kwargs)
214 self.output = output
215
216 def __str__(self):
217 error_type = type(self).__name__
218 return f"{error_type}:{self.output}"
219
220
221class TeeStringIO(io.StringIO):
222 """StringIO class that can write to an additional destination."""
223
224 def __init__(
225 self, io: Union[io.TextIOWrapper, None], *args, **kwargs
226 ) -> None:
227 super().__init__(*args, **kwargs)
228 self.io = io
229
230 def write(self, s: str) -> int:
231 """Write to additional destination."""
Daniel Kutikb0430b52023-10-23 21:16:04 +0200232 ret = super().write(s)
Jason Changdaf2ad32023-08-31 17:06:36 -0700233 if self.io is not None:
234 self.io.write(s)
Daniel Kutikb0430b52023-10-23 21:16:04 +0200235 return ret
Jason Changdaf2ad32023-08-31 17:06:36 -0700236
237
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800238class Sync(Command, MirrorSafeCommand):
Gavin Makea2e3302023-03-11 06:46:20 +0000239 COMMON = True
240 MULTI_MANIFEST_SUPPORT = True
241 helpSummary = "Update working tree to the latest revision"
242 helpUsage = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700243%prog [...]
244"""
Gavin Makea2e3302023-03-11 06:46:20 +0000245 helpDescription = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246The '%prog' command synchronizes local project directories
247with the remote repositories specified in the manifest. If a local
248project does not yet exist, it will clone a new local directory from
249the remote repository and set up tracking branches as specified in
250the manifest. If the local project already exists, '%prog'
251will update the remote branches and rebase any new local changes
252on top of the new remote changes.
253
254'%prog' will synchronize all projects listed at the command
255line. Projects can be specified either by name, or by a relative
256or absolute path to the project's local directory. If no projects
257are specified, '%prog' will synchronize all projects listed in
258the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700259
260The -d/--detach option can be used to switch specified projects
261back to the manifest revision. This option is especially helpful
262if the project is currently on a topic branch, but the manifest
263revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700264
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700265The -s/--smart-sync option can be used to sync to a known good
266build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200267manifest. The -t/--smart-tag option is similar and allows you to
268specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700269
David Pursehousecf76b1b2012-09-14 10:31:42 +0900270The -u/--manifest-server-username and -p/--manifest-server-password
271options can be used to specify a username and password to authenticate
272with the manifest server when using the -s or -t option.
273
274If -u and -p are not specified when using the -s or -t option, '%prog'
275will attempt to read authentication credentials for the manifest server
276from the user's .netrc file.
277
278'%prog' will not use authentication credentials from -u/-p or .netrc
279if the manifest server specified in the manifest file already includes
280credentials.
281
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400282By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400283to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500284
Kevin Degiabaa7f32014-11-12 11:27:45 -0700285The --force-sync option can be used to overwrite existing git
286directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900287object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700288refs may be removed when overwriting.
289
Josip Sokcevicedadb252024-02-29 09:48:37 -0800290The --force-checkout option can be used to force git to switch revs even if the
291index or the working tree differs from HEAD, and if there are untracked files.
292WARNING: This may cause data to be lost since uncommitted changes may be
293removed.
294
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500295The --force-remove-dirty option can be used to remove previously used
296projects with uncommitted changes. WARNING: This may cause data to be
297lost since uncommitted changes may be removed with projects that no longer
298exist in the manifest.
299
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700300The --no-clone-bundle option disables any attempt to use
301$URL/clone.bundle to bootstrap a new Git repository from a
302resumeable bundle file on a content delivery network. This
303may be necessary if there are problems with the local Python
304HTTP client or proxy configuration, but the Git binary works.
305
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800306The --fetch-submodules option enables fetching Git submodules
307of a project from server.
308
David Pursehousef2fad612015-01-29 14:36:28 +0900309The -c/--current-branch option can be used to only fetch objects that
310are on the branch specified by a project's revision.
311
David Pursehouseb1553542014-09-04 21:28:09 +0900312The --optimized-fetch option can be used to only fetch projects that
313are fixed to a sha1 revision if the sha1 revision does not already
314exist locally.
315
David Pursehouse74cfd272015-10-14 10:50:15 +0900316The --prune option can be used to remove any refs that no longer
317exist on the remote.
318
LaMont Jones7efab532022-09-01 15:41:12 +0000319The --auto-gc option can be used to trigger garbage collection on all
320projects. By default, repo does not run garbage collection.
321
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400322# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700323
324If at least one project remote URL uses an SSH connection (ssh://,
325git+ssh://, or user@host:path syntax) repo will automatically
326enable the SSH ControlMaster option when connecting to that host.
327This feature permits other projects in the same '%prog' session to
328reuse the same SSH tunnel, saving connection setup overheads.
329
330To disable this behavior on UNIX platforms, set the GIT_SSH
331environment variable to 'ssh'. For example:
332
333 export GIT_SSH=ssh
334 %prog
335
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400336# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700337
338This feature is automatically disabled on Windows, due to the lack
339of UNIX domain socket support.
340
341This feature is not compatible with url.insteadof rewrites in the
342user's ~/.gitconfig. '%prog' is currently not able to perform the
343rewrite early enough to establish the ControlMaster tunnel.
344
345If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
346later is required to fix a server side protocol bug.
347
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700348"""
Gavin Makea2e3302023-03-11 06:46:20 +0000349 # A value of 0 means we want parallel jobs, but we'll determine the default
350 # value later on.
351 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700352
Gavin Makea2e3302023-03-11 06:46:20 +0000353 def _Options(self, p, show_smart=True):
354 p.add_option(
355 "--jobs-network",
356 default=None,
357 type=int,
358 metavar="JOBS",
359 help="number of network jobs to run in parallel (defaults to "
360 "--jobs or 1)",
361 )
362 p.add_option(
363 "--jobs-checkout",
364 default=None,
365 type=int,
366 metavar="JOBS",
367 help="number of local checkout jobs to run in parallel (defaults "
368 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
369 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400370
Gavin Makea2e3302023-03-11 06:46:20 +0000371 p.add_option(
372 "-f",
373 "--force-broken",
374 dest="force_broken",
375 action="store_true",
376 help="obsolete option (to be deleted in the future)",
377 )
378 p.add_option(
379 "--fail-fast",
380 dest="fail_fast",
381 action="store_true",
382 help="stop syncing after first error is hit",
383 )
384 p.add_option(
385 "--force-sync",
386 dest="force_sync",
387 action="store_true",
388 help="overwrite an existing git directory if it needs to "
389 "point to a different object directory. WARNING: this "
390 "may cause loss of data",
391 )
392 p.add_option(
Josip Sokcevicedadb252024-02-29 09:48:37 -0800393 "--force-checkout",
394 dest="force_checkout",
395 action="store_true",
396 help="force checkout even if it results in throwing away "
397 "uncommitted modifications. "
398 "WARNING: this may cause loss of data",
399 )
400 p.add_option(
Gavin Makea2e3302023-03-11 06:46:20 +0000401 "--force-remove-dirty",
402 dest="force_remove_dirty",
403 action="store_true",
404 help="force remove projects with uncommitted modifications if "
405 "projects no longer exist in the manifest. "
406 "WARNING: this may cause loss of data",
407 )
408 p.add_option(
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +0200409 "--rebase",
410 dest="rebase",
411 action="store_true",
412 help="rebase local commits regardless of whether they are "
413 "published",
414 )
415 p.add_option(
Gavin Makea2e3302023-03-11 06:46:20 +0000416 "-l",
417 "--local-only",
418 dest="local_only",
419 action="store_true",
420 help="only update working tree, don't fetch",
421 )
422 p.add_option(
423 "--no-manifest-update",
424 "--nmu",
425 dest="mp_update",
426 action="store_false",
427 default="true",
428 help="use the existing manifest checkout as-is. "
429 "(do not update to the latest revision)",
430 )
431 p.add_option(
432 "-n",
433 "--network-only",
434 dest="network_only",
435 action="store_true",
436 help="fetch only, don't update working tree",
437 )
438 p.add_option(
439 "-d",
440 "--detach",
441 dest="detach_head",
442 action="store_true",
443 help="detach projects back to manifest revision",
444 )
445 p.add_option(
446 "-c",
447 "--current-branch",
448 dest="current_branch_only",
449 action="store_true",
450 help="fetch only current branch from server",
451 )
452 p.add_option(
453 "--no-current-branch",
454 dest="current_branch_only",
455 action="store_false",
456 help="fetch all branches from server",
457 )
458 p.add_option(
459 "-m",
460 "--manifest-name",
461 dest="manifest_name",
462 help="temporary manifest to use for this sync",
463 metavar="NAME.xml",
464 )
465 p.add_option(
466 "--clone-bundle",
467 action="store_true",
468 help="enable use of /clone.bundle on HTTP/HTTPS",
469 )
470 p.add_option(
471 "--no-clone-bundle",
472 dest="clone_bundle",
473 action="store_false",
474 help="disable use of /clone.bundle on HTTP/HTTPS",
475 )
476 p.add_option(
477 "-u",
478 "--manifest-server-username",
479 action="store",
480 dest="manifest_server_username",
481 help="username to authenticate with the manifest server",
482 )
483 p.add_option(
484 "-p",
485 "--manifest-server-password",
486 action="store",
487 dest="manifest_server_password",
488 help="password to authenticate with the manifest server",
489 )
490 p.add_option(
491 "--fetch-submodules",
492 dest="fetch_submodules",
493 action="store_true",
494 help="fetch submodules from server",
495 )
496 p.add_option(
497 "--use-superproject",
498 action="store_true",
499 help="use the manifest superproject to sync projects; implies -c",
500 )
501 p.add_option(
502 "--no-use-superproject",
503 action="store_false",
504 dest="use_superproject",
505 help="disable use of manifest superprojects",
506 )
507 p.add_option("--tags", action="store_true", help="fetch tags")
508 p.add_option(
509 "--no-tags",
510 dest="tags",
511 action="store_false",
512 help="don't fetch tags (default)",
513 )
514 p.add_option(
515 "--optimized-fetch",
516 dest="optimized_fetch",
517 action="store_true",
518 help="only fetch projects fixed to sha1 if revision does not exist "
519 "locally",
520 )
521 p.add_option(
522 "--retry-fetches",
523 default=0,
524 action="store",
525 type="int",
526 help="number of times to retry fetches on transient errors",
527 )
528 p.add_option(
529 "--prune",
530 action="store_true",
531 help="delete refs that no longer exist on the remote (default)",
532 )
533 p.add_option(
534 "--no-prune",
535 dest="prune",
536 action="store_false",
537 help="do not delete refs that no longer exist on the remote",
538 )
539 p.add_option(
540 "--auto-gc",
541 action="store_true",
542 default=None,
543 help="run garbage collection on all synced projects",
544 )
545 p.add_option(
546 "--no-auto-gc",
547 dest="auto_gc",
548 action="store_false",
549 help="do not run garbage collection on any projects (default)",
550 )
551 if show_smart:
552 p.add_option(
553 "-s",
554 "--smart-sync",
555 dest="smart_sync",
556 action="store_true",
557 help="smart sync using manifest from the latest known good "
558 "build",
559 )
560 p.add_option(
561 "-t",
562 "--smart-tag",
563 dest="smart_tag",
564 action="store",
565 help="smart sync using manifest from a known tag",
566 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700567
Gavin Makea2e3302023-03-11 06:46:20 +0000568 g = p.add_option_group("repo Version options")
569 g.add_option(
570 "--no-repo-verify",
571 dest="repo_verify",
572 default=True,
573 action="store_false",
574 help="do not verify repo source code",
575 )
576 g.add_option(
577 "--repo-upgraded",
578 dest="repo_upgraded",
579 action="store_true",
Mike Frysinger06ddc8c2023-08-21 21:26:51 -0400580 help=optparse.SUPPRESS_HELP,
Gavin Makea2e3302023-03-11 06:46:20 +0000581 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700582
Gavin Makea2e3302023-03-11 06:46:20 +0000583 def _GetBranch(self, manifest_project):
584 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000585
Gavin Makea2e3302023-03-11 06:46:20 +0000586 Args:
587 manifest_project: The manifestProject to query.
588 """
589 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
590 branch = b.merge
591 if branch.startswith(R_HEADS):
592 branch = branch[len(R_HEADS) :]
593 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800594
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800595 @classmethod
596 def _GetCurrentBranchOnly(cls, opt, manifest):
Gavin Makea2e3302023-03-11 06:46:20 +0000597 """Returns whether current-branch or use-superproject options are
598 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200599
Gavin Makea2e3302023-03-11 06:46:20 +0000600 Args:
601 opt: Program options returned from optparse. See _Options().
602 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000603
Gavin Makea2e3302023-03-11 06:46:20 +0000604 Returns:
605 True if a superproject is requested, otherwise the value of the
606 current_branch option (True, False or None).
607 """
608 return (
609 git_superproject.UseSuperproject(opt.use_superproject, manifest)
610 or opt.current_branch_only
611 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700612
Gavin Makea2e3302023-03-11 06:46:20 +0000613 def _UpdateProjectsRevisionId(
614 self, opt, args, superproject_logging_data, manifest
615 ):
616 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800617
Gavin Makea2e3302023-03-11 06:46:20 +0000618 This function updates each project's revisionId with the commit hash
619 from the superproject. It writes the updated manifest into a file and
620 reloads the manifest from it. When appropriate, sub manifests are also
621 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800622
Gavin Makea2e3302023-03-11 06:46:20 +0000623 Args:
624 opt: Program options returned from optparse. See _Options().
625 args: Arguments to pass to GetProjects. See the GetProjects
626 docstring for details.
627 superproject_logging_data: A dictionary of superproject data to log.
628 manifest: The manifest to use.
629 """
630 have_superproject = manifest.superproject or any(
631 m.superproject for m in manifest.all_children
632 )
633 if not have_superproject:
634 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000635
Gavin Makea2e3302023-03-11 06:46:20 +0000636 if opt.local_only and manifest.superproject:
637 manifest_path = manifest.superproject.manifest_path
638 if manifest_path:
639 self._ReloadManifest(manifest_path, manifest)
640 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700641
Gavin Makea2e3302023-03-11 06:46:20 +0000642 all_projects = self.GetProjects(
643 args,
644 missing_ok=True,
645 submodules_ok=opt.fetch_submodules,
646 manifest=manifest,
647 all_manifests=not opt.this_manifest_only,
648 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000649
Gavin Makea2e3302023-03-11 06:46:20 +0000650 per_manifest = collections.defaultdict(list)
651 if opt.this_manifest_only:
652 per_manifest[manifest.path_prefix] = all_projects
653 else:
654 for p in all_projects:
655 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000656
Gavin Makea2e3302023-03-11 06:46:20 +0000657 superproject_logging_data = {}
658 need_unload = False
659 for m in self.ManifestList(opt):
660 if m.path_prefix not in per_manifest:
661 continue
662 use_super = git_superproject.UseSuperproject(
663 opt.use_superproject, m
664 )
665 if superproject_logging_data:
666 superproject_logging_data["multimanifest"] = True
667 superproject_logging_data.update(
668 superproject=use_super,
669 haslocalmanifests=bool(m.HasLocalManifests),
670 hassuperprojecttag=bool(m.superproject),
671 )
672 if use_super and (m.IsMirror or m.IsArchive):
673 # Don't use superproject, because we have no working tree.
674 use_super = False
675 superproject_logging_data["superproject"] = False
676 superproject_logging_data["noworktree"] = True
677 if opt.use_superproject is not False:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000678 logger.warning(
679 "%s: not using superproject because there is no "
680 "working tree.",
681 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000682 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000683
Gavin Makea2e3302023-03-11 06:46:20 +0000684 if not use_super:
685 continue
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -0800686 m.superproject.SetQuiet(not opt.verbose)
Gavin Makea2e3302023-03-11 06:46:20 +0000687 print_messages = git_superproject.PrintMessages(
688 opt.use_superproject, m
689 )
690 m.superproject.SetPrintMessages(print_messages)
691 update_result = m.superproject.UpdateProjectsRevisionId(
692 per_manifest[m.path_prefix], git_event_log=self.git_event_log
693 )
694 manifest_path = update_result.manifest_path
695 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
696 if manifest_path:
697 m.SetManifestOverride(manifest_path)
698 need_unload = True
699 else:
700 if print_messages:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000701 logger.warning(
702 "%s: warning: Update of revisionId from superproject "
703 "has failed, repo sync will not use superproject to "
704 "fetch the source. Please resync with the "
705 "--no-use-superproject option to avoid this repo "
706 "warning.",
707 m.path_prefix,
Gavin Makea2e3302023-03-11 06:46:20 +0000708 )
709 if update_result.fatal and opt.use_superproject is not None:
Jason Chang32b59562023-07-14 16:45:35 -0700710 raise SuperprojectError()
Gavin Makea2e3302023-03-11 06:46:20 +0000711 if need_unload:
712 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800713
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800714 @classmethod
715 def _FetchProjectList(cls, opt, projects):
Gavin Makea2e3302023-03-11 06:46:20 +0000716 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500717
Gavin Makea2e3302023-03-11 06:46:20 +0000718 The projects we're given share the same underlying git object store, so
719 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800720
Gavin Mak551285f2023-05-04 04:48:43 +0000721 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700722
Gavin Makea2e3302023-03-11 06:46:20 +0000723 Args:
724 opt: Program options returned from optparse. See _Options().
725 projects: Projects to fetch.
726 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800727 return [cls._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700728
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800729 @classmethod
730 def _FetchOne(cls, opt, project_idx):
Gavin Makea2e3302023-03-11 06:46:20 +0000731 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700732
Gavin Makea2e3302023-03-11 06:46:20 +0000733 Args:
734 opt: Program options returned from optparse. See _Options().
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800735 project_idx: Project index for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700736
Gavin Makea2e3302023-03-11 06:46:20 +0000737 Returns:
738 Whether the fetch was successful.
739 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800740 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +0000741 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000742 k = f"{project.name} @ {project.relpath}"
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800743 cls.get_parallel_context()["sync_dict"][k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000744 success = False
745 remote_fetched = False
Jason Chang32b59562023-07-14 16:45:35 -0700746 errors = []
Jason Changdaf2ad32023-08-31 17:06:36 -0700747 buf = TeeStringIO(sys.stdout if opt.verbose else None)
Gavin Makea2e3302023-03-11 06:46:20 +0000748 try:
749 sync_result = project.Sync_NetworkHalf(
750 quiet=opt.quiet,
751 verbose=opt.verbose,
752 output_redir=buf,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800753 current_branch_only=cls._GetCurrentBranchOnly(
Gavin Makea2e3302023-03-11 06:46:20 +0000754 opt, project.manifest
755 ),
756 force_sync=opt.force_sync,
757 clone_bundle=opt.clone_bundle,
758 tags=opt.tags,
759 archive=project.manifest.IsArchive,
760 optimized_fetch=opt.optimized_fetch,
761 retry_fetches=opt.retry_fetches,
762 prune=opt.prune,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800763 ssh_proxy=cls.get_parallel_context()["ssh_proxy"],
Gavin Makea2e3302023-03-11 06:46:20 +0000764 clone_filter=project.manifest.CloneFilter,
765 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700766 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000767 )
768 success = sync_result.success
769 remote_fetched = sync_result.remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700770 if sync_result.error:
771 errors.append(sync_result.error)
Doug Andersonfc06ced2011-03-16 15:49:18 -0700772
Gavin Makea2e3302023-03-11 06:46:20 +0000773 output = buf.getvalue()
Jason Changdaf2ad32023-08-31 17:06:36 -0700774 if output and buf.io is None and not success:
Gavin Makea2e3302023-03-11 06:46:20 +0000775 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700776
Gavin Makea2e3302023-03-11 06:46:20 +0000777 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000778 logger.error(
779 "error: Cannot fetch %s from %s",
780 project.name,
781 project.remote.url,
Gavin Makea2e3302023-03-11 06:46:20 +0000782 )
783 except KeyboardInterrupt:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000784 logger.error("Keyboard interrupt while processing %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +0000785 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000786 logger.error("error.GitError: Cannot fetch %s", e)
Jason Chang32b59562023-07-14 16:45:35 -0700787 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000788 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000789 logger.error(
790 "error: Cannot fetch %s (%s: %s)",
791 project.name,
792 type(e).__name__,
793 e,
Gavin Makea2e3302023-03-11 06:46:20 +0000794 )
Jason Chang32b59562023-07-14 16:45:35 -0700795 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +0000796 raise
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800797 finally:
798 del cls.get_parallel_context()["sync_dict"][k]
Mike Frysinger7b586f22021-02-23 18:38:39 -0500799
Gavin Makea2e3302023-03-11 06:46:20 +0000800 finish = time.time()
Jason Chang32b59562023-07-14 16:45:35 -0700801 return _FetchOneResult(
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800802 success, errors, project_idx, start, finish, remote_fetched
Jason Chang32b59562023-07-14 16:45:35 -0700803 )
David James8d201162013-10-11 17:03:19 -0700804
Gavin Mak04cba4a2023-05-24 21:28:28 +0000805 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000806 earliest_time = float("inf")
807 earliest_proj = None
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800808 items = self.get_parallel_context()["sync_dict"].items()
Gavin Mak945c0062023-05-30 20:04:07 +0000809 for project, t in items:
Gavin Mak551285f2023-05-04 04:48:43 +0000810 if t < earliest_time:
811 earliest_time = t
812 earliest_proj = project
813
Josip Sokcevic71122f92023-05-26 02:44:37 +0000814 if not earliest_proj:
Gavin Mak945c0062023-05-30 20:04:07 +0000815 # This function is called when sync is still running but in some
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800816 # cases (by chance), sync_dict can contain no entries. Return some
Gavin Mak945c0062023-05-30 20:04:07 +0000817 # text to indicate that sync is still working.
818 return "..working.."
Josip Sokcevic71122f92023-05-26 02:44:37 +0000819
Gavin Mak551285f2023-05-04 04:48:43 +0000820 elapsed = time.time() - earliest_time
Gavin Mak945c0062023-05-30 20:04:07 +0000821 jobs = jobs_str(len(items))
Gavin Mak04cba4a2023-05-24 21:28:28 +0000822 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000823
Kuang-che Wuab2d3212024-11-06 13:03:42 +0800824 @classmethod
825 def InitWorker(cls):
826 # Force connect to the manager server now.
827 # This is good because workers are initialized one by one. Without this,
828 # multiple workers may connect to the manager when handling the first
829 # job at the same time. Then the connection may fail if too many
830 # connections are pending and execeeded the socket listening backlog,
831 # especially on MacOS.
832 len(cls.get_parallel_context()["sync_dict"])
833
Jason Changdaf2ad32023-08-31 17:06:36 -0700834 def _Fetch(self, projects, opt, err_event, ssh_proxy, errors):
Gavin Makea2e3302023-03-11 06:46:20 +0000835 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500836
Gavin Makea2e3302023-03-11 06:46:20 +0000837 fetched = set()
838 remote_fetched = set()
Gavin Makedcaa942023-04-27 05:58:57 +0000839 pm = Progress(
840 "Fetching",
841 len(projects),
842 delay=False,
843 quiet=opt.quiet,
844 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000845 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000846 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800847
Gavin Mak551285f2023-05-04 04:48:43 +0000848 sync_event = _threading.Event()
849
850 def _MonitorSyncLoop():
851 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000852 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000853 if sync_event.wait(timeout=1):
854 return
855
856 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
857 sync_progress_thread.daemon = True
Gavin Mak551285f2023-05-04 04:48:43 +0000858
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800859 def _ProcessResults(pool, pm, results_sets):
Gavin Makea2e3302023-03-11 06:46:20 +0000860 ret = True
861 for results in results_sets:
862 for result in results:
863 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800864 project = projects[result.project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +0000865 start = result.start
866 finish = result.finish
867 self._fetch_times.Set(project, finish - start)
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000868 self._local_sync_state.SetFetchTime(project)
Gavin Makea2e3302023-03-11 06:46:20 +0000869 self.event_log.AddSync(
870 project,
871 event_log.TASK_SYNC_NETWORK,
872 start,
873 finish,
874 success,
875 )
Jason Chang32b59562023-07-14 16:45:35 -0700876 if result.errors:
877 errors.extend(result.errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000878 if result.remote_fetched:
879 remote_fetched.add(project)
880 # Check for any errors before running any more tasks.
881 # ...we'll let existing jobs finish, though.
882 if not success:
883 ret = False
884 else:
885 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000886 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000887 if not ret and opt.fail_fast:
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800888 if pool:
889 pool.close()
Gavin Makea2e3302023-03-11 06:46:20 +0000890 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500891 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700892
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800893 with self.ParallelContext():
894 self.get_parallel_context()["projects"] = projects
895 self.get_parallel_context()[
896 "sync_dict"
897 ] = multiprocessing.Manager().dict()
Mike Frysingerebf04a42021-02-23 20:48:04 -0500898
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800899 objdir_project_map = dict()
900 for index, project in enumerate(projects):
901 objdir_project_map.setdefault(project.objdir, []).append(index)
902 projects_list = list(objdir_project_map.values())
903
Peter Kjellerstedt616e3142024-11-20 21:10:29 +0100904 jobs = max(1, min(opt.jobs_network, len(projects_list)))
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800905
906 # We pass the ssh proxy settings via the class. This allows
907 # multiprocessing to pickle it up when spawning children. We can't
908 # pass it as an argument to _FetchProjectList below as
909 # multiprocessing is unable to pickle those.
910 self.get_parallel_context()["ssh_proxy"] = ssh_proxy
911
912 sync_progress_thread.start()
Josip Sokcevic454fdaf2024-10-07 17:33:38 +0000913 if not opt.quiet:
Gavin Makea2e3302023-03-11 06:46:20 +0000914 pm.update(inc=0, msg="warming up")
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800915 try:
916 ret = self.ExecuteInParallel(
917 jobs,
Gavin Makea2e3302023-03-11 06:46:20 +0000918 functools.partial(self._FetchProjectList, opt),
919 projects_list,
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800920 callback=_ProcessResults,
921 output=pm,
922 # Use chunksize=1 to avoid the chance that some workers are
923 # idle while other workers still have more than one job in
924 # their chunk queue.
925 chunksize=1,
Kuang-che Wuab2d3212024-11-06 13:03:42 +0800926 initializer=self.InitWorker,
Gavin Makea2e3302023-03-11 06:46:20 +0000927 )
Kuang-che Wu39ffd992024-10-18 23:32:08 +0800928 finally:
929 sync_event.set()
930 sync_progress_thread.join()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000931
Gavin Makea2e3302023-03-11 06:46:20 +0000932 self._fetch_times.Save()
Gavin Mak1d2e99d2023-07-22 02:56:44 +0000933 self._local_sync_state.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000934
Gavin Makea2e3302023-03-11 06:46:20 +0000935 if not self.outer_client.manifest.IsArchive:
936 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400937
Jason Changdaf2ad32023-08-31 17:06:36 -0700938 return _FetchResult(ret, fetched)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000939
Gavin Makea2e3302023-03-11 06:46:20 +0000940 def _FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -0700941 self, opt, args, all_projects, err_event, ssh_proxy, manifest, errors
Gavin Makea2e3302023-03-11 06:46:20 +0000942 ):
943 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400944
Gavin Makea2e3302023-03-11 06:46:20 +0000945 Args:
946 opt: Program options returned from optparse. See _Options().
947 args: Command line args used to filter out projects.
948 all_projects: List of all projects that should be fetched.
949 err_event: Whether an error was hit while processing.
950 ssh_proxy: SSH manager for clients & masters.
951 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700952
Gavin Makea2e3302023-03-11 06:46:20 +0000953 Returns:
954 List of all projects that should be checked out.
955 """
956 rp = manifest.repoProject
LaMont Jones891e8f72022-09-08 20:17:58 +0000957
Gavin Makea2e3302023-03-11 06:46:20 +0000958 to_fetch = []
959 now = time.time()
960 if _ONE_DAY_S <= (now - rp.LastFetch):
961 to_fetch.append(rp)
962 to_fetch.extend(all_projects)
963 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700964
Jason Changdaf2ad32023-08-31 17:06:36 -0700965 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +0000966 success = result.success
967 fetched = result.projects
Jason Chang32b59562023-07-14 16:45:35 -0700968
Gavin Makea2e3302023-03-11 06:46:20 +0000969 if not success:
970 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700971
Fredrik de Grootebdf0402024-10-22 14:14:59 +0200972 # Call self update, unless requested not to
973 if os.environ.get("REPO_SKIP_SELF_UPDATE", "0") == "0":
974 _PostRepoFetch(rp, opt.repo_verify)
Gavin Makea2e3302023-03-11 06:46:20 +0000975 if opt.network_only:
976 # Bail out now; the rest touches the working tree.
977 if err_event.is_set():
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000978 e = SyncError(
Jason Chang32b59562023-07-14 16:45:35 -0700979 "error: Exited sync due to fetch errors.",
980 aggregate_errors=errors,
981 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +0000982
983 logger.error(e)
984 raise e
Jason Changdaf2ad32023-08-31 17:06:36 -0700985 return _FetchMainResult([])
Dave Borowitz18857212012-10-23 17:02:59 -0700986
Gavin Makea2e3302023-03-11 06:46:20 +0000987 # Iteratively fetch missing and/or nested unregistered submodules.
988 previously_missing_set = set()
989 while True:
990 self._ReloadManifest(None, manifest)
991 all_projects = self.GetProjects(
992 args,
993 missing_ok=True,
994 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000995 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000996 all_manifests=not opt.this_manifest_only,
997 )
998 missing = []
999 for project in all_projects:
1000 if project.gitdir not in fetched:
1001 missing.append(project)
1002 if not missing:
1003 break
1004 # Stop us from non-stopped fetching actually-missing repos: If set
1005 # of missing repos has not been changed from last fetch, we break.
Jason R. Coombs0bcffd82023-10-20 23:29:42 +05451006 missing_set = {p.name for p in missing}
Gavin Makea2e3302023-03-11 06:46:20 +00001007 if previously_missing_set == missing_set:
1008 break
1009 previously_missing_set = missing_set
Jason Changdaf2ad32023-08-31 17:06:36 -07001010 result = self._Fetch(missing, opt, err_event, ssh_proxy, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001011 success = result.success
1012 new_fetched = result.projects
1013 if not success:
1014 err_event.set()
1015 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001016
Jason Changdaf2ad32023-08-31 17:06:36 -07001017 return _FetchMainResult(all_projects)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001018
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001019 @classmethod
Josip Sokcevicedadb252024-02-29 09:48:37 -08001020 def _CheckoutOne(
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001021 cls,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001022 detach_head,
1023 force_sync,
1024 force_checkout,
1025 force_rebase,
1026 verbose,
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001027 project_idx,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001028 ):
Gavin Makea2e3302023-03-11 06:46:20 +00001029 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +08001030
Gavin Makea2e3302023-03-11 06:46:20 +00001031 Args:
1032 detach_head: Whether to leave a detached HEAD.
Josip Sokcevicedadb252024-02-29 09:48:37 -08001033 force_sync: Force checking out of .git directory (e.g. overwrite
1034 existing git directory that was previously linked to a different
1035 object directory).
1036 force_checkout: Force checking out of the repo content.
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001037 force_rebase: Force rebase.
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001038 verbose: Whether to show verbose messages.
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001039 project_idx: Project index for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +08001040
Gavin Makea2e3302023-03-11 06:46:20 +00001041 Returns:
1042 Whether the fetch was successful.
1043 """
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001044 project = cls.get_parallel_context()["projects"][project_idx]
Gavin Makea2e3302023-03-11 06:46:20 +00001045 start = time.time()
1046 syncbuf = SyncBuffer(
1047 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001048 )
Gavin Makea2e3302023-03-11 06:46:20 +00001049 success = False
Jason Chang32b59562023-07-14 16:45:35 -07001050 errors = []
David Pursehouse59b41742015-05-07 14:36:09 +09001051 try:
Jason Chang32b59562023-07-14 16:45:35 -07001052 project.Sync_LocalHalf(
Josip Sokcevicedadb252024-02-29 09:48:37 -08001053 syncbuf,
1054 force_sync=force_sync,
1055 force_checkout=force_checkout,
Jeroen Dhollanderc44ad092024-08-20 10:28:41 +02001056 force_rebase=force_rebase,
Josip Sokcevicedadb252024-02-29 09:48:37 -08001057 errors=errors,
1058 verbose=verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001059 )
Gavin Makea2e3302023-03-11 06:46:20 +00001060 success = syncbuf.Finish()
1061 except GitError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001062 logger.error(
1063 "error.GitError: Cannot checkout %s: %s", project.name, e
Gavin Makea2e3302023-03-11 06:46:20 +00001064 )
Jason Chang32b59562023-07-14 16:45:35 -07001065 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001066 except Exception as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001067 logger.error(
1068 "error: Cannot checkout %s: %s: %s",
1069 project.name,
1070 type(e).__name__,
1071 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001072 )
1073 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -07001074
Gavin Makea2e3302023-03-11 06:46:20 +00001075 if not success:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001076 logger.error("error: Cannot checkout %s", project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001077 finish = time.time()
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001078 return _CheckoutOneResult(success, errors, project_idx, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -04001079
Jason Chang32b59562023-07-14 16:45:35 -07001080 def _Checkout(self, all_projects, opt, err_results, checkout_errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001081 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001082
Gavin Makea2e3302023-03-11 06:46:20 +00001083 Args:
1084 all_projects: List of all projects that should be checked out.
1085 opt: Program options returned from optparse. See _Options().
1086 err_results: A list of strings, paths to git repos where checkout
1087 failed.
1088 """
1089 # Only checkout projects with worktrees.
1090 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001091
Gavin Makea2e3302023-03-11 06:46:20 +00001092 def _ProcessResults(pool, pm, results):
1093 ret = True
1094 for result in results:
1095 success = result.success
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001096 project = self.get_parallel_context()["projects"][
1097 result.project_idx
1098 ]
Gavin Makea2e3302023-03-11 06:46:20 +00001099 start = result.start
1100 finish = result.finish
1101 self.event_log.AddSync(
1102 project, event_log.TASK_SYNC_LOCAL, start, finish, success
1103 )
Jason Chang32b59562023-07-14 16:45:35 -07001104
1105 if result.errors:
1106 checkout_errors.extend(result.errors)
1107
Gavin Makea2e3302023-03-11 06:46:20 +00001108 # Check for any errors before running any more tasks.
1109 # ...we'll let existing jobs finish, though.
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001110 if success:
1111 self._local_sync_state.SetCheckoutTime(project)
1112 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001113 ret = False
1114 err_results.append(
1115 project.RelPath(local=opt.this_manifest_only)
1116 )
1117 if opt.fail_fast:
1118 if pool:
1119 pool.close()
1120 return ret
1121 pm.update(msg=project.name)
1122 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -08001123
Josip Sokcevic55545722024-02-22 16:38:00 -08001124 for projects in _SafeCheckoutOrder(all_projects):
Kuang-che Wu39ffd992024-10-18 23:32:08 +08001125 with self.ParallelContext():
1126 self.get_parallel_context()["projects"] = projects
1127 proc_res = self.ExecuteInParallel(
1128 opt.jobs_checkout,
1129 functools.partial(
1130 self._CheckoutOne,
1131 opt.detach_head,
1132 opt.force_sync,
1133 opt.force_checkout,
1134 opt.rebase,
1135 opt.verbose,
1136 ),
1137 range(len(projects)),
1138 callback=_ProcessResults,
1139 output=Progress(
1140 "Checking out", len(all_projects), quiet=opt.quiet
1141 ),
1142 # Use chunksize=1 to avoid the chance that some workers are
1143 # idle while other workers still have more than one job in
1144 # their chunk queue.
1145 chunksize=1,
1146 )
Simran Basib9a1b732015-08-20 12:19:28 -07001147
Gavin Mak1d2e99d2023-07-22 02:56:44 +00001148 self._local_sync_state.Save()
1149 return proc_res and not err_results
1150
Gavin Makea2e3302023-03-11 06:46:20 +00001151 @staticmethod
1152 def _GetPreciousObjectsState(project: Project, opt):
1153 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -04001154
Gavin Makea2e3302023-03-11 06:46:20 +00001155 Args:
1156 project (Project): the project to examine, and possibly correct.
1157 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -08001158
Gavin Makea2e3302023-03-11 06:46:20 +00001159 Returns:
1160 Expected state of extensions.preciousObjects:
1161 False: Should be disabled. (not present)
1162 True: Should be enabled.
1163 """
1164 if project.use_git_worktrees:
1165 return False
1166 projects = project.manifest.GetProjectsWithName(
1167 project.name, all_manifests=True
1168 )
1169 if len(projects) == 1:
1170 return False
1171 if len(projects) > 1:
1172 # Objects are potentially shared with another project.
1173 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
1174 # - When False, shared projects share (via symlink)
1175 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1176 # objects directory. All objects are precious, since there is no
1177 # project with a complete set of refs.
1178 # - When True, shared projects share (via info/alternates)
1179 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1180 # store, which is written only on the first clone of the project,
1181 # and is not written subsequently. (When Sync_NetworkHalf sees
1182 # that it exists, it makes sure that the alternates file points
1183 # there, and uses a project-local .git/objects directory for all
1184 # syncs going forward.
1185 # We do not support switching between the options. The environment
1186 # variable is present for testing and migration only.
1187 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001188
Gavin Makea2e3302023-03-11 06:46:20 +00001189 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001190
Gavin Makea2e3302023-03-11 06:46:20 +00001191 def _SetPreciousObjectsState(self, project: Project, opt):
1192 """Correct the preciousObjects state for the project.
1193
1194 Args:
1195 project: the project to examine, and possibly correct.
1196 opt: options given to sync.
1197 """
1198 expected = self._GetPreciousObjectsState(project, opt)
1199 actual = (
1200 project.config.GetBoolean("extensions.preciousObjects") or False
1201 )
1202 relpath = project.RelPath(local=opt.this_manifest_only)
1203
1204 if expected != actual:
1205 # If this is unexpected, log it and repair.
1206 Trace(
1207 f"{relpath} expected preciousObjects={expected}, got {actual}"
1208 )
1209 if expected:
1210 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001211 print(
1212 "\r%s: Shared project %s found, disabling pruning."
1213 % (relpath, project.name)
Gavin Makea2e3302023-03-11 06:46:20 +00001214 )
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001215
Gavin Makea2e3302023-03-11 06:46:20 +00001216 if git_require((2, 7, 0)):
1217 project.EnableRepositoryExtension("preciousObjects")
1218 else:
1219 # This isn't perfect, but it's the best we can do with old
1220 # git.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001221 logger.warning(
1222 "%s: WARNING: shared projects are unreliable when "
Gavin Makea2e3302023-03-11 06:46:20 +00001223 "using old versions of git; please upgrade to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001224 "git-2.7.0+.",
1225 relpath,
Gavin Makea2e3302023-03-11 06:46:20 +00001226 )
1227 project.config.SetString("gc.pruneExpire", "never")
1228 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001229 project.config.SetString("extensions.preciousObjects", None)
1230 project.config.SetString("gc.pruneExpire", None)
1231
1232 def _GCProjects(self, projects, opt, err_event):
1233 """Perform garbage collection.
1234
1235 If We are skipping garbage collection (opt.auto_gc not set), we still
1236 want to potentially mark objects precious, so that `git gc` does not
1237 discard shared objects.
1238 """
1239 if not opt.auto_gc:
1240 # Just repair preciousObjects state, and return.
1241 for project in projects:
1242 self._SetPreciousObjectsState(project, opt)
1243 return
1244
1245 pm = Progress(
1246 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1247 )
1248 pm.update(inc=0, msg="prescan")
1249
1250 tidy_dirs = {}
1251 for project in projects:
1252 self._SetPreciousObjectsState(project, opt)
1253
1254 project.config.SetString("gc.autoDetach", "false")
1255 # Only call git gc once per objdir, but call pack-refs for the
1256 # remainder.
1257 if project.objdir not in tidy_dirs:
1258 tidy_dirs[project.objdir] = (
1259 True, # Run a full gc.
1260 project.bare_git,
1261 )
1262 elif project.gitdir not in tidy_dirs:
1263 tidy_dirs[project.gitdir] = (
1264 False, # Do not run a full gc; just run pack-refs.
1265 project.bare_git,
1266 )
1267
1268 jobs = opt.jobs
1269
1270 if jobs < 2:
1271 for run_gc, bare_git in tidy_dirs.values():
1272 pm.update(msg=bare_git._project.name)
1273
1274 if run_gc:
1275 bare_git.gc("--auto")
1276 else:
1277 bare_git.pack_refs()
1278 pm.end()
1279 return
1280
1281 cpu_count = os.cpu_count()
1282 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1283
1284 threads = set()
1285 sem = _threading.Semaphore(jobs)
1286
1287 def tidy_up(run_gc, bare_git):
1288 pm.start(bare_git._project.name)
1289 try:
1290 try:
1291 if run_gc:
1292 bare_git.gc("--auto", config=config)
1293 else:
1294 bare_git.pack_refs(config=config)
1295 except GitError:
1296 err_event.set()
1297 except Exception:
1298 err_event.set()
1299 raise
1300 finally:
1301 pm.finish(bare_git._project.name)
1302 sem.release()
1303
1304 for run_gc, bare_git in tidy_dirs.values():
1305 if err_event.is_set() and opt.fail_fast:
1306 break
1307 sem.acquire()
1308 t = _threading.Thread(
1309 target=tidy_up,
1310 args=(
1311 run_gc,
1312 bare_git,
1313 ),
1314 )
1315 t.daemon = True
1316 threads.add(t)
1317 t.start()
1318
1319 for t in threads:
1320 t.join()
1321 pm.end()
1322
1323 def _ReloadManifest(self, manifest_name, manifest):
1324 """Reload the manfiest from the file specified by the |manifest_name|.
1325
1326 It unloads the manifest if |manifest_name| is None.
1327
1328 Args:
1329 manifest_name: Manifest file to be reloaded.
1330 manifest: The manifest to use.
1331 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001332 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001333 # Override calls Unload already.
1334 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001335 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001336 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001337
Gavin Makea2e3302023-03-11 06:46:20 +00001338 def UpdateProjectList(self, opt, manifest):
1339 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001340
Gavin Makea2e3302023-03-11 06:46:20 +00001341 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001342
Gavin Makea2e3302023-03-11 06:46:20 +00001343 Args:
1344 opt: Program options returned from optparse. See _Options().
1345 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001346
Gavin Makea2e3302023-03-11 06:46:20 +00001347 Returns:
1348 0: success
1349 1: failure
1350 """
1351 new_project_paths = []
1352 for project in self.GetProjects(
1353 None, missing_ok=True, manifest=manifest, all_manifests=False
1354 ):
1355 if project.relpath:
1356 new_project_paths.append(project.relpath)
1357 file_name = "project.list"
1358 file_path = os.path.join(manifest.subdir, file_name)
1359 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001360
Gavin Makea2e3302023-03-11 06:46:20 +00001361 if os.path.exists(file_path):
Jason R. Coombs034950b2023-10-20 23:32:02 +05451362 with open(file_path) as fd:
Gavin Makea2e3302023-03-11 06:46:20 +00001363 old_project_paths = fd.read().split("\n")
1364 # In reversed order, so subfolders are deleted before parent folder.
1365 for path in sorted(old_project_paths, reverse=True):
1366 if not path:
1367 continue
1368 if path not in new_project_paths:
1369 # If the path has already been deleted, we don't need to do
1370 # it.
1371 gitdir = os.path.join(manifest.topdir, path, ".git")
1372 if os.path.exists(gitdir):
1373 project = Project(
1374 manifest=manifest,
1375 name=path,
1376 remote=RemoteSpec("origin"),
1377 gitdir=gitdir,
1378 objdir=gitdir,
1379 use_git_worktrees=os.path.isfile(gitdir),
1380 worktree=os.path.join(manifest.topdir, path),
1381 relpath=path,
1382 revisionExpr="HEAD",
1383 revisionId=None,
1384 groups=None,
1385 )
Jason Chang32b59562023-07-14 16:45:35 -07001386 project.DeleteWorktree(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001387 verbose=opt.verbose, force=opt.force_remove_dirty
Jason Chang32b59562023-07-14 16:45:35 -07001388 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001389
Gavin Makea2e3302023-03-11 06:46:20 +00001390 new_project_paths.sort()
1391 with open(file_path, "w") as fd:
1392 fd.write("\n".join(new_project_paths))
1393 fd.write("\n")
1394 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001395
Gavin Makea2e3302023-03-11 06:46:20 +00001396 def UpdateCopyLinkfileList(self, manifest):
1397 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001398
Gavin Makea2e3302023-03-11 06:46:20 +00001399 Returns:
1400 Whether update was successful.
1401 """
1402 new_paths = {}
1403 new_linkfile_paths = []
1404 new_copyfile_paths = []
1405 for project in self.GetProjects(
1406 None, missing_ok=True, manifest=manifest, all_manifests=False
1407 ):
1408 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1409 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001410
Gavin Makea2e3302023-03-11 06:46:20 +00001411 new_paths = {
1412 "linkfile": new_linkfile_paths,
1413 "copyfile": new_copyfile_paths,
1414 }
jiajia tanga590e642021-04-25 20:02:02 +08001415
Gavin Makea2e3302023-03-11 06:46:20 +00001416 copylinkfile_name = "copy-link-files.json"
1417 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1418 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001419
Gavin Makea2e3302023-03-11 06:46:20 +00001420 if os.path.exists(copylinkfile_path):
1421 with open(copylinkfile_path, "rb") as fp:
1422 try:
1423 old_copylinkfile_paths = json.load(fp)
1424 except Exception:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001425 logger.error(
1426 "error: %s is not a json formatted file.",
1427 copylinkfile_path,
Gavin Makea2e3302023-03-11 06:46:20 +00001428 )
1429 platform_utils.remove(copylinkfile_path)
Jason Chang32b59562023-07-14 16:45:35 -07001430 raise
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001431
Gavin Makea2e3302023-03-11 06:46:20 +00001432 need_remove_files = []
1433 need_remove_files.extend(
1434 set(old_copylinkfile_paths.get("linkfile", []))
1435 - set(new_linkfile_paths)
1436 )
1437 need_remove_files.extend(
1438 set(old_copylinkfile_paths.get("copyfile", []))
1439 - set(new_copyfile_paths)
1440 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001441
Gavin Makea2e3302023-03-11 06:46:20 +00001442 for need_remove_file in need_remove_files:
1443 # Try to remove the updated copyfile or linkfile.
1444 # So, if the file is not exist, nothing need to do.
Josip Sokcevic9500aca2024-12-13 18:24:20 +00001445 platform_utils.remove(
1446 os.path.join(self.client.topdir, need_remove_file),
1447 missing_ok=True,
1448 )
Raman Tenneti7954de12021-07-28 14:36:49 -07001449
Gavin Makea2e3302023-03-11 06:46:20 +00001450 # Create copy-link-files.json, save dest path of "copyfile" and
1451 # "linkfile".
1452 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1453 json.dump(new_paths, fp)
1454 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001455
Gavin Makea2e3302023-03-11 06:46:20 +00001456 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1457 if not manifest.manifest_server:
Jason Chang32b59562023-07-14 16:45:35 -07001458 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001459 "error: cannot smart sync: no manifest server defined in "
Jason Chang32b59562023-07-14 16:45:35 -07001460 "manifest"
Gavin Makea2e3302023-03-11 06:46:20 +00001461 )
Gavin Makea2e3302023-03-11 06:46:20 +00001462
1463 manifest_server = manifest.manifest_server
1464 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001465 print("Using manifest server %s" % manifest_server)
Gavin Makea2e3302023-03-11 06:46:20 +00001466
1467 if "@" not in manifest_server:
1468 username = None
1469 password = None
1470 if opt.manifest_server_username and opt.manifest_server_password:
1471 username = opt.manifest_server_username
1472 password = opt.manifest_server_password
1473 else:
1474 try:
1475 info = netrc.netrc()
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451476 except OSError:
Gavin Makea2e3302023-03-11 06:46:20 +00001477 # .netrc file does not exist or could not be opened.
1478 pass
1479 else:
1480 try:
1481 parse_result = urllib.parse.urlparse(manifest_server)
1482 if parse_result.hostname:
1483 auth = info.authenticators(parse_result.hostname)
1484 if auth:
1485 username, _account, password = auth
1486 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001487 logger.error(
1488 "No credentials found for %s in .netrc",
1489 parse_result.hostname,
Gavin Makea2e3302023-03-11 06:46:20 +00001490 )
1491 except netrc.NetrcParseError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001492 logger.error("Error parsing .netrc file: %s", e)
Gavin Makea2e3302023-03-11 06:46:20 +00001493
1494 if username and password:
1495 manifest_server = manifest_server.replace(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -04001496 "://", f"://{username}:{password}@", 1
Gavin Makea2e3302023-03-11 06:46:20 +00001497 )
1498
1499 transport = PersistentTransport(manifest_server)
1500 if manifest_server.startswith("persistent-"):
1501 manifest_server = manifest_server[len("persistent-") :]
1502
1503 try:
1504 server = xmlrpc.client.Server(manifest_server, transport=transport)
1505 if opt.smart_sync:
1506 branch = self._GetBranch(manifest.manifestProject)
1507
1508 if "SYNC_TARGET" in os.environ:
1509 target = os.environ["SYNC_TARGET"]
1510 [success, manifest_str] = server.GetApprovedManifest(
1511 branch, target
1512 )
1513 elif (
1514 "TARGET_PRODUCT" in os.environ
1515 and "TARGET_BUILD_VARIANT" in os.environ
Navil1e19f7d2024-09-11 16:49:49 +00001516 and "TARGET_RELEASE" in os.environ
1517 ):
1518 target = "%s-%s-%s" % (
1519 os.environ["TARGET_PRODUCT"],
1520 os.environ["TARGET_RELEASE"],
1521 os.environ["TARGET_BUILD_VARIANT"],
1522 )
1523 [success, manifest_str] = server.GetApprovedManifest(
1524 branch, target
1525 )
1526 elif (
1527 "TARGET_PRODUCT" in os.environ
1528 and "TARGET_BUILD_VARIANT" in os.environ
Gavin Makea2e3302023-03-11 06:46:20 +00001529 ):
1530 target = "%s-%s" % (
1531 os.environ["TARGET_PRODUCT"],
1532 os.environ["TARGET_BUILD_VARIANT"],
1533 )
1534 [success, manifest_str] = server.GetApprovedManifest(
1535 branch, target
1536 )
1537 else:
1538 [success, manifest_str] = server.GetApprovedManifest(branch)
1539 else:
1540 assert opt.smart_tag
1541 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1542
1543 if success:
1544 manifest_name = os.path.basename(smart_sync_manifest_path)
1545 try:
1546 with open(smart_sync_manifest_path, "w") as f:
1547 f.write(manifest_str)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451548 except OSError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001549 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001550 "error: cannot write manifest to %s:\n%s"
1551 % (smart_sync_manifest_path, e),
Jason Chang32b59562023-07-14 16:45:35 -07001552 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001553 )
Gavin Makea2e3302023-03-11 06:46:20 +00001554 self._ReloadManifest(manifest_name, manifest)
1555 else:
Jason Chang32b59562023-07-14 16:45:35 -07001556 raise SmartSyncError(
1557 "error: manifest server RPC call failed: %s" % manifest_str
Gavin Makea2e3302023-03-11 06:46:20 +00001558 )
Jason R. Coombsae824fb2023-10-20 23:32:40 +05451559 except (OSError, xmlrpc.client.Fault) as e:
Jason Chang32b59562023-07-14 16:45:35 -07001560 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001561 "error: cannot connect to manifest server %s:\n%s"
1562 % (manifest.manifest_server, e),
Jason Chang32b59562023-07-14 16:45:35 -07001563 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001564 )
Gavin Makea2e3302023-03-11 06:46:20 +00001565 except xmlrpc.client.ProtocolError as e:
Jason Chang32b59562023-07-14 16:45:35 -07001566 raise SmartSyncError(
Gavin Makea2e3302023-03-11 06:46:20 +00001567 "error: cannot connect to manifest server %s:\n%d %s"
1568 % (manifest.manifest_server, e.errcode, e.errmsg),
Jason Chang32b59562023-07-14 16:45:35 -07001569 aggregate_errors=[e],
Gavin Makea2e3302023-03-11 06:46:20 +00001570 )
Gavin Makea2e3302023-03-11 06:46:20 +00001571
1572 return manifest_name
1573
Jason Changdaf2ad32023-08-31 17:06:36 -07001574 def _UpdateAllManifestProjects(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001575 """Fetch & update the local manifest project.
1576
1577 After syncing the manifest project, if the manifest has any sub
1578 manifests, those are recursively processed.
1579
1580 Args:
1581 opt: Program options returned from optparse. See _Options().
1582 mp: the manifestProject to query.
1583 manifest_name: Manifest file to be reloaded.
1584 """
1585 if not mp.standalone_manifest_url:
Jason Changdaf2ad32023-08-31 17:06:36 -07001586 self._UpdateManifestProject(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001587
1588 if mp.manifest.submanifests:
1589 for submanifest in mp.manifest.submanifests.values():
1590 child = submanifest.repo_client.manifest
1591 child.manifestProject.SyncWithPossibleInit(
1592 submanifest,
1593 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1594 verbose=opt.verbose,
1595 tags=opt.tags,
1596 git_event_log=self.git_event_log,
1597 )
1598 self._UpdateAllManifestProjects(
Jason Changdaf2ad32023-08-31 17:06:36 -07001599 opt, child.manifestProject, None, errors
Gavin Makea2e3302023-03-11 06:46:20 +00001600 )
1601
Jason Changdaf2ad32023-08-31 17:06:36 -07001602 def _UpdateManifestProject(self, opt, mp, manifest_name, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001603 """Fetch & update the local manifest project.
1604
1605 Args:
1606 opt: Program options returned from optparse. See _Options().
1607 mp: the manifestProject to query.
1608 manifest_name: Manifest file to be reloaded.
1609 """
1610 if not opt.local_only:
1611 start = time.time()
Jason Changdaf2ad32023-08-31 17:06:36 -07001612 buf = TeeStringIO(sys.stdout)
1613 try:
1614 result = mp.Sync_NetworkHalf(
Tomasz Wasilczyk208f3442024-01-05 12:23:10 -08001615 quiet=not opt.verbose,
Jason Changdaf2ad32023-08-31 17:06:36 -07001616 output_redir=buf,
1617 verbose=opt.verbose,
1618 current_branch_only=self._GetCurrentBranchOnly(
1619 opt, mp.manifest
1620 ),
1621 force_sync=opt.force_sync,
1622 tags=opt.tags,
1623 optimized_fetch=opt.optimized_fetch,
1624 retry_fetches=opt.retry_fetches,
1625 submodules=mp.manifest.HasSubmodules,
1626 clone_filter=mp.manifest.CloneFilter,
1627 partial_clone_exclude=mp.manifest.PartialCloneExclude,
1628 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
1629 )
1630 if result.error:
1631 errors.append(result.error)
1632 except KeyboardInterrupt:
1633 errors.append(
1634 ManifestInterruptError(buf.getvalue(), project=mp.name)
1635 )
1636 raise
1637
Gavin Makea2e3302023-03-11 06:46:20 +00001638 finish = time.time()
1639 self.event_log.AddSync(
Jason Chang32b59562023-07-14 16:45:35 -07001640 mp, event_log.TASK_SYNC_NETWORK, start, finish, result.success
Gavin Makea2e3302023-03-11 06:46:20 +00001641 )
1642
1643 if mp.HasChanges:
Jason Chang32b59562023-07-14 16:45:35 -07001644 errors = []
Gavin Makea2e3302023-03-11 06:46:20 +00001645 syncbuf = SyncBuffer(mp.config)
1646 start = time.time()
Jason Chang32b59562023-07-14 16:45:35 -07001647 mp.Sync_LocalHalf(
Tomasz Wasilczyk4c809212023-12-08 13:42:17 -08001648 syncbuf,
1649 submodules=mp.manifest.HasSubmodules,
1650 errors=errors,
1651 verbose=opt.verbose,
Jason Chang32b59562023-07-14 16:45:35 -07001652 )
Gavin Makea2e3302023-03-11 06:46:20 +00001653 clean = syncbuf.Finish()
1654 self.event_log.AddSync(
1655 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1656 )
1657 if not clean:
Yiwei Zhangd379e772023-12-20 20:39:59 +00001658 raise UpdateManifestError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001659 self._ReloadManifest(manifest_name, mp.manifest)
1660
1661 def ValidateOptions(self, opt, args):
1662 if opt.force_broken:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001663 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001664 "warning: -f/--force-broken is now the default behavior, and "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001665 "the options are deprecated"
Gavin Makea2e3302023-03-11 06:46:20 +00001666 )
1667 if opt.network_only and opt.detach_head:
1668 self.OptionParser.error("cannot combine -n and -d")
1669 if opt.network_only and opt.local_only:
1670 self.OptionParser.error("cannot combine -n and -l")
1671 if opt.manifest_name and opt.smart_sync:
1672 self.OptionParser.error("cannot combine -m and -s")
1673 if opt.manifest_name and opt.smart_tag:
1674 self.OptionParser.error("cannot combine -m and -t")
1675 if opt.manifest_server_username or opt.manifest_server_password:
1676 if not (opt.smart_sync or opt.smart_tag):
1677 self.OptionParser.error(
1678 "-u and -p may only be combined with -s or -t"
1679 )
1680 if None in [
1681 opt.manifest_server_username,
1682 opt.manifest_server_password,
1683 ]:
1684 self.OptionParser.error("both -u and -p must be given")
1685
1686 if opt.prune is None:
1687 opt.prune = True
1688
Gavin Makea2e3302023-03-11 06:46:20 +00001689 def _ValidateOptionsWithManifest(self, opt, mp):
1690 """Like ValidateOptions, but after we've updated the manifest.
1691
1692 Needed to handle sync-xxx option defaults in the manifest.
1693
1694 Args:
1695 opt: The options to process.
1696 mp: The manifest project to pull defaults from.
1697 """
1698 if not opt.jobs:
1699 # If the user hasn't made a choice, use the manifest value.
1700 opt.jobs = mp.manifest.default.sync_j
1701 if opt.jobs:
1702 # If --jobs has a non-default value, propagate it as the default for
1703 # --jobs-xxx flags too.
1704 if not opt.jobs_network:
1705 opt.jobs_network = opt.jobs
1706 if not opt.jobs_checkout:
1707 opt.jobs_checkout = opt.jobs
1708 else:
1709 # Neither user nor manifest have made a choice, so setup defaults.
1710 if not opt.jobs_network:
1711 opt.jobs_network = 1
1712 if not opt.jobs_checkout:
1713 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1714 opt.jobs = os.cpu_count()
1715
1716 # Try to stay under user rlimit settings.
1717 #
1718 # Since each worker requires at 3 file descriptors to run `git fetch`,
1719 # use that to scale down the number of jobs. Unfortunately there isn't
1720 # an easy way to determine this reliably as systems change, but it was
1721 # last measured by hand in 2011.
1722 soft_limit, _ = _rlimit_nofile()
1723 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1724 opt.jobs = min(opt.jobs, jobs_soft_limit)
1725 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1726 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1727
1728 def Execute(self, opt, args):
Jason Chang32b59562023-07-14 16:45:35 -07001729 errors = []
1730 try:
1731 self._ExecuteHelper(opt, args, errors)
Jason Chang26fa3182024-02-05 15:15:20 -08001732 except (RepoExitError, RepoChangedException):
Jason Chang32b59562023-07-14 16:45:35 -07001733 raise
1734 except (KeyboardInterrupt, Exception) as e:
1735 raise RepoUnhandledExceptionError(e, aggregate_errors=errors)
1736
1737 def _ExecuteHelper(self, opt, args, errors):
Gavin Makea2e3302023-03-11 06:46:20 +00001738 manifest = self.outer_manifest
1739 if not opt.outer_manifest:
1740 manifest = self.manifest
1741
1742 if opt.manifest_name:
1743 manifest.Override(opt.manifest_name)
1744
1745 manifest_name = opt.manifest_name
1746 smart_sync_manifest_path = os.path.join(
1747 manifest.manifestProject.worktree, "smart_sync_override.xml"
1748 )
1749
1750 if opt.clone_bundle is None:
1751 opt.clone_bundle = manifest.CloneBundle
1752
1753 if opt.smart_sync or opt.smart_tag:
1754 manifest_name = self._SmartSyncSetup(
1755 opt, smart_sync_manifest_path, manifest
1756 )
1757 else:
1758 if os.path.isfile(smart_sync_manifest_path):
1759 try:
1760 platform_utils.remove(smart_sync_manifest_path)
1761 except OSError as e:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001762 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001763 "error: failed to remove existing smart sync override "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001764 "manifest: %s",
1765 e,
Gavin Makea2e3302023-03-11 06:46:20 +00001766 )
1767
1768 err_event = multiprocessing.Event()
1769
1770 rp = manifest.repoProject
1771 rp.PreSync()
1772 cb = rp.CurrentBranch
1773 if cb:
1774 base = rp.GetBranch(cb).merge
1775 if not base or not base.startswith("refs/heads/"):
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001776 logger.warning(
Gavin Makea2e3302023-03-11 06:46:20 +00001777 "warning: repo is not tracking a remote branch, so it will "
1778 "not receive updates; run `repo init --repo-rev=stable` to "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001779 "fix."
Gavin Makea2e3302023-03-11 06:46:20 +00001780 )
1781
1782 for m in self.ManifestList(opt):
1783 if not m.manifestProject.standalone_manifest_url:
1784 m.manifestProject.PreSync()
1785
1786 if opt.repo_upgraded:
1787 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1788
1789 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001790
1791 if _REPO_ALLOW_SHALLOW is not None:
1792 if _REPO_ALLOW_SHALLOW == "1":
1793 mp.ConfigureCloneFilterForDepth(None)
1794 elif (
1795 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1796 ):
1797 mp.ConfigureCloneFilterForDepth("blob:none")
1798
Gavin Makea2e3302023-03-11 06:46:20 +00001799 if opt.mp_update:
Jason Changdaf2ad32023-08-31 17:06:36 -07001800 self._UpdateAllManifestProjects(opt, mp, manifest_name, errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001801 else:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001802 print("Skipping update of local manifest project.")
Gavin Makea2e3302023-03-11 06:46:20 +00001803
1804 # Now that the manifests are up-to-date, setup options whose defaults
1805 # might be in the manifest.
1806 self._ValidateOptionsWithManifest(opt, mp)
1807
1808 superproject_logging_data = {}
1809 self._UpdateProjectsRevisionId(
1810 opt, args, superproject_logging_data, manifest
1811 )
1812
Gavin Makea2e3302023-03-11 06:46:20 +00001813 all_projects = self.GetProjects(
1814 args,
1815 missing_ok=True,
1816 submodules_ok=opt.fetch_submodules,
1817 manifest=manifest,
1818 all_manifests=not opt.this_manifest_only,
1819 )
1820
1821 err_network_sync = False
1822 err_update_projects = False
1823 err_update_linkfiles = False
1824
Jason Chang1d3b4fb2023-06-20 16:55:27 -07001825 # Log the repo projects by existing and new.
1826 existing = [x for x in all_projects if x.Exists]
1827 mp.config.SetString("repo.existingprojectcount", str(len(existing)))
1828 mp.config.SetString(
1829 "repo.newprojectcount", str(len(all_projects) - len(existing))
1830 )
1831
Gavin Makea2e3302023-03-11 06:46:20 +00001832 self._fetch_times = _FetchTimes(manifest)
Gavin Mak16109a62023-08-22 01:24:46 +00001833 self._local_sync_state = LocalSyncState(manifest)
Gavin Makea2e3302023-03-11 06:46:20 +00001834 if not opt.local_only:
1835 with multiprocessing.Manager() as manager:
1836 with ssh.ProxyManager(manager) as ssh_proxy:
1837 # Initialize the socket dir once in the parent.
1838 ssh_proxy.sock()
1839 result = self._FetchMain(
Jason Changdaf2ad32023-08-31 17:06:36 -07001840 opt,
1841 args,
1842 all_projects,
1843 err_event,
1844 ssh_proxy,
1845 manifest,
1846 errors,
Gavin Makea2e3302023-03-11 06:46:20 +00001847 )
1848 all_projects = result.all_projects
1849
1850 if opt.network_only:
1851 return
1852
1853 # If we saw an error, exit with code 1 so that other scripts can
1854 # check.
1855 if err_event.is_set():
1856 err_network_sync = True
1857 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001858 logger.error(
1859 "error: Exited sync due to fetch errors.\n"
Gavin Makea2e3302023-03-11 06:46:20 +00001860 "Local checkouts *not* updated. Resolve network issues "
1861 "& retry.\n"
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001862 "`repo sync -l` will update some local checkouts."
Gavin Makea2e3302023-03-11 06:46:20 +00001863 )
Jason Chang32b59562023-07-14 16:45:35 -07001864 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001865
1866 for m in self.ManifestList(opt):
1867 if m.IsMirror or m.IsArchive:
1868 # Bail out now, we have no working tree.
1869 continue
1870
Jason Chang32b59562023-07-14 16:45:35 -07001871 try:
1872 self.UpdateProjectList(opt, m)
1873 except Exception as e:
Gavin Makea2e3302023-03-11 06:46:20 +00001874 err_event.set()
1875 err_update_projects = True
Jason Chang32b59562023-07-14 16:45:35 -07001876 errors.append(e)
1877 if isinstance(e, DeleteWorktreeError):
1878 errors.extend(e.aggregate_errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001879 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001880 logger.error("error: Local checkouts *not* updated.")
Jason Chang32b59562023-07-14 16:45:35 -07001881 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001882
Jason Chang32b59562023-07-14 16:45:35 -07001883 try:
1884 self.UpdateCopyLinkfileList(m)
1885 except Exception as e:
1886 err_update_linkfiles = True
1887 errors.append(e)
Gavin Makea2e3302023-03-11 06:46:20 +00001888 err_event.set()
1889 if opt.fail_fast:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001890 logger.error(
1891 "error: Local update copyfile or linkfile failed."
Gavin Makea2e3302023-03-11 06:46:20 +00001892 )
Jason Chang32b59562023-07-14 16:45:35 -07001893 raise SyncFailFastError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001894
1895 err_results = []
1896 # NB: We don't exit here because this is the last step.
Jason Chang32b59562023-07-14 16:45:35 -07001897 err_checkout = not self._Checkout(
1898 all_projects, opt, err_results, errors
1899 )
Gavin Makea2e3302023-03-11 06:46:20 +00001900 if err_checkout:
1901 err_event.set()
1902
1903 printed_notices = set()
1904 # If there's a notice that's supposed to print at the end of the sync,
1905 # print it now... But avoid printing duplicate messages, and preserve
1906 # order.
1907 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1908 if m.notice and m.notice not in printed_notices:
1909 print(m.notice)
1910 printed_notices.add(m.notice)
1911
1912 # If we saw an error, exit with code 1 so that other scripts can check.
1913 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001914
1915 def print_and_log(err_msg):
1916 self.git_event_log.ErrorEvent(err_msg)
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001917 logger.error("%s", err_msg)
Josip Sokcevic131fc962023-05-12 17:00:46 -07001918
1919 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001920 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001921 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001922 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001923 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001924 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001925 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001926 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001927 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001928 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001929 # Don't log repositories, as it may contain sensitive info.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001930 logger.error("Failing repos:\n%s", "\n".join(err_results))
Josip Sokcevic131fc962023-05-12 17:00:46 -07001931 # Not useful to log.
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001932 logger.error(
Gavin Makea2e3302023-03-11 06:46:20 +00001933 'Try re-running with "-j1 --fail-fast" to exit at the first '
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001934 "error."
Gavin Makea2e3302023-03-11 06:46:20 +00001935 )
Jason Chang32b59562023-07-14 16:45:35 -07001936 raise SyncError(aggregate_errors=errors)
Gavin Makea2e3302023-03-11 06:46:20 +00001937
1938 # Log the previous sync analysis state from the config.
1939 self.git_event_log.LogDataConfigEvents(
1940 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1941 )
1942
1943 # Update and log with the new sync analysis state.
1944 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1945 self.git_event_log.LogDataConfigEvents(
1946 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1947 )
1948
Gavin Makf0aeb222023-08-08 04:43:36 +00001949 self._local_sync_state.PruneRemovedProjects()
1950 if self._local_sync_state.IsPartiallySynced():
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001951 logger.warning(
Gavin Makf0aeb222023-08-08 04:43:36 +00001952 "warning: Partial syncs are not supported. For the best "
Aravind Vasudevane914ec22023-08-31 20:57:31 +00001953 "experience, sync the entire tree."
Gavin Makf0aeb222023-08-08 04:43:36 +00001954 )
1955
Gavin Makea2e3302023-03-11 06:46:20 +00001956 if not opt.quiet:
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00001957 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001958
David Pursehouse819827a2020-02-12 15:20:19 +09001959
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001960def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001961 # Link the docs for the internal .repo/ layout for people.
1962 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1963 if not platform_utils.islink(link):
1964 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1965 try:
1966 platform_utils.symlink(target, link)
1967 except Exception:
1968 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001969
Gavin Makea2e3302023-03-11 06:46:20 +00001970 wrapper = Wrapper()
1971 if wrapper.NeedSetupGnuPG():
1972 wrapper.SetupGnuPG(quiet)
1973 for project in manifest.projects:
1974 if project.Exists:
1975 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001976
David Pursehouse819827a2020-02-12 15:20:19 +09001977
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001978def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001979 if rp.HasChanges:
Aravind Vasudevan8bc50002023-10-13 19:22:47 +00001980 logger.warning("info: A new version of repo is available")
Gavin Makea2e3302023-03-11 06:46:20 +00001981 wrapper = Wrapper()
1982 try:
1983 rev = rp.bare_git.describe(rp.GetRevisionId())
1984 except GitError:
1985 rev = None
1986 _, new_rev = wrapper.check_repo_rev(
1987 rp.gitdir, rev, repo_verify=repo_verify
1988 )
1989 # See if we're held back due to missing signed tag.
1990 current_revid = rp.bare_git.rev_parse("HEAD")
1991 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1992 if current_revid != new_revid:
1993 # We want to switch to the new rev, but also not trash any
1994 # uncommitted changes. This helps with local testing/hacking.
1995 # If a local change has been made, we will throw that away.
1996 # We also have to make sure this will switch to an older commit if
1997 # that's the latest tag in order to support release rollback.
1998 try:
1999 rp.work_git.reset("--keep", new_rev)
2000 except GitError as e:
Jason Chang32b59562023-07-14 16:45:35 -07002001 raise RepoUnhandledExceptionError(e)
Aravind Vasudevan83c66ec2023-09-28 19:06:59 +00002002 print("info: Restarting repo with latest version")
Gavin Makea2e3302023-03-11 06:46:20 +00002003 raise RepoChangedException(["--repo-upgraded"])
2004 else:
Aravind Vasudevane914ec22023-08-31 20:57:31 +00002005 logger.warning("warning: Skipped upgrade to unverified version")
Shawn O. Pearcee756c412009-04-13 11:51:15 -07002006 else:
Gavin Makea2e3302023-03-11 06:46:20 +00002007 if verbose:
Aravind Vasudevance0ed792023-10-06 18:36:22 +00002008 print("repo version %s is current" % rp.work_git.describe(HEAD))
Shawn O. Pearcee756c412009-04-13 11:51:15 -07002009
David Pursehouse819827a2020-02-12 15:20:19 +09002010
Mike Frysingerd4aee652023-10-19 05:13:32 -04002011class _FetchTimes:
Gavin Makea2e3302023-03-11 06:46:20 +00002012 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07002013
Gavin Makea2e3302023-03-11 06:46:20 +00002014 def __init__(self, manifest):
2015 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00002016 self._saved = None
2017 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002018
Gavin Makea2e3302023-03-11 06:46:20 +00002019 def Get(self, project):
2020 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00002021 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07002022
Gavin Makea2e3302023-03-11 06:46:20 +00002023 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00002024 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00002025
2026 # For shared projects, save the longest time.
2027 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07002028
Gavin Makea2e3302023-03-11 06:46:20 +00002029 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002030 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002031 try:
2032 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002033 self._saved = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452034 except (OSError, ValueError):
Gavin Makea2e3302023-03-11 06:46:20 +00002035 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00002036 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07002037
Gavin Makea2e3302023-03-11 06:46:20 +00002038 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00002039 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00002040 return
Dave Borowitzd9478582012-10-23 16:35:39 -07002041
Gavin Mak041f9772023-05-10 20:41:12 +00002042 for name, t in self._seen.items():
2043 # Keep a moving average across the previous/current sync runs.
2044 old = self._saved.get(name, t)
2045 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07002046
Gavin Makea2e3302023-03-11 06:46:20 +00002047 try:
2048 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00002049 json.dump(self._seen, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452050 except (OSError, TypeError):
Gavin Makea2e3302023-03-11 06:46:20 +00002051 platform_utils.remove(self._path, missing_ok=True)
2052
Dan Willemsen0745bb22015-08-17 13:41:45 -07002053
Mike Frysingerd4aee652023-10-19 05:13:32 -04002054class LocalSyncState:
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002055 _LAST_FETCH = "last_fetch"
2056 _LAST_CHECKOUT = "last_checkout"
2057
2058 def __init__(self, manifest):
Gavin Makf0aeb222023-08-08 04:43:36 +00002059 self._manifest = manifest
2060 self._path = os.path.join(
2061 self._manifest.repodir, ".repo_localsyncstate.json"
2062 )
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002063 self._time = time.time()
2064 self._state = None
2065 self._Load()
2066
2067 def SetFetchTime(self, project):
2068 self._Set(project, self._LAST_FETCH)
2069
2070 def SetCheckoutTime(self, project):
2071 self._Set(project, self._LAST_CHECKOUT)
2072
2073 def GetFetchTime(self, project):
2074 return self._Get(project, self._LAST_FETCH)
2075
2076 def GetCheckoutTime(self, project):
2077 return self._Get(project, self._LAST_CHECKOUT)
2078
2079 def _Get(self, project, key):
2080 self._Load()
2081 p = project.relpath
2082 if p not in self._state:
2083 return
2084 return self._state[p].get(key)
2085
2086 def _Set(self, project, key):
2087 p = project.relpath
2088 if p not in self._state:
2089 self._state[p] = {}
2090 self._state[p][key] = self._time
2091
2092 def _Load(self):
2093 if self._state is None:
2094 try:
2095 with open(self._path) as f:
2096 self._state = json.load(f)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452097 except (OSError, ValueError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002098 platform_utils.remove(self._path, missing_ok=True)
2099 self._state = {}
2100
2101 def Save(self):
2102 if not self._state:
2103 return
2104 try:
2105 with open(self._path, "w") as f:
2106 json.dump(self._state, f, indent=2)
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452107 except (OSError, TypeError):
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002108 platform_utils.remove(self._path, missing_ok=True)
2109
Gavin Makf0aeb222023-08-08 04:43:36 +00002110 def PruneRemovedProjects(self):
2111 """Remove entries don't exist on disk and save."""
2112 if not self._state:
2113 return
2114 delete = set()
2115 for path in self._state:
2116 gitdir = os.path.join(self._manifest.topdir, path, ".git")
Matt Schulte0dd0a832023-11-30 11:00:16 -08002117 if not os.path.exists(gitdir) or os.path.islink(gitdir):
Gavin Makf0aeb222023-08-08 04:43:36 +00002118 delete.add(path)
2119 if not delete:
2120 return
2121 for path in delete:
2122 del self._state[path]
2123 self.Save()
2124
2125 def IsPartiallySynced(self):
2126 """Return whether a partial sync state is detected."""
2127 self._Load()
2128 prev_checkout_t = None
Gavin Mak321b7932023-08-22 03:10:01 +00002129 for path, data in self._state.items():
2130 if path == self._manifest.repoProject.relpath:
2131 # The repo project isn't included in most syncs so we should
2132 # ignore it here.
2133 continue
Gavin Makf0aeb222023-08-08 04:43:36 +00002134 checkout_t = data.get(self._LAST_CHECKOUT)
2135 if not checkout_t:
2136 return True
2137 prev_checkout_t = prev_checkout_t or checkout_t
2138 if prev_checkout_t != checkout_t:
2139 return True
2140 return False
2141
Gavin Mak1d2e99d2023-07-22 02:56:44 +00002142
Dan Willemsen0745bb22015-08-17 13:41:45 -07002143# This is a replacement for xmlrpc.client.Transport using urllib2
2144# and supporting persistent-http[s]. It cannot change hosts from
2145# request to request like the normal transport, the real url
2146# is passed during initialization.
2147class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00002148 def __init__(self, orig_host):
Daniel Kutikb99272c2023-10-23 21:20:07 +02002149 super().__init__()
Gavin Makea2e3302023-03-11 06:46:20 +00002150 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07002151
Gavin Makea2e3302023-03-11 06:46:20 +00002152 def request(self, host, handler, request_body, verbose=False):
2153 with GetUrlCookieFile(self.orig_host, not verbose) as (
2154 cookiefile,
2155 proxy,
2156 ):
2157 # Python doesn't understand cookies with the #HttpOnly_ prefix
2158 # Since we're only using them for HTTP, copy the file temporarily,
2159 # stripping those prefixes away.
2160 if cookiefile:
2161 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
2162 tmpcookiefile.write("# HTTP Cookie File")
2163 try:
2164 with open(cookiefile) as f:
2165 for line in f:
2166 if line.startswith("#HttpOnly_"):
2167 line = line[len("#HttpOnly_") :]
2168 tmpcookiefile.write(line)
2169 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002170
Gavin Makea2e3302023-03-11 06:46:20 +00002171 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
2172 try:
2173 cookiejar.load()
2174 except cookielib.LoadError:
2175 cookiejar = cookielib.CookieJar()
2176 finally:
2177 tmpcookiefile.close()
2178 else:
2179 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002180
Gavin Makea2e3302023-03-11 06:46:20 +00002181 proxyhandler = urllib.request.ProxyHandler
2182 if proxy:
2183 proxyhandler = urllib.request.ProxyHandler(
2184 {"http": proxy, "https": proxy}
2185 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002186
Gavin Makea2e3302023-03-11 06:46:20 +00002187 opener = urllib.request.build_opener(
2188 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
2189 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002190
Gavin Makea2e3302023-03-11 06:46:20 +00002191 url = urllib.parse.urljoin(self.orig_host, handler)
2192 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002193
Gavin Makea2e3302023-03-11 06:46:20 +00002194 scheme = parse_results.scheme
2195 if scheme == "persistent-http":
2196 scheme = "http"
2197 if scheme == "persistent-https":
2198 # If we're proxying through persistent-https, use http. The
2199 # proxy itself will do the https.
2200 if proxy:
2201 scheme = "http"
2202 else:
2203 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07002204
Gavin Makea2e3302023-03-11 06:46:20 +00002205 # Parse out any authentication information using the base class.
2206 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07002207
Gavin Makea2e3302023-03-11 06:46:20 +00002208 url = urllib.parse.urlunparse(
2209 (
2210 scheme,
2211 host,
2212 parse_results.path,
2213 parse_results.params,
2214 parse_results.query,
2215 parse_results.fragment,
2216 )
2217 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07002218
Gavin Makea2e3302023-03-11 06:46:20 +00002219 request = urllib.request.Request(url, request_body)
2220 if extra_headers is not None:
2221 for name, header in extra_headers:
2222 request.add_header(name, header)
2223 request.add_header("Content-Type", "text/xml")
2224 try:
2225 response = opener.open(request)
2226 except urllib.error.HTTPError as e:
2227 if e.code == 501:
2228 # We may have been redirected through a login process
2229 # but our POST turned into a GET. Retry.
2230 response = opener.open(request)
2231 else:
2232 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07002233
Gavin Makea2e3302023-03-11 06:46:20 +00002234 p, u = xmlrpc.client.getparser()
2235 # Response should be fairly small, so read it all at once.
2236 # This way we can show it to the user in case of error (e.g. HTML).
2237 data = response.read()
2238 try:
2239 p.feed(data)
2240 except xml.parsers.expat.ExpatError as e:
Jason R. Coombsae824fb2023-10-20 23:32:40 +05452241 raise OSError(
Gavin Makea2e3302023-03-11 06:46:20 +00002242 f"Parsing the manifest failed: {e}\n"
2243 f"Please report this to your manifest server admin.\n"
2244 f'Here is the full response:\n{data.decode("utf-8")}'
2245 )
2246 p.close()
2247 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07002248
Gavin Makea2e3302023-03-11 06:46:20 +00002249 def close(self):
2250 pass