blob: 0e4f34c0744ca4c0559af087b24c77ddef9e3a39 [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
Shawn O. Pearce2a1ccb22009-04-10 16:51:53 -070022from optparse import SUPPRESS_HELP
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070023import os
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -070024import socket
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070025import sys
Dan Willemsen0745bb22015-08-17 13:41:45 -070026import tempfile
Shawn O. Pearcef6906872009-04-18 10:49:00 -070027import time
LaMont Jones78dcd372022-10-25 22:38:07 +000028from typing import NamedTuple, List, Set
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
Roy Lee18afd7f2010-05-09 04:32:08 +080035try:
Gavin Makea2e3302023-03-11 06:46:20 +000036 import threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080037except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000038 import dummy_threading as _threading
Roy Lee18afd7f2010-05-09 04:32:08 +080039
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070040try:
Gavin Makea2e3302023-03-11 06:46:20 +000041 import resource
David Pursehouse819827a2020-02-12 15:20:19 +090042
Gavin Makea2e3302023-03-11 06:46:20 +000043 def _rlimit_nofile():
44 return resource.getrlimit(resource.RLIMIT_NOFILE)
45
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070046except ImportError:
Gavin Makea2e3302023-03-11 06:46:20 +000047
48 def _rlimit_nofile():
49 return (256, 256)
50
Shawn O. Pearce97d2b2f2011-09-22 17:23:41 -070051
David Rileye0684ad2017-04-05 00:02:59 -070052import event_log
Mike Frysinger347f9ed2021-03-15 14:58:52 -040053from git_command import git_require
David Pursehouseba7bc732015-08-20 16:55:42 +090054from git_config import GetUrlCookieFile
David Pursehoused94aaef2012-09-07 09:52:04 +090055from git_refs import R_HEADS, HEAD
Raman Tenneti6a872c92021-01-14 19:17:50 -080056import git_superproject
Simran Basibdb52712015-08-10 13:23:23 -070057import gitc_utils
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -070058from project import Project
59from project import RemoteSpec
Gavin Makea2e3302023-03-11 06:46:20 +000060from command import (
61 Command,
62 DEFAULT_LOCAL_JOBS,
63 MirrorSafeCommand,
64 WORKER_BATCH_SIZE,
65)
Daniel Kutik035f22a2022-12-13 12:34:23 +010066from error import RepoChangedException, GitError
Renaud Paquaya65adf72016-11-03 10:37:53 -070067import platform_utils
Shawn O. Pearce350cde42009-04-16 11:21:18 -070068from project import SyncBuffer
Gavin Mak04cba4a2023-05-24 21:28:28 +000069from progress import Progress, elapsed_str, jobs_str
Joanna Wanga6c52f52022-11-03 16:51:19 -040070from repo_trace import Trace
Mike Frysinger19e409c2021-05-05 19:44:35 -040071import ssh
Conley Owens094cdbe2014-01-30 15:09:59 -080072from wrapper import Wrapper
Dan Willemsen5ea32d12015-09-08 13:27:20 -070073from manifest_xml import GitcManifest
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -070074
Dave Borowitz67700e92012-10-23 15:00:54 -070075_ONE_DAY_S = 24 * 60 * 60
76
LaMont Jonesd7935532022-12-01 20:18:46 +000077# Env var to implicitly turn auto-gc back on. This was added to allow a user to
LaMont Jones100a2142022-12-02 22:54:11 +000078# revert a change in default behavior in v2.29.9. Remove after 2023-04-01.
Gavin Makea2e3302023-03-11 06:46:20 +000079_REPO_AUTO_GC = "REPO_AUTO_GC"
80_AUTO_GC = os.environ.get(_REPO_AUTO_GC) == "1"
LaMont Jones5ed8c632022-11-10 00:10:44 +000081
Jason Chang17833322023-05-23 13:06:55 -070082_REPO_ALLOW_SHALLOW = os.environ.get("REPO_ALLOW_SHALLOW")
83
David Pursehouse819827a2020-02-12 15:20:19 +090084
LaMont Jones1eddca82022-09-01 15:15:04 +000085class _FetchOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +000086 """_FetchOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +000087
Gavin Makea2e3302023-03-11 06:46:20 +000088 Attributes:
89 success (bool): True if successful.
90 project (Project): The fetched project.
91 start (float): The starting time.time().
92 finish (float): The ending time.time().
93 remote_fetched (bool): True if the remote was actually queried.
94 """
95
96 success: bool
97 project: Project
98 start: float
99 finish: float
100 remote_fetched: bool
LaMont Jones1eddca82022-09-01 15:15:04 +0000101
102
103class _FetchResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000104 """_Fetch return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000105
Gavin Makea2e3302023-03-11 06:46:20 +0000106 Attributes:
107 success (bool): True if successful.
108 projects (Set[str]): The names of the git directories of fetched projects.
109 """
110
111 success: bool
112 projects: Set[str]
LaMont Jones1eddca82022-09-01 15:15:04 +0000113
114
115class _FetchMainResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000116 """_FetchMain return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000117
Gavin Makea2e3302023-03-11 06:46:20 +0000118 Attributes:
119 all_projects (List[Project]): The fetched projects.
120 """
121
122 all_projects: List[Project]
LaMont Jones1eddca82022-09-01 15:15:04 +0000123
124
125class _CheckoutOneResult(NamedTuple):
Gavin Makea2e3302023-03-11 06:46:20 +0000126 """_CheckoutOne return value.
LaMont Jones1eddca82022-09-01 15:15:04 +0000127
Gavin Makea2e3302023-03-11 06:46:20 +0000128 Attributes:
129 success (bool): True if successful.
130 project (Project): The project.
131 start (float): The starting time.time().
132 finish (float): The ending time.time().
133 """
134
135 success: bool
136 project: Project
137 start: float
138 finish: float
LaMont Jones1eddca82022-09-01 15:15:04 +0000139
140
Shawn O. Pearcec95583b2009-03-03 17:47:06 -0800141class Sync(Command, MirrorSafeCommand):
Gavin Makea2e3302023-03-11 06:46:20 +0000142 COMMON = True
143 MULTI_MANIFEST_SUPPORT = True
144 helpSummary = "Update working tree to the latest revision"
145 helpUsage = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700146%prog [...]
147"""
Gavin Makea2e3302023-03-11 06:46:20 +0000148 helpDescription = """
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700149The '%prog' command synchronizes local project directories
150with the remote repositories specified in the manifest. If a local
151project does not yet exist, it will clone a new local directory from
152the remote repository and set up tracking branches as specified in
153the manifest. If the local project already exists, '%prog'
154will update the remote branches and rebase any new local changes
155on top of the new remote changes.
156
157'%prog' will synchronize all projects listed at the command
158line. Projects can be specified either by name, or by a relative
159or absolute path to the project's local directory. If no projects
160are specified, '%prog' will synchronize all projects listed in
161the manifest.
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700162
163The -d/--detach option can be used to switch specified projects
164back to the manifest revision. This option is especially helpful
165if the project is currently on a topic branch, but the manifest
166revision is temporarily needed.
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700167
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700168The -s/--smart-sync option can be used to sync to a known good
169build as specified by the manifest-server element in the current
Victor Boivie08c880d2011-04-19 10:32:52 +0200170manifest. The -t/--smart-tag option is similar and allows you to
171specify a custom tag/label.
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700172
David Pursehousecf76b1b2012-09-14 10:31:42 +0900173The -u/--manifest-server-username and -p/--manifest-server-password
174options can be used to specify a username and password to authenticate
175with the manifest server when using the -s or -t option.
176
177If -u and -p are not specified when using the -s or -t option, '%prog'
178will attempt to read authentication credentials for the manifest server
179from the user's .netrc file.
180
181'%prog' will not use authentication credentials from -u/-p or .netrc
182if the manifest server specified in the manifest file already includes
183credentials.
184
Mike Frysingerd9e5cf02019-08-26 03:12:55 -0400185By default, all projects will be synced. The --fail-fast option can be used
Mike Frysinger7ae210a2020-05-24 14:56:52 -0400186to halt syncing as soon as possible when the first project fails to sync.
Andrei Warkentin5df6de02010-07-02 17:58:31 -0500187
Kevin Degiabaa7f32014-11-12 11:27:45 -0700188The --force-sync option can be used to overwrite existing git
189directories if they have previously been linked to a different
Roger Shimizuac29ac32020-06-06 02:33:40 +0900190object directory. WARNING: This may cause data to be lost since
Kevin Degiabaa7f32014-11-12 11:27:45 -0700191refs may be removed when overwriting.
192
Oleksii Okolielovd3c0f592018-12-17 19:23:44 -0500193The --force-remove-dirty option can be used to remove previously used
194projects with uncommitted changes. WARNING: This may cause data to be
195lost since uncommitted changes may be removed with projects that no longer
196exist in the manifest.
197
Shawn O. Pearcee02ac0a2012-03-14 15:36:59 -0700198The --no-clone-bundle option disables any attempt to use
199$URL/clone.bundle to bootstrap a new Git repository from a
200resumeable bundle file on a content delivery network. This
201may be necessary if there are problems with the local Python
202HTTP client or proxy configuration, but the Git binary works.
203
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +0800204The --fetch-submodules option enables fetching Git submodules
205of a project from server.
206
David Pursehousef2fad612015-01-29 14:36:28 +0900207The -c/--current-branch option can be used to only fetch objects that
208are on the branch specified by a project's revision.
209
David Pursehouseb1553542014-09-04 21:28:09 +0900210The --optimized-fetch option can be used to only fetch projects that
211are fixed to a sha1 revision if the sha1 revision does not already
212exist locally.
213
David Pursehouse74cfd272015-10-14 10:50:15 +0900214The --prune option can be used to remove any refs that no longer
215exist on the remote.
216
LaMont Jones7efab532022-09-01 15:41:12 +0000217The --auto-gc option can be used to trigger garbage collection on all
218projects. By default, repo does not run garbage collection.
219
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400220# SSH Connections
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700221
222If at least one project remote URL uses an SSH connection (ssh://,
223git+ssh://, or user@host:path syntax) repo will automatically
224enable the SSH ControlMaster option when connecting to that host.
225This feature permits other projects in the same '%prog' session to
226reuse the same SSH tunnel, saving connection setup overheads.
227
228To disable this behavior on UNIX platforms, set the GIT_SSH
229environment variable to 'ssh'. For example:
230
231 export GIT_SSH=ssh
232 %prog
233
Mike Frysingerb8f7bb02018-10-10 01:05:11 -0400234# Compatibility
Shawn O. Pearceeb7af872009-04-21 08:02:04 -0700235
236This feature is automatically disabled on Windows, due to the lack
237of UNIX domain socket support.
238
239This feature is not compatible with url.insteadof rewrites in the
240user's ~/.gitconfig. '%prog' is currently not able to perform the
241rewrite early enough to establish the ControlMaster tunnel.
242
243If the remote SSH daemon is Gerrit Code Review, version 2.0.10 or
244later is required to fix a server side protocol bug.
245
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700246"""
Gavin Makea2e3302023-03-11 06:46:20 +0000247 # A value of 0 means we want parallel jobs, but we'll determine the default
248 # value later on.
249 PARALLEL_JOBS = 0
Shawn O. Pearce6392c872011-09-22 17:44:31 -0700250
Gavin Makea2e3302023-03-11 06:46:20 +0000251 def _Options(self, p, show_smart=True):
252 p.add_option(
253 "--jobs-network",
254 default=None,
255 type=int,
256 metavar="JOBS",
257 help="number of network jobs to run in parallel (defaults to "
258 "--jobs or 1)",
259 )
260 p.add_option(
261 "--jobs-checkout",
262 default=None,
263 type=int,
264 metavar="JOBS",
265 help="number of local checkout jobs to run in parallel (defaults "
266 f"to --jobs or {DEFAULT_LOCAL_JOBS})",
267 )
Mike Frysinger49de8ef2021-04-09 00:21:02 -0400268
Gavin Makea2e3302023-03-11 06:46:20 +0000269 p.add_option(
270 "-f",
271 "--force-broken",
272 dest="force_broken",
273 action="store_true",
274 help="obsolete option (to be deleted in the future)",
275 )
276 p.add_option(
277 "--fail-fast",
278 dest="fail_fast",
279 action="store_true",
280 help="stop syncing after first error is hit",
281 )
282 p.add_option(
283 "--force-sync",
284 dest="force_sync",
285 action="store_true",
286 help="overwrite an existing git directory if it needs to "
287 "point to a different object directory. WARNING: this "
288 "may cause loss of data",
289 )
290 p.add_option(
291 "--force-remove-dirty",
292 dest="force_remove_dirty",
293 action="store_true",
294 help="force remove projects with uncommitted modifications if "
295 "projects no longer exist in the manifest. "
296 "WARNING: this may cause loss of data",
297 )
298 p.add_option(
299 "-l",
300 "--local-only",
301 dest="local_only",
302 action="store_true",
303 help="only update working tree, don't fetch",
304 )
305 p.add_option(
306 "--no-manifest-update",
307 "--nmu",
308 dest="mp_update",
309 action="store_false",
310 default="true",
311 help="use the existing manifest checkout as-is. "
312 "(do not update to the latest revision)",
313 )
314 p.add_option(
315 "-n",
316 "--network-only",
317 dest="network_only",
318 action="store_true",
319 help="fetch only, don't update working tree",
320 )
321 p.add_option(
322 "-d",
323 "--detach",
324 dest="detach_head",
325 action="store_true",
326 help="detach projects back to manifest revision",
327 )
328 p.add_option(
329 "-c",
330 "--current-branch",
331 dest="current_branch_only",
332 action="store_true",
333 help="fetch only current branch from server",
334 )
335 p.add_option(
336 "--no-current-branch",
337 dest="current_branch_only",
338 action="store_false",
339 help="fetch all branches from server",
340 )
341 p.add_option(
342 "-m",
343 "--manifest-name",
344 dest="manifest_name",
345 help="temporary manifest to use for this sync",
346 metavar="NAME.xml",
347 )
348 p.add_option(
349 "--clone-bundle",
350 action="store_true",
351 help="enable use of /clone.bundle on HTTP/HTTPS",
352 )
353 p.add_option(
354 "--no-clone-bundle",
355 dest="clone_bundle",
356 action="store_false",
357 help="disable use of /clone.bundle on HTTP/HTTPS",
358 )
359 p.add_option(
360 "-u",
361 "--manifest-server-username",
362 action="store",
363 dest="manifest_server_username",
364 help="username to authenticate with the manifest server",
365 )
366 p.add_option(
367 "-p",
368 "--manifest-server-password",
369 action="store",
370 dest="manifest_server_password",
371 help="password to authenticate with the manifest server",
372 )
373 p.add_option(
374 "--fetch-submodules",
375 dest="fetch_submodules",
376 action="store_true",
377 help="fetch submodules from server",
378 )
379 p.add_option(
380 "--use-superproject",
381 action="store_true",
382 help="use the manifest superproject to sync projects; implies -c",
383 )
384 p.add_option(
385 "--no-use-superproject",
386 action="store_false",
387 dest="use_superproject",
388 help="disable use of manifest superprojects",
389 )
390 p.add_option("--tags", action="store_true", help="fetch tags")
391 p.add_option(
392 "--no-tags",
393 dest="tags",
394 action="store_false",
395 help="don't fetch tags (default)",
396 )
397 p.add_option(
398 "--optimized-fetch",
399 dest="optimized_fetch",
400 action="store_true",
401 help="only fetch projects fixed to sha1 if revision does not exist "
402 "locally",
403 )
404 p.add_option(
405 "--retry-fetches",
406 default=0,
407 action="store",
408 type="int",
409 help="number of times to retry fetches on transient errors",
410 )
411 p.add_option(
412 "--prune",
413 action="store_true",
414 help="delete refs that no longer exist on the remote (default)",
415 )
416 p.add_option(
417 "--no-prune",
418 dest="prune",
419 action="store_false",
420 help="do not delete refs that no longer exist on the remote",
421 )
422 p.add_option(
423 "--auto-gc",
424 action="store_true",
425 default=None,
426 help="run garbage collection on all synced projects",
427 )
428 p.add_option(
429 "--no-auto-gc",
430 dest="auto_gc",
431 action="store_false",
432 help="do not run garbage collection on any projects (default)",
433 )
434 if show_smart:
435 p.add_option(
436 "-s",
437 "--smart-sync",
438 dest="smart_sync",
439 action="store_true",
440 help="smart sync using manifest from the latest known good "
441 "build",
442 )
443 p.add_option(
444 "-t",
445 "--smart-tag",
446 dest="smart_tag",
447 action="store",
448 help="smart sync using manifest from a known tag",
449 )
Shawn O. Pearce3e768c92009-04-10 16:59:36 -0700450
Gavin Makea2e3302023-03-11 06:46:20 +0000451 g = p.add_option_group("repo Version options")
452 g.add_option(
453 "--no-repo-verify",
454 dest="repo_verify",
455 default=True,
456 action="store_false",
457 help="do not verify repo source code",
458 )
459 g.add_option(
460 "--repo-upgraded",
461 dest="repo_upgraded",
462 action="store_true",
463 help=SUPPRESS_HELP,
464 )
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700465
Gavin Makea2e3302023-03-11 06:46:20 +0000466 def _GetBranch(self, manifest_project):
467 """Returns the branch name for getting the approved smartsync manifest.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000468
Gavin Makea2e3302023-03-11 06:46:20 +0000469 Args:
470 manifest_project: The manifestProject to query.
471 """
472 b = manifest_project.GetBranch(manifest_project.CurrentBranch)
473 branch = b.merge
474 if branch.startswith(R_HEADS):
475 branch = branch[len(R_HEADS) :]
476 return branch
Raman Tenneti8d43dea2021-02-07 16:30:27 -0800477
Gavin Makea2e3302023-03-11 06:46:20 +0000478 def _GetCurrentBranchOnly(self, opt, manifest):
479 """Returns whether current-branch or use-superproject options are
480 enabled.
Daniel Anderssond52ca422022-04-01 12:55:38 +0200481
Gavin Makea2e3302023-03-11 06:46:20 +0000482 Args:
483 opt: Program options returned from optparse. See _Options().
484 manifest: The manifest to use.
LaMont Jonesa46047a2022-04-07 21:57:06 +0000485
Gavin Makea2e3302023-03-11 06:46:20 +0000486 Returns:
487 True if a superproject is requested, otherwise the value of the
488 current_branch option (True, False or None).
489 """
490 return (
491 git_superproject.UseSuperproject(opt.use_superproject, manifest)
492 or opt.current_branch_only
493 )
Raman Tenneti2ae44d72021-03-23 15:12:27 -0700494
Gavin Makea2e3302023-03-11 06:46:20 +0000495 def _UpdateProjectsRevisionId(
496 self, opt, args, superproject_logging_data, manifest
497 ):
498 """Update revisionId of projects with the commit from the superproject.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800499
Gavin Makea2e3302023-03-11 06:46:20 +0000500 This function updates each project's revisionId with the commit hash
501 from the superproject. It writes the updated manifest into a file and
502 reloads the manifest from it. When appropriate, sub manifests are also
503 processed.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800504
Gavin Makea2e3302023-03-11 06:46:20 +0000505 Args:
506 opt: Program options returned from optparse. See _Options().
507 args: Arguments to pass to GetProjects. See the GetProjects
508 docstring for details.
509 superproject_logging_data: A dictionary of superproject data to log.
510 manifest: The manifest to use.
511 """
512 have_superproject = manifest.superproject or any(
513 m.superproject for m in manifest.all_children
514 )
515 if not have_superproject:
516 return
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000517
Gavin Makea2e3302023-03-11 06:46:20 +0000518 if opt.local_only and manifest.superproject:
519 manifest_path = manifest.superproject.manifest_path
520 if manifest_path:
521 self._ReloadManifest(manifest_path, manifest)
522 return
Raman Tennetiae86a462021-07-27 08:54:59 -0700523
Gavin Makea2e3302023-03-11 06:46:20 +0000524 all_projects = self.GetProjects(
525 args,
526 missing_ok=True,
527 submodules_ok=opt.fetch_submodules,
528 manifest=manifest,
529 all_manifests=not opt.this_manifest_only,
530 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000531
Gavin Makea2e3302023-03-11 06:46:20 +0000532 per_manifest = collections.defaultdict(list)
533 if opt.this_manifest_only:
534 per_manifest[manifest.path_prefix] = all_projects
535 else:
536 for p in all_projects:
537 per_manifest[p.manifest.path_prefix].append(p)
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000538
Gavin Makea2e3302023-03-11 06:46:20 +0000539 superproject_logging_data = {}
540 need_unload = False
541 for m in self.ManifestList(opt):
542 if m.path_prefix not in per_manifest:
543 continue
544 use_super = git_superproject.UseSuperproject(
545 opt.use_superproject, m
546 )
547 if superproject_logging_data:
548 superproject_logging_data["multimanifest"] = True
549 superproject_logging_data.update(
550 superproject=use_super,
551 haslocalmanifests=bool(m.HasLocalManifests),
552 hassuperprojecttag=bool(m.superproject),
553 )
554 if use_super and (m.IsMirror or m.IsArchive):
555 # Don't use superproject, because we have no working tree.
556 use_super = False
557 superproject_logging_data["superproject"] = False
558 superproject_logging_data["noworktree"] = True
559 if opt.use_superproject is not False:
560 print(
561 f"{m.path_prefix}: not using superproject because "
562 "there is no working tree."
563 )
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000564
Gavin Makea2e3302023-03-11 06:46:20 +0000565 if not use_super:
566 continue
567 m.superproject.SetQuiet(opt.quiet)
568 print_messages = git_superproject.PrintMessages(
569 opt.use_superproject, m
570 )
571 m.superproject.SetPrintMessages(print_messages)
572 update_result = m.superproject.UpdateProjectsRevisionId(
573 per_manifest[m.path_prefix], git_event_log=self.git_event_log
574 )
575 manifest_path = update_result.manifest_path
576 superproject_logging_data["updatedrevisionid"] = bool(manifest_path)
577 if manifest_path:
578 m.SetManifestOverride(manifest_path)
579 need_unload = True
580 else:
581 if print_messages:
582 print(
583 f"{m.path_prefix}: warning: Update of revisionId from "
584 "superproject has failed, repo sync will not use "
585 "superproject to fetch the source. ",
586 "Please resync with the --no-use-superproject option "
587 "to avoid this repo warning.",
588 file=sys.stderr,
589 )
590 if update_result.fatal and opt.use_superproject is not None:
591 sys.exit(1)
592 if need_unload:
593 m.outer_client.manifest.Unload()
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800594
Gavin Makea2e3302023-03-11 06:46:20 +0000595 def _FetchProjectList(self, opt, projects):
596 """Main function of the fetch worker.
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500597
Gavin Makea2e3302023-03-11 06:46:20 +0000598 The projects we're given share the same underlying git object store, so
599 we have to fetch them in serial.
Roy Lee18afd7f2010-05-09 04:32:08 +0800600
Gavin Mak551285f2023-05-04 04:48:43 +0000601 Delegates most of the work to _FetchOne.
David James8d201162013-10-11 17:03:19 -0700602
Gavin Makea2e3302023-03-11 06:46:20 +0000603 Args:
604 opt: Program options returned from optparse. See _Options().
605 projects: Projects to fetch.
606 """
607 return [self._FetchOne(opt, x) for x in projects]
David James8d201162013-10-11 17:03:19 -0700608
Gavin Makea2e3302023-03-11 06:46:20 +0000609 def _FetchOne(self, opt, project):
610 """Fetch git objects for a single project.
David James8d201162013-10-11 17:03:19 -0700611
Gavin Makea2e3302023-03-11 06:46:20 +0000612 Args:
613 opt: Program options returned from optparse. See _Options().
614 project: Project object for the project to fetch.
David James8d201162013-10-11 17:03:19 -0700615
Gavin Makea2e3302023-03-11 06:46:20 +0000616 Returns:
617 Whether the fetch was successful.
618 """
619 start = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000620 k = f"{project.name} @ {project.relpath}"
621 self._sync_dict[k] = start
Gavin Makea2e3302023-03-11 06:46:20 +0000622 success = False
623 remote_fetched = False
624 buf = io.StringIO()
625 try:
626 sync_result = project.Sync_NetworkHalf(
627 quiet=opt.quiet,
628 verbose=opt.verbose,
629 output_redir=buf,
630 current_branch_only=self._GetCurrentBranchOnly(
631 opt, project.manifest
632 ),
633 force_sync=opt.force_sync,
634 clone_bundle=opt.clone_bundle,
635 tags=opt.tags,
636 archive=project.manifest.IsArchive,
637 optimized_fetch=opt.optimized_fetch,
638 retry_fetches=opt.retry_fetches,
639 prune=opt.prune,
640 ssh_proxy=self.ssh_proxy,
641 clone_filter=project.manifest.CloneFilter,
642 partial_clone_exclude=project.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -0700643 clone_filter_for_depth=project.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +0000644 )
645 success = sync_result.success
646 remote_fetched = sync_result.remote_fetched
Doug Andersonfc06ced2011-03-16 15:49:18 -0700647
Gavin Makea2e3302023-03-11 06:46:20 +0000648 output = buf.getvalue()
649 if (opt.verbose or not success) and output:
650 print("\n" + output.rstrip())
Doug Andersonfc06ced2011-03-16 15:49:18 -0700651
Gavin Makea2e3302023-03-11 06:46:20 +0000652 if not success:
653 print(
654 "error: Cannot fetch %s from %s"
655 % (project.name, project.remote.url),
656 file=sys.stderr,
657 )
658 except KeyboardInterrupt:
659 print(f"Keyboard interrupt while processing {project.name}")
660 except GitError as e:
661 print("error.GitError: Cannot fetch %s" % str(e), file=sys.stderr)
662 except Exception as e:
663 print(
664 "error: Cannot fetch %s (%s: %s)"
665 % (project.name, type(e).__name__, str(e)),
666 file=sys.stderr,
667 )
Gavin Mak551285f2023-05-04 04:48:43 +0000668 del self._sync_dict[k]
Gavin Makea2e3302023-03-11 06:46:20 +0000669 raise
Mike Frysinger7b586f22021-02-23 18:38:39 -0500670
Gavin Makea2e3302023-03-11 06:46:20 +0000671 finish = time.time()
Gavin Mak551285f2023-05-04 04:48:43 +0000672 del self._sync_dict[k]
Gavin Makea2e3302023-03-11 06:46:20 +0000673 return _FetchOneResult(success, project, start, finish, remote_fetched)
David James8d201162013-10-11 17:03:19 -0700674
Gavin Makea2e3302023-03-11 06:46:20 +0000675 @classmethod
676 def _FetchInitChild(cls, ssh_proxy):
677 cls.ssh_proxy = ssh_proxy
Mike Frysinger339f2df2021-05-06 00:44:42 -0400678
Gavin Mak04cba4a2023-05-24 21:28:28 +0000679 def _GetSyncProgressMessage(self):
Gavin Mak551285f2023-05-04 04:48:43 +0000680 earliest_time = float("inf")
681 earliest_proj = None
682 for project, t in self._sync_dict.items():
683 if t < earliest_time:
684 earliest_time = t
685 earliest_proj = project
686
Josip Sokcevic71122f92023-05-26 02:44:37 +0000687 if not earliest_proj:
688 return None
689
Gavin Mak551285f2023-05-04 04:48:43 +0000690 elapsed = time.time() - earliest_time
Gavin Mak04cba4a2023-05-24 21:28:28 +0000691 jobs = jobs_str(len(self._sync_dict))
692 return f"{jobs} | {elapsed_str(elapsed)} {earliest_proj}"
Gavin Mak551285f2023-05-04 04:48:43 +0000693
Gavin Makea2e3302023-03-11 06:46:20 +0000694 def _Fetch(self, projects, opt, err_event, ssh_proxy):
695 ret = True
Mike Frysingerb2fa30a2021-02-24 00:15:32 -0500696
Gavin Makea2e3302023-03-11 06:46:20 +0000697 jobs = opt.jobs_network
698 fetched = set()
699 remote_fetched = set()
Gavin Makedcaa942023-04-27 05:58:57 +0000700 pm = Progress(
701 "Fetching",
702 len(projects),
703 delay=False,
704 quiet=opt.quiet,
705 show_elapsed=True,
Gavin Mak551285f2023-05-04 04:48:43 +0000706 elide=True,
Gavin Makedcaa942023-04-27 05:58:57 +0000707 )
Roy Lee18afd7f2010-05-09 04:32:08 +0800708
Gavin Mak551285f2023-05-04 04:48:43 +0000709 self._sync_dict = multiprocessing.Manager().dict()
710 sync_event = _threading.Event()
711
712 def _MonitorSyncLoop():
713 while True:
Gavin Mak04cba4a2023-05-24 21:28:28 +0000714 pm.update(inc=0, msg=self._GetSyncProgressMessage())
Gavin Mak551285f2023-05-04 04:48:43 +0000715 if sync_event.wait(timeout=1):
716 return
717
718 sync_progress_thread = _threading.Thread(target=_MonitorSyncLoop)
719 sync_progress_thread.daemon = True
720 sync_progress_thread.start()
721
Gavin Makea2e3302023-03-11 06:46:20 +0000722 objdir_project_map = dict()
723 for project in projects:
724 objdir_project_map.setdefault(project.objdir, []).append(project)
725 projects_list = list(objdir_project_map.values())
David James8d201162013-10-11 17:03:19 -0700726
Gavin Makea2e3302023-03-11 06:46:20 +0000727 def _ProcessResults(results_sets):
728 ret = True
729 for results in results_sets:
730 for result in results:
731 success = result.success
732 project = result.project
733 start = result.start
734 finish = result.finish
735 self._fetch_times.Set(project, finish - start)
736 self.event_log.AddSync(
737 project,
738 event_log.TASK_SYNC_NETWORK,
739 start,
740 finish,
741 success,
742 )
743 if result.remote_fetched:
744 remote_fetched.add(project)
745 # Check for any errors before running any more tasks.
746 # ...we'll let existing jobs finish, though.
747 if not success:
748 ret = False
749 else:
750 fetched.add(project.gitdir)
Gavin Mak551285f2023-05-04 04:48:43 +0000751 pm.update()
Gavin Makea2e3302023-03-11 06:46:20 +0000752 if not ret and opt.fail_fast:
753 break
Mike Frysingerb5d075d2021-03-01 00:56:38 -0500754 return ret
Xin Li745be2e2019-06-03 11:24:30 -0700755
Gavin Makea2e3302023-03-11 06:46:20 +0000756 # We pass the ssh proxy settings via the class. This allows
757 # multiprocessing to pickle it up when spawning children. We can't pass
758 # it as an argument to _FetchProjectList below as multiprocessing is
759 # unable to pickle those.
760 Sync.ssh_proxy = None
Mike Frysingerebf04a42021-02-23 20:48:04 -0500761
Gavin Makea2e3302023-03-11 06:46:20 +0000762 # NB: Multiprocessing is heavy, so don't spin it up for one job.
763 if len(projects_list) == 1 or jobs == 1:
764 self._FetchInitChild(ssh_proxy)
765 if not _ProcessResults(
766 self._FetchProjectList(opt, x) for x in projects_list
767 ):
768 ret = False
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000769 else:
Gavin Makea2e3302023-03-11 06:46:20 +0000770 # Favor throughput over responsiveness when quiet. It seems that
771 # imap() will yield results in batches relative to chunksize, so
772 # even as the children finish a sync, we won't see the result until
773 # one child finishes ~chunksize jobs. When using a large --jobs
774 # with large chunksize, this can be jarring as there will be a large
775 # initial delay where repo looks like it isn't doing anything and
776 # sits at 0%, but then suddenly completes a lot of jobs all at once.
777 # Since this code is more network bound, we can accept a bit more
778 # CPU overhead with a smaller chunksize so that the user sees more
779 # immediate & continuous feedback.
780 if opt.quiet:
781 chunksize = WORKER_BATCH_SIZE
782 else:
783 pm.update(inc=0, msg="warming up")
784 chunksize = 4
785 with multiprocessing.Pool(
786 jobs, initializer=self._FetchInitChild, initargs=(ssh_proxy,)
787 ) as pool:
788 results = pool.imap_unordered(
789 functools.partial(self._FetchProjectList, opt),
790 projects_list,
791 chunksize=chunksize,
792 )
793 if not _ProcessResults(results):
794 ret = False
795 pool.close()
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000796
Gavin Makea2e3302023-03-11 06:46:20 +0000797 # Cleanup the reference now that we're done with it, and we're going to
798 # release any resources it points to. If we don't, later
799 # multiprocessing usage (e.g. checkouts) will try to pickle and then
800 # crash.
801 del Sync.ssh_proxy
LaMont Jones7efab532022-09-01 15:41:12 +0000802
Gavin Mak551285f2023-05-04 04:48:43 +0000803 sync_event.set()
Gavin Makea2e3302023-03-11 06:46:20 +0000804 pm.end()
805 self._fetch_times.Save()
LaMont Jones43549d82022-11-30 19:55:30 +0000806
Gavin Makea2e3302023-03-11 06:46:20 +0000807 if not self.outer_client.manifest.IsArchive:
808 self._GCProjects(projects, opt, err_event)
Mike Frysinger65af2602021-04-08 22:47:44 -0400809
Gavin Makea2e3302023-03-11 06:46:20 +0000810 return _FetchResult(ret, fetched)
LaMont Jonesfa8d9392022-11-02 22:01:29 +0000811
Gavin Makea2e3302023-03-11 06:46:20 +0000812 def _FetchMain(
813 self, opt, args, all_projects, err_event, ssh_proxy, manifest
814 ):
815 """The main network fetch loop.
Mike Frysinger65af2602021-04-08 22:47:44 -0400816
Gavin Makea2e3302023-03-11 06:46:20 +0000817 Args:
818 opt: Program options returned from optparse. See _Options().
819 args: Command line args used to filter out projects.
820 all_projects: List of all projects that should be fetched.
821 err_event: Whether an error was hit while processing.
822 ssh_proxy: SSH manager for clients & masters.
823 manifest: The manifest to use.
Dave Borowitz18857212012-10-23 17:02:59 -0700824
Gavin Makea2e3302023-03-11 06:46:20 +0000825 Returns:
826 List of all projects that should be checked out.
827 """
828 rp = manifest.repoProject
LaMont Jones891e8f72022-09-08 20:17:58 +0000829
Gavin Makea2e3302023-03-11 06:46:20 +0000830 to_fetch = []
831 now = time.time()
832 if _ONE_DAY_S <= (now - rp.LastFetch):
833 to_fetch.append(rp)
834 to_fetch.extend(all_projects)
835 to_fetch.sort(key=self._fetch_times.Get, reverse=True)
Dave Borowitz18857212012-10-23 17:02:59 -0700836
Gavin Makea2e3302023-03-11 06:46:20 +0000837 result = self._Fetch(to_fetch, opt, err_event, ssh_proxy)
838 success = result.success
839 fetched = result.projects
840 if not success:
841 err_event.set()
Dave Borowitz18857212012-10-23 17:02:59 -0700842
Gavin Makea2e3302023-03-11 06:46:20 +0000843 _PostRepoFetch(rp, opt.repo_verify)
844 if opt.network_only:
845 # Bail out now; the rest touches the working tree.
846 if err_event.is_set():
847 print(
848 "\nerror: Exited sync due to fetch errors.\n",
849 file=sys.stderr,
850 )
851 sys.exit(1)
852 return _FetchMainResult([])
Dave Borowitz18857212012-10-23 17:02:59 -0700853
Gavin Makea2e3302023-03-11 06:46:20 +0000854 # Iteratively fetch missing and/or nested unregistered submodules.
855 previously_missing_set = set()
856 while True:
857 self._ReloadManifest(None, manifest)
858 all_projects = self.GetProjects(
859 args,
860 missing_ok=True,
861 submodules_ok=opt.fetch_submodules,
LaMont Jonesa46047a2022-04-07 21:57:06 +0000862 manifest=manifest,
Gavin Makea2e3302023-03-11 06:46:20 +0000863 all_manifests=not opt.this_manifest_only,
864 )
865 missing = []
866 for project in all_projects:
867 if project.gitdir not in fetched:
868 missing.append(project)
869 if not missing:
870 break
871 # Stop us from non-stopped fetching actually-missing repos: If set
872 # of missing repos has not been changed from last fetch, we break.
873 missing_set = set(p.name for p in missing)
874 if previously_missing_set == missing_set:
875 break
876 previously_missing_set = missing_set
877 result = self._Fetch(missing, opt, err_event, ssh_proxy)
878 success = result.success
879 new_fetched = result.projects
880 if not success:
881 err_event.set()
882 fetched.update(new_fetched)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700883
Gavin Makea2e3302023-03-11 06:46:20 +0000884 return _FetchMainResult(all_projects)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -0700885
Gavin Makea2e3302023-03-11 06:46:20 +0000886 def _CheckoutOne(self, detach_head, force_sync, project):
887 """Checkout work tree for one project
jiajia tanga590e642021-04-25 20:02:02 +0800888
Gavin Makea2e3302023-03-11 06:46:20 +0000889 Args:
890 detach_head: Whether to leave a detached HEAD.
891 force_sync: Force checking out of the repo.
892 project: Project object for the project to checkout.
jiajia tanga590e642021-04-25 20:02:02 +0800893
Gavin Makea2e3302023-03-11 06:46:20 +0000894 Returns:
895 Whether the fetch was successful.
896 """
897 start = time.time()
898 syncbuf = SyncBuffer(
899 project.manifest.manifestProject.config, detach_head=detach_head
LaMont Jonesbdcba7d2022-04-11 22:50:11 +0000900 )
Gavin Makea2e3302023-03-11 06:46:20 +0000901 success = False
David Pursehouse59b41742015-05-07 14:36:09 +0900902 try:
Gavin Makea2e3302023-03-11 06:46:20 +0000903 project.Sync_LocalHalf(syncbuf, force_sync=force_sync)
904 success = syncbuf.Finish()
905 except GitError as e:
906 print(
907 "error.GitError: Cannot checkout %s: %s"
908 % (project.name, str(e)),
909 file=sys.stderr,
910 )
911 except Exception as e:
912 print(
913 "error: Cannot checkout %s: %s: %s"
914 % (project.name, type(e).__name__, str(e)),
915 file=sys.stderr,
916 )
917 raise
Nico Sallembiena1bfd2c2010-04-06 10:40:01 -0700918
Gavin Makea2e3302023-03-11 06:46:20 +0000919 if not success:
920 print("error: Cannot checkout %s" % (project.name), file=sys.stderr)
921 finish = time.time()
922 return _CheckoutOneResult(success, project, start, finish)
Mike Frysinger5a033082019-09-23 19:21:20 -0400923
Gavin Makea2e3302023-03-11 06:46:20 +0000924 def _Checkout(self, all_projects, opt, err_results):
925 """Checkout projects listed in all_projects
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700926
Gavin Makea2e3302023-03-11 06:46:20 +0000927 Args:
928 all_projects: List of all projects that should be checked out.
929 opt: Program options returned from optparse. See _Options().
930 err_results: A list of strings, paths to git repos where checkout
931 failed.
932 """
933 # Only checkout projects with worktrees.
934 all_projects = [x for x in all_projects if x.worktree]
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -0700935
Gavin Makea2e3302023-03-11 06:46:20 +0000936 def _ProcessResults(pool, pm, results):
937 ret = True
938 for result in results:
939 success = result.success
940 project = result.project
941 start = result.start
942 finish = result.finish
943 self.event_log.AddSync(
944 project, event_log.TASK_SYNC_LOCAL, start, finish, success
945 )
946 # Check for any errors before running any more tasks.
947 # ...we'll let existing jobs finish, though.
948 if not success:
949 ret = False
950 err_results.append(
951 project.RelPath(local=opt.this_manifest_only)
952 )
953 if opt.fail_fast:
954 if pool:
955 pool.close()
956 return ret
957 pm.update(msg=project.name)
958 return ret
Shawn O. Pearcec9ef7442008-11-03 10:32:09 -0800959
Gavin Makea2e3302023-03-11 06:46:20 +0000960 return (
961 self.ExecuteInParallel(
962 opt.jobs_checkout,
963 functools.partial(
964 self._CheckoutOne, opt.detach_head, opt.force_sync
965 ),
966 all_projects,
967 callback=_ProcessResults,
968 output=Progress(
969 "Checking out", len(all_projects), quiet=opt.quiet
970 ),
971 )
972 and not err_results
973 )
Simran Basib9a1b732015-08-20 12:19:28 -0700974
Gavin Makea2e3302023-03-11 06:46:20 +0000975 @staticmethod
976 def _GetPreciousObjectsState(project: Project, opt):
977 """Get the preciousObjects state for the project.
Mike Frysinger355f4392022-07-20 17:15:29 -0400978
Gavin Makea2e3302023-03-11 06:46:20 +0000979 Args:
980 project (Project): the project to examine, and possibly correct.
981 opt (optparse.Values): options given to sync.
Raman Tenneti1fd7bc22021-02-04 14:39:38 -0800982
Gavin Makea2e3302023-03-11 06:46:20 +0000983 Returns:
984 Expected state of extensions.preciousObjects:
985 False: Should be disabled. (not present)
986 True: Should be enabled.
987 """
988 if project.use_git_worktrees:
989 return False
990 projects = project.manifest.GetProjectsWithName(
991 project.name, all_manifests=True
992 )
993 if len(projects) == 1:
994 return False
995 if len(projects) > 1:
996 # Objects are potentially shared with another project.
997 # See the logic in Project.Sync_NetworkHalf regarding UseAlternates.
998 # - When False, shared projects share (via symlink)
999 # .repo/project-objects/{PROJECT_NAME}.git as the one-and-only
1000 # objects directory. All objects are precious, since there is no
1001 # project with a complete set of refs.
1002 # - When True, shared projects share (via info/alternates)
1003 # .repo/project-objects/{PROJECT_NAME}.git as an alternate object
1004 # store, which is written only on the first clone of the project,
1005 # and is not written subsequently. (When Sync_NetworkHalf sees
1006 # that it exists, it makes sure that the alternates file points
1007 # there, and uses a project-local .git/objects directory for all
1008 # syncs going forward.
1009 # We do not support switching between the options. The environment
1010 # variable is present for testing and migration only.
1011 return not project.UseAlternates
Simran Basib9a1b732015-08-20 12:19:28 -07001012
Gavin Makea2e3302023-03-11 06:46:20 +00001013 return False
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001014
Gavin Makea2e3302023-03-11 06:46:20 +00001015 def _SetPreciousObjectsState(self, project: Project, opt):
1016 """Correct the preciousObjects state for the project.
1017
1018 Args:
1019 project: the project to examine, and possibly correct.
1020 opt: options given to sync.
1021 """
1022 expected = self._GetPreciousObjectsState(project, opt)
1023 actual = (
1024 project.config.GetBoolean("extensions.preciousObjects") or False
1025 )
1026 relpath = project.RelPath(local=opt.this_manifest_only)
1027
1028 if expected != actual:
1029 # If this is unexpected, log it and repair.
1030 Trace(
1031 f"{relpath} expected preciousObjects={expected}, got {actual}"
1032 )
1033 if expected:
1034 if not opt.quiet:
1035 print(
1036 "\r%s: Shared project %s found, disabling pruning."
1037 % (relpath, project.name)
1038 )
1039 if git_require((2, 7, 0)):
1040 project.EnableRepositoryExtension("preciousObjects")
1041 else:
1042 # This isn't perfect, but it's the best we can do with old
1043 # git.
1044 print(
1045 "\r%s: WARNING: shared projects are unreliable when "
1046 "using old versions of git; please upgrade to "
1047 "git-2.7.0+." % (relpath,),
1048 file=sys.stderr,
1049 )
1050 project.config.SetString("gc.pruneExpire", "never")
1051 else:
1052 if not opt.quiet:
1053 print(f"\r{relpath}: not shared, disabling pruning.")
1054 project.config.SetString("extensions.preciousObjects", None)
1055 project.config.SetString("gc.pruneExpire", None)
1056
1057 def _GCProjects(self, projects, opt, err_event):
1058 """Perform garbage collection.
1059
1060 If We are skipping garbage collection (opt.auto_gc not set), we still
1061 want to potentially mark objects precious, so that `git gc` does not
1062 discard shared objects.
1063 """
1064 if not opt.auto_gc:
1065 # Just repair preciousObjects state, and return.
1066 for project in projects:
1067 self._SetPreciousObjectsState(project, opt)
1068 return
1069
1070 pm = Progress(
1071 "Garbage collecting", len(projects), delay=False, quiet=opt.quiet
1072 )
1073 pm.update(inc=0, msg="prescan")
1074
1075 tidy_dirs = {}
1076 for project in projects:
1077 self._SetPreciousObjectsState(project, opt)
1078
1079 project.config.SetString("gc.autoDetach", "false")
1080 # Only call git gc once per objdir, but call pack-refs for the
1081 # remainder.
1082 if project.objdir not in tidy_dirs:
1083 tidy_dirs[project.objdir] = (
1084 True, # Run a full gc.
1085 project.bare_git,
1086 )
1087 elif project.gitdir not in tidy_dirs:
1088 tidy_dirs[project.gitdir] = (
1089 False, # Do not run a full gc; just run pack-refs.
1090 project.bare_git,
1091 )
1092
1093 jobs = opt.jobs
1094
1095 if jobs < 2:
1096 for run_gc, bare_git in tidy_dirs.values():
1097 pm.update(msg=bare_git._project.name)
1098
1099 if run_gc:
1100 bare_git.gc("--auto")
1101 else:
1102 bare_git.pack_refs()
1103 pm.end()
1104 return
1105
1106 cpu_count = os.cpu_count()
1107 config = {"pack.threads": cpu_count // jobs if cpu_count > jobs else 1}
1108
1109 threads = set()
1110 sem = _threading.Semaphore(jobs)
1111
1112 def tidy_up(run_gc, bare_git):
1113 pm.start(bare_git._project.name)
1114 try:
1115 try:
1116 if run_gc:
1117 bare_git.gc("--auto", config=config)
1118 else:
1119 bare_git.pack_refs(config=config)
1120 except GitError:
1121 err_event.set()
1122 except Exception:
1123 err_event.set()
1124 raise
1125 finally:
1126 pm.finish(bare_git._project.name)
1127 sem.release()
1128
1129 for run_gc, bare_git in tidy_dirs.values():
1130 if err_event.is_set() and opt.fail_fast:
1131 break
1132 sem.acquire()
1133 t = _threading.Thread(
1134 target=tidy_up,
1135 args=(
1136 run_gc,
1137 bare_git,
1138 ),
1139 )
1140 t.daemon = True
1141 threads.add(t)
1142 t.start()
1143
1144 for t in threads:
1145 t.join()
1146 pm.end()
1147
1148 def _ReloadManifest(self, manifest_name, manifest):
1149 """Reload the manfiest from the file specified by the |manifest_name|.
1150
1151 It unloads the manifest if |manifest_name| is None.
1152
1153 Args:
1154 manifest_name: Manifest file to be reloaded.
1155 manifest: The manifest to use.
1156 """
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001157 if manifest_name:
Gavin Makea2e3302023-03-11 06:46:20 +00001158 # Override calls Unload already.
1159 manifest.Override(manifest_name)
Dan Willemsen5ea32d12015-09-08 13:27:20 -07001160 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001161 manifest.Unload()
Simran Basib9a1b732015-08-20 12:19:28 -07001162
Gavin Makea2e3302023-03-11 06:46:20 +00001163 def UpdateProjectList(self, opt, manifest):
1164 """Update the cached projects list for |manifest|
LaMont Jonesbdcba7d2022-04-11 22:50:11 +00001165
Gavin Makea2e3302023-03-11 06:46:20 +00001166 In a multi-manifest checkout, each manifest has its own project.list.
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001167
Gavin Makea2e3302023-03-11 06:46:20 +00001168 Args:
1169 opt: Program options returned from optparse. See _Options().
1170 manifest: The manifest to use.
Mike Frysinger5a033082019-09-23 19:21:20 -04001171
Gavin Makea2e3302023-03-11 06:46:20 +00001172 Returns:
1173 0: success
1174 1: failure
1175 """
1176 new_project_paths = []
1177 for project in self.GetProjects(
1178 None, missing_ok=True, manifest=manifest, all_manifests=False
1179 ):
1180 if project.relpath:
1181 new_project_paths.append(project.relpath)
1182 file_name = "project.list"
1183 file_path = os.path.join(manifest.subdir, file_name)
1184 old_project_paths = []
Mike Frysinger339f2df2021-05-06 00:44:42 -04001185
Gavin Makea2e3302023-03-11 06:46:20 +00001186 if os.path.exists(file_path):
1187 with open(file_path, "r") as fd:
1188 old_project_paths = fd.read().split("\n")
1189 # In reversed order, so subfolders are deleted before parent folder.
1190 for path in sorted(old_project_paths, reverse=True):
1191 if not path:
1192 continue
1193 if path not in new_project_paths:
1194 # If the path has already been deleted, we don't need to do
1195 # it.
1196 gitdir = os.path.join(manifest.topdir, path, ".git")
1197 if os.path.exists(gitdir):
1198 project = Project(
1199 manifest=manifest,
1200 name=path,
1201 remote=RemoteSpec("origin"),
1202 gitdir=gitdir,
1203 objdir=gitdir,
1204 use_git_worktrees=os.path.isfile(gitdir),
1205 worktree=os.path.join(manifest.topdir, path),
1206 relpath=path,
1207 revisionExpr="HEAD",
1208 revisionId=None,
1209 groups=None,
1210 )
1211 if not project.DeleteWorktree(
1212 quiet=opt.quiet, force=opt.force_remove_dirty
1213 ):
1214 return 1
Mike Frysinger5a033082019-09-23 19:21:20 -04001215
Gavin Makea2e3302023-03-11 06:46:20 +00001216 new_project_paths.sort()
1217 with open(file_path, "w") as fd:
1218 fd.write("\n".join(new_project_paths))
1219 fd.write("\n")
1220 return 0
Che-Liang Chioub2bd91c2012-01-11 11:28:42 +08001221
Gavin Makea2e3302023-03-11 06:46:20 +00001222 def UpdateCopyLinkfileList(self, manifest):
1223 """Save all dests of copyfile and linkfile, and update them if needed.
Shawn O. Pearcecd1d7ff2009-06-04 16:15:53 -07001224
Gavin Makea2e3302023-03-11 06:46:20 +00001225 Returns:
1226 Whether update was successful.
1227 """
1228 new_paths = {}
1229 new_linkfile_paths = []
1230 new_copyfile_paths = []
1231 for project in self.GetProjects(
1232 None, missing_ok=True, manifest=manifest, all_manifests=False
1233 ):
1234 new_linkfile_paths.extend(x.dest for x in project.linkfiles)
1235 new_copyfile_paths.extend(x.dest for x in project.copyfiles)
Jaikumar Ganesh4f2517f2009-06-01 21:10:33 -07001236
Gavin Makea2e3302023-03-11 06:46:20 +00001237 new_paths = {
1238 "linkfile": new_linkfile_paths,
1239 "copyfile": new_copyfile_paths,
1240 }
jiajia tanga590e642021-04-25 20:02:02 +08001241
Gavin Makea2e3302023-03-11 06:46:20 +00001242 copylinkfile_name = "copy-link-files.json"
1243 copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
1244 old_copylinkfile_paths = {}
The Android Open Source Projectcf31fe92008-10-21 07:00:00 -07001245
Gavin Makea2e3302023-03-11 06:46:20 +00001246 if os.path.exists(copylinkfile_path):
1247 with open(copylinkfile_path, "rb") as fp:
1248 try:
1249 old_copylinkfile_paths = json.load(fp)
1250 except Exception:
1251 print(
1252 "error: %s is not a json formatted file."
1253 % copylinkfile_path,
1254 file=sys.stderr,
1255 )
1256 platform_utils.remove(copylinkfile_path)
1257 return False
Doug Anderson2b8db3c2010-11-01 15:08:06 -07001258
Gavin Makea2e3302023-03-11 06:46:20 +00001259 need_remove_files = []
1260 need_remove_files.extend(
1261 set(old_copylinkfile_paths.get("linkfile", []))
1262 - set(new_linkfile_paths)
1263 )
1264 need_remove_files.extend(
1265 set(old_copylinkfile_paths.get("copyfile", []))
1266 - set(new_copyfile_paths)
1267 )
Mike Frysinger5a033082019-09-23 19:21:20 -04001268
Gavin Makea2e3302023-03-11 06:46:20 +00001269 for need_remove_file in need_remove_files:
1270 # Try to remove the updated copyfile or linkfile.
1271 # So, if the file is not exist, nothing need to do.
1272 platform_utils.remove(need_remove_file, missing_ok=True)
Raman Tenneti7954de12021-07-28 14:36:49 -07001273
Gavin Makea2e3302023-03-11 06:46:20 +00001274 # Create copy-link-files.json, save dest path of "copyfile" and
1275 # "linkfile".
1276 with open(copylinkfile_path, "w", encoding="utf-8") as fp:
1277 json.dump(new_paths, fp)
1278 return True
Raman Tenneti7954de12021-07-28 14:36:49 -07001279
Gavin Makea2e3302023-03-11 06:46:20 +00001280 def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
1281 if not manifest.manifest_server:
1282 print(
1283 "error: cannot smart sync: no manifest server defined in "
1284 "manifest",
1285 file=sys.stderr,
1286 )
1287 sys.exit(1)
1288
1289 manifest_server = manifest.manifest_server
1290 if not opt.quiet:
1291 print("Using manifest server %s" % manifest_server)
1292
1293 if "@" not in manifest_server:
1294 username = None
1295 password = None
1296 if opt.manifest_server_username and opt.manifest_server_password:
1297 username = opt.manifest_server_username
1298 password = opt.manifest_server_password
1299 else:
1300 try:
1301 info = netrc.netrc()
1302 except IOError:
1303 # .netrc file does not exist or could not be opened.
1304 pass
1305 else:
1306 try:
1307 parse_result = urllib.parse.urlparse(manifest_server)
1308 if parse_result.hostname:
1309 auth = info.authenticators(parse_result.hostname)
1310 if auth:
1311 username, _account, password = auth
1312 else:
1313 print(
1314 "No credentials found for %s in .netrc"
1315 % parse_result.hostname,
1316 file=sys.stderr,
1317 )
1318 except netrc.NetrcParseError as e:
1319 print(
1320 "Error parsing .netrc file: %s" % e, file=sys.stderr
1321 )
1322
1323 if username and password:
1324 manifest_server = manifest_server.replace(
1325 "://", "://%s:%s@" % (username, password), 1
1326 )
1327
1328 transport = PersistentTransport(manifest_server)
1329 if manifest_server.startswith("persistent-"):
1330 manifest_server = manifest_server[len("persistent-") :]
1331
1332 try:
1333 server = xmlrpc.client.Server(manifest_server, transport=transport)
1334 if opt.smart_sync:
1335 branch = self._GetBranch(manifest.manifestProject)
1336
1337 if "SYNC_TARGET" in os.environ:
1338 target = os.environ["SYNC_TARGET"]
1339 [success, manifest_str] = server.GetApprovedManifest(
1340 branch, target
1341 )
1342 elif (
1343 "TARGET_PRODUCT" in os.environ
1344 and "TARGET_BUILD_VARIANT" in os.environ
1345 ):
1346 target = "%s-%s" % (
1347 os.environ["TARGET_PRODUCT"],
1348 os.environ["TARGET_BUILD_VARIANT"],
1349 )
1350 [success, manifest_str] = server.GetApprovedManifest(
1351 branch, target
1352 )
1353 else:
1354 [success, manifest_str] = server.GetApprovedManifest(branch)
1355 else:
1356 assert opt.smart_tag
1357 [success, manifest_str] = server.GetManifest(opt.smart_tag)
1358
1359 if success:
1360 manifest_name = os.path.basename(smart_sync_manifest_path)
1361 try:
1362 with open(smart_sync_manifest_path, "w") as f:
1363 f.write(manifest_str)
1364 except IOError as e:
1365 print(
1366 "error: cannot write manifest to %s:\n%s"
1367 % (smart_sync_manifest_path, e),
1368 file=sys.stderr,
1369 )
1370 sys.exit(1)
1371 self._ReloadManifest(manifest_name, manifest)
1372 else:
1373 print(
1374 "error: manifest server RPC call failed: %s" % manifest_str,
1375 file=sys.stderr,
1376 )
1377 sys.exit(1)
1378 except (socket.error, IOError, xmlrpc.client.Fault) as e:
1379 print(
1380 "error: cannot connect to manifest server %s:\n%s"
1381 % (manifest.manifest_server, e),
1382 file=sys.stderr,
1383 )
1384 sys.exit(1)
1385 except xmlrpc.client.ProtocolError as e:
1386 print(
1387 "error: cannot connect to manifest server %s:\n%d %s"
1388 % (manifest.manifest_server, e.errcode, e.errmsg),
1389 file=sys.stderr,
1390 )
1391 sys.exit(1)
1392
1393 return manifest_name
1394
1395 def _UpdateAllManifestProjects(self, opt, mp, manifest_name):
1396 """Fetch & update the local manifest project.
1397
1398 After syncing the manifest project, if the manifest has any sub
1399 manifests, those are recursively processed.
1400
1401 Args:
1402 opt: Program options returned from optparse. See _Options().
1403 mp: the manifestProject to query.
1404 manifest_name: Manifest file to be reloaded.
1405 """
1406 if not mp.standalone_manifest_url:
1407 self._UpdateManifestProject(opt, mp, manifest_name)
1408
1409 if mp.manifest.submanifests:
1410 for submanifest in mp.manifest.submanifests.values():
1411 child = submanifest.repo_client.manifest
1412 child.manifestProject.SyncWithPossibleInit(
1413 submanifest,
1414 current_branch_only=self._GetCurrentBranchOnly(opt, child),
1415 verbose=opt.verbose,
1416 tags=opt.tags,
1417 git_event_log=self.git_event_log,
1418 )
1419 self._UpdateAllManifestProjects(
1420 opt, child.manifestProject, None
1421 )
1422
1423 def _UpdateManifestProject(self, opt, mp, manifest_name):
1424 """Fetch & update the local manifest project.
1425
1426 Args:
1427 opt: Program options returned from optparse. See _Options().
1428 mp: the manifestProject to query.
1429 manifest_name: Manifest file to be reloaded.
1430 """
1431 if not opt.local_only:
1432 start = time.time()
1433 success = mp.Sync_NetworkHalf(
1434 quiet=opt.quiet,
1435 verbose=opt.verbose,
1436 current_branch_only=self._GetCurrentBranchOnly(
1437 opt, mp.manifest
1438 ),
1439 force_sync=opt.force_sync,
1440 tags=opt.tags,
1441 optimized_fetch=opt.optimized_fetch,
1442 retry_fetches=opt.retry_fetches,
1443 submodules=mp.manifest.HasSubmodules,
1444 clone_filter=mp.manifest.CloneFilter,
1445 partial_clone_exclude=mp.manifest.PartialCloneExclude,
Jason Chang17833322023-05-23 13:06:55 -07001446 clone_filter_for_depth=mp.manifest.CloneFilterForDepth,
Gavin Makea2e3302023-03-11 06:46:20 +00001447 )
1448 finish = time.time()
1449 self.event_log.AddSync(
1450 mp, event_log.TASK_SYNC_NETWORK, start, finish, success
1451 )
1452
1453 if mp.HasChanges:
1454 syncbuf = SyncBuffer(mp.config)
1455 start = time.time()
1456 mp.Sync_LocalHalf(syncbuf, submodules=mp.manifest.HasSubmodules)
1457 clean = syncbuf.Finish()
1458 self.event_log.AddSync(
1459 mp, event_log.TASK_SYNC_LOCAL, start, time.time(), clean
1460 )
1461 if not clean:
1462 sys.exit(1)
1463 self._ReloadManifest(manifest_name, mp.manifest)
1464
1465 def ValidateOptions(self, opt, args):
1466 if opt.force_broken:
1467 print(
1468 "warning: -f/--force-broken is now the default behavior, and "
1469 "the options are deprecated",
1470 file=sys.stderr,
1471 )
1472 if opt.network_only and opt.detach_head:
1473 self.OptionParser.error("cannot combine -n and -d")
1474 if opt.network_only and opt.local_only:
1475 self.OptionParser.error("cannot combine -n and -l")
1476 if opt.manifest_name and opt.smart_sync:
1477 self.OptionParser.error("cannot combine -m and -s")
1478 if opt.manifest_name and opt.smart_tag:
1479 self.OptionParser.error("cannot combine -m and -t")
1480 if opt.manifest_server_username or opt.manifest_server_password:
1481 if not (opt.smart_sync or opt.smart_tag):
1482 self.OptionParser.error(
1483 "-u and -p may only be combined with -s or -t"
1484 )
1485 if None in [
1486 opt.manifest_server_username,
1487 opt.manifest_server_password,
1488 ]:
1489 self.OptionParser.error("both -u and -p must be given")
1490
1491 if opt.prune is None:
1492 opt.prune = True
1493
1494 if opt.auto_gc is None and _AUTO_GC:
1495 print(
1496 f"Will run `git gc --auto` because {_REPO_AUTO_GC} is set.",
1497 f"{_REPO_AUTO_GC} is deprecated and will be removed in a ",
1498 "future release. Use `--auto-gc` instead.",
1499 file=sys.stderr,
1500 )
1501 opt.auto_gc = True
1502
1503 def _ValidateOptionsWithManifest(self, opt, mp):
1504 """Like ValidateOptions, but after we've updated the manifest.
1505
1506 Needed to handle sync-xxx option defaults in the manifest.
1507
1508 Args:
1509 opt: The options to process.
1510 mp: The manifest project to pull defaults from.
1511 """
1512 if not opt.jobs:
1513 # If the user hasn't made a choice, use the manifest value.
1514 opt.jobs = mp.manifest.default.sync_j
1515 if opt.jobs:
1516 # If --jobs has a non-default value, propagate it as the default for
1517 # --jobs-xxx flags too.
1518 if not opt.jobs_network:
1519 opt.jobs_network = opt.jobs
1520 if not opt.jobs_checkout:
1521 opt.jobs_checkout = opt.jobs
1522 else:
1523 # Neither user nor manifest have made a choice, so setup defaults.
1524 if not opt.jobs_network:
1525 opt.jobs_network = 1
1526 if not opt.jobs_checkout:
1527 opt.jobs_checkout = DEFAULT_LOCAL_JOBS
1528 opt.jobs = os.cpu_count()
1529
1530 # Try to stay under user rlimit settings.
1531 #
1532 # Since each worker requires at 3 file descriptors to run `git fetch`,
1533 # use that to scale down the number of jobs. Unfortunately there isn't
1534 # an easy way to determine this reliably as systems change, but it was
1535 # last measured by hand in 2011.
1536 soft_limit, _ = _rlimit_nofile()
1537 jobs_soft_limit = max(1, (soft_limit - 5) // 3)
1538 opt.jobs = min(opt.jobs, jobs_soft_limit)
1539 opt.jobs_network = min(opt.jobs_network, jobs_soft_limit)
1540 opt.jobs_checkout = min(opt.jobs_checkout, jobs_soft_limit)
1541
1542 def Execute(self, opt, args):
1543 manifest = self.outer_manifest
1544 if not opt.outer_manifest:
1545 manifest = self.manifest
1546
1547 if opt.manifest_name:
1548 manifest.Override(opt.manifest_name)
1549
1550 manifest_name = opt.manifest_name
1551 smart_sync_manifest_path = os.path.join(
1552 manifest.manifestProject.worktree, "smart_sync_override.xml"
1553 )
1554
1555 if opt.clone_bundle is None:
1556 opt.clone_bundle = manifest.CloneBundle
1557
1558 if opt.smart_sync or opt.smart_tag:
1559 manifest_name = self._SmartSyncSetup(
1560 opt, smart_sync_manifest_path, manifest
1561 )
1562 else:
1563 if os.path.isfile(smart_sync_manifest_path):
1564 try:
1565 platform_utils.remove(smart_sync_manifest_path)
1566 except OSError as e:
1567 print(
1568 "error: failed to remove existing smart sync override "
1569 "manifest: %s" % e,
1570 file=sys.stderr,
1571 )
1572
1573 err_event = multiprocessing.Event()
1574
1575 rp = manifest.repoProject
1576 rp.PreSync()
1577 cb = rp.CurrentBranch
1578 if cb:
1579 base = rp.GetBranch(cb).merge
1580 if not base or not base.startswith("refs/heads/"):
1581 print(
1582 "warning: repo is not tracking a remote branch, so it will "
1583 "not receive updates; run `repo init --repo-rev=stable` to "
1584 "fix.",
1585 file=sys.stderr,
1586 )
1587
1588 for m in self.ManifestList(opt):
1589 if not m.manifestProject.standalone_manifest_url:
1590 m.manifestProject.PreSync()
1591
1592 if opt.repo_upgraded:
1593 _PostRepoUpgrade(manifest, quiet=opt.quiet)
1594
1595 mp = manifest.manifestProject
Jason Chang17833322023-05-23 13:06:55 -07001596
1597 if _REPO_ALLOW_SHALLOW is not None:
1598 if _REPO_ALLOW_SHALLOW == "1":
1599 mp.ConfigureCloneFilterForDepth(None)
1600 elif (
1601 _REPO_ALLOW_SHALLOW == "0" and mp.clone_filter_for_depth is None
1602 ):
1603 mp.ConfigureCloneFilterForDepth("blob:none")
1604
Gavin Makea2e3302023-03-11 06:46:20 +00001605 if opt.mp_update:
1606 self._UpdateAllManifestProjects(opt, mp, manifest_name)
1607 else:
1608 print("Skipping update of local manifest project.")
1609
1610 # Now that the manifests are up-to-date, setup options whose defaults
1611 # might be in the manifest.
1612 self._ValidateOptionsWithManifest(opt, mp)
1613
1614 superproject_logging_data = {}
1615 self._UpdateProjectsRevisionId(
1616 opt, args, superproject_logging_data, manifest
1617 )
1618
1619 if self.gitc_manifest:
1620 gitc_manifest_projects = self.GetProjects(args, missing_ok=True)
1621 gitc_projects = []
1622 opened_projects = []
1623 for project in gitc_manifest_projects:
1624 if (
1625 project.relpath in self.gitc_manifest.paths
1626 and self.gitc_manifest.paths[project.relpath].old_revision
1627 ):
1628 opened_projects.append(project.relpath)
1629 else:
1630 gitc_projects.append(project.relpath)
1631
1632 if not args:
1633 gitc_projects = None
1634
1635 if gitc_projects != [] and not opt.local_only:
1636 print(
1637 "Updating GITC client: %s"
1638 % self.gitc_manifest.gitc_client_name
1639 )
1640 manifest = GitcManifest(
1641 self.repodir, self.gitc_manifest.gitc_client_name
1642 )
1643 if manifest_name:
1644 manifest.Override(manifest_name)
1645 else:
1646 manifest.Override(manifest.manifestFile)
1647 gitc_utils.generate_gitc_manifest(
1648 self.gitc_manifest, manifest, gitc_projects
1649 )
1650 print("GITC client successfully synced.")
1651
1652 # The opened projects need to be synced as normal, therefore we
1653 # generate a new args list to represent the opened projects.
1654 # TODO: make this more reliable -- if there's a project name/path
1655 # overlap, this may choose the wrong project.
1656 args = [
1657 os.path.relpath(manifest.paths[path].worktree, os.getcwd())
1658 for path in opened_projects
1659 ]
1660 if not args:
1661 return
1662
1663 all_projects = self.GetProjects(
1664 args,
1665 missing_ok=True,
1666 submodules_ok=opt.fetch_submodules,
1667 manifest=manifest,
1668 all_manifests=not opt.this_manifest_only,
1669 )
1670
1671 err_network_sync = False
1672 err_update_projects = False
1673 err_update_linkfiles = False
1674
1675 self._fetch_times = _FetchTimes(manifest)
1676 if not opt.local_only:
1677 with multiprocessing.Manager() as manager:
1678 with ssh.ProxyManager(manager) as ssh_proxy:
1679 # Initialize the socket dir once in the parent.
1680 ssh_proxy.sock()
1681 result = self._FetchMain(
1682 opt, args, all_projects, err_event, ssh_proxy, manifest
1683 )
1684 all_projects = result.all_projects
1685
1686 if opt.network_only:
1687 return
1688
1689 # If we saw an error, exit with code 1 so that other scripts can
1690 # check.
1691 if err_event.is_set():
1692 err_network_sync = True
1693 if opt.fail_fast:
1694 print(
1695 "\nerror: Exited sync due to fetch errors.\n"
1696 "Local checkouts *not* updated. Resolve network issues "
1697 "& retry.\n"
1698 "`repo sync -l` will update some local checkouts.",
1699 file=sys.stderr,
1700 )
1701 sys.exit(1)
1702
1703 for m in self.ManifestList(opt):
1704 if m.IsMirror or m.IsArchive:
1705 # Bail out now, we have no working tree.
1706 continue
1707
1708 if self.UpdateProjectList(opt, m):
1709 err_event.set()
1710 err_update_projects = True
1711 if opt.fail_fast:
1712 print(
1713 "\nerror: Local checkouts *not* updated.",
1714 file=sys.stderr,
1715 )
1716 sys.exit(1)
1717
1718 err_update_linkfiles = not self.UpdateCopyLinkfileList(m)
1719 if err_update_linkfiles:
1720 err_event.set()
1721 if opt.fail_fast:
1722 print(
1723 "\nerror: Local update copyfile or linkfile failed.",
1724 file=sys.stderr,
1725 )
1726 sys.exit(1)
1727
1728 err_results = []
1729 # NB: We don't exit here because this is the last step.
1730 err_checkout = not self._Checkout(all_projects, opt, err_results)
1731 if err_checkout:
1732 err_event.set()
1733
1734 printed_notices = set()
1735 # If there's a notice that's supposed to print at the end of the sync,
1736 # print it now... But avoid printing duplicate messages, and preserve
1737 # order.
1738 for m in sorted(self.ManifestList(opt), key=lambda x: x.path_prefix):
1739 if m.notice and m.notice not in printed_notices:
1740 print(m.notice)
1741 printed_notices.add(m.notice)
1742
1743 # If we saw an error, exit with code 1 so that other scripts can check.
1744 if err_event.is_set():
Josip Sokcevic131fc962023-05-12 17:00:46 -07001745 # Add a new line so it's easier to read.
1746 print("\n", file=sys.stderr)
1747
1748 def print_and_log(err_msg):
1749 self.git_event_log.ErrorEvent(err_msg)
1750 print(err_msg, file=sys.stderr)
1751
1752 print_and_log("error: Unable to fully sync the tree")
Gavin Makea2e3302023-03-11 06:46:20 +00001753 if err_network_sync:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001754 print_and_log("error: Downloading network changes failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001755 if err_update_projects:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001756 print_and_log("error: Updating local project lists failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001757 if err_update_linkfiles:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001758 print_and_log("error: Updating copyfiles or linkfiles failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001759 if err_checkout:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001760 print_and_log("error: Checking out local projects failed.")
Gavin Makea2e3302023-03-11 06:46:20 +00001761 if err_results:
Josip Sokcevic131fc962023-05-12 17:00:46 -07001762 # Don't log repositories, as it may contain sensitive info.
Gavin Makea2e3302023-03-11 06:46:20 +00001763 print(
1764 "Failing repos:\n%s" % "\n".join(err_results),
1765 file=sys.stderr,
1766 )
Josip Sokcevic131fc962023-05-12 17:00:46 -07001767 # Not useful to log.
Gavin Makea2e3302023-03-11 06:46:20 +00001768 print(
1769 'Try re-running with "-j1 --fail-fast" to exit at the first '
1770 "error.",
1771 file=sys.stderr,
1772 )
1773 sys.exit(1)
1774
1775 # Log the previous sync analysis state from the config.
1776 self.git_event_log.LogDataConfigEvents(
1777 mp.config.GetSyncAnalysisStateData(), "previous_sync_state"
1778 )
1779
1780 # Update and log with the new sync analysis state.
1781 mp.config.UpdateSyncAnalysisState(opt, superproject_logging_data)
1782 self.git_event_log.LogDataConfigEvents(
1783 mp.config.GetSyncAnalysisStateData(), "current_sync_state"
1784 )
1785
1786 if not opt.quiet:
1787 print("repo sync has finished successfully.")
Mike Frysingere19d9e12020-02-12 11:23:32 -05001788
David Pursehouse819827a2020-02-12 15:20:19 +09001789
Shawn O. Pearce80d2ceb2012-10-26 12:23:05 -07001790def _PostRepoUpgrade(manifest, quiet=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001791 # Link the docs for the internal .repo/ layout for people.
1792 link = os.path.join(manifest.repodir, "internal-fs-layout.md")
1793 if not platform_utils.islink(link):
1794 target = os.path.join("repo", "docs", "internal-fs-layout.md")
1795 try:
1796 platform_utils.symlink(target, link)
1797 except Exception:
1798 pass
Mike Frysingerfdeb20f2021-11-14 03:53:04 -05001799
Gavin Makea2e3302023-03-11 06:46:20 +00001800 wrapper = Wrapper()
1801 if wrapper.NeedSetupGnuPG():
1802 wrapper.SetupGnuPG(quiet)
1803 for project in manifest.projects:
1804 if project.Exists:
1805 project.PostRepoUpgrade()
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001806
David Pursehouse819827a2020-02-12 15:20:19 +09001807
Mike Frysingerc58ec4d2020-02-17 14:36:08 -05001808def _PostRepoFetch(rp, repo_verify=True, verbose=False):
Gavin Makea2e3302023-03-11 06:46:20 +00001809 if rp.HasChanges:
1810 print("info: A new version of repo is available", file=sys.stderr)
1811 wrapper = Wrapper()
1812 try:
1813 rev = rp.bare_git.describe(rp.GetRevisionId())
1814 except GitError:
1815 rev = None
1816 _, new_rev = wrapper.check_repo_rev(
1817 rp.gitdir, rev, repo_verify=repo_verify
1818 )
1819 # See if we're held back due to missing signed tag.
1820 current_revid = rp.bare_git.rev_parse("HEAD")
1821 new_revid = rp.bare_git.rev_parse("--verify", new_rev)
1822 if current_revid != new_revid:
1823 # We want to switch to the new rev, but also not trash any
1824 # uncommitted changes. This helps with local testing/hacking.
1825 # If a local change has been made, we will throw that away.
1826 # We also have to make sure this will switch to an older commit if
1827 # that's the latest tag in order to support release rollback.
1828 try:
1829 rp.work_git.reset("--keep", new_rev)
1830 except GitError as e:
1831 sys.exit(str(e))
1832 print("info: Restarting repo with latest version", file=sys.stderr)
1833 raise RepoChangedException(["--repo-upgraded"])
1834 else:
1835 print(
1836 "warning: Skipped upgrade to unverified version",
1837 file=sys.stderr,
1838 )
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001839 else:
Gavin Makea2e3302023-03-11 06:46:20 +00001840 if verbose:
1841 print(
1842 "repo version %s is current" % rp.work_git.describe(HEAD),
1843 file=sys.stderr,
1844 )
Shawn O. Pearcee756c412009-04-13 11:51:15 -07001845
David Pursehouse819827a2020-02-12 15:20:19 +09001846
Dave Borowitz67700e92012-10-23 15:00:54 -07001847class _FetchTimes(object):
Gavin Makea2e3302023-03-11 06:46:20 +00001848 _ALPHA = 0.5
Dave Borowitzd9478582012-10-23 16:35:39 -07001849
Gavin Makea2e3302023-03-11 06:46:20 +00001850 def __init__(self, manifest):
1851 self._path = os.path.join(manifest.repodir, ".repo_fetchtimes.json")
Gavin Mak041f9772023-05-10 20:41:12 +00001852 self._saved = None
1853 self._seen = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001854
Gavin Makea2e3302023-03-11 06:46:20 +00001855 def Get(self, project):
1856 self._Load()
Gavin Mak041f9772023-05-10 20:41:12 +00001857 return self._saved.get(project.name, _ONE_DAY_S)
Dave Borowitz67700e92012-10-23 15:00:54 -07001858
Gavin Makea2e3302023-03-11 06:46:20 +00001859 def Set(self, project, t):
Gavin Makea2e3302023-03-11 06:46:20 +00001860 name = project.name
Gavin Mak041f9772023-05-10 20:41:12 +00001861
1862 # For shared projects, save the longest time.
1863 self._seen[name] = max(self._seen.get(name, 0), t)
Dave Borowitz67700e92012-10-23 15:00:54 -07001864
Gavin Makea2e3302023-03-11 06:46:20 +00001865 def _Load(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001866 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001867 try:
1868 with open(self._path) as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001869 self._saved = json.load(f)
Gavin Makea2e3302023-03-11 06:46:20 +00001870 except (IOError, ValueError):
1871 platform_utils.remove(self._path, missing_ok=True)
Gavin Mak041f9772023-05-10 20:41:12 +00001872 self._saved = {}
Dave Borowitz67700e92012-10-23 15:00:54 -07001873
Gavin Makea2e3302023-03-11 06:46:20 +00001874 def Save(self):
Gavin Mak041f9772023-05-10 20:41:12 +00001875 if self._saved is None:
Gavin Makea2e3302023-03-11 06:46:20 +00001876 return
Dave Borowitzd9478582012-10-23 16:35:39 -07001877
Gavin Mak041f9772023-05-10 20:41:12 +00001878 for name, t in self._seen.items():
1879 # Keep a moving average across the previous/current sync runs.
1880 old = self._saved.get(name, t)
1881 self._seen[name] = (self._ALPHA * t) + ((1 - self._ALPHA) * old)
Dave Borowitzd9478582012-10-23 16:35:39 -07001882
Gavin Makea2e3302023-03-11 06:46:20 +00001883 try:
1884 with open(self._path, "w") as f:
Gavin Mak041f9772023-05-10 20:41:12 +00001885 json.dump(self._seen, f, indent=2)
Gavin Makea2e3302023-03-11 06:46:20 +00001886 except (IOError, TypeError):
1887 platform_utils.remove(self._path, missing_ok=True)
1888
Dan Willemsen0745bb22015-08-17 13:41:45 -07001889
1890# This is a replacement for xmlrpc.client.Transport using urllib2
1891# and supporting persistent-http[s]. It cannot change hosts from
1892# request to request like the normal transport, the real url
1893# is passed during initialization.
David Pursehouse819827a2020-02-12 15:20:19 +09001894
1895
Dan Willemsen0745bb22015-08-17 13:41:45 -07001896class PersistentTransport(xmlrpc.client.Transport):
Gavin Makea2e3302023-03-11 06:46:20 +00001897 def __init__(self, orig_host):
1898 self.orig_host = orig_host
Dan Willemsen0745bb22015-08-17 13:41:45 -07001899
Gavin Makea2e3302023-03-11 06:46:20 +00001900 def request(self, host, handler, request_body, verbose=False):
1901 with GetUrlCookieFile(self.orig_host, not verbose) as (
1902 cookiefile,
1903 proxy,
1904 ):
1905 # Python doesn't understand cookies with the #HttpOnly_ prefix
1906 # Since we're only using them for HTTP, copy the file temporarily,
1907 # stripping those prefixes away.
1908 if cookiefile:
1909 tmpcookiefile = tempfile.NamedTemporaryFile(mode="w")
1910 tmpcookiefile.write("# HTTP Cookie File")
1911 try:
1912 with open(cookiefile) as f:
1913 for line in f:
1914 if line.startswith("#HttpOnly_"):
1915 line = line[len("#HttpOnly_") :]
1916 tmpcookiefile.write(line)
1917 tmpcookiefile.flush()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001918
Gavin Makea2e3302023-03-11 06:46:20 +00001919 cookiejar = cookielib.MozillaCookieJar(tmpcookiefile.name)
1920 try:
1921 cookiejar.load()
1922 except cookielib.LoadError:
1923 cookiejar = cookielib.CookieJar()
1924 finally:
1925 tmpcookiefile.close()
1926 else:
1927 cookiejar = cookielib.CookieJar()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001928
Gavin Makea2e3302023-03-11 06:46:20 +00001929 proxyhandler = urllib.request.ProxyHandler
1930 if proxy:
1931 proxyhandler = urllib.request.ProxyHandler(
1932 {"http": proxy, "https": proxy}
1933 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07001934
Gavin Makea2e3302023-03-11 06:46:20 +00001935 opener = urllib.request.build_opener(
1936 urllib.request.HTTPCookieProcessor(cookiejar), proxyhandler
1937 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07001938
Gavin Makea2e3302023-03-11 06:46:20 +00001939 url = urllib.parse.urljoin(self.orig_host, handler)
1940 parse_results = urllib.parse.urlparse(url)
Dan Willemsen0745bb22015-08-17 13:41:45 -07001941
Gavin Makea2e3302023-03-11 06:46:20 +00001942 scheme = parse_results.scheme
1943 if scheme == "persistent-http":
1944 scheme = "http"
1945 if scheme == "persistent-https":
1946 # If we're proxying through persistent-https, use http. The
1947 # proxy itself will do the https.
1948 if proxy:
1949 scheme = "http"
1950 else:
1951 scheme = "https"
Dan Willemsen0745bb22015-08-17 13:41:45 -07001952
Gavin Makea2e3302023-03-11 06:46:20 +00001953 # Parse out any authentication information using the base class.
1954 host, extra_headers, _ = self.get_host_info(parse_results.netloc)
Dan Willemsen0745bb22015-08-17 13:41:45 -07001955
Gavin Makea2e3302023-03-11 06:46:20 +00001956 url = urllib.parse.urlunparse(
1957 (
1958 scheme,
1959 host,
1960 parse_results.path,
1961 parse_results.params,
1962 parse_results.query,
1963 parse_results.fragment,
1964 )
1965 )
Dan Willemsen0745bb22015-08-17 13:41:45 -07001966
Gavin Makea2e3302023-03-11 06:46:20 +00001967 request = urllib.request.Request(url, request_body)
1968 if extra_headers is not None:
1969 for name, header in extra_headers:
1970 request.add_header(name, header)
1971 request.add_header("Content-Type", "text/xml")
1972 try:
1973 response = opener.open(request)
1974 except urllib.error.HTTPError as e:
1975 if e.code == 501:
1976 # We may have been redirected through a login process
1977 # but our POST turned into a GET. Retry.
1978 response = opener.open(request)
1979 else:
1980 raise
Dan Willemsen0745bb22015-08-17 13:41:45 -07001981
Gavin Makea2e3302023-03-11 06:46:20 +00001982 p, u = xmlrpc.client.getparser()
1983 # Response should be fairly small, so read it all at once.
1984 # This way we can show it to the user in case of error (e.g. HTML).
1985 data = response.read()
1986 try:
1987 p.feed(data)
1988 except xml.parsers.expat.ExpatError as e:
1989 raise IOError(
1990 f"Parsing the manifest failed: {e}\n"
1991 f"Please report this to your manifest server admin.\n"
1992 f'Here is the full response:\n{data.decode("utf-8")}'
1993 )
1994 p.close()
1995 return u.close()
Dan Willemsen0745bb22015-08-17 13:41:45 -07001996
Gavin Makea2e3302023-03-11 06:46:20 +00001997 def close(self):
1998 pass