blob: 27381421124fd9889f76b2ddf56847fc476f3b58 [file] [log] [blame]
Mike Frysingerd6b52e62023-07-18 21:48:36 +00001#!/usr/bin/env python
Mike Frysingereb386eb2020-11-30 18:17:40 +00002#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""Repo launcher.
18
19This is a standalone tool that people may copy to anywhere in their system.
20It is used to get an initial repo client checkout, and after that it runs the
21copy of repo in the checkout.
22"""
23
Mike Frysingereb386eb2020-11-30 18:17:40 +000024import datetime
25import os
26import platform
27import shlex
28import subprocess
29import sys
30
31
Mike Frysinger9b5dd7a2021-01-13 19:42:12 +000032# These should never be newer than the main.py version since this needs to be a
33# bit more flexible with older systems. See that file for more details on the
34# versions we select.
35MIN_PYTHON_VERSION_SOFT = (3, 6)
Josip Sokcevicfd6e5272023-10-31 17:30:59 +000036MIN_PYTHON_VERSION_HARD = (3, 6)
Mike Frysinger9b5dd7a2021-01-13 19:42:12 +000037
38
Mike Frysingereb386eb2020-11-30 18:17:40 +000039# Keep basic logic in sync with repo_trace.py.
Josip Sokcevicfd6e5272023-10-31 17:30:59 +000040class Trace:
Mike Frysinger7e251262023-09-05 17:05:29 +000041 """Trace helper logic."""
Mike Frysingereb386eb2020-11-30 18:17:40 +000042
Mike Frysinger7e251262023-09-05 17:05:29 +000043 REPO_TRACE = "REPO_TRACE"
Mike Frysingereb386eb2020-11-30 18:17:40 +000044
Mike Frysinger7e251262023-09-05 17:05:29 +000045 def __init__(self):
46 self.set(os.environ.get(self.REPO_TRACE) == "1")
Mike Frysingereb386eb2020-11-30 18:17:40 +000047
Mike Frysinger7e251262023-09-05 17:05:29 +000048 def set(self, value):
49 self.enabled = bool(value)
Mike Frysingereb386eb2020-11-30 18:17:40 +000050
Mike Frysinger7e251262023-09-05 17:05:29 +000051 def print(self, *args, **kwargs):
52 if self.enabled:
53 print(*args, **kwargs)
Mike Frysingereb386eb2020-11-30 18:17:40 +000054
55
56trace = Trace()
57
58
59def exec_command(cmd):
Mike Frysinger7e251262023-09-05 17:05:29 +000060 """Execute |cmd| or return None on failure."""
61 trace.print(":", " ".join(cmd))
62 try:
63 if platform.system() == "Windows":
64 ret = subprocess.call(cmd)
65 sys.exit(ret)
66 else:
67 os.execvp(cmd[0], cmd)
68 except Exception:
69 pass
Mike Frysingereb386eb2020-11-30 18:17:40 +000070
71
72def check_python_version():
Mike Frysinger7e251262023-09-05 17:05:29 +000073 """Make sure the active Python version is recent enough."""
Mike Frysingereb386eb2020-11-30 18:17:40 +000074
Mike Frysinger7e251262023-09-05 17:05:29 +000075 def reexec(prog):
76 exec_command([prog] + sys.argv)
Mike Frysingereb386eb2020-11-30 18:17:40 +000077
Mike Frysinger7e251262023-09-05 17:05:29 +000078 ver = sys.version_info
79 major = ver.major
80 minor = ver.minor
Mike Frysingereb386eb2020-11-30 18:17:40 +000081
Mike Frysinger7e251262023-09-05 17:05:29 +000082 # Abort on very old Python 2 versions.
83 if (major, minor) < (2, 7):
84 print(
85 "repo: error: Your Python version is too old. "
86 "Please use Python {}.{} or newer instead.".format(
87 *MIN_PYTHON_VERSION_SOFT
88 ),
89 file=sys.stderr,
90 )
91 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +000092
Mike Frysinger7e251262023-09-05 17:05:29 +000093 # Try to re-exec the version specific Python 3 if needed.
94 if (major, minor) < MIN_PYTHON_VERSION_SOFT:
95 # Python makes releases ~once a year, so try our min version +10 to help
96 # bridge the gap. This is the fallback anyways so perf isn't critical.
97 min_major, min_minor = MIN_PYTHON_VERSION_SOFT
98 for inc in range(0, 10):
Josip Sokcevicfd6e5272023-10-31 17:30:59 +000099 reexec(f"python{min_major}.{min_minor + inc}")
Mike Frysinger9b5dd7a2021-01-13 19:42:12 +0000100
Mike Frysinger7e251262023-09-05 17:05:29 +0000101 # Fallback to older versions if possible.
102 for inc in range(
103 MIN_PYTHON_VERSION_SOFT[1] - MIN_PYTHON_VERSION_HARD[1], 0, -1
104 ):
105 # Don't downgrade, and don't reexec ourselves (which would infinite loop).
106 if (min_major, min_minor - inc) <= (major, minor):
107 break
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000108 reexec(f"python{min_major}.{min_minor - inc}")
Mike Frysingereb386eb2020-11-30 18:17:40 +0000109
Mike Frysinger7e251262023-09-05 17:05:29 +0000110 # Try the generic Python 3 wrapper, but only if it's new enough. If it
111 # isn't, we want to just give up below and make the user resolve things.
112 try:
113 proc = subprocess.Popen(
114 [
115 "python3",
116 "-c",
117 "import sys; "
118 "print(sys.version_info.major, sys.version_info.minor)",
119 ],
120 stdout=subprocess.PIPE,
121 stderr=subprocess.PIPE,
122 )
123 (output, _) = proc.communicate()
124 python3_ver = tuple(int(x) for x in output.decode("utf-8").split())
125 except (OSError, subprocess.CalledProcessError):
126 python3_ver = None
Mike Frysingereb386eb2020-11-30 18:17:40 +0000127
Mike Frysinger7e251262023-09-05 17:05:29 +0000128 # If the python3 version looks like it's new enough, give it a try.
129 if (
130 python3_ver
131 and python3_ver >= MIN_PYTHON_VERSION_HARD
132 and python3_ver != (major, minor)
133 ):
134 reexec("python3")
135
136 # We're still here, so diagnose things for the user.
137 if major < 3:
138 print(
139 "repo: error: Python 2 is no longer supported; "
140 "Please upgrade to Python {}.{}+.".format(
141 *MIN_PYTHON_VERSION_HARD
142 ),
143 file=sys.stderr,
144 )
145 sys.exit(1)
146 elif (major, minor) < MIN_PYTHON_VERSION_HARD:
147 print(
148 "repo: error: Python 3 version is too old; "
149 "Please use Python {}.{} or newer.".format(
150 *MIN_PYTHON_VERSION_HARD
151 ),
152 file=sys.stderr,
153 )
154 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000155
156
Mike Frysinger7e251262023-09-05 17:05:29 +0000157if __name__ == "__main__":
158 check_python_version()
Mike Frysingereb386eb2020-11-30 18:17:40 +0000159
160
161# repo default configuration
162#
Mike Frysinger7e251262023-09-05 17:05:29 +0000163REPO_URL = os.environ.get("REPO_URL", None)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000164if not REPO_URL:
Mike Frysinger7e251262023-09-05 17:05:29 +0000165 REPO_URL = "https://gerrit.googlesource.com/git-repo"
166REPO_REV = os.environ.get("REPO_REV")
Mike Frysingereb386eb2020-11-30 18:17:40 +0000167if not REPO_REV:
Mike Frysinger7e251262023-09-05 17:05:29 +0000168 REPO_REV = "stable"
Mike Frysinger4bedb9d2021-05-11 15:37:51 +0000169# URL to file bug reports for repo tool issues.
Mike Frysinger7e251262023-09-05 17:05:29 +0000170BUG_URL = "https://issues.gerritcodereview.com/issues/new?component=1370071"
Mike Frysingereb386eb2020-11-30 18:17:40 +0000171
172# increment this whenever we make important changes to this script
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000173VERSION = (2, 39)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000174
175# increment this if the MAINTAINER_KEYS block is modified
176KEYRING_VERSION = (2, 3)
177
178# Each individual key entry is created by using:
179# gpg --armor --export keyid
180MAINTAINER_KEYS = """
181
182 Repo Maintainer <repo@android.kernel.org>
183-----BEGIN PGP PUBLIC KEY BLOCK-----
184
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
250cZ7aFsJF4PtcDrfdejyAxbtsSHI=
251=82Tj
252-----END PGP PUBLIC KEY BLOCK-----
253"""
254
Mike Frysinger7e251262023-09-05 17:05:29 +0000255GIT = "git" # our git command
Mike Frysingereb386eb2020-11-30 18:17:40 +0000256# NB: The version of git that the repo launcher requires may be much older than
257# the version of git that the main repo source tree requires. Keeping this at
258# an older version also makes it easier for users to upgrade/rollback as needed.
259#
260# git-1.7 is in (EOL) Ubuntu Precise.
Mike Frysinger7e251262023-09-05 17:05:29 +0000261MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version
262repodir = ".repo" # name of repo's private directory
263S_repo = "repo" # special repo repository
264S_manifests = "manifests" # special manifest repository
265REPO_MAIN = S_repo + "/main.py" # main script
266GITC_CONFIG_FILE = "/gitc/.config"
267GITC_FS_ROOT_DIR = "/gitc/manifest-rw/"
Mike Frysingereb386eb2020-11-30 18:17:40 +0000268
269
270import collections
271import errno
Mike Frysinger75c98322021-03-08 21:22:10 +0000272import json
Mike Frysingereb386eb2020-11-30 18:17:40 +0000273import optparse
274import re
275import shutil
276import stat
277
Mike Frysinger7e251262023-09-05 17:05:29 +0000278
Mike Frysingereb386eb2020-11-30 18:17:40 +0000279if sys.version_info[0] == 3:
Mike Frysinger7e251262023-09-05 17:05:29 +0000280 import urllib.error
281 import urllib.request
Mike Frysingereb386eb2020-11-30 18:17:40 +0000282else:
Mike Frysinger7e251262023-09-05 17:05:29 +0000283 import imp
284
285 import urllib2
286
287 urllib = imp.new_module("urllib")
288 urllib.request = urllib2
289 urllib.error = urllib2
Mike Frysingereb386eb2020-11-30 18:17:40 +0000290
291
Mike Frysinger7e251262023-09-05 17:05:29 +0000292repo_config_dir = os.getenv("REPO_CONFIG_DIR", os.path.expanduser("~"))
293home_dot_repo = os.path.join(repo_config_dir, ".repoconfig")
294gpg_dir = os.path.join(home_dot_repo, "gnupg")
Mike Frysingereb386eb2020-11-30 18:17:40 +0000295
296
297def GetParser(gitc_init=False):
Mike Frysinger7e251262023-09-05 17:05:29 +0000298 """Setup the CLI parser."""
299 if gitc_init:
300 sys.exit("repo: fatal: GITC not supported.")
301 else:
302 usage = "repo init [options] [-u] url"
Mike Frysingereb386eb2020-11-30 18:17:40 +0000303
Mike Frysinger7e251262023-09-05 17:05:29 +0000304 parser = optparse.OptionParser(usage=usage)
305 InitParser(parser)
306 return parser
Mike Frysinger496b2252021-04-19 21:56:50 +0000307
308
Mike Frysinger7e251262023-09-05 17:05:29 +0000309def InitParser(parser):
310 """Setup the CLI parser."""
311 # NB: Keep in sync with command.py:_CommonOptions().
Mike Frysingereb386eb2020-11-30 18:17:40 +0000312
Mike Frysinger7e251262023-09-05 17:05:29 +0000313 # Logging.
314 group = parser.add_option_group("Logging options")
315 group.add_option(
316 "-v",
317 "--verbose",
318 dest="output_mode",
319 action="store_true",
320 help="show all output",
321 )
322 group.add_option(
323 "-q",
324 "--quiet",
325 dest="output_mode",
326 action="store_false",
327 help="only show errors",
328 )
Mike Frysingereb386eb2020-11-30 18:17:40 +0000329
Mike Frysinger7e251262023-09-05 17:05:29 +0000330 # Manifest.
331 group = parser.add_option_group("Manifest options")
332 group.add_option(
333 "-u",
334 "--manifest-url",
335 help="manifest repository location",
336 metavar="URL",
337 )
338 group.add_option(
339 "-b",
340 "--manifest-branch",
341 metavar="REVISION",
342 help="manifest branch or revision (use HEAD for default)",
343 )
344 group.add_option(
345 "-m",
346 "--manifest-name",
347 default="default.xml",
348 help="initial manifest file",
349 metavar="NAME.xml",
350 )
351 group.add_option(
352 "-g",
353 "--groups",
354 default="default",
355 help="restrict manifest projects to ones with specified "
356 "group(s) [default|all|G1,G2,G3|G4,-G5,-G6]",
357 metavar="GROUP",
358 )
359 group.add_option(
360 "-p",
361 "--platform",
362 default="auto",
363 help="restrict manifest projects to ones with a specified "
364 "platform group [auto|all|none|linux|darwin|...]",
365 metavar="PLATFORM",
366 )
367 group.add_option(
368 "--submodules",
369 action="store_true",
370 help="sync any submodules associated with the manifest repo",
371 )
372 group.add_option(
373 "--standalone-manifest",
374 action="store_true",
375 help="download the manifest as a static file "
376 "rather then create a git checkout of "
377 "the manifest repo",
378 )
379 group.add_option(
380 "--manifest-depth",
381 type="int",
382 default=0,
383 metavar="DEPTH",
384 help="create a shallow clone of the manifest repo with "
385 "given depth (0 for full clone); see git clone "
386 "(default: %default)",
387 )
Mike Frysinger496b2252021-04-19 21:56:50 +0000388
Mike Frysinger7e251262023-09-05 17:05:29 +0000389 # Options that only affect manifest project, and not any of the projects
390 # specified in the manifest itself.
391 group = parser.add_option_group("Manifest (only) checkout options")
Mike Frysinger496b2252021-04-19 21:56:50 +0000392
Mike Frysinger7e251262023-09-05 17:05:29 +0000393 group.add_option(
394 "--current-branch",
395 "-c",
396 default=True,
397 dest="current_branch_only",
398 action="store_true",
399 help="fetch only current manifest branch from server (default)",
400 )
401 group.add_option(
402 "--no-current-branch",
403 dest="current_branch_only",
404 action="store_false",
405 help="fetch all manifest branches from server",
406 )
407 group.add_option(
408 "--tags", action="store_true", help="fetch tags in the manifest"
409 )
410 group.add_option(
411 "--no-tags",
412 dest="tags",
413 action="store_false",
414 help="don't fetch tags in the manifest",
415 )
Mike Frysinger496b2252021-04-19 21:56:50 +0000416
Mike Frysinger7e251262023-09-05 17:05:29 +0000417 # These are fundamentally different ways of structuring the checkout.
418 group = parser.add_option_group("Checkout modes")
419 group.add_option(
420 "--mirror",
421 action="store_true",
422 help="create a replica of the remote repositories "
423 "rather than a client working directory",
424 )
425 group.add_option(
426 "--archive",
427 action="store_true",
428 help="checkout an archive instead of a git repository for "
429 "each project. See git archive.",
430 )
431 group.add_option(
432 "--worktree",
433 action="store_true",
434 help="use git-worktree to manage projects",
435 )
Mike Frysingereb386eb2020-11-30 18:17:40 +0000436
Mike Frysinger7e251262023-09-05 17:05:29 +0000437 # These are fundamentally different ways of structuring the checkout.
438 group = parser.add_option_group("Project checkout optimizations")
439 group.add_option(
440 "--reference", help="location of mirror directory", metavar="DIR"
441 )
442 group.add_option(
443 "--dissociate",
444 action="store_true",
445 help="dissociate from reference mirrors after clone",
446 )
447 group.add_option(
448 "--depth",
449 type="int",
450 default=None,
451 help="create a shallow clone with given depth; " "see git clone",
452 )
453 group.add_option(
454 "--partial-clone",
455 action="store_true",
456 help="perform partial clone (https://git-scm.com/"
457 "docs/gitrepository-layout#_code_partialclone_code)",
458 )
459 group.add_option(
460 "--no-partial-clone",
461 action="store_false",
462 help="disable use of partial clone (https://git-scm.com/"
463 "docs/gitrepository-layout#_code_partialclone_code)",
464 )
465 group.add_option(
466 "--partial-clone-exclude",
467 action="store",
468 help="exclude the specified projects (a comma-delimited "
469 "project names) from partial clone (https://git-scm.com"
470 "/docs/gitrepository-layout#_code_partialclone_code)",
471 )
472 group.add_option(
473 "--clone-filter",
474 action="store",
475 default="blob:none",
476 help="filter for use with --partial-clone " "[default: %default]",
477 )
478 group.add_option(
479 "--use-superproject",
480 action="store_true",
481 default=None,
482 help="use the manifest superproject to sync projects; implies -c",
483 )
484 group.add_option(
485 "--no-use-superproject",
486 action="store_false",
487 dest="use_superproject",
488 help="disable use of manifest superprojects",
489 )
490 group.add_option(
491 "--clone-bundle",
492 action="store_true",
493 help="enable use of /clone.bundle on HTTP/HTTPS "
494 "(default if not --partial-clone)",
495 )
496 group.add_option(
497 "--no-clone-bundle",
498 dest="clone_bundle",
499 action="store_false",
500 help="disable use of /clone.bundle on HTTP/HTTPS (default if --partial-clone)",
501 )
502 group.add_option(
503 "--git-lfs", action="store_true", help="enable Git LFS support"
504 )
505 group.add_option(
506 "--no-git-lfs",
507 dest="git_lfs",
508 action="store_false",
509 help="disable Git LFS support",
510 )
Mike Frysingereb386eb2020-11-30 18:17:40 +0000511
Mike Frysinger7e251262023-09-05 17:05:29 +0000512 # Tool.
513 group = parser.add_option_group("repo Version options")
514 group.add_option(
515 "--repo-url", metavar="URL", help="repo repository location ($REPO_URL)"
516 )
517 group.add_option(
518 "--repo-rev", metavar="REV", help="repo branch or revision ($REPO_REV)"
519 )
520 group.add_option(
521 "--repo-branch", dest="repo_rev", help=optparse.SUPPRESS_HELP
522 )
523 group.add_option(
524 "--no-repo-verify",
525 dest="repo_verify",
526 default=True,
527 action="store_false",
528 help="do not verify repo source code",
529 )
Mike Frysingereb386eb2020-11-30 18:17:40 +0000530
Mike Frysinger7e251262023-09-05 17:05:29 +0000531 # Other.
532 group = parser.add_option_group("Other options")
533 group.add_option(
534 "--config-name",
535 action="store_true",
536 default=False,
537 help="Always prompt for name/e-mail",
538 )
Mike Frysingereb386eb2020-11-30 18:17:40 +0000539
Mike Frysinger7e251262023-09-05 17:05:29 +0000540 return parser
Mike Frysingereb386eb2020-11-30 18:17:40 +0000541
542
543# This is a poor replacement for subprocess.run until we require Python 3.6+.
544RunResult = collections.namedtuple(
Mike Frysinger7e251262023-09-05 17:05:29 +0000545 "RunResult", ("returncode", "stdout", "stderr")
546)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000547
548
549class RunError(Exception):
Mike Frysinger7e251262023-09-05 17:05:29 +0000550 """Error when running a command failed."""
Mike Frysingereb386eb2020-11-30 18:17:40 +0000551
552
553def run_command(cmd, **kwargs):
Mike Frysinger7e251262023-09-05 17:05:29 +0000554 """Run |cmd| and return its output."""
555 check = kwargs.pop("check", False)
556 if kwargs.pop("capture_output", False):
557 kwargs.setdefault("stdout", subprocess.PIPE)
558 kwargs.setdefault("stderr", subprocess.PIPE)
559 cmd_input = kwargs.pop("input", None)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000560
Mike Frysinger7e251262023-09-05 17:05:29 +0000561 def decode(output):
562 """Decode |output| to text."""
563 if output is None:
564 return output
565 try:
566 return output.decode("utf-8")
567 except UnicodeError:
568 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000569 f"repo: warning: Invalid UTF-8 output:\ncmd: {cmd!r}\n{output}",
Mike Frysinger7e251262023-09-05 17:05:29 +0000570 file=sys.stderr,
571 )
572 return output.decode("utf-8", "backslashreplace")
Mike Frysingereb386eb2020-11-30 18:17:40 +0000573
Mike Frysinger7e251262023-09-05 17:05:29 +0000574 # Run & package the results.
575 proc = subprocess.Popen(cmd, **kwargs)
576 (stdout, stderr) = proc.communicate(input=cmd_input)
577 dbg = ": " + " ".join(cmd)
578 if cmd_input is not None:
579 dbg += " 0<|"
580 if stdout == subprocess.PIPE:
581 dbg += " 1>|"
582 if stderr == subprocess.PIPE:
583 dbg += " 2>|"
584 elif stderr == subprocess.STDOUT:
585 dbg += " 2>&1"
586 trace.print(dbg)
587 ret = RunResult(proc.returncode, decode(stdout), decode(stderr))
Mike Frysingereb386eb2020-11-30 18:17:40 +0000588
Mike Frysinger7e251262023-09-05 17:05:29 +0000589 # If things failed, print useful debugging output.
590 if check and ret.returncode:
591 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000592 f'repo: error: "{cmd[0]}" failed with exit status {ret.returncode}',
Mike Frysinger7e251262023-09-05 17:05:29 +0000593 file=sys.stderr,
594 )
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000595 cwd = kwargs.get("cwd", os.getcwd())
596 print(f" cwd: {cwd}\n cmd: {cmd!r}", file=sys.stderr)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000597
Mike Frysinger7e251262023-09-05 17:05:29 +0000598 def _print_output(name, output):
599 if output:
600 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000601 f" {name}:"
602 + "".join(f"\n >> {x}" for x in output.splitlines()),
Mike Frysinger7e251262023-09-05 17:05:29 +0000603 file=sys.stderr,
604 )
Mike Frysingereb386eb2020-11-30 18:17:40 +0000605
Mike Frysinger7e251262023-09-05 17:05:29 +0000606 _print_output("stdout", ret.stdout)
607 _print_output("stderr", ret.stderr)
608 raise RunError(ret)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000609
Mike Frysinger7e251262023-09-05 17:05:29 +0000610 return ret
Mike Frysingereb386eb2020-11-30 18:17:40 +0000611
612
613_gitc_manifest_dir = None
614
615
616def get_gitc_manifest_dir():
Mike Frysinger7e251262023-09-05 17:05:29 +0000617 global _gitc_manifest_dir
618 if _gitc_manifest_dir is None:
619 _gitc_manifest_dir = ""
620 try:
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000621 with open(GITC_CONFIG_FILE) as gitc_config:
Mike Frysinger7e251262023-09-05 17:05:29 +0000622 for line in gitc_config:
623 match = re.match("gitc_dir=(?P<gitc_manifest_dir>.*)", line)
624 if match:
625 _gitc_manifest_dir = match.group("gitc_manifest_dir")
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000626 except OSError:
Mike Frysinger7e251262023-09-05 17:05:29 +0000627 pass
628 return _gitc_manifest_dir
Mike Frysingereb386eb2020-11-30 18:17:40 +0000629
630
631def gitc_parse_clientdir(gitc_fs_path):
Mike Frysinger7e251262023-09-05 17:05:29 +0000632 """Parse a path in the GITC FS and return its client name.
Mike Frysingereb386eb2020-11-30 18:17:40 +0000633
Mike Frysinger7e251262023-09-05 17:05:29 +0000634 Args:
635 gitc_fs_path: A subdirectory path within the GITC_FS_ROOT_DIR.
Mike Frysingereb386eb2020-11-30 18:17:40 +0000636
Mike Frysinger7e251262023-09-05 17:05:29 +0000637 Returns:
638 The GITC client name.
639 """
640 if gitc_fs_path == GITC_FS_ROOT_DIR:
641 return None
642 if not gitc_fs_path.startswith(GITC_FS_ROOT_DIR):
643 manifest_dir = get_gitc_manifest_dir()
644 if manifest_dir == "":
645 return None
646 if manifest_dir[-1] != "/":
647 manifest_dir += "/"
648 if gitc_fs_path == manifest_dir:
649 return None
650 if not gitc_fs_path.startswith(manifest_dir):
651 return None
652 return gitc_fs_path.split(manifest_dir)[1].split("/")[0]
653 return gitc_fs_path.split(GITC_FS_ROOT_DIR)[1].split("/")[0]
Mike Frysingereb386eb2020-11-30 18:17:40 +0000654
655
656class CloneFailure(Exception):
657
Mike Frysinger7e251262023-09-05 17:05:29 +0000658 """Indicate the remote clone of repo itself failed."""
Mike Frysingereb386eb2020-11-30 18:17:40 +0000659
660
661def check_repo_verify(repo_verify, quiet=False):
Mike Frysinger7e251262023-09-05 17:05:29 +0000662 """Check the --repo-verify state."""
663 if not repo_verify:
664 print(
665 "repo: warning: verification of repo code has been disabled;\n"
666 "repo will not be able to verify the integrity of itself.\n",
667 file=sys.stderr,
668 )
669 return False
Mike Frysingereb386eb2020-11-30 18:17:40 +0000670
Mike Frysinger7e251262023-09-05 17:05:29 +0000671 if NeedSetupGnuPG():
672 return SetupGnuPG(quiet)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000673
Mike Frysinger7e251262023-09-05 17:05:29 +0000674 return True
Mike Frysingereb386eb2020-11-30 18:17:40 +0000675
676
677def check_repo_rev(dst, rev, repo_verify=True, quiet=False):
Mike Frysinger7e251262023-09-05 17:05:29 +0000678 """Check that |rev| is valid."""
679 do_verify = check_repo_verify(repo_verify, quiet=quiet)
680 remote_ref, local_rev = resolve_repo_rev(dst, rev)
681 if not quiet and not remote_ref.startswith("refs/heads/"):
682 print(
683 "warning: repo is not tracking a remote branch, so it will not "
684 "receive updates",
685 file=sys.stderr,
686 )
687 if do_verify:
688 rev = verify_rev(dst, remote_ref, local_rev, quiet)
689 else:
690 rev = local_rev
691 return (remote_ref, rev)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000692
693
694def _Init(args, gitc_init=False):
Mike Frysinger7e251262023-09-05 17:05:29 +0000695 """Installs repo by cloning it over the network."""
696 parser = GetParser(gitc_init=gitc_init)
697 opt, args = parser.parse_args(args)
Mike Frysinger75c98322021-03-08 21:22:10 +0000698 if args:
Mike Frysinger7e251262023-09-05 17:05:29 +0000699 if not opt.manifest_url:
700 opt.manifest_url = args.pop(0)
701 if args:
702 parser.print_usage()
703 sys.exit(1)
704 opt.quiet = opt.output_mode is False
705 opt.verbose = opt.output_mode is True
Mike Frysingereb386eb2020-11-30 18:17:40 +0000706
Mike Frysinger7e251262023-09-05 17:05:29 +0000707 if opt.clone_bundle is None:
708 opt.clone_bundle = False if opt.partial_clone else True
Mike Frysingereb386eb2020-11-30 18:17:40 +0000709
Mike Frysinger7e251262023-09-05 17:05:29 +0000710 url = opt.repo_url or REPO_URL
711 rev = opt.repo_rev or REPO_REV
Mike Frysingereb386eb2020-11-30 18:17:40 +0000712
Mike Frysinger7e251262023-09-05 17:05:29 +0000713 try:
714 os.mkdir(repodir)
715 except OSError as e:
716 if e.errno != errno.EEXIST:
717 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000718 f"fatal: cannot make {repodir} directory: {e.strerror}",
Mike Frysinger7e251262023-09-05 17:05:29 +0000719 file=sys.stderr,
720 )
721 # Don't raise CloneFailure; that would delete the
722 # name. Instead exit immediately.
723 #
724 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000725
Mike Frysinger7e251262023-09-05 17:05:29 +0000726 _CheckGitVersion()
727 try:
728 if not opt.quiet:
729 print("Downloading Repo source from", url)
730 dst_final = os.path.abspath(os.path.join(repodir, S_repo))
731 dst = dst_final + ".tmp"
732 shutil.rmtree(dst, ignore_errors=True)
733 _Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000734
Mike Frysinger7e251262023-09-05 17:05:29 +0000735 remote_ref, rev = check_repo_rev(
736 dst, rev, opt.repo_verify, quiet=opt.quiet
737 )
738 _Checkout(dst, remote_ref, rev, opt.quiet)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000739
Mike Frysinger7e251262023-09-05 17:05:29 +0000740 if not os.path.isfile(os.path.join(dst, "repo")):
741 print(
742 "fatal: '%s' does not look like a git-repo repository, is "
743 "--repo-url set correctly?" % url,
744 file=sys.stderr,
745 )
746 raise CloneFailure()
Mike Frysingereb386eb2020-11-30 18:17:40 +0000747
Mike Frysinger7e251262023-09-05 17:05:29 +0000748 os.rename(dst, dst_final)
Mike Frysinger19b3eb52022-08-23 00:42:50 +0000749
Mike Frysinger7e251262023-09-05 17:05:29 +0000750 except CloneFailure:
751 print("fatal: double check your --repo-rev setting.", file=sys.stderr)
752 if opt.quiet:
753 print(
754 "fatal: repo init failed; run without --quiet to see why",
755 file=sys.stderr,
756 )
757 raise
Mike Frysingereb386eb2020-11-30 18:17:40 +0000758
759
760def run_git(*args, **kwargs):
Mike Frysinger7e251262023-09-05 17:05:29 +0000761 """Run git and return execution details."""
762 kwargs.setdefault("capture_output", True)
763 kwargs.setdefault("check", True)
764 try:
765 return run_command([GIT] + list(args), **kwargs)
766 except OSError as e:
767 print(file=sys.stderr)
768 print('repo: error: "%s" is not available' % GIT, file=sys.stderr)
769 print("repo: error: %s" % e, file=sys.stderr)
770 print(file=sys.stderr)
771 print(
772 "Please make sure %s is installed and in your path." % GIT,
773 file=sys.stderr,
774 )
775 sys.exit(1)
776 except RunError:
777 raise CloneFailure()
Mike Frysingereb386eb2020-11-30 18:17:40 +0000778
779
780# The git version info broken down into components for easy analysis.
781# Similar to Python's sys.version_info.
782GitVersion = collections.namedtuple(
Mike Frysinger7e251262023-09-05 17:05:29 +0000783 "GitVersion", ("major", "minor", "micro", "full")
784)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000785
786
787def ParseGitVersion(ver_str=None):
Mike Frysinger7e251262023-09-05 17:05:29 +0000788 if ver_str is None:
789 # Load the version ourselves.
790 ver_str = run_git("--version").stdout
Mike Frysingereb386eb2020-11-30 18:17:40 +0000791
Mike Frysinger7e251262023-09-05 17:05:29 +0000792 if not ver_str.startswith("git version "):
793 return None
Mike Frysingereb386eb2020-11-30 18:17:40 +0000794
Mike Frysinger7e251262023-09-05 17:05:29 +0000795 full_version = ver_str[len("git version ") :].strip()
796 num_ver_str = full_version.split("-")[0]
797 to_tuple = []
798 for num_str in num_ver_str.split(".")[:3]:
799 if num_str.isdigit():
800 to_tuple.append(int(num_str))
801 else:
802 to_tuple.append(0)
803 to_tuple.append(full_version)
804 return GitVersion(*to_tuple)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000805
806
807def _CheckGitVersion():
Mike Frysinger7e251262023-09-05 17:05:29 +0000808 ver_act = ParseGitVersion()
809 if ver_act is None:
810 print("fatal: unable to detect git version", file=sys.stderr)
811 raise CloneFailure()
Mike Frysingereb386eb2020-11-30 18:17:40 +0000812
Mike Frysinger7e251262023-09-05 17:05:29 +0000813 if ver_act < MIN_GIT_VERSION:
814 need = ".".join(map(str, MIN_GIT_VERSION))
815 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000816 f"fatal: git {need} or later required; found {ver_act.full}",
Mike Frysinger7e251262023-09-05 17:05:29 +0000817 file=sys.stderr,
818 )
819 raise CloneFailure()
Mike Frysingereb386eb2020-11-30 18:17:40 +0000820
821
822def SetGitTrace2ParentSid(env=None):
Mike Frysinger7e251262023-09-05 17:05:29 +0000823 """Set up GIT_TRACE2_PARENT_SID for git tracing."""
824 # We roughly follow the format git itself uses in trace2/tr2_sid.c.
825 # (1) Be unique (2) be valid filename (3) be fixed length.
826 #
827 # Since we always export this variable, we try to avoid more expensive calls.
828 # e.g. We don't attempt hostname lookups or hashing the results.
829 if env is None:
830 env = os.environ
Mike Frysingereb386eb2020-11-30 18:17:40 +0000831
Mike Frysinger7e251262023-09-05 17:05:29 +0000832 KEY = "GIT_TRACE2_PARENT_SID"
Mike Frysingereb386eb2020-11-30 18:17:40 +0000833
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000834 now = datetime.datetime.now(datetime.timezone.utc)
835 timestamp = now.strftime("%Y%m%dT%H%M%SZ")
836 value = f"repo-{timestamp}-P{os.getpid():08x}"
Mike Frysingereb386eb2020-11-30 18:17:40 +0000837
Mike Frysinger7e251262023-09-05 17:05:29 +0000838 # If it's already set, then append ourselves.
839 if KEY in env:
840 value = env[KEY] + "/" + value
Mike Frysingereb386eb2020-11-30 18:17:40 +0000841
Mike Frysinger7e251262023-09-05 17:05:29 +0000842 _setenv(KEY, value, env=env)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000843
844
845def _setenv(key, value, env=None):
Mike Frysinger7e251262023-09-05 17:05:29 +0000846 """Set |key| in the OS environment |env| to |value|."""
847 if env is None:
848 env = os.environ
849 # Environment handling across systems is messy.
850 try:
851 env[key] = value
852 except UnicodeEncodeError:
853 env[key] = value.encode()
Mike Frysingereb386eb2020-11-30 18:17:40 +0000854
855
856def NeedSetupGnuPG():
Mike Frysinger7e251262023-09-05 17:05:29 +0000857 if not os.path.isdir(home_dot_repo):
858 return True
Mike Frysingereb386eb2020-11-30 18:17:40 +0000859
Mike Frysinger7e251262023-09-05 17:05:29 +0000860 kv = os.path.join(home_dot_repo, "keyring-version")
861 if not os.path.exists(kv):
862 return True
Mike Frysingereb386eb2020-11-30 18:17:40 +0000863
Mike Frysinger7e251262023-09-05 17:05:29 +0000864 kv = open(kv).read()
865 if not kv:
866 return True
Mike Frysingereb386eb2020-11-30 18:17:40 +0000867
Mike Frysinger7e251262023-09-05 17:05:29 +0000868 kv = tuple(map(int, kv.split(".")))
869 if kv < KEYRING_VERSION:
870 return True
871 return False
Mike Frysingereb386eb2020-11-30 18:17:40 +0000872
873
874def SetupGnuPG(quiet):
Mike Frysinger7e251262023-09-05 17:05:29 +0000875 try:
876 os.mkdir(home_dot_repo)
877 except OSError as e:
878 if e.errno != errno.EEXIST:
879 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000880 f"fatal: cannot make {home_dot_repo} directory: {e.strerror}",
Mike Frysinger7e251262023-09-05 17:05:29 +0000881 file=sys.stderr,
882 )
883 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000884
Mike Frysinger7e251262023-09-05 17:05:29 +0000885 try:
886 os.mkdir(gpg_dir, stat.S_IRWXU)
887 except OSError as e:
888 if e.errno != errno.EEXIST:
889 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000890 f"fatal: cannot make {gpg_dir} directory: {e.strerror}",
Mike Frysinger7e251262023-09-05 17:05:29 +0000891 file=sys.stderr,
892 )
893 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000894
Mike Frysingereb386eb2020-11-30 18:17:40 +0000895 if not quiet:
Mike Frysinger7e251262023-09-05 17:05:29 +0000896 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000897 "repo: Updating release signing keys to keyset ver "
898 + ".".join(str(x) for x in KEYRING_VERSION),
Mike Frysinger7e251262023-09-05 17:05:29 +0000899 )
900 # NB: We use --homedir (and cwd below) because some environments (Windows) do
901 # not correctly handle full native paths. We avoid the issue by changing to
902 # the right dir with cwd=gpg_dir before executing gpg, and then telling gpg to
903 # use the cwd (.) as its homedir which leaves the path resolution logic to it.
904 cmd = ["gpg", "--homedir", ".", "--import"]
905 try:
906 # gpg can be pretty chatty. Always capture the output and if something goes
907 # wrong, the builtin check failure will dump stdout & stderr for debugging.
908 run_command(
909 cmd,
910 stdin=subprocess.PIPE,
911 capture_output=True,
912 cwd=gpg_dir,
913 check=True,
914 input=MAINTAINER_KEYS.encode("utf-8"),
915 )
916 except OSError:
917 if not quiet:
918 print("warning: gpg (GnuPG) is not available.", file=sys.stderr)
919 print(
920 "warning: Installing it is strongly encouraged.",
921 file=sys.stderr,
922 )
923 print(file=sys.stderr)
924 return False
Mike Frysingereb386eb2020-11-30 18:17:40 +0000925
Mike Frysinger7e251262023-09-05 17:05:29 +0000926 with open(os.path.join(home_dot_repo, "keyring-version"), "w") as fd:
927 fd.write(".".join(map(str, KEYRING_VERSION)) + "\n")
928 return True
Mike Frysingereb386eb2020-11-30 18:17:40 +0000929
930
931def _SetConfig(cwd, name, value):
Mike Frysinger7e251262023-09-05 17:05:29 +0000932 """Set a git configuration option to the specified value."""
933 run_git("config", name, value, cwd=cwd)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000934
935
936def _GetRepoConfig(name):
Mike Frysinger7e251262023-09-05 17:05:29 +0000937 """Read a repo configuration option."""
938 config = os.path.join(home_dot_repo, "config")
939 if not os.path.exists(config):
940 return None
Mike Frysingereb386eb2020-11-30 18:17:40 +0000941
Mike Frysinger7e251262023-09-05 17:05:29 +0000942 cmd = ["config", "--file", config, "--get", name]
943 ret = run_git(*cmd, check=False)
944 if ret.returncode == 0:
945 return ret.stdout
946 elif ret.returncode == 1:
947 return None
948 else:
949 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +0000950 f"repo: error: git {' '.join(cmd)} failed:\n{ret.stderr}",
Mike Frysinger7e251262023-09-05 17:05:29 +0000951 file=sys.stderr,
952 )
953 raise RunError()
Mike Frysingereb386eb2020-11-30 18:17:40 +0000954
955
956def _InitHttp():
Mike Frysinger7e251262023-09-05 17:05:29 +0000957 handlers = []
Mike Frysingereb386eb2020-11-30 18:17:40 +0000958
Mike Frysinger7e251262023-09-05 17:05:29 +0000959 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
960 try:
961 import netrc
Mike Frysingereb386eb2020-11-30 18:17:40 +0000962
Mike Frysinger7e251262023-09-05 17:05:29 +0000963 n = netrc.netrc()
964 for host in n.hosts:
965 p = n.hosts[host]
966 mgr.add_password(p[1], "http://%s/" % host, p[0], p[2])
967 mgr.add_password(p[1], "https://%s/" % host, p[0], p[2])
968 except Exception:
969 pass
970 handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
971 handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
972
973 if "http_proxy" in os.environ:
974 url = os.environ["http_proxy"]
975 handlers.append(
976 urllib.request.ProxyHandler({"http": url, "https": url})
977 )
978 if "REPO_CURL_VERBOSE" in os.environ:
979 handlers.append(urllib.request.HTTPHandler(debuglevel=1))
980 handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
981 urllib.request.install_opener(urllib.request.build_opener(*handlers))
Mike Frysingereb386eb2020-11-30 18:17:40 +0000982
983
984def _Fetch(url, cwd, src, quiet, verbose):
Mike Frysinger7e251262023-09-05 17:05:29 +0000985 cmd = ["fetch"]
986 if not verbose:
987 cmd.append("--quiet")
988 err = None
989 if not quiet and sys.stdout.isatty():
990 cmd.append("--progress")
991 elif not verbose:
992 err = subprocess.PIPE
993 cmd.append(src)
994 cmd.append("+refs/heads/*:refs/remotes/origin/*")
995 cmd.append("+refs/tags/*:refs/tags/*")
996 run_git(*cmd, stderr=err, capture_output=False, cwd=cwd)
Mike Frysingereb386eb2020-11-30 18:17:40 +0000997
998
999def _DownloadBundle(url, cwd, quiet, verbose):
Mike Frysinger7e251262023-09-05 17:05:29 +00001000 if not url.endswith("/"):
1001 url += "/"
1002 url += "clone.bundle"
Mike Frysingereb386eb2020-11-30 18:17:40 +00001003
Mike Frysinger7e251262023-09-05 17:05:29 +00001004 ret = run_git(
1005 "config", "--get-regexp", "url.*.insteadof", cwd=cwd, check=False
1006 )
1007 for line in ret.stdout.splitlines():
1008 m = re.compile(r"^url\.(.*)\.insteadof (.*)$").match(line)
1009 if m:
1010 new_url = m.group(1)
1011 old_url = m.group(2)
1012 if url.startswith(old_url):
1013 url = new_url + url[len(old_url) :]
1014 break
Mike Frysingereb386eb2020-11-30 18:17:40 +00001015
Mike Frysinger7e251262023-09-05 17:05:29 +00001016 if not url.startswith("http:") and not url.startswith("https:"):
1017 return False
Mike Frysingereb386eb2020-11-30 18:17:40 +00001018
Mike Frysinger7e251262023-09-05 17:05:29 +00001019 dest = open(os.path.join(cwd, ".git", "clone.bundle"), "w+b")
Mike Frysingereb386eb2020-11-30 18:17:40 +00001020 try:
Mike Frysinger7e251262023-09-05 17:05:29 +00001021 try:
1022 r = urllib.request.urlopen(url)
1023 except urllib.error.HTTPError as e:
1024 if e.code not in [400, 401, 403, 404, 501]:
1025 print("warning: Cannot get %s" % url, file=sys.stderr)
1026 print("warning: HTTP error %s" % e.code, file=sys.stderr)
1027 return False
1028 except urllib.error.URLError as e:
1029 print("fatal: Cannot get %s" % url, file=sys.stderr)
1030 print("fatal: error %s" % e.reason, file=sys.stderr)
1031 raise CloneFailure()
1032 try:
1033 if verbose:
1034 print("Downloading clone bundle %s" % url, file=sys.stderr)
1035 while True:
1036 buf = r.read(8192)
1037 if not buf:
1038 return True
1039 dest.write(buf)
1040 finally:
1041 r.close()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001042 finally:
Mike Frysinger7e251262023-09-05 17:05:29 +00001043 dest.close()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001044
1045
1046def _ImportBundle(cwd):
Mike Frysinger7e251262023-09-05 17:05:29 +00001047 path = os.path.join(cwd, ".git", "clone.bundle")
1048 try:
1049 _Fetch(cwd, cwd, path, True, False)
1050 finally:
1051 os.remove(path)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001052
1053
1054def _Clone(url, cwd, clone_bundle, quiet, verbose):
Mike Frysinger7e251262023-09-05 17:05:29 +00001055 """Clones a git repository to a new subdirectory of repodir"""
1056 if verbose:
1057 print("Cloning git repository", url)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001058
Mike Frysinger7e251262023-09-05 17:05:29 +00001059 try:
1060 os.mkdir(cwd)
1061 except OSError as e:
1062 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001063 f"fatal: cannot make {cwd} directory: {e.strerror}",
Mike Frysinger7e251262023-09-05 17:05:29 +00001064 file=sys.stderr,
1065 )
1066 raise CloneFailure()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001067
Mike Frysinger7e251262023-09-05 17:05:29 +00001068 run_git("init", "--quiet", cwd=cwd)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001069
Mike Frysinger7e251262023-09-05 17:05:29 +00001070 _InitHttp()
1071 _SetConfig(cwd, "remote.origin.url", url)
1072 _SetConfig(
1073 cwd, "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"
1074 )
1075 if clone_bundle and _DownloadBundle(url, cwd, quiet, verbose):
1076 _ImportBundle(cwd)
1077 _Fetch(url, cwd, "origin", quiet, verbose)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001078
1079
1080def resolve_repo_rev(cwd, committish):
Mike Frysinger7e251262023-09-05 17:05:29 +00001081 """Figure out what REPO_REV represents.
Mike Frysingereb386eb2020-11-30 18:17:40 +00001082
Mike Frysinger7e251262023-09-05 17:05:29 +00001083 We support:
1084 * refs/heads/xxx: Branch.
1085 * refs/tags/xxx: Tag.
1086 * xxx: Branch or tag or commit.
Mike Frysingereb386eb2020-11-30 18:17:40 +00001087
Mike Frysinger7e251262023-09-05 17:05:29 +00001088 Args:
1089 cwd: The git checkout to run in.
1090 committish: The REPO_REV argument to resolve.
Mike Frysingereb386eb2020-11-30 18:17:40 +00001091
Mike Frysinger7e251262023-09-05 17:05:29 +00001092 Returns:
1093 A tuple of (remote ref, commit) as makes sense for the committish.
1094 For branches, this will look like ('refs/heads/stable', <revision>).
1095 For tags, this will look like ('refs/tags/v1.0', <revision>).
1096 For commits, this will be (<revision>, <revision>).
1097 """
Mike Frysingereb386eb2020-11-30 18:17:40 +00001098
Mike Frysinger7e251262023-09-05 17:05:29 +00001099 def resolve(committish):
1100 ret = run_git(
1101 "rev-parse",
1102 "--verify",
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001103 f"{committish}^{{commit}}",
Mike Frysinger7e251262023-09-05 17:05:29 +00001104 cwd=cwd,
1105 check=False,
1106 )
1107 return None if ret.returncode else ret.stdout.strip()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001108
Mike Frysinger7e251262023-09-05 17:05:29 +00001109 # An explicit branch.
1110 if committish.startswith("refs/heads/"):
1111 remote_ref = committish
1112 committish = committish[len("refs/heads/") :]
1113 rev = resolve("refs/remotes/origin/%s" % committish)
1114 if rev is None:
1115 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001116 f'repo: error: unknown branch "{committish}"',
Mike Frysinger7e251262023-09-05 17:05:29 +00001117 file=sys.stderr,
1118 )
1119 raise CloneFailure()
1120 return (remote_ref, rev)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001121
Mike Frysinger7e251262023-09-05 17:05:29 +00001122 # An explicit tag.
1123 if committish.startswith("refs/tags/"):
1124 remote_ref = committish
1125 committish = committish[len("refs/tags/") :]
1126 rev = resolve(remote_ref)
1127 if rev is None:
1128 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001129 f'repo: error: unknown tag "{committish}"',
1130 file=sys.stderr,
Mike Frysinger7e251262023-09-05 17:05:29 +00001131 )
1132 raise CloneFailure()
1133 return (remote_ref, rev)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001134
Mike Frysinger7e251262023-09-05 17:05:29 +00001135 # See if it's a short branch name.
1136 rev = resolve("refs/remotes/origin/%s" % committish)
1137 if rev:
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001138 return (f"refs/heads/{committish}", rev)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001139
Mike Frysinger7e251262023-09-05 17:05:29 +00001140 # See if it's a tag.
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001141 rev = resolve(f"refs/tags/{committish}")
Mike Frysinger7e251262023-09-05 17:05:29 +00001142 if rev:
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001143 return (f"refs/tags/{committish}", rev)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001144
Mike Frysinger7e251262023-09-05 17:05:29 +00001145 # See if it's a commit.
1146 rev = resolve(committish)
1147 if rev and rev.lower().startswith(committish.lower()):
1148 return (rev, rev)
1149
1150 # Give up!
1151 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001152 f'repo: error: unable to resolve "{committish}"',
1153 file=sys.stderr,
Mike Frysinger7e251262023-09-05 17:05:29 +00001154 )
1155 raise CloneFailure()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001156
1157
1158def verify_rev(cwd, remote_ref, rev, quiet):
Mike Frysinger7e251262023-09-05 17:05:29 +00001159 """Verify the commit has been signed by a tag."""
1160 ret = run_git("describe", rev, cwd=cwd)
1161 cur = ret.stdout.strip()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001162
Mike Frysinger7e251262023-09-05 17:05:29 +00001163 m = re.compile(r"^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$").match(cur)
1164 if m:
1165 cur = m.group(1)
1166 if not quiet:
1167 print(file=sys.stderr)
1168 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001169 f"warning: '{remote_ref}' is not signed; "
1170 f"falling back to signed release '{cur}'",
Mike Frysinger7e251262023-09-05 17:05:29 +00001171 file=sys.stderr,
1172 )
1173 print(file=sys.stderr)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001174
Mike Frysinger7e251262023-09-05 17:05:29 +00001175 env = os.environ.copy()
1176 _setenv("GNUPGHOME", gpg_dir, env)
1177 run_git("tag", "-v", cur, cwd=cwd, env=env)
1178 return "%s^0" % cur
Mike Frysingereb386eb2020-11-30 18:17:40 +00001179
1180
1181def _Checkout(cwd, remote_ref, rev, quiet):
Mike Frysinger7e251262023-09-05 17:05:29 +00001182 """Checkout an upstream branch into the repository and track it."""
1183 run_git("update-ref", "refs/heads/default", rev, cwd=cwd)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001184
Mike Frysinger7e251262023-09-05 17:05:29 +00001185 _SetConfig(cwd, "branch.default.remote", "origin")
1186 _SetConfig(cwd, "branch.default.merge", remote_ref)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001187
Mike Frysinger7e251262023-09-05 17:05:29 +00001188 run_git("symbolic-ref", "HEAD", "refs/heads/default", cwd=cwd)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001189
Mike Frysinger7e251262023-09-05 17:05:29 +00001190 cmd = ["read-tree", "--reset", "-u"]
1191 if not quiet:
1192 cmd.append("-v")
1193 cmd.append("HEAD")
1194 run_git(*cmd, cwd=cwd)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001195
1196
1197def _FindRepo():
Mike Frysinger7e251262023-09-05 17:05:29 +00001198 """Look for a repo installation, starting at the current directory."""
1199 curdir = os.getcwd()
1200 repo = None
Mike Frysingereb386eb2020-11-30 18:17:40 +00001201
Mike Frysinger7e251262023-09-05 17:05:29 +00001202 olddir = None
1203 while curdir != olddir and not repo:
1204 repo = os.path.join(curdir, repodir, REPO_MAIN)
1205 if not os.path.isfile(repo):
1206 repo = None
1207 olddir = curdir
1208 curdir = os.path.dirname(curdir)
1209 return (repo, os.path.join(curdir, repodir))
Mike Frysingereb386eb2020-11-30 18:17:40 +00001210
1211
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001212class _Options:
Mike Frysinger7e251262023-09-05 17:05:29 +00001213 help = False
1214 version = False
Mike Frysingereb386eb2020-11-30 18:17:40 +00001215
1216
1217def _ExpandAlias(name):
Mike Frysinger7e251262023-09-05 17:05:29 +00001218 """Look up user registered aliases."""
1219 # We don't resolve aliases for existing subcommands. This matches git.
1220 if name in {"gitc-init", "help", "init"}:
1221 return name, []
Mike Frysingereb386eb2020-11-30 18:17:40 +00001222
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001223 alias = _GetRepoConfig(f"alias.{name}")
Mike Frysinger7e251262023-09-05 17:05:29 +00001224 if alias is None:
1225 return name, []
Mike Frysingereb386eb2020-11-30 18:17:40 +00001226
Mike Frysinger7e251262023-09-05 17:05:29 +00001227 args = alias.strip().split(" ", 1)
1228 name = args[0]
1229 if len(args) == 2:
1230 args = shlex.split(args[1])
1231 else:
1232 args = []
1233 return name, args
Mike Frysingereb386eb2020-11-30 18:17:40 +00001234
1235
1236def _ParseArguments(args):
Mike Frysinger7e251262023-09-05 17:05:29 +00001237 cmd = None
1238 opt = _Options()
1239 arg = []
Mike Frysingereb386eb2020-11-30 18:17:40 +00001240
Mike Frysinger7e251262023-09-05 17:05:29 +00001241 for i in range(len(args)):
1242 a = args[i]
1243 if a == "-h" or a == "--help":
1244 opt.help = True
1245 elif a == "--version":
1246 opt.version = True
1247 elif a == "--trace":
1248 trace.set(True)
1249 elif not a.startswith("-"):
1250 cmd = a
1251 arg = args[i + 1 :]
1252 break
1253 return cmd, opt, arg
Mike Frysingereb386eb2020-11-30 18:17:40 +00001254
1255
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001256class Requirements:
Mike Frysinger7e251262023-09-05 17:05:29 +00001257 """Helper for checking repo's system requirements."""
Mike Frysinger75c98322021-03-08 21:22:10 +00001258
Mike Frysinger7e251262023-09-05 17:05:29 +00001259 REQUIREMENTS_NAME = "requirements.json"
Mike Frysinger75c98322021-03-08 21:22:10 +00001260
Mike Frysinger7e251262023-09-05 17:05:29 +00001261 def __init__(self, requirements):
1262 """Initialize.
Mike Frysinger75c98322021-03-08 21:22:10 +00001263
Mike Frysinger7e251262023-09-05 17:05:29 +00001264 Args:
1265 requirements: A dictionary of settings.
1266 """
1267 self.requirements = requirements
Mike Frysinger75c98322021-03-08 21:22:10 +00001268
Mike Frysinger7e251262023-09-05 17:05:29 +00001269 @classmethod
1270 def from_dir(cls, path):
1271 return cls.from_file(os.path.join(path, cls.REQUIREMENTS_NAME))
Mike Frysinger75c98322021-03-08 21:22:10 +00001272
Mike Frysinger7e251262023-09-05 17:05:29 +00001273 @classmethod
1274 def from_file(cls, path):
1275 try:
1276 with open(path, "rb") as f:
1277 data = f.read()
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001278 except OSError:
Mike Frysinger7e251262023-09-05 17:05:29 +00001279 # NB: EnvironmentError is used for Python 2 & 3 compatibility.
1280 # If we couldn't open the file, assume it's an old source tree.
1281 return None
Mike Frysinger75c98322021-03-08 21:22:10 +00001282
Mike Frysinger7e251262023-09-05 17:05:29 +00001283 return cls.from_data(data)
Mike Frysinger75c98322021-03-08 21:22:10 +00001284
Mike Frysinger7e251262023-09-05 17:05:29 +00001285 @classmethod
1286 def from_data(cls, data):
1287 comment_line = re.compile(rb"^ *#")
1288 strip_data = b"".join(
1289 x for x in data.splitlines() if not comment_line.match(x)
1290 )
1291 try:
1292 json_data = json.loads(strip_data)
1293 except Exception: # pylint: disable=broad-except
1294 # If we couldn't parse it, assume it's incompatible.
1295 return None
Mike Frysinger75c98322021-03-08 21:22:10 +00001296
Mike Frysinger7e251262023-09-05 17:05:29 +00001297 return cls(json_data)
Mike Frysinger75c98322021-03-08 21:22:10 +00001298
Mike Frysinger7e251262023-09-05 17:05:29 +00001299 def _get_soft_ver(self, pkg):
1300 """Return the soft version for |pkg| if it exists."""
1301 return self.requirements.get(pkg, {}).get("soft", ())
Mike Frysinger75c98322021-03-08 21:22:10 +00001302
Mike Frysinger7e251262023-09-05 17:05:29 +00001303 def _get_hard_ver(self, pkg):
1304 """Return the hard version for |pkg| if it exists."""
1305 return self.requirements.get(pkg, {}).get("hard", ())
Mike Frysinger75c98322021-03-08 21:22:10 +00001306
Mike Frysinger7e251262023-09-05 17:05:29 +00001307 @staticmethod
1308 def _format_ver(ver):
1309 """Return a dotted version from |ver|."""
1310 return ".".join(str(x) for x in ver)
Mike Frysinger75c98322021-03-08 21:22:10 +00001311
Mike Frysinger7e251262023-09-05 17:05:29 +00001312 def assert_ver(self, pkg, curr_ver):
1313 """Verify |pkg|'s |curr_ver| is new enough."""
1314 curr_ver = tuple(curr_ver)
1315 soft_ver = tuple(self._get_soft_ver(pkg))
1316 hard_ver = tuple(self._get_hard_ver(pkg))
1317 if curr_ver < hard_ver:
1318 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001319 f'repo: error: Your version of "{pkg}" '
1320 f"({self._format_ver(curr_ver)}) is unsupported; "
1321 "Please upgrade to at least version "
1322 f"{self._format_ver(soft_ver)} to continue.",
Mike Frysinger7e251262023-09-05 17:05:29 +00001323 file=sys.stderr,
1324 )
1325 sys.exit(1)
Mike Frysinger75c98322021-03-08 21:22:10 +00001326
Mike Frysinger7e251262023-09-05 17:05:29 +00001327 if curr_ver < soft_ver:
1328 print(
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001329 f'repo: error: Your version of "{pkg}" '
1330 f"({self._format_ver(curr_ver)}) is no longer supported; "
1331 "Please upgrade to at least version "
1332 f"{self._format_ver(soft_ver)} to continue.",
Mike Frysinger7e251262023-09-05 17:05:29 +00001333 file=sys.stderr,
1334 )
Mike Frysinger75c98322021-03-08 21:22:10 +00001335
Mike Frysinger7e251262023-09-05 17:05:29 +00001336 def assert_all(self):
1337 """Assert all of the requirements are satisified."""
1338 # See if we need a repo launcher upgrade first.
1339 self.assert_ver("repo", VERSION)
Mike Frysinger75c98322021-03-08 21:22:10 +00001340
Mike Frysinger7e251262023-09-05 17:05:29 +00001341 # Check python before we try to import the repo code.
1342 self.assert_ver("python", sys.version_info)
Mike Frysinger75c98322021-03-08 21:22:10 +00001343
Mike Frysinger7e251262023-09-05 17:05:29 +00001344 # Check git while we're at it.
1345 self.assert_ver("git", ParseGitVersion())
Mike Frysinger75c98322021-03-08 21:22:10 +00001346
1347
Mike Frysingereb386eb2020-11-30 18:17:40 +00001348def _Usage():
Mike Frysinger7e251262023-09-05 17:05:29 +00001349 gitc_usage = ""
1350 if get_gitc_manifest_dir():
1351 gitc_usage = " gitc-init Initialize a GITC Client.\n"
Mike Frysingereb386eb2020-11-30 18:17:40 +00001352
Mike Frysinger7e251262023-09-05 17:05:29 +00001353 print(
1354 """usage: repo COMMAND [ARGS]
Mike Frysingereb386eb2020-11-30 18:17:40 +00001355
1356repo is not yet installed. Use "repo init" to install it here.
1357
1358The most commonly used repo commands are:
1359
1360 init Install repo in the current working directory
Mike Frysinger7e251262023-09-05 17:05:29 +00001361"""
1362 + gitc_usage
1363 + """ help Display detailed help on a command
Mike Frysingereb386eb2020-11-30 18:17:40 +00001364
1365For access to the full online help, install repo ("repo init").
Mike Frysinger7e251262023-09-05 17:05:29 +00001366"""
1367 )
1368 print("Bug reports:", BUG_URL)
1369 sys.exit(0)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001370
1371
1372def _Help(args):
Mike Frysinger7e251262023-09-05 17:05:29 +00001373 if args:
1374 if args[0] in {"init", "gitc-init"}:
1375 parser = GetParser(gitc_init=args[0] == "gitc-init")
1376 parser.print_help()
1377 sys.exit(0)
1378 else:
1379 print(
1380 "error: '%s' is not a bootstrap command.\n"
1381 ' For access to online help, install repo ("repo init").'
1382 % args[0],
1383 file=sys.stderr,
1384 )
Mike Frysingereb386eb2020-11-30 18:17:40 +00001385 else:
Mike Frysinger7e251262023-09-05 17:05:29 +00001386 _Usage()
1387 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001388
1389
1390def _Version():
Mike Frysinger7e251262023-09-05 17:05:29 +00001391 """Show version information."""
1392 print("<repo not installed>")
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001393 print(f"repo launcher version {'.'.join(str(x) for x in VERSION)}")
1394 print(f" (from {__file__})")
1395 print(f"git {ParseGitVersion().full}")
1396 print(f"Python {sys.version}")
Mike Frysinger7e251262023-09-05 17:05:29 +00001397 uname = platform.uname()
1398 if sys.version_info.major < 3:
1399 # Python 3 returns a named tuple, but Python 2 is simpler.
1400 print(uname)
1401 else:
Josip Sokcevicfd6e5272023-10-31 17:30:59 +00001402 print(f"OS {uname.system} {uname.release} ({uname.version})")
1403 processor = uname.processor if uname.processor else "unknown"
1404 print(f"CPU {uname.machine} ({processor})")
Mike Frysinger7e251262023-09-05 17:05:29 +00001405 print("Bug reports:", BUG_URL)
1406 sys.exit(0)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001407
1408
1409def _NotInstalled():
Mike Frysinger7e251262023-09-05 17:05:29 +00001410 print(
1411 'error: repo is not installed. Use "repo init" to install it here.',
1412 file=sys.stderr,
1413 )
1414 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001415
1416
1417def _NoCommands(cmd):
Mike Frysinger7e251262023-09-05 17:05:29 +00001418 print(
1419 """error: command '%s' requires repo to be installed first.
1420 Use "repo init" to install it here."""
1421 % cmd,
1422 file=sys.stderr,
1423 )
1424 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001425
1426
1427def _RunSelf(wrapper_path):
Mike Frysinger7e251262023-09-05 17:05:29 +00001428 my_dir = os.path.dirname(wrapper_path)
1429 my_main = os.path.join(my_dir, "main.py")
1430 my_git = os.path.join(my_dir, ".git")
Mike Frysingereb386eb2020-11-30 18:17:40 +00001431
Mike Frysinger7e251262023-09-05 17:05:29 +00001432 if os.path.isfile(my_main) and os.path.isdir(my_git):
1433 for name in ["git_config.py", "project.py", "subcmds"]:
1434 if not os.path.exists(os.path.join(my_dir, name)):
1435 return None, None
1436 return my_main, my_git
1437 return None, None
Mike Frysingereb386eb2020-11-30 18:17:40 +00001438
1439
1440def _SetDefaultsTo(gitdir):
Mike Frysinger7e251262023-09-05 17:05:29 +00001441 global REPO_URL
1442 global REPO_REV
Mike Frysingereb386eb2020-11-30 18:17:40 +00001443
Mike Frysinger7e251262023-09-05 17:05:29 +00001444 REPO_URL = gitdir
1445 ret = run_git("--git-dir=%s" % gitdir, "symbolic-ref", "HEAD", check=False)
1446 if ret.returncode:
1447 # If we're not tracking a branch (bisect/etc...), then fall back to commit.
1448 print(
1449 "repo: warning: %s has no current branch; using HEAD" % gitdir,
1450 file=sys.stderr,
1451 )
1452 try:
1453 ret = run_git("rev-parse", "HEAD", cwd=gitdir)
1454 except CloneFailure:
1455 print("fatal: %s has invalid HEAD" % gitdir, file=sys.stderr)
1456 sys.exit(1)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001457
Mike Frysinger7e251262023-09-05 17:05:29 +00001458 REPO_REV = ret.stdout.strip()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001459
1460
1461def main(orig_args):
Mike Frysinger7e251262023-09-05 17:05:29 +00001462 cmd, opt, args = _ParseArguments(orig_args)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001463
Mike Frysinger7e251262023-09-05 17:05:29 +00001464 # We run this early as we run some git commands ourselves.
1465 SetGitTrace2ParentSid()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001466
Mike Frysinger7e251262023-09-05 17:05:29 +00001467 repo_main, rel_repo_dir = None, None
1468 # Don't use the local repo copy, make sure to switch to the gitc client first.
1469 if cmd != "gitc-init":
1470 repo_main, rel_repo_dir = _FindRepo()
Mike Frysingereb386eb2020-11-30 18:17:40 +00001471
Mike Frysinger7e251262023-09-05 17:05:29 +00001472 wrapper_path = os.path.abspath(__file__)
1473 my_main, my_git = _RunSelf(wrapper_path)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001474
Mike Frysinger7e251262023-09-05 17:05:29 +00001475 cwd = os.getcwd()
1476 if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()):
1477 print(
1478 "error: repo cannot be used in the GITC local manifest directory."
1479 "\nIf you want to work on this GITC client please rerun this "
1480 "command from the corresponding client under /gitc/",
1481 file=sys.stderr,
1482 )
Mike Frysingereb386eb2020-11-30 18:17:40 +00001483 sys.exit(1)
Mike Frysinger7e251262023-09-05 17:05:29 +00001484 if not repo_main:
1485 # Only expand aliases here since we'll be parsing the CLI ourselves.
1486 # If we had repo_main, alias expansion would happen in main.py.
1487 cmd, alias_args = _ExpandAlias(cmd)
1488 args = alias_args + args
Mike Frysingereb386eb2020-11-30 18:17:40 +00001489
Mike Frysinger7e251262023-09-05 17:05:29 +00001490 if opt.help:
1491 _Usage()
1492 if cmd == "help":
1493 _Help(args)
1494 if opt.version or cmd == "version":
1495 _Version()
1496 if not cmd:
1497 _NotInstalled()
1498 if cmd == "init" or cmd == "gitc-init":
1499 if my_git:
1500 _SetDefaultsTo(my_git)
1501 try:
1502 _Init(args, gitc_init=(cmd == "gitc-init"))
1503 except CloneFailure:
1504 path = os.path.join(repodir, S_repo)
1505 print(
1506 "fatal: cloning the git-repo repository failed, will remove "
1507 "'%s' " % path,
1508 file=sys.stderr,
1509 )
1510 shutil.rmtree(path, ignore_errors=True)
1511 shutil.rmtree(path + ".tmp", ignore_errors=True)
1512 sys.exit(1)
1513 repo_main, rel_repo_dir = _FindRepo()
1514 else:
1515 _NoCommands(cmd)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001516
Mike Frysinger7e251262023-09-05 17:05:29 +00001517 if my_main:
1518 repo_main = my_main
Mike Frysingereb386eb2020-11-30 18:17:40 +00001519
Mike Frysinger7e251262023-09-05 17:05:29 +00001520 if not repo_main:
1521 print("fatal: unable to find repo entry point", file=sys.stderr)
1522 sys.exit(1)
Mike Frysinger75c98322021-03-08 21:22:10 +00001523
Mike Frysinger7e251262023-09-05 17:05:29 +00001524 reqs = Requirements.from_dir(os.path.dirname(repo_main))
1525 if reqs:
1526 reqs.assert_all()
1527
1528 ver_str = ".".join(map(str, VERSION))
1529 me = [
1530 sys.executable,
1531 repo_main,
1532 "--repo-dir=%s" % rel_repo_dir,
1533 "--wrapper-version=%s" % ver_str,
1534 "--wrapper-path=%s" % wrapper_path,
1535 "--",
1536 ]
1537 me.extend(orig_args)
1538 exec_command(me)
1539 print("fatal: unable to start %s" % repo_main, file=sys.stderr)
1540 sys.exit(148)
Mike Frysingereb386eb2020-11-30 18:17:40 +00001541
1542
Mike Frysinger7e251262023-09-05 17:05:29 +00001543if __name__ == "__main__":
1544 main(sys.argv[1:])