summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2023-09-01 13:58:46 -0400
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2023-09-01 18:08:58 +0000
commitb861511db919b93483b69136fd0f2c6ddab6b4ea (patch)
treef8f584f5a6a18e54e4d95844de5ac1fa6f18b8c4
parente914ec293a414dc6cf092c1323c1c059943e706f (diff)
downloadgit-repo-b861511db919b93483b69136fd0f2c6ddab6b4ea.tar.gz
fix black formatting of standalone programs
Black will only check .py files when given a dir and --check, so list our few standalone programs explicitly. This causes the repo launcher to be reformatted since it was missed in the previous mass reformat. Bug: b/267675342 Change-Id: Ic90a7f5d84fc02e9fccb05945310fd067e2ed764 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/385034 Tested-by: Mike Frysinger <vapier@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com> Commit-Queue: Mike Frysinger <vapier@google.com>
-rwxr-xr-xrepo2202
-rwxr-xr-xrun_tests10
2 files changed, 1220 insertions, 992 deletions
diff --git a/repo b/repo
index 79237752..d2a4df2e 100755
--- a/repo
+++ b/repo
@@ -41,112 +41,136 @@ MIN_PYTHON_VERSION_HARD = (3, 5)
41 41
42# Keep basic logic in sync with repo_trace.py. 42# Keep basic logic in sync with repo_trace.py.
43class Trace(object): 43class Trace(object):
44 """Trace helper logic.""" 44 """Trace helper logic."""
45 45
46 REPO_TRACE = 'REPO_TRACE' 46 REPO_TRACE = "REPO_TRACE"
47 47
48 def __init__(self): 48 def __init__(self):
49 self.set(os.environ.get(self.REPO_TRACE) == '1') 49 self.set(os.environ.get(self.REPO_TRACE) == "1")
50 50
51 def set(self, value): 51 def set(self, value):
52 self.enabled = bool(value) 52 self.enabled = bool(value)
53 53
54 def print(self, *args, **kwargs): 54 def print(self, *args, **kwargs):
55 if self.enabled: 55 if self.enabled:
56 print(*args, **kwargs) 56 print(*args, **kwargs)
57 57
58 58
59trace = Trace() 59trace = Trace()
60 60
61 61
62def exec_command(cmd): 62def exec_command(cmd):
63 """Execute |cmd| or return None on failure.""" 63 """Execute |cmd| or return None on failure."""
64 trace.print(':', ' '.join(cmd)) 64 trace.print(":", " ".join(cmd))
65 try: 65 try:
66 if platform.system() == 'Windows': 66 if platform.system() == "Windows":
67 ret = subprocess.call(cmd) 67 ret = subprocess.call(cmd)
68 sys.exit(ret) 68 sys.exit(ret)
69 else: 69 else:
70 os.execvp(cmd[0], cmd) 70 os.execvp(cmd[0], cmd)
71 except Exception: 71 except Exception:
72 pass 72 pass
73 73
74 74
75def check_python_version(): 75def check_python_version():
76 """Make sure the active Python version is recent enough.""" 76 """Make sure the active Python version is recent enough."""
77 def reexec(prog): 77
78 exec_command([prog] + sys.argv) 78 def reexec(prog):
79 79 exec_command([prog] + sys.argv)
80 ver = sys.version_info 80
81 major = ver.major 81 ver = sys.version_info
82 minor = ver.minor 82 major = ver.major
83 83 minor = ver.minor
84 # Abort on very old Python 2 versions. 84
85 if (major, minor) < (2, 7): 85 # Abort on very old Python 2 versions.
86 print('repo: error: Your Python version is too old. ' 86 if (major, minor) < (2, 7):
87 'Please use Python {}.{} or newer instead.'.format( 87 print(
88 *MIN_PYTHON_VERSION_SOFT), file=sys.stderr) 88 "repo: error: Your Python version is too old. "
89 sys.exit(1) 89 "Please use Python {}.{} or newer instead.".format(
90 *MIN_PYTHON_VERSION_SOFT
91 ),
92 file=sys.stderr,
93 )
94 sys.exit(1)
90 95
91 # Try to re-exec the version specific Python 3 if needed. 96 # Try to re-exec the version specific Python 3 if needed.
92 if (major, minor) < MIN_PYTHON_VERSION_SOFT: 97 if (major, minor) < MIN_PYTHON_VERSION_SOFT:
93 # Python makes releases ~once a year, so try our min version +10 to help 98 # Python makes releases ~once a year, so try our min version +10 to help
94 # bridge the gap. This is the fallback anyways so perf isn't critical. 99 # bridge the gap. This is the fallback anyways so perf isn't critical.
95 min_major, min_minor = MIN_PYTHON_VERSION_SOFT 100 min_major, min_minor = MIN_PYTHON_VERSION_SOFT
96 for inc in range(0, 10): 101 for inc in range(0, 10):
97 reexec('python{}.{}'.format(min_major, min_minor + inc)) 102 reexec("python{}.{}".format(min_major, min_minor + inc))
98 103
99 # Fallback to older versions if possible. 104 # Fallback to older versions if possible.
100 for inc in range(MIN_PYTHON_VERSION_SOFT[1] - MIN_PYTHON_VERSION_HARD[1], 0, -1): 105 for inc in range(
101 # Don't downgrade, and don't reexec ourselves (which would infinite loop). 106 MIN_PYTHON_VERSION_SOFT[1] - MIN_PYTHON_VERSION_HARD[1], 0, -1
102 if (min_major, min_minor - inc) <= (major, minor): 107 ):
103 break 108 # Don't downgrade, and don't reexec ourselves (which would infinite loop).
104 reexec('python{}.{}'.format(min_major, min_minor - inc)) 109 if (min_major, min_minor - inc) <= (major, minor):
105 110 break
106 # Try the generic Python 3 wrapper, but only if it's new enough. If it 111 reexec("python{}.{}".format(min_major, min_minor - inc))
107 # isn't, we want to just give up below and make the user resolve things. 112
108 try: 113 # Try the generic Python 3 wrapper, but only if it's new enough. If it
109 proc = subprocess.Popen( 114 # isn't, we want to just give up below and make the user resolve things.
110 ['python3', '-c', 'import sys; ' 115 try:
111 'print(sys.version_info.major, sys.version_info.minor)'], 116 proc = subprocess.Popen(
112 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 117 [
113 (output, _) = proc.communicate() 118 "python3",
114 python3_ver = tuple(int(x) for x in output.decode('utf-8').split()) 119 "-c",
115 except (OSError, subprocess.CalledProcessError): 120 "import sys; "
116 python3_ver = None 121 "print(sys.version_info.major, sys.version_info.minor)",
117 122 ],
118 # If the python3 version looks like it's new enough, give it a try. 123 stdout=subprocess.PIPE,
119 if (python3_ver and python3_ver >= MIN_PYTHON_VERSION_HARD 124 stderr=subprocess.PIPE,
120 and python3_ver != (major, minor)): 125 )
121 reexec('python3') 126 (output, _) = proc.communicate()
122 127 python3_ver = tuple(int(x) for x in output.decode("utf-8").split())
123 # We're still here, so diagnose things for the user. 128 except (OSError, subprocess.CalledProcessError):
124 if major < 3: 129 python3_ver = None
125 print('repo: error: Python 2 is no longer supported; ' 130
126 'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION_HARD), 131 # If the python3 version looks like it's new enough, give it a try.
127 file=sys.stderr) 132 if (
128 sys.exit(1) 133 python3_ver
129 elif (major, minor) < MIN_PYTHON_VERSION_HARD: 134 and python3_ver >= MIN_PYTHON_VERSION_HARD
130 print('repo: error: Python 3 version is too old; ' 135 and python3_ver != (major, minor)
131 'Please use Python {}.{} or newer.'.format(*MIN_PYTHON_VERSION_HARD), 136 ):
132 file=sys.stderr) 137 reexec("python3")
133 sys.exit(1) 138
134 139 # We're still here, so diagnose things for the user.
135 140 if major < 3:
136if __name__ == '__main__': 141 print(
137 check_python_version() 142 "repo: error: Python 2 is no longer supported; "
143 "Please upgrade to Python {}.{}+.".format(
144 *MIN_PYTHON_VERSION_HARD
145 ),
146 file=sys.stderr,
147 )
148 sys.exit(1)
149 elif (major, minor) < MIN_PYTHON_VERSION_HARD:
150 print(
151 "repo: error: Python 3 version is too old; "
152 "Please use Python {}.{} or newer.".format(
153 *MIN_PYTHON_VERSION_HARD
154 ),
155 file=sys.stderr,
156 )
157 sys.exit(1)
158
159
160if __name__ == "__main__":
161 check_python_version()
138 162
139 163
140# repo default configuration 164# repo default configuration
141# 165#
142REPO_URL = os.environ.get('REPO_URL', None) 166REPO_URL = os.environ.get("REPO_URL", None)
143if not REPO_URL: 167if not REPO_URL:
144 REPO_URL = 'https://gerrit.googlesource.com/git-repo' 168 REPO_URL = "https://gerrit.googlesource.com/git-repo"
145REPO_REV = os.environ.get('REPO_REV') 169REPO_REV = os.environ.get("REPO_REV")
146if not REPO_REV: 170if not REPO_REV:
147 REPO_REV = 'stable' 171 REPO_REV = "stable"
148# URL to file bug reports for repo tool issues. 172# URL to file bug reports for repo tool issues.
149BUG_URL = 'https://issues.gerritcodereview.com/issues/new?component=1370071' 173BUG_URL = "https://issues.gerritcodereview.com/issues/new?component=1370071"
150 174
151# increment this whenever we make important changes to this script 175# increment this whenever we make important changes to this script
152VERSION = (2, 36) 176VERSION = (2, 36)
@@ -231,19 +255,19 @@ cZ7aFsJF4PtcDrfdejyAxbtsSHI=
231-----END PGP PUBLIC KEY BLOCK----- 255-----END PGP PUBLIC KEY BLOCK-----
232""" 256"""
233 257
234GIT = 'git' # our git command 258GIT = "git" # our git command
235# NB: The version of git that the repo launcher requires may be much older than 259# NB: The version of git that the repo launcher requires may be much older than
236# the version of git that the main repo source tree requires. Keeping this at 260# the version of git that the main repo source tree requires. Keeping this at
237# an older version also makes it easier for users to upgrade/rollback as needed. 261# an older version also makes it easier for users to upgrade/rollback as needed.
238# 262#
239# git-1.7 is in (EOL) Ubuntu Precise. 263# git-1.7 is in (EOL) Ubuntu Precise.
240MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version 264MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version
241repodir = '.repo' # name of repo's private directory 265repodir = ".repo" # name of repo's private directory
242S_repo = 'repo' # special repo repository 266S_repo = "repo" # special repo repository
243S_manifests = 'manifests' # special manifest repository 267S_manifests = "manifests" # special manifest repository
244REPO_MAIN = S_repo + '/main.py' # main script 268REPO_MAIN = S_repo + "/main.py" # main script
245GITC_CONFIG_FILE = '/gitc/.config' 269GITC_CONFIG_FILE = "/gitc/.config"
246GITC_FS_ROOT_DIR = '/gitc/manifest-rw/' 270GITC_FS_ROOT_DIR = "/gitc/manifest-rw/"
247 271
248 272
249import collections 273import collections
@@ -256,1074 +280,1270 @@ import stat
256 280
257 281
258if sys.version_info[0] == 3: 282if sys.version_info[0] == 3:
259 import urllib.error 283 import urllib.error
260 import urllib.request 284 import urllib.request
261else: 285else:
262 import imp 286 import imp
287
288 import urllib2
263 289
264 import urllib2 290 urllib = imp.new_module("urllib")
265 urllib = imp.new_module('urllib') 291 urllib.request = urllib2
266 urllib.request = urllib2 292 urllib.error = urllib2
267 urllib.error = urllib2
268 293
269 294
270repo_config_dir = os.getenv('REPO_CONFIG_DIR', os.path.expanduser('~')) 295repo_config_dir = os.getenv("REPO_CONFIG_DIR", os.path.expanduser("~"))
271home_dot_repo = os.path.join(repo_config_dir, '.repoconfig') 296home_dot_repo = os.path.join(repo_config_dir, ".repoconfig")
272gpg_dir = os.path.join(home_dot_repo, 'gnupg') 297gpg_dir = os.path.join(home_dot_repo, "gnupg")
273 298
274 299
275def GetParser(gitc_init=False): 300def GetParser(gitc_init=False):
276 """Setup the CLI parser.""" 301 """Setup the CLI parser."""
277 if gitc_init: 302 if gitc_init:
278 sys.exit('repo: fatal: GITC not supported.') 303 sys.exit("repo: fatal: GITC not supported.")
279 else: 304 else:
280 usage = 'repo init [options] [-u] url' 305 usage = "repo init [options] [-u] url"
281 306
282 parser = optparse.OptionParser(usage=usage) 307 parser = optparse.OptionParser(usage=usage)
283 InitParser(parser) 308 InitParser(parser)
284 return parser 309 return parser
285 310
286 311
287def InitParser(parser): 312def InitParser(parser):
288 """Setup the CLI parser.""" 313 """Setup the CLI parser."""
289 # NB: Keep in sync with command.py:_CommonOptions(). 314 # NB: Keep in sync with command.py:_CommonOptions().
290 315
291 # Logging. 316 # Logging.
292 group = parser.add_option_group('Logging options') 317 group = parser.add_option_group("Logging options")
293 group.add_option('-v', '--verbose', 318 group.add_option(
294 dest='output_mode', action='store_true', 319 "-v",
295 help='show all output') 320 "--verbose",
296 group.add_option('-q', '--quiet', 321 dest="output_mode",
297 dest='output_mode', action='store_false', 322 action="store_true",
298 help='only show errors') 323 help="show all output",
299 324 )
300 # Manifest. 325 group.add_option(
301 group = parser.add_option_group('Manifest options') 326 "-q",
302 group.add_option('-u', '--manifest-url', 327 "--quiet",
303 help='manifest repository location', metavar='URL') 328 dest="output_mode",
304 group.add_option('-b', '--manifest-branch', metavar='REVISION', 329 action="store_false",
305 help='manifest branch or revision (use HEAD for default)') 330 help="only show errors",
306 group.add_option('-m', '--manifest-name', default='default.xml', 331 )
307 help='initial manifest file', metavar='NAME.xml') 332
308 group.add_option('-g', '--groups', default='default', 333 # Manifest.
309 help='restrict manifest projects to ones with specified ' 334 group = parser.add_option_group("Manifest options")
310 'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]', 335 group.add_option(
311 metavar='GROUP') 336 "-u",
312 group.add_option('-p', '--platform', default='auto', 337 "--manifest-url",
313 help='restrict manifest projects to ones with a specified ' 338 help="manifest repository location",
314 'platform group [auto|all|none|linux|darwin|...]', 339 metavar="URL",
315 metavar='PLATFORM') 340 )
316 group.add_option('--submodules', action='store_true', 341 group.add_option(
317 help='sync any submodules associated with the manifest repo') 342 "-b",
318 group.add_option('--standalone-manifest', action='store_true', 343 "--manifest-branch",
319 help='download the manifest as a static file ' 344 metavar="REVISION",
320 'rather then create a git checkout of ' 345 help="manifest branch or revision (use HEAD for default)",
321 'the manifest repo') 346 )
322 group.add_option('--manifest-depth', type='int', default=0, metavar='DEPTH', 347 group.add_option(
323 help='create a shallow clone of the manifest repo with ' 348 "-m",
324 'given depth (0 for full clone); see git clone ' 349 "--manifest-name",
325 '(default: %default)') 350 default="default.xml",
326 351 help="initial manifest file",
327 # Options that only affect manifest project, and not any of the projects 352 metavar="NAME.xml",
328 # specified in the manifest itself. 353 )
329 group = parser.add_option_group('Manifest (only) checkout options') 354 group.add_option(
330 355 "-g",
331 group.add_option('--current-branch', '-c', default=True, 356 "--groups",
332 dest='current_branch_only', action='store_true', 357 default="default",
333 help='fetch only current manifest branch from server (default)') 358 help="restrict manifest projects to ones with specified "
334 group.add_option('--no-current-branch', 359 "group(s) [default|all|G1,G2,G3|G4,-G5,-G6]",
335 dest='current_branch_only', action='store_false', 360 metavar="GROUP",
336 help='fetch all manifest branches from server') 361 )
337 group.add_option('--tags', 362 group.add_option(
338 action='store_true', 363 "-p",
339 help='fetch tags in the manifest') 364 "--platform",
340 group.add_option('--no-tags', 365 default="auto",
341 dest='tags', action='store_false', 366 help="restrict manifest projects to ones with a specified "
342 help="don't fetch tags in the manifest") 367 "platform group [auto|all|none|linux|darwin|...]",
343 368 metavar="PLATFORM",
344 # These are fundamentally different ways of structuring the checkout. 369 )
345 group = parser.add_option_group('Checkout modes') 370 group.add_option(
346 group.add_option('--mirror', action='store_true', 371 "--submodules",
347 help='create a replica of the remote repositories ' 372 action="store_true",
348 'rather than a client working directory') 373 help="sync any submodules associated with the manifest repo",
349 group.add_option('--archive', action='store_true', 374 )
350 help='checkout an archive instead of a git repository for ' 375 group.add_option(
351 'each project. See git archive.') 376 "--standalone-manifest",
352 group.add_option('--worktree', action='store_true', 377 action="store_true",
353 help='use git-worktree to manage projects') 378 help="download the manifest as a static file "
354 379 "rather then create a git checkout of "
355 # These are fundamentally different ways of structuring the checkout. 380 "the manifest repo",
356 group = parser.add_option_group('Project checkout optimizations') 381 )
357 group.add_option('--reference', 382 group.add_option(
358 help='location of mirror directory', metavar='DIR') 383 "--manifest-depth",
359 group.add_option('--dissociate', action='store_true', 384 type="int",
360 help='dissociate from reference mirrors after clone') 385 default=0,
361 group.add_option('--depth', type='int', default=None, 386 metavar="DEPTH",
362 help='create a shallow clone with given depth; ' 387 help="create a shallow clone of the manifest repo with "
363 'see git clone') 388 "given depth (0 for full clone); see git clone "
364 group.add_option('--partial-clone', action='store_true', 389 "(default: %default)",
365 help='perform partial clone (https://git-scm.com/' 390 )
366 'docs/gitrepository-layout#_code_partialclone_code)') 391
367 group.add_option('--no-partial-clone', action='store_false', 392 # Options that only affect manifest project, and not any of the projects
368 help='disable use of partial clone (https://git-scm.com/' 393 # specified in the manifest itself.
369 'docs/gitrepository-layout#_code_partialclone_code)') 394 group = parser.add_option_group("Manifest (only) checkout options")
370 group.add_option('--partial-clone-exclude', action='store', 395
371 help='exclude the specified projects (a comma-delimited ' 396 group.add_option(
372 'project names) from partial clone (https://git-scm.com' 397 "--current-branch",
373 '/docs/gitrepository-layout#_code_partialclone_code)') 398 "-c",
374 group.add_option('--clone-filter', action='store', default='blob:none', 399 default=True,
375 help='filter for use with --partial-clone ' 400 dest="current_branch_only",
376 '[default: %default]') 401 action="store_true",
377 group.add_option('--use-superproject', action='store_true', default=None, 402 help="fetch only current manifest branch from server (default)",
378 help='use the manifest superproject to sync projects; implies -c') 403 )
379 group.add_option('--no-use-superproject', action='store_false', 404 group.add_option(
380 dest='use_superproject', 405 "--no-current-branch",
381 help='disable use of manifest superprojects') 406 dest="current_branch_only",
382 group.add_option('--clone-bundle', action='store_true', 407 action="store_false",
383 help='enable use of /clone.bundle on HTTP/HTTPS ' 408 help="fetch all manifest branches from server",
384 '(default if not --partial-clone)') 409 )
385 group.add_option('--no-clone-bundle', 410 group.add_option(
386 dest='clone_bundle', action='store_false', 411 "--tags", action="store_true", help="fetch tags in the manifest"
387 help='disable use of /clone.bundle on HTTP/HTTPS (default if --partial-clone)') 412 )
388 group.add_option('--git-lfs', action='store_true', 413 group.add_option(
389 help='enable Git LFS support') 414 "--no-tags",
390 group.add_option('--no-git-lfs', 415 dest="tags",
391 dest='git_lfs', action='store_false', 416 action="store_false",
392 help='disable Git LFS support') 417 help="don't fetch tags in the manifest",
393 418 )
394 # Tool. 419
395 group = parser.add_option_group('repo Version options') 420 # These are fundamentally different ways of structuring the checkout.
396 group.add_option('--repo-url', metavar='URL', 421 group = parser.add_option_group("Checkout modes")
397 help='repo repository location ($REPO_URL)') 422 group.add_option(
398 group.add_option('--repo-rev', metavar='REV', 423 "--mirror",
399 help='repo branch or revision ($REPO_REV)') 424 action="store_true",
400 group.add_option('--repo-branch', dest='repo_rev', 425 help="create a replica of the remote repositories "
401 help=optparse.SUPPRESS_HELP) 426 "rather than a client working directory",
402 group.add_option('--no-repo-verify', 427 )
403 dest='repo_verify', default=True, action='store_false', 428 group.add_option(
404 help='do not verify repo source code') 429 "--archive",
405 430 action="store_true",
406 # Other. 431 help="checkout an archive instead of a git repository for "
407 group = parser.add_option_group('Other options') 432 "each project. See git archive.",
408 group.add_option('--config-name', 433 )
409 action='store_true', default=False, 434 group.add_option(
410 help='Always prompt for name/e-mail') 435 "--worktree",
411 436 action="store_true",
412 return parser 437 help="use git-worktree to manage projects",
438 )
439
440 # These are fundamentally different ways of structuring the checkout.
441 group = parser.add_option_group("Project checkout optimizations")
442 group.add_option(
443 "--reference", help="location of mirror directory", metavar="DIR"
444 )
445 group.add_option(
446 "--dissociate",
447 action="store_true",
448 help="dissociate from reference mirrors after clone",
449 )
450 group.add_option(
451 "--depth",
452 type="int",
453 default=None,
454 help="create a shallow clone with given depth; " "see git clone",
455 )
456 group.add_option(
457 "--partial-clone",
458 action="store_true",
459 help="perform partial clone (https://git-scm.com/"
460 "docs/gitrepository-layout#_code_partialclone_code)",
461 )
462 group.add_option(
463 "--no-partial-clone",
464 action="store_false",
465 help="disable use of partial clone (https://git-scm.com/"
466 "docs/gitrepository-layout#_code_partialclone_code)",
467 )
468 group.add_option(
469 "--partial-clone-exclude",
470 action="store",
471 help="exclude the specified projects (a comma-delimited "
472 "project names) from partial clone (https://git-scm.com"
473 "/docs/gitrepository-layout#_code_partialclone_code)",
474 )
475 group.add_option(
476 "--clone-filter",
477 action="store",
478 default="blob:none",
479 help="filter for use with --partial-clone " "[default: %default]",
480 )
481 group.add_option(
482 "--use-superproject",
483 action="store_true",
484 default=None,
485 help="use the manifest superproject to sync projects; implies -c",
486 )
487 group.add_option(
488 "--no-use-superproject",
489 action="store_false",
490 dest="use_superproject",
491 help="disable use of manifest superprojects",
492 )
493 group.add_option(
494 "--clone-bundle",
495 action="store_true",
496 help="enable use of /clone.bundle on HTTP/HTTPS "
497 "(default if not --partial-clone)",
498 )
499 group.add_option(
500 "--no-clone-bundle",
501 dest="clone_bundle",
502 action="store_false",
503 help="disable use of /clone.bundle on HTTP/HTTPS (default if --partial-clone)",
504 )
505 group.add_option(
506 "--git-lfs", action="store_true", help="enable Git LFS support"
507 )
508 group.add_option(
509 "--no-git-lfs",
510 dest="git_lfs",
511 action="store_false",
512 help="disable Git LFS support",
513 )
514
515 # Tool.
516 group = parser.add_option_group("repo Version options")
517 group.add_option(
518 "--repo-url", metavar="URL", help="repo repository location ($REPO_URL)"
519 )
520 group.add_option(
521 "--repo-rev", metavar="REV", help="repo branch or revision ($REPO_REV)"
522 )
523 group.add_option(
524 "--repo-branch", dest="repo_rev", help=optparse.SUPPRESS_HELP
525 )
526 group.add_option(
527 "--no-repo-verify",
528 dest="repo_verify",
529 default=True,
530 action="store_false",
531 help="do not verify repo source code",
532 )
533
534 # Other.
535 group = parser.add_option_group("Other options")
536 group.add_option(
537 "--config-name",
538 action="store_true",
539 default=False,
540 help="Always prompt for name/e-mail",
541 )
542
543 return parser
413 544
414 545
415# This is a poor replacement for subprocess.run until we require Python 3.6+. 546# This is a poor replacement for subprocess.run until we require Python 3.6+.
416RunResult = collections.namedtuple( 547RunResult = collections.namedtuple(
417 'RunResult', ('returncode', 'stdout', 'stderr')) 548 "RunResult", ("returncode", "stdout", "stderr")
549)
418 550
419 551
420class RunError(Exception): 552class RunError(Exception):
421 """Error when running a command failed.""" 553 """Error when running a command failed."""
422 554
423 555
424def run_command(cmd, **kwargs): 556def run_command(cmd, **kwargs):
425 """Run |cmd| and return its output.""" 557 """Run |cmd| and return its output."""
426 check = kwargs.pop('check', False) 558 check = kwargs.pop("check", False)
427 if kwargs.pop('capture_output', False): 559 if kwargs.pop("capture_output", False):
428 kwargs.setdefault('stdout', subprocess.PIPE) 560 kwargs.setdefault("stdout", subprocess.PIPE)
429 kwargs.setdefault('stderr', subprocess.PIPE) 561 kwargs.setdefault("stderr", subprocess.PIPE)
430 cmd_input = kwargs.pop('input', None) 562 cmd_input = kwargs.pop("input", None)
431 563
432 def decode(output): 564 def decode(output):
433 """Decode |output| to text.""" 565 """Decode |output| to text."""
434 if output is None: 566 if output is None:
435 return output 567 return output
436 try: 568 try:
437 return output.decode('utf-8') 569 return output.decode("utf-8")
438 except UnicodeError: 570 except UnicodeError:
439 print('repo: warning: Invalid UTF-8 output:\ncmd: %r\n%r' % (cmd, output), 571 print(
440 file=sys.stderr) 572 "repo: warning: Invalid UTF-8 output:\ncmd: %r\n%r"
441 return output.decode('utf-8', 'backslashreplace') 573 % (cmd, output),
442 574 file=sys.stderr,
443 # Run & package the results. 575 )
444 proc = subprocess.Popen(cmd, **kwargs) 576 return output.decode("utf-8", "backslashreplace")
445 (stdout, stderr) = proc.communicate(input=cmd_input) 577
446 dbg = ': ' + ' '.join(cmd) 578 # Run & package the results.
447 if cmd_input is not None: 579 proc = subprocess.Popen(cmd, **kwargs)
448 dbg += ' 0<|' 580 (stdout, stderr) = proc.communicate(input=cmd_input)
449 if stdout == subprocess.PIPE: 581 dbg = ": " + " ".join(cmd)
450 dbg += ' 1>|' 582 if cmd_input is not None:
451 if stderr == subprocess.PIPE: 583 dbg += " 0<|"
452 dbg += ' 2>|' 584 if stdout == subprocess.PIPE:
453 elif stderr == subprocess.STDOUT: 585 dbg += " 1>|"
454 dbg += ' 2>&1' 586 if stderr == subprocess.PIPE:
455 trace.print(dbg) 587 dbg += " 2>|"
456 ret = RunResult(proc.returncode, decode(stdout), decode(stderr)) 588 elif stderr == subprocess.STDOUT:
457 589 dbg += " 2>&1"
458 # If things failed, print useful debugging output. 590 trace.print(dbg)
459 if check and ret.returncode: 591 ret = RunResult(proc.returncode, decode(stdout), decode(stderr))
460 print('repo: error: "%s" failed with exit status %s' % 592
461 (cmd[0], ret.returncode), file=sys.stderr) 593 # If things failed, print useful debugging output.
462 print(' cwd: %s\n cmd: %r' % 594 if check and ret.returncode:
463 (kwargs.get('cwd', os.getcwd()), cmd), file=sys.stderr) 595 print(
464 596 'repo: error: "%s" failed with exit status %s'
465 def _print_output(name, output): 597 % (cmd[0], ret.returncode),
466 if output: 598 file=sys.stderr,
467 print(' %s:\n >> %s' % (name, '\n >> '.join(output.splitlines())), 599 )
468 file=sys.stderr) 600 print(
469 601 " cwd: %s\n cmd: %r" % (kwargs.get("cwd", os.getcwd()), cmd),
470 _print_output('stdout', ret.stdout) 602 file=sys.stderr,
471 _print_output('stderr', ret.stderr) 603 )
472 raise RunError(ret) 604
473 605 def _print_output(name, output):
474 return ret 606 if output:
607 print(
608 " %s:\n >> %s"
609 % (name, "\n >> ".join(output.splitlines())),
610 file=sys.stderr,
611 )
612
613 _print_output("stdout", ret.stdout)
614 _print_output("stderr", ret.stderr)
615 raise RunError(ret)
616
617 return ret
475 618
476 619
477_gitc_manifest_dir = None 620_gitc_manifest_dir = None
478 621
479 622
480def get_gitc_manifest_dir(): 623def get_gitc_manifest_dir():
481 global _gitc_manifest_dir 624 global _gitc_manifest_dir
482 if _gitc_manifest_dir is None: 625 if _gitc_manifest_dir is None:
483 _gitc_manifest_dir = '' 626 _gitc_manifest_dir = ""
484 try: 627 try:
485 with open(GITC_CONFIG_FILE, 'r') as gitc_config: 628 with open(GITC_CONFIG_FILE, "r") as gitc_config:
486 for line in gitc_config: 629 for line in gitc_config:
487 match = re.match('gitc_dir=(?P<gitc_manifest_dir>.*)', line) 630 match = re.match("gitc_dir=(?P<gitc_manifest_dir>.*)", line)
488 if match: 631 if match:
489 _gitc_manifest_dir = match.group('gitc_manifest_dir') 632 _gitc_manifest_dir = match.group("gitc_manifest_dir")
490 except IOError: 633 except IOError:
491 pass 634 pass
492 return _gitc_manifest_dir 635 return _gitc_manifest_dir
493 636
494 637
495def gitc_parse_clientdir(gitc_fs_path): 638def gitc_parse_clientdir(gitc_fs_path):
496 """Parse a path in the GITC FS and return its client name. 639 """Parse a path in the GITC FS and return its client name.
497 640
498 Args: 641 Args:
499 gitc_fs_path: A subdirectory path within the GITC_FS_ROOT_DIR. 642 gitc_fs_path: A subdirectory path within the GITC_FS_ROOT_DIR.
500 643
501 Returns: 644 Returns:
502 The GITC client name. 645 The GITC client name.
503 """ 646 """
504 if gitc_fs_path == GITC_FS_ROOT_DIR: 647 if gitc_fs_path == GITC_FS_ROOT_DIR:
505 return None 648 return None
506 if not gitc_fs_path.startswith(GITC_FS_ROOT_DIR): 649 if not gitc_fs_path.startswith(GITC_FS_ROOT_DIR):
507 manifest_dir = get_gitc_manifest_dir() 650 manifest_dir = get_gitc_manifest_dir()
508 if manifest_dir == '': 651 if manifest_dir == "":
509 return None 652 return None
510 if manifest_dir[-1] != '/': 653 if manifest_dir[-1] != "/":
511 manifest_dir += '/' 654 manifest_dir += "/"
512 if gitc_fs_path == manifest_dir: 655 if gitc_fs_path == manifest_dir:
513 return None 656 return None
514 if not gitc_fs_path.startswith(manifest_dir): 657 if not gitc_fs_path.startswith(manifest_dir):
515 return None 658 return None
516 return gitc_fs_path.split(manifest_dir)[1].split('/')[0] 659 return gitc_fs_path.split(manifest_dir)[1].split("/")[0]
517 return gitc_fs_path.split(GITC_FS_ROOT_DIR)[1].split('/')[0] 660 return gitc_fs_path.split(GITC_FS_ROOT_DIR)[1].split("/")[0]
518 661
519 662
520class CloneFailure(Exception): 663class CloneFailure(Exception):
521 664
522 """Indicate the remote clone of repo itself failed. 665 """Indicate the remote clone of repo itself failed."""
523 """
524 666
525 667
526def check_repo_verify(repo_verify, quiet=False): 668def check_repo_verify(repo_verify, quiet=False):
527 """Check the --repo-verify state.""" 669 """Check the --repo-verify state."""
528 if not repo_verify: 670 if not repo_verify:
529 print('repo: warning: verification of repo code has been disabled;\n' 671 print(
530 'repo will not be able to verify the integrity of itself.\n', 672 "repo: warning: verification of repo code has been disabled;\n"
531 file=sys.stderr) 673 "repo will not be able to verify the integrity of itself.\n",
532 return False 674 file=sys.stderr,
675 )
676 return False
677
678 if NeedSetupGnuPG():
679 return SetupGnuPG(quiet)
533 680
534 if NeedSetupGnuPG(): 681 return True
535 return SetupGnuPG(quiet)
536
537 return True
538 682
539 683
540def check_repo_rev(dst, rev, repo_verify=True, quiet=False): 684def check_repo_rev(dst, rev, repo_verify=True, quiet=False):
541 """Check that |rev| is valid.""" 685 """Check that |rev| is valid."""
542 do_verify = check_repo_verify(repo_verify, quiet=quiet) 686 do_verify = check_repo_verify(repo_verify, quiet=quiet)
543 remote_ref, local_rev = resolve_repo_rev(dst, rev) 687 remote_ref, local_rev = resolve_repo_rev(dst, rev)
544 if not quiet and not remote_ref.startswith('refs/heads/'): 688 if not quiet and not remote_ref.startswith("refs/heads/"):
545 print('warning: repo is not tracking a remote branch, so it will not ' 689 print(
546 'receive updates', file=sys.stderr) 690 "warning: repo is not tracking a remote branch, so it will not "
547 if do_verify: 691 "receive updates",
548 rev = verify_rev(dst, remote_ref, local_rev, quiet) 692 file=sys.stderr,
549 else: 693 )
550 rev = local_rev 694 if do_verify:
551 return (remote_ref, rev) 695 rev = verify_rev(dst, remote_ref, local_rev, quiet)
696 else:
697 rev = local_rev
698 return (remote_ref, rev)
552 699
553 700
554def _Init(args, gitc_init=False): 701def _Init(args, gitc_init=False):
555 """Installs repo by cloning it over the network. 702 """Installs repo by cloning it over the network."""
556 """ 703 parser = GetParser(gitc_init=gitc_init)
557 parser = GetParser(gitc_init=gitc_init) 704 opt, args = parser.parse_args(args)
558 opt, args = parser.parse_args(args)
559 if args:
560 if not opt.manifest_url:
561 opt.manifest_url = args.pop(0)
562 if args: 705 if args:
563 parser.print_usage() 706 if not opt.manifest_url:
564 sys.exit(1) 707 opt.manifest_url = args.pop(0)
565 opt.quiet = opt.output_mode is False 708 if args:
566 opt.verbose = opt.output_mode is True 709 parser.print_usage()
567 710 sys.exit(1)
568 if opt.clone_bundle is None: 711 opt.quiet = opt.output_mode is False
569 opt.clone_bundle = False if opt.partial_clone else True 712 opt.verbose = opt.output_mode is True
570 713
571 url = opt.repo_url or REPO_URL 714 if opt.clone_bundle is None:
572 rev = opt.repo_rev or REPO_REV 715 opt.clone_bundle = False if opt.partial_clone else True
573 716
574 try: 717 url = opt.repo_url or REPO_URL
575 os.mkdir(repodir) 718 rev = opt.repo_rev or REPO_REV
576 except OSError as e: 719
577 if e.errno != errno.EEXIST: 720 try:
578 print('fatal: cannot make %s directory: %s' 721 os.mkdir(repodir)
579 % (repodir, e.strerror), file=sys.stderr) 722 except OSError as e:
580 # Don't raise CloneFailure; that would delete the 723 if e.errno != errno.EEXIST:
581 # name. Instead exit immediately. 724 print(
582 # 725 "fatal: cannot make %s directory: %s" % (repodir, e.strerror),
583 sys.exit(1) 726 file=sys.stderr,
584 727 )
585 _CheckGitVersion() 728 # Don't raise CloneFailure; that would delete the
586 try: 729 # name. Instead exit immediately.
587 if not opt.quiet: 730 #
588 print('Downloading Repo source from', url) 731 sys.exit(1)
589 dst_final = os.path.abspath(os.path.join(repodir, S_repo)) 732
590 dst = dst_final + '.tmp' 733 _CheckGitVersion()
591 shutil.rmtree(dst, ignore_errors=True) 734 try:
592 _Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose) 735 if not opt.quiet:
593 736 print("Downloading Repo source from", url)
594 remote_ref, rev = check_repo_rev(dst, rev, opt.repo_verify, quiet=opt.quiet) 737 dst_final = os.path.abspath(os.path.join(repodir, S_repo))
595 _Checkout(dst, remote_ref, rev, opt.quiet) 738 dst = dst_final + ".tmp"
596 739 shutil.rmtree(dst, ignore_errors=True)
597 if not os.path.isfile(os.path.join(dst, 'repo')): 740 _Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose)
598 print("fatal: '%s' does not look like a git-repo repository, is " 741
599 "--repo-url set correctly?" % url, file=sys.stderr) 742 remote_ref, rev = check_repo_rev(
600 raise CloneFailure() 743 dst, rev, opt.repo_verify, quiet=opt.quiet
601 744 )
602 os.rename(dst, dst_final) 745 _Checkout(dst, remote_ref, rev, opt.quiet)
603 746
604 except CloneFailure: 747 if not os.path.isfile(os.path.join(dst, "repo")):
605 print('fatal: double check your --repo-rev setting.', file=sys.stderr) 748 print(
606 if opt.quiet: 749 "fatal: '%s' does not look like a git-repo repository, is "
607 print('fatal: repo init failed; run without --quiet to see why', 750 "--repo-url set correctly?" % url,
608 file=sys.stderr) 751 file=sys.stderr,
609 raise 752 )
753 raise CloneFailure()
754
755 os.rename(dst, dst_final)
756
757 except CloneFailure:
758 print("fatal: double check your --repo-rev setting.", file=sys.stderr)
759 if opt.quiet:
760 print(
761 "fatal: repo init failed; run without --quiet to see why",
762 file=sys.stderr,
763 )
764 raise
610 765
611 766
612def run_git(*args, **kwargs): 767def run_git(*args, **kwargs):
613 """Run git and return execution details.""" 768 """Run git and return execution details."""
614 kwargs.setdefault('capture_output', True) 769 kwargs.setdefault("capture_output", True)
615 kwargs.setdefault('check', True) 770 kwargs.setdefault("check", True)
616 try: 771 try:
617 return run_command([GIT] + list(args), **kwargs) 772 return run_command([GIT] + list(args), **kwargs)
618 except OSError as e: 773 except OSError as e:
619 print(file=sys.stderr) 774 print(file=sys.stderr)
620 print('repo: error: "%s" is not available' % GIT, file=sys.stderr) 775 print('repo: error: "%s" is not available' % GIT, file=sys.stderr)
621 print('repo: error: %s' % e, file=sys.stderr) 776 print("repo: error: %s" % e, file=sys.stderr)
622 print(file=sys.stderr) 777 print(file=sys.stderr)
623 print('Please make sure %s is installed and in your path.' % GIT, 778 print(
624 file=sys.stderr) 779 "Please make sure %s is installed and in your path." % GIT,
625 sys.exit(1) 780 file=sys.stderr,
626 except RunError: 781 )
627 raise CloneFailure() 782 sys.exit(1)
783 except RunError:
784 raise CloneFailure()
628 785
629 786
630# The git version info broken down into components for easy analysis. 787# The git version info broken down into components for easy analysis.
631# Similar to Python's sys.version_info. 788# Similar to Python's sys.version_info.
632GitVersion = collections.namedtuple( 789GitVersion = collections.namedtuple(
633 'GitVersion', ('major', 'minor', 'micro', 'full')) 790 "GitVersion", ("major", "minor", "micro", "full")
791)
634 792
635 793
636def ParseGitVersion(ver_str=None): 794def ParseGitVersion(ver_str=None):
637 if ver_str is None: 795 if ver_str is None:
638 # Load the version ourselves. 796 # Load the version ourselves.
639 ver_str = run_git('--version').stdout 797 ver_str = run_git("--version").stdout
640 798
641 if not ver_str.startswith('git version '): 799 if not ver_str.startswith("git version "):
642 return None 800 return None
643 801
644 full_version = ver_str[len('git version '):].strip() 802 full_version = ver_str[len("git version ") :].strip()
645 num_ver_str = full_version.split('-')[0] 803 num_ver_str = full_version.split("-")[0]
646 to_tuple = [] 804 to_tuple = []
647 for num_str in num_ver_str.split('.')[:3]: 805 for num_str in num_ver_str.split(".")[:3]:
648 if num_str.isdigit(): 806 if num_str.isdigit():
649 to_tuple.append(int(num_str)) 807 to_tuple.append(int(num_str))
650 else: 808 else:
651 to_tuple.append(0) 809 to_tuple.append(0)
652 to_tuple.append(full_version) 810 to_tuple.append(full_version)
653 return GitVersion(*to_tuple) 811 return GitVersion(*to_tuple)
654 812
655 813
656def _CheckGitVersion(): 814def _CheckGitVersion():
657 ver_act = ParseGitVersion() 815 ver_act = ParseGitVersion()
658 if ver_act is None: 816 if ver_act is None:
659 print('fatal: unable to detect git version', file=sys.stderr) 817 print("fatal: unable to detect git version", file=sys.stderr)
660 raise CloneFailure() 818 raise CloneFailure()
661 819
662 if ver_act < MIN_GIT_VERSION: 820 if ver_act < MIN_GIT_VERSION:
663 need = '.'.join(map(str, MIN_GIT_VERSION)) 821 need = ".".join(map(str, MIN_GIT_VERSION))
664 print('fatal: git %s or later required; found %s' % (need, ver_act.full), 822 print(
665 file=sys.stderr) 823 "fatal: git %s or later required; found %s" % (need, ver_act.full),
666 raise CloneFailure() 824 file=sys.stderr,
825 )
826 raise CloneFailure()
667 827
668 828
669def SetGitTrace2ParentSid(env=None): 829def SetGitTrace2ParentSid(env=None):
670 """Set up GIT_TRACE2_PARENT_SID for git tracing.""" 830 """Set up GIT_TRACE2_PARENT_SID for git tracing."""
671 # We roughly follow the format git itself uses in trace2/tr2_sid.c. 831 # We roughly follow the format git itself uses in trace2/tr2_sid.c.
672 # (1) Be unique (2) be valid filename (3) be fixed length. 832 # (1) Be unique (2) be valid filename (3) be fixed length.
673 # 833 #
674 # Since we always export this variable, we try to avoid more expensive calls. 834 # Since we always export this variable, we try to avoid more expensive calls.
675 # e.g. We don't attempt hostname lookups or hashing the results. 835 # e.g. We don't attempt hostname lookups or hashing the results.
676 if env is None: 836 if env is None:
677 env = os.environ 837 env = os.environ
678 838
679 KEY = 'GIT_TRACE2_PARENT_SID' 839 KEY = "GIT_TRACE2_PARENT_SID"
680 840
681 now = datetime.datetime.utcnow() 841 now = datetime.datetime.utcnow()
682 value = 'repo-%s-P%08x' % (now.strftime('%Y%m%dT%H%M%SZ'), os.getpid()) 842 value = "repo-%s-P%08x" % (now.strftime("%Y%m%dT%H%M%SZ"), os.getpid())
683 843
684 # If it's already set, then append ourselves. 844 # If it's already set, then append ourselves.
685 if KEY in env: 845 if KEY in env:
686 value = env[KEY] + '/' + value 846 value = env[KEY] + "/" + value
687 847
688 _setenv(KEY, value, env=env) 848 _setenv(KEY, value, env=env)
689 849
690 850
691def _setenv(key, value, env=None): 851def _setenv(key, value, env=None):
692 """Set |key| in the OS environment |env| to |value|.""" 852 """Set |key| in the OS environment |env| to |value|."""
693 if env is None: 853 if env is None:
694 env = os.environ 854 env = os.environ
695 # Environment handling across systems is messy. 855 # Environment handling across systems is messy.
696 try: 856 try:
697 env[key] = value 857 env[key] = value
698 except UnicodeEncodeError: 858 except UnicodeEncodeError:
699 env[key] = value.encode() 859 env[key] = value.encode()
700 860
701 861
702def NeedSetupGnuPG(): 862def NeedSetupGnuPG():
703 if not os.path.isdir(home_dot_repo): 863 if not os.path.isdir(home_dot_repo):
704 return True 864 return True
705 865
706 kv = os.path.join(home_dot_repo, 'keyring-version') 866 kv = os.path.join(home_dot_repo, "keyring-version")
707 if not os.path.exists(kv): 867 if not os.path.exists(kv):
708 return True 868 return True
709 869
710 kv = open(kv).read() 870 kv = open(kv).read()
711 if not kv: 871 if not kv:
712 return True 872 return True
713 873
714 kv = tuple(map(int, kv.split('.'))) 874 kv = tuple(map(int, kv.split(".")))
715 if kv < KEYRING_VERSION: 875 if kv < KEYRING_VERSION:
716 return True 876 return True
717 return False 877 return False
718 878
719 879
720def SetupGnuPG(quiet): 880def SetupGnuPG(quiet):
721 try: 881 try:
722 os.mkdir(home_dot_repo) 882 os.mkdir(home_dot_repo)
723 except OSError as e: 883 except OSError as e:
724 if e.errno != errno.EEXIST: 884 if e.errno != errno.EEXIST:
725 print('fatal: cannot make %s directory: %s' 885 print(
726 % (home_dot_repo, e.strerror), file=sys.stderr) 886 "fatal: cannot make %s directory: %s"
727 sys.exit(1) 887 % (home_dot_repo, e.strerror),
728 888 file=sys.stderr,
729 try: 889 )
730 os.mkdir(gpg_dir, stat.S_IRWXU) 890 sys.exit(1)
731 except OSError as e: 891
732 if e.errno != errno.EEXIST: 892 try:
733 print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror), 893 os.mkdir(gpg_dir, stat.S_IRWXU)
734 file=sys.stderr) 894 except OSError as e:
735 sys.exit(1) 895 if e.errno != errno.EEXIST:
736 896 print(
737 if not quiet: 897 "fatal: cannot make %s directory: %s" % (gpg_dir, e.strerror),
738 print('repo: Updating release signing keys to keyset ver %s' % 898 file=sys.stderr,
739 ('.'.join(str(x) for x in KEYRING_VERSION),)) 899 )
740 # NB: We use --homedir (and cwd below) because some environments (Windows) do 900 sys.exit(1)
741 # not correctly handle full native paths. We avoid the issue by changing to
742 # the right dir with cwd=gpg_dir before executing gpg, and then telling gpg to
743 # use the cwd (.) as its homedir which leaves the path resolution logic to it.
744 cmd = ['gpg', '--homedir', '.', '--import']
745 try:
746 # gpg can be pretty chatty. Always capture the output and if something goes
747 # wrong, the builtin check failure will dump stdout & stderr for debugging.
748 run_command(cmd, stdin=subprocess.PIPE, capture_output=True,
749 cwd=gpg_dir, check=True,
750 input=MAINTAINER_KEYS.encode('utf-8'))
751 except OSError:
752 if not quiet:
753 print('warning: gpg (GnuPG) is not available.', file=sys.stderr)
754 print('warning: Installing it is strongly encouraged.', file=sys.stderr)
755 print(file=sys.stderr)
756 return False
757 901
758 with open(os.path.join(home_dot_repo, 'keyring-version'), 'w') as fd: 902 if not quiet:
759 fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n') 903 print(
760 return True 904 "repo: Updating release signing keys to keyset ver %s"
905 % (".".join(str(x) for x in KEYRING_VERSION),)
906 )
907 # NB: We use --homedir (and cwd below) because some environments (Windows) do
908 # not correctly handle full native paths. We avoid the issue by changing to
909 # the right dir with cwd=gpg_dir before executing gpg, and then telling gpg to
910 # use the cwd (.) as its homedir which leaves the path resolution logic to it.
911 cmd = ["gpg", "--homedir", ".", "--import"]
912 try:
913 # gpg can be pretty chatty. Always capture the output and if something goes
914 # wrong, the builtin check failure will dump stdout & stderr for debugging.
915 run_command(
916 cmd,
917 stdin=subprocess.PIPE,
918 capture_output=True,
919 cwd=gpg_dir,
920 check=True,
921 input=MAINTAINER_KEYS.encode("utf-8"),
922 )
923 except OSError:
924 if not quiet:
925 print("warning: gpg (GnuPG) is not available.", file=sys.stderr)
926 print(
927 "warning: Installing it is strongly encouraged.",
928 file=sys.stderr,
929 )
930 print(file=sys.stderr)
931 return False
932
933 with open(os.path.join(home_dot_repo, "keyring-version"), "w") as fd:
934 fd.write(".".join(map(str, KEYRING_VERSION)) + "\n")
935 return True
761 936
762 937
763def _SetConfig(cwd, name, value): 938def _SetConfig(cwd, name, value):
764 """Set a git configuration option to the specified value. 939 """Set a git configuration option to the specified value."""
765 """ 940 run_git("config", name, value, cwd=cwd)
766 run_git('config', name, value, cwd=cwd)
767 941
768 942
769def _GetRepoConfig(name): 943def _GetRepoConfig(name):
770 """Read a repo configuration option.""" 944 """Read a repo configuration option."""
771 config = os.path.join(home_dot_repo, 'config') 945 config = os.path.join(home_dot_repo, "config")
772 if not os.path.exists(config): 946 if not os.path.exists(config):
773 return None 947 return None
774 948
775 cmd = ['config', '--file', config, '--get', name] 949 cmd = ["config", "--file", config, "--get", name]
776 ret = run_git(*cmd, check=False) 950 ret = run_git(*cmd, check=False)
777 if ret.returncode == 0: 951 if ret.returncode == 0:
778 return ret.stdout 952 return ret.stdout
779 elif ret.returncode == 1: 953 elif ret.returncode == 1:
780 return None 954 return None
781 else: 955 else:
782 print('repo: error: git %s failed:\n%s' % (' '.join(cmd), ret.stderr), 956 print(
783 file=sys.stderr) 957 "repo: error: git %s failed:\n%s" % (" ".join(cmd), ret.stderr),
784 raise RunError() 958 file=sys.stderr,
959 )
960 raise RunError()
785 961
786 962
787def _InitHttp(): 963def _InitHttp():
788 handlers = [] 964 handlers = []
789 965
790 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() 966 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm()
791 try: 967 try:
792 import netrc 968 import netrc
793 n = netrc.netrc() 969
794 for host in n.hosts: 970 n = netrc.netrc()
795 p = n.hosts[host] 971 for host in n.hosts:
796 mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2]) 972 p = n.hosts[host]
797 mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2]) 973 mgr.add_password(p[1], "http://%s/" % host, p[0], p[2])
798 except Exception: 974 mgr.add_password(p[1], "https://%s/" % host, p[0], p[2])
799 pass 975 except Exception:
800 handlers.append(urllib.request.HTTPBasicAuthHandler(mgr)) 976 pass
801 handlers.append(urllib.request.HTTPDigestAuthHandler(mgr)) 977 handlers.append(urllib.request.HTTPBasicAuthHandler(mgr))
802 978 handlers.append(urllib.request.HTTPDigestAuthHandler(mgr))
803 if 'http_proxy' in os.environ: 979
804 url = os.environ['http_proxy'] 980 if "http_proxy" in os.environ:
805 handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url})) 981 url = os.environ["http_proxy"]
806 if 'REPO_CURL_VERBOSE' in os.environ: 982 handlers.append(
807 handlers.append(urllib.request.HTTPHandler(debuglevel=1)) 983 urllib.request.ProxyHandler({"http": url, "https": url})
808 handlers.append(urllib.request.HTTPSHandler(debuglevel=1)) 984 )
809 urllib.request.install_opener(urllib.request.build_opener(*handlers)) 985 if "REPO_CURL_VERBOSE" in os.environ:
986 handlers.append(urllib.request.HTTPHandler(debuglevel=1))
987 handlers.append(urllib.request.HTTPSHandler(debuglevel=1))
988 urllib.request.install_opener(urllib.request.build_opener(*handlers))
810 989
811 990
812def _Fetch(url, cwd, src, quiet, verbose): 991def _Fetch(url, cwd, src, quiet, verbose):
813 cmd = ['fetch'] 992 cmd = ["fetch"]
814 if not verbose: 993 if not verbose:
815 cmd.append('--quiet') 994 cmd.append("--quiet")
816 err = None 995 err = None
817 if not quiet and sys.stdout.isatty(): 996 if not quiet and sys.stdout.isatty():
818 cmd.append('--progress') 997 cmd.append("--progress")
819 elif not verbose: 998 elif not verbose:
820 err = subprocess.PIPE 999 err = subprocess.PIPE
821 cmd.append(src) 1000 cmd.append(src)
822 cmd.append('+refs/heads/*:refs/remotes/origin/*') 1001 cmd.append("+refs/heads/*:refs/remotes/origin/*")
823 cmd.append('+refs/tags/*:refs/tags/*') 1002 cmd.append("+refs/tags/*:refs/tags/*")
824 run_git(*cmd, stderr=err, capture_output=False, cwd=cwd) 1003 run_git(*cmd, stderr=err, capture_output=False, cwd=cwd)
825 1004
826 1005
827def _DownloadBundle(url, cwd, quiet, verbose): 1006def _DownloadBundle(url, cwd, quiet, verbose):
828 if not url.endswith('/'): 1007 if not url.endswith("/"):
829 url += '/' 1008 url += "/"
830 url += 'clone.bundle' 1009 url += "clone.bundle"
831 1010
832 ret = run_git('config', '--get-regexp', 'url.*.insteadof', cwd=cwd, 1011 ret = run_git(
833 check=False) 1012 "config", "--get-regexp", "url.*.insteadof", cwd=cwd, check=False
834 for line in ret.stdout.splitlines(): 1013 )
835 m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line) 1014 for line in ret.stdout.splitlines():
836 if m: 1015 m = re.compile(r"^url\.(.*)\.insteadof (.*)$").match(line)
837 new_url = m.group(1) 1016 if m:
838 old_url = m.group(2) 1017 new_url = m.group(1)
839 if url.startswith(old_url): 1018 old_url = m.group(2)
840 url = new_url + url[len(old_url):] 1019 if url.startswith(old_url):
841 break 1020 url = new_url + url[len(old_url) :]
842 1021 break
843 if not url.startswith('http:') and not url.startswith('https:'): 1022
844 return False 1023 if not url.startswith("http:") and not url.startswith("https:"):
845 1024 return False
846 dest = open(os.path.join(cwd, '.git', 'clone.bundle'), 'w+b') 1025
847 try: 1026 dest = open(os.path.join(cwd, ".git", "clone.bundle"), "w+b")
848 try:
849 r = urllib.request.urlopen(url)
850 except urllib.error.HTTPError as e:
851 if e.code not in [400, 401, 403, 404, 501]:
852 print('warning: Cannot get %s' % url, file=sys.stderr)
853 print('warning: HTTP error %s' % e.code, file=sys.stderr)
854 return False
855 except urllib.error.URLError as e:
856 print('fatal: Cannot get %s' % url, file=sys.stderr)
857 print('fatal: error %s' % e.reason, file=sys.stderr)
858 raise CloneFailure()
859 try: 1027 try:
860 if verbose: 1028 try:
861 print('Downloading clone bundle %s' % url, file=sys.stderr) 1029 r = urllib.request.urlopen(url)
862 while True: 1030 except urllib.error.HTTPError as e:
863 buf = r.read(8192) 1031 if e.code not in [400, 401, 403, 404, 501]:
864 if not buf: 1032 print("warning: Cannot get %s" % url, file=sys.stderr)
865 return True 1033 print("warning: HTTP error %s" % e.code, file=sys.stderr)
866 dest.write(buf) 1034 return False
1035 except urllib.error.URLError as e:
1036 print("fatal: Cannot get %s" % url, file=sys.stderr)
1037 print("fatal: error %s" % e.reason, file=sys.stderr)
1038 raise CloneFailure()
1039 try:
1040 if verbose:
1041 print("Downloading clone bundle %s" % url, file=sys.stderr)
1042 while True:
1043 buf = r.read(8192)
1044 if not buf:
1045 return True
1046 dest.write(buf)
1047 finally:
1048 r.close()
867 finally: 1049 finally:
868 r.close() 1050 dest.close()
869 finally:
870 dest.close()
871 1051
872 1052
873def _ImportBundle(cwd): 1053def _ImportBundle(cwd):
874 path = os.path.join(cwd, '.git', 'clone.bundle') 1054 path = os.path.join(cwd, ".git", "clone.bundle")
875 try: 1055 try:
876 _Fetch(cwd, cwd, path, True, False) 1056 _Fetch(cwd, cwd, path, True, False)
877 finally: 1057 finally:
878 os.remove(path) 1058 os.remove(path)
879 1059
880 1060
881def _Clone(url, cwd, clone_bundle, quiet, verbose): 1061def _Clone(url, cwd, clone_bundle, quiet, verbose):
882 """Clones a git repository to a new subdirectory of repodir 1062 """Clones a git repository to a new subdirectory of repodir"""
883 """ 1063 if verbose:
884 if verbose: 1064 print("Cloning git repository", url)
885 print('Cloning git repository', url)
886
887 try:
888 os.mkdir(cwd)
889 except OSError as e:
890 print('fatal: cannot make %s directory: %s' % (cwd, e.strerror),
891 file=sys.stderr)
892 raise CloneFailure()
893
894 run_git('init', '--quiet', cwd=cwd)
895 1065
896 _InitHttp() 1066 try:
897 _SetConfig(cwd, 'remote.origin.url', url) 1067 os.mkdir(cwd)
898 _SetConfig(cwd, 1068 except OSError as e:
899 'remote.origin.fetch', 1069 print(
900 '+refs/heads/*:refs/remotes/origin/*') 1070 "fatal: cannot make %s directory: %s" % (cwd, e.strerror),
901 if clone_bundle and _DownloadBundle(url, cwd, quiet, verbose): 1071 file=sys.stderr,
902 _ImportBundle(cwd) 1072 )
903 _Fetch(url, cwd, 'origin', quiet, verbose) 1073 raise CloneFailure()
1074
1075 run_git("init", "--quiet", cwd=cwd)
1076
1077 _InitHttp()
1078 _SetConfig(cwd, "remote.origin.url", url)
1079 _SetConfig(
1080 cwd, "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"
1081 )
1082 if clone_bundle and _DownloadBundle(url, cwd, quiet, verbose):
1083 _ImportBundle(cwd)
1084 _Fetch(url, cwd, "origin", quiet, verbose)
904 1085
905 1086
906def resolve_repo_rev(cwd, committish): 1087def resolve_repo_rev(cwd, committish):
907 """Figure out what REPO_REV represents. 1088 """Figure out what REPO_REV represents.
908
909 We support:
910 * refs/heads/xxx: Branch.
911 * refs/tags/xxx: Tag.
912 * xxx: Branch or tag or commit.
913
914 Args:
915 cwd: The git checkout to run in.
916 committish: The REPO_REV argument to resolve.
917
918 Returns:
919 A tuple of (remote ref, commit) as makes sense for the committish.
920 For branches, this will look like ('refs/heads/stable', <revision>).
921 For tags, this will look like ('refs/tags/v1.0', <revision>).
922 For commits, this will be (<revision>, <revision>).
923 """
924 def resolve(committish):
925 ret = run_git('rev-parse', '--verify', '%s^{commit}' % (committish,),
926 cwd=cwd, check=False)
927 return None if ret.returncode else ret.stdout.strip()
928
929 # An explicit branch.
930 if committish.startswith('refs/heads/'):
931 remote_ref = committish
932 committish = committish[len('refs/heads/'):]
933 rev = resolve('refs/remotes/origin/%s' % committish)
934 if rev is None:
935 print('repo: error: unknown branch "%s"' % (committish,),
936 file=sys.stderr)
937 raise CloneFailure()
938 return (remote_ref, rev)
939
940 # An explicit tag.
941 if committish.startswith('refs/tags/'):
942 remote_ref = committish
943 committish = committish[len('refs/tags/'):]
944 rev = resolve(remote_ref)
945 if rev is None:
946 print('repo: error: unknown tag "%s"' % (committish,),
947 file=sys.stderr)
948 raise CloneFailure()
949 return (remote_ref, rev)
950
951 # See if it's a short branch name.
952 rev = resolve('refs/remotes/origin/%s' % committish)
953 if rev:
954 return ('refs/heads/%s' % (committish,), rev)
955 1089
956 # See if it's a tag. 1090 We support:
957 rev = resolve('refs/tags/%s' % committish) 1091 * refs/heads/xxx: Branch.
958 if rev: 1092 * refs/tags/xxx: Tag.
959 return ('refs/tags/%s' % (committish,), rev) 1093 * xxx: Branch or tag or commit.
960 1094
961 # See if it's a commit. 1095 Args:
962 rev = resolve(committish) 1096 cwd: The git checkout to run in.
963 if rev and rev.lower().startswith(committish.lower()): 1097 committish: The REPO_REV argument to resolve.
964 return (rev, rev) 1098
1099 Returns:
1100 A tuple of (remote ref, commit) as makes sense for the committish.
1101 For branches, this will look like ('refs/heads/stable', <revision>).
1102 For tags, this will look like ('refs/tags/v1.0', <revision>).
1103 For commits, this will be (<revision>, <revision>).
1104 """
965 1105
966 # Give up! 1106 def resolve(committish):
967 print('repo: error: unable to resolve "%s"' % (committish,), file=sys.stderr) 1107 ret = run_git(
968 raise CloneFailure() 1108 "rev-parse",
1109 "--verify",
1110 "%s^{commit}" % (committish,),
1111 cwd=cwd,
1112 check=False,
1113 )
1114 return None if ret.returncode else ret.stdout.strip()
1115
1116 # An explicit branch.
1117 if committish.startswith("refs/heads/"):
1118 remote_ref = committish
1119 committish = committish[len("refs/heads/") :]
1120 rev = resolve("refs/remotes/origin/%s" % committish)
1121 if rev is None:
1122 print(
1123 'repo: error: unknown branch "%s"' % (committish,),
1124 file=sys.stderr,
1125 )
1126 raise CloneFailure()
1127 return (remote_ref, rev)
1128
1129 # An explicit tag.
1130 if committish.startswith("refs/tags/"):
1131 remote_ref = committish
1132 committish = committish[len("refs/tags/") :]
1133 rev = resolve(remote_ref)
1134 if rev is None:
1135 print(
1136 'repo: error: unknown tag "%s"' % (committish,), file=sys.stderr
1137 )
1138 raise CloneFailure()
1139 return (remote_ref, rev)
1140
1141 # See if it's a short branch name.
1142 rev = resolve("refs/remotes/origin/%s" % committish)
1143 if rev:
1144 return ("refs/heads/%s" % (committish,), rev)
1145
1146 # See if it's a tag.
1147 rev = resolve("refs/tags/%s" % committish)
1148 if rev:
1149 return ("refs/tags/%s" % (committish,), rev)
1150
1151 # See if it's a commit.
1152 rev = resolve(committish)
1153 if rev and rev.lower().startswith(committish.lower()):
1154 return (rev, rev)
1155
1156 # Give up!
1157 print(
1158 'repo: error: unable to resolve "%s"' % (committish,), file=sys.stderr
1159 )
1160 raise CloneFailure()
969 1161
970 1162
971def verify_rev(cwd, remote_ref, rev, quiet): 1163def verify_rev(cwd, remote_ref, rev, quiet):
972 """Verify the commit has been signed by a tag.""" 1164 """Verify the commit has been signed by a tag."""
973 ret = run_git('describe', rev, cwd=cwd) 1165 ret = run_git("describe", rev, cwd=cwd)
974 cur = ret.stdout.strip() 1166 cur = ret.stdout.strip()
975 1167
976 m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur) 1168 m = re.compile(r"^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$").match(cur)
977 if m: 1169 if m:
978 cur = m.group(1) 1170 cur = m.group(1)
979 if not quiet: 1171 if not quiet:
980 print(file=sys.stderr) 1172 print(file=sys.stderr)
981 print("warning: '%s' is not signed; falling back to signed release '%s'" 1173 print(
982 % (remote_ref, cur), file=sys.stderr) 1174 "warning: '%s' is not signed; falling back to signed release '%s'"
983 print(file=sys.stderr) 1175 % (remote_ref, cur),
984 1176 file=sys.stderr,
985 env = os.environ.copy() 1177 )
986 _setenv('GNUPGHOME', gpg_dir, env) 1178 print(file=sys.stderr)
987 run_git('tag', '-v', cur, cwd=cwd, env=env) 1179
988 return '%s^0' % cur 1180 env = os.environ.copy()
1181 _setenv("GNUPGHOME", gpg_dir, env)
1182 run_git("tag", "-v", cur, cwd=cwd, env=env)
1183 return "%s^0" % cur
989 1184
990 1185
991def _Checkout(cwd, remote_ref, rev, quiet): 1186def _Checkout(cwd, remote_ref, rev, quiet):
992 """Checkout an upstream branch into the repository and track it. 1187 """Checkout an upstream branch into the repository and track it."""
993 """ 1188 run_git("update-ref", "refs/heads/default", rev, cwd=cwd)
994 run_git('update-ref', 'refs/heads/default', rev, cwd=cwd)
995 1189
996 _SetConfig(cwd, 'branch.default.remote', 'origin') 1190 _SetConfig(cwd, "branch.default.remote", "origin")
997 _SetConfig(cwd, 'branch.default.merge', remote_ref) 1191 _SetConfig(cwd, "branch.default.merge", remote_ref)
998 1192
999 run_git('symbolic-ref', 'HEAD', 'refs/heads/default', cwd=cwd) 1193 run_git("symbolic-ref", "HEAD", "refs/heads/default", cwd=cwd)
1000 1194
1001 cmd = ['read-tree', '--reset', '-u'] 1195 cmd = ["read-tree", "--reset", "-u"]
1002 if not quiet: 1196 if not quiet:
1003 cmd.append('-v') 1197 cmd.append("-v")
1004 cmd.append('HEAD') 1198 cmd.append("HEAD")
1005 run_git(*cmd, cwd=cwd) 1199 run_git(*cmd, cwd=cwd)
1006 1200
1007 1201
1008def _FindRepo(): 1202def _FindRepo():
1009 """Look for a repo installation, starting at the current directory. 1203 """Look for a repo installation, starting at the current directory."""
1010 """ 1204 curdir = os.getcwd()
1011 curdir = os.getcwd() 1205 repo = None
1012 repo = None
1013 1206
1014 olddir = None 1207 olddir = None
1015 while curdir != olddir and not repo: 1208 while curdir != olddir and not repo:
1016 repo = os.path.join(curdir, repodir, REPO_MAIN) 1209 repo = os.path.join(curdir, repodir, REPO_MAIN)
1017 if not os.path.isfile(repo): 1210 if not os.path.isfile(repo):
1018 repo = None 1211 repo = None
1019 olddir = curdir 1212 olddir = curdir
1020 curdir = os.path.dirname(curdir) 1213 curdir = os.path.dirname(curdir)
1021 return (repo, os.path.join(curdir, repodir)) 1214 return (repo, os.path.join(curdir, repodir))
1022 1215
1023 1216
1024class _Options(object): 1217class _Options(object):
1025 help = False 1218 help = False
1026 version = False 1219 version = False
1027 1220
1028 1221
1029def _ExpandAlias(name): 1222def _ExpandAlias(name):
1030 """Look up user registered aliases.""" 1223 """Look up user registered aliases."""
1031 # We don't resolve aliases for existing subcommands. This matches git. 1224 # We don't resolve aliases for existing subcommands. This matches git.
1032 if name in {'gitc-init', 'help', 'init'}: 1225 if name in {"gitc-init", "help", "init"}:
1033 return name, [] 1226 return name, []
1034 1227
1035 alias = _GetRepoConfig('alias.%s' % (name,)) 1228 alias = _GetRepoConfig("alias.%s" % (name,))
1036 if alias is None: 1229 if alias is None:
1037 return name, [] 1230 return name, []
1038 1231
1039 args = alias.strip().split(' ', 1) 1232 args = alias.strip().split(" ", 1)
1040 name = args[0] 1233 name = args[0]
1041 if len(args) == 2: 1234 if len(args) == 2:
1042 args = shlex.split(args[1]) 1235 args = shlex.split(args[1])
1043 else: 1236 else:
1044 args = [] 1237 args = []
1045 return name, args 1238 return name, args
1046 1239
1047 1240
1048def _ParseArguments(args): 1241def _ParseArguments(args):
1049 cmd = None 1242 cmd = None
1050 opt = _Options() 1243 opt = _Options()
1051 arg = [] 1244 arg = []
1052 1245
1053 for i in range(len(args)): 1246 for i in range(len(args)):
1054 a = args[i] 1247 a = args[i]
1055 if a == '-h' or a == '--help': 1248 if a == "-h" or a == "--help":
1056 opt.help = True 1249 opt.help = True
1057 elif a == '--version': 1250 elif a == "--version":
1058 opt.version = True 1251 opt.version = True
1059 elif a == '--trace': 1252 elif a == "--trace":
1060 trace.set(True) 1253 trace.set(True)
1061 elif not a.startswith('-'): 1254 elif not a.startswith("-"):
1062 cmd = a 1255 cmd = a
1063 arg = args[i + 1:] 1256 arg = args[i + 1 :]
1064 break 1257 break
1065 return cmd, opt, arg 1258 return cmd, opt, arg
1066 1259
1067 1260
1068class Requirements(object): 1261class Requirements(object):
1069 """Helper for checking repo's system requirements.""" 1262 """Helper for checking repo's system requirements."""
1070 1263
1071 REQUIREMENTS_NAME = 'requirements.json' 1264 REQUIREMENTS_NAME = "requirements.json"
1072 1265
1073 def __init__(self, requirements): 1266 def __init__(self, requirements):
1074 """Initialize. 1267 """Initialize.
1075 1268
1076 Args: 1269 Args:
1077 requirements: A dictionary of settings. 1270 requirements: A dictionary of settings.
1078 """ 1271 """
1079 self.requirements = requirements 1272 self.requirements = requirements
1080 1273
1081 @classmethod 1274 @classmethod
1082 def from_dir(cls, path): 1275 def from_dir(cls, path):
1083 return cls.from_file(os.path.join(path, cls.REQUIREMENTS_NAME)) 1276 return cls.from_file(os.path.join(path, cls.REQUIREMENTS_NAME))
1084 1277
1085 @classmethod 1278 @classmethod
1086 def from_file(cls, path): 1279 def from_file(cls, path):
1087 try: 1280 try:
1088 with open(path, 'rb') as f: 1281 with open(path, "rb") as f:
1089 data = f.read() 1282 data = f.read()
1090 except EnvironmentError: 1283 except EnvironmentError:
1091 # NB: EnvironmentError is used for Python 2 & 3 compatibility. 1284 # NB: EnvironmentError is used for Python 2 & 3 compatibility.
1092 # If we couldn't open the file, assume it's an old source tree. 1285 # If we couldn't open the file, assume it's an old source tree.
1093 return None 1286 return None
1094 1287
1095 return cls.from_data(data) 1288 return cls.from_data(data)
1096 1289
1097 @classmethod 1290 @classmethod
1098 def from_data(cls, data): 1291 def from_data(cls, data):
1099 comment_line = re.compile(br'^ *#') 1292 comment_line = re.compile(rb"^ *#")
1100 strip_data = b''.join(x for x in data.splitlines() if not comment_line.match(x)) 1293 strip_data = b"".join(
1101 try: 1294 x for x in data.splitlines() if not comment_line.match(x)
1102 json_data = json.loads(strip_data) 1295 )
1103 except Exception: # pylint: disable=broad-except 1296 try:
1104 # If we couldn't parse it, assume it's incompatible. 1297 json_data = json.loads(strip_data)
1105 return None 1298 except Exception: # pylint: disable=broad-except
1106 1299 # If we couldn't parse it, assume it's incompatible.
1107 return cls(json_data) 1300 return None
1108 1301
1109 def _get_soft_ver(self, pkg): 1302 return cls(json_data)
1110 """Return the soft version for |pkg| if it exists.""" 1303
1111 return self.requirements.get(pkg, {}).get('soft', ()) 1304 def _get_soft_ver(self, pkg):
1112 1305 """Return the soft version for |pkg| if it exists."""
1113 def _get_hard_ver(self, pkg): 1306 return self.requirements.get(pkg, {}).get("soft", ())
1114 """Return the hard version for |pkg| if it exists.""" 1307
1115 return self.requirements.get(pkg, {}).get('hard', ()) 1308 def _get_hard_ver(self, pkg):
1116 1309 """Return the hard version for |pkg| if it exists."""
1117 @staticmethod 1310 return self.requirements.get(pkg, {}).get("hard", ())
1118 def _format_ver(ver): 1311
1119 """Return a dotted version from |ver|.""" 1312 @staticmethod
1120 return '.'.join(str(x) for x in ver) 1313 def _format_ver(ver):
1121 1314 """Return a dotted version from |ver|."""
1122 def assert_ver(self, pkg, curr_ver): 1315 return ".".join(str(x) for x in ver)
1123 """Verify |pkg|'s |curr_ver| is new enough.""" 1316
1124 curr_ver = tuple(curr_ver) 1317 def assert_ver(self, pkg, curr_ver):
1125 soft_ver = tuple(self._get_soft_ver(pkg)) 1318 """Verify |pkg|'s |curr_ver| is new enough."""
1126 hard_ver = tuple(self._get_hard_ver(pkg)) 1319 curr_ver = tuple(curr_ver)
1127 if curr_ver < hard_ver: 1320 soft_ver = tuple(self._get_soft_ver(pkg))
1128 print('repo: error: Your version of "%s" (%s) is unsupported; ' 1321 hard_ver = tuple(self._get_hard_ver(pkg))
1129 'Please upgrade to at least version %s to continue.' % 1322 if curr_ver < hard_ver:
1130 (pkg, self._format_ver(curr_ver), self._format_ver(soft_ver)), 1323 print(
1131 file=sys.stderr) 1324 'repo: error: Your version of "%s" (%s) is unsupported; '
1132 sys.exit(1) 1325 "Please upgrade to at least version %s to continue."
1133 1326 % (pkg, self._format_ver(curr_ver), self._format_ver(soft_ver)),
1134 if curr_ver < soft_ver: 1327 file=sys.stderr,
1135 print('repo: warning: Your version of "%s" (%s) is no longer supported; ' 1328 )
1136 'Please upgrade to at least version %s to avoid breakage.' % 1329 sys.exit(1)
1137 (pkg, self._format_ver(curr_ver), self._format_ver(soft_ver)), 1330
1138 file=sys.stderr) 1331 if curr_ver < soft_ver:
1139 1332 print(
1140 def assert_all(self): 1333 'repo: warning: Your version of "%s" (%s) is no longer supported; '
1141 """Assert all of the requirements are satisified.""" 1334 "Please upgrade to at least version %s to avoid breakage."
1142 # See if we need a repo launcher upgrade first. 1335 % (pkg, self._format_ver(curr_ver), self._format_ver(soft_ver)),
1143 self.assert_ver('repo', VERSION) 1336 file=sys.stderr,
1144 1337 )
1145 # Check python before we try to import the repo code. 1338
1146 self.assert_ver('python', sys.version_info) 1339 def assert_all(self):
1147 1340 """Assert all of the requirements are satisified."""
1148 # Check git while we're at it. 1341 # See if we need a repo launcher upgrade first.
1149 self.assert_ver('git', ParseGitVersion()) 1342 self.assert_ver("repo", VERSION)
1343
1344 # Check python before we try to import the repo code.
1345 self.assert_ver("python", sys.version_info)
1346
1347 # Check git while we're at it.
1348 self.assert_ver("git", ParseGitVersion())
1150 1349
1151 1350
1152def _Usage(): 1351def _Usage():
1153 gitc_usage = "" 1352 gitc_usage = ""
1154 if get_gitc_manifest_dir(): 1353 if get_gitc_manifest_dir():
1155 gitc_usage = " gitc-init Initialize a GITC Client.\n" 1354 gitc_usage = " gitc-init Initialize a GITC Client.\n"
1156 1355
1157 print( 1356 print(
1158 """usage: repo COMMAND [ARGS] 1357 """usage: repo COMMAND [ARGS]
1159 1358
1160repo is not yet installed. Use "repo init" to install it here. 1359repo is not yet installed. Use "repo init" to install it here.
1161 1360
1162The most commonly used repo commands are: 1361The most commonly used repo commands are:
1163 1362
1164 init Install repo in the current working directory 1363 init Install repo in the current working directory
1165""" + gitc_usage + 1364"""
1166 """ help Display detailed help on a command 1365 + gitc_usage
1366 + """ help Display detailed help on a command
1167 1367
1168For access to the full online help, install repo ("repo init"). 1368For access to the full online help, install repo ("repo init").
1169""") 1369"""
1170 print('Bug reports:', BUG_URL) 1370 )
1171 sys.exit(0) 1371 print("Bug reports:", BUG_URL)
1372 sys.exit(0)
1172 1373
1173 1374
1174def _Help(args): 1375def _Help(args):
1175 if args: 1376 if args:
1176 if args[0] in {'init', 'gitc-init'}: 1377 if args[0] in {"init", "gitc-init"}:
1177 parser = GetParser(gitc_init=args[0] == 'gitc-init') 1378 parser = GetParser(gitc_init=args[0] == "gitc-init")
1178 parser.print_help() 1379 parser.print_help()
1179 sys.exit(0) 1380 sys.exit(0)
1381 else:
1382 print(
1383 "error: '%s' is not a bootstrap command.\n"
1384 ' For access to online help, install repo ("repo init").'
1385 % args[0],
1386 file=sys.stderr,
1387 )
1180 else: 1388 else:
1181 print("error: '%s' is not a bootstrap command.\n" 1389 _Usage()
1182 ' For access to online help, install repo ("repo init").' 1390 sys.exit(1)
1183 % args[0], file=sys.stderr)
1184 else:
1185 _Usage()
1186 sys.exit(1)
1187 1391
1188 1392
1189def _Version(): 1393def _Version():
1190 """Show version information.""" 1394 """Show version information."""
1191 print('<repo not installed>') 1395 print("<repo not installed>")
1192 print('repo launcher version %s' % ('.'.join(str(x) for x in VERSION),)) 1396 print("repo launcher version %s" % (".".join(str(x) for x in VERSION),))
1193 print(' (from %s)' % (__file__,)) 1397 print(" (from %s)" % (__file__,))
1194 print('git %s' % (ParseGitVersion().full,)) 1398 print("git %s" % (ParseGitVersion().full,))
1195 print('Python %s' % sys.version) 1399 print("Python %s" % sys.version)
1196 uname = platform.uname() 1400 uname = platform.uname()
1197 if sys.version_info.major < 3: 1401 if sys.version_info.major < 3:
1198 # Python 3 returns a named tuple, but Python 2 is simpler. 1402 # Python 3 returns a named tuple, but Python 2 is simpler.
1199 print(uname) 1403 print(uname)
1200 else: 1404 else:
1201 print('OS %s %s (%s)' % (uname.system, uname.release, uname.version)) 1405 print("OS %s %s (%s)" % (uname.system, uname.release, uname.version))
1202 print('CPU %s (%s)' % 1406 print(
1203 (uname.machine, uname.processor if uname.processor else 'unknown')) 1407 "CPU %s (%s)"
1204 print('Bug reports:', BUG_URL) 1408 % (uname.machine, uname.processor if uname.processor else "unknown")
1205 sys.exit(0) 1409 )
1410 print("Bug reports:", BUG_URL)
1411 sys.exit(0)
1206 1412
1207 1413
1208def _NotInstalled(): 1414def _NotInstalled():
1209 print('error: repo is not installed. Use "repo init" to install it here.', 1415 print(
1210 file=sys.stderr) 1416 'error: repo is not installed. Use "repo init" to install it here.',
1211 sys.exit(1) 1417 file=sys.stderr,
1418 )
1419 sys.exit(1)
1212 1420
1213 1421
1214def _NoCommands(cmd): 1422def _NoCommands(cmd):
1215 print("""error: command '%s' requires repo to be installed first. 1423 print(
1216 Use "repo init" to install it here.""" % cmd, file=sys.stderr) 1424 """error: command '%s' requires repo to be installed first.
1217 sys.exit(1) 1425 Use "repo init" to install it here."""
1426 % cmd,
1427 file=sys.stderr,
1428 )
1429 sys.exit(1)
1218 1430
1219 1431
1220def _RunSelf(wrapper_path): 1432def _RunSelf(wrapper_path):
1221 my_dir = os.path.dirname(wrapper_path) 1433 my_dir = os.path.dirname(wrapper_path)
1222 my_main = os.path.join(my_dir, 'main.py') 1434 my_main = os.path.join(my_dir, "main.py")
1223 my_git = os.path.join(my_dir, '.git') 1435 my_git = os.path.join(my_dir, ".git")
1224 1436
1225 if os.path.isfile(my_main) and os.path.isdir(my_git): 1437 if os.path.isfile(my_main) and os.path.isdir(my_git):
1226 for name in ['git_config.py', 1438 for name in ["git_config.py", "project.py", "subcmds"]:
1227 'project.py', 1439 if not os.path.exists(os.path.join(my_dir, name)):
1228 'subcmds']: 1440 return None, None
1229 if not os.path.exists(os.path.join(my_dir, name)): 1441 return my_main, my_git
1230 return None, None 1442 return None, None
1231 return my_main, my_git
1232 return None, None
1233 1443
1234 1444
1235def _SetDefaultsTo(gitdir): 1445def _SetDefaultsTo(gitdir):
1236 global REPO_URL 1446 global REPO_URL
1237 global REPO_REV 1447 global REPO_REV
1238 1448
1239 REPO_URL = gitdir 1449 REPO_URL = gitdir
1240 ret = run_git('--git-dir=%s' % gitdir, 'symbolic-ref', 'HEAD', check=False) 1450 ret = run_git("--git-dir=%s" % gitdir, "symbolic-ref", "HEAD", check=False)
1241 if ret.returncode: 1451 if ret.returncode:
1242 # If we're not tracking a branch (bisect/etc...), then fall back to commit. 1452 # If we're not tracking a branch (bisect/etc...), then fall back to commit.
1243 print('repo: warning: %s has no current branch; using HEAD' % gitdir, 1453 print(
1244 file=sys.stderr) 1454 "repo: warning: %s has no current branch; using HEAD" % gitdir,
1245 try: 1455 file=sys.stderr,
1246 ret = run_git('rev-parse', 'HEAD', cwd=gitdir) 1456 )
1247 except CloneFailure: 1457 try:
1248 print('fatal: %s has invalid HEAD' % gitdir, file=sys.stderr) 1458 ret = run_git("rev-parse", "HEAD", cwd=gitdir)
1249 sys.exit(1) 1459 except CloneFailure:
1250 1460 print("fatal: %s has invalid HEAD" % gitdir, file=sys.stderr)
1251 REPO_REV = ret.stdout.strip() 1461 sys.exit(1)
1462
1463 REPO_REV = ret.stdout.strip()
1252 1464
1253 1465
1254def main(orig_args): 1466def main(orig_args):
1255 cmd, opt, args = _ParseArguments(orig_args) 1467 cmd, opt, args = _ParseArguments(orig_args)
1256 1468
1257 # We run this early as we run some git commands ourselves. 1469 # We run this early as we run some git commands ourselves.
1258 SetGitTrace2ParentSid() 1470 SetGitTrace2ParentSid()
1259 1471
1260 repo_main, rel_repo_dir = None, None 1472 repo_main, rel_repo_dir = None, None
1261 # Don't use the local repo copy, make sure to switch to the gitc client first. 1473 # Don't use the local repo copy, make sure to switch to the gitc client first.
1262 if cmd != 'gitc-init': 1474 if cmd != "gitc-init":
1263 repo_main, rel_repo_dir = _FindRepo() 1475 repo_main, rel_repo_dir = _FindRepo()
1264 1476
1265 wrapper_path = os.path.abspath(__file__) 1477 wrapper_path = os.path.abspath(__file__)
1266 my_main, my_git = _RunSelf(wrapper_path) 1478 my_main, my_git = _RunSelf(wrapper_path)
1267 1479
1268 cwd = os.getcwd() 1480 cwd = os.getcwd()
1269 if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()): 1481 if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()):
1270 print('error: repo cannot be used in the GITC local manifest directory.' 1482 print(
1271 '\nIf you want to work on this GITC client please rerun this ' 1483 "error: repo cannot be used in the GITC local manifest directory."
1272 'command from the corresponding client under /gitc/', 1484 "\nIf you want to work on this GITC client please rerun this "
1273 file=sys.stderr) 1485 "command from the corresponding client under /gitc/",
1274 sys.exit(1) 1486 file=sys.stderr,
1275 if not repo_main: 1487 )
1276 # Only expand aliases here since we'll be parsing the CLI ourselves. 1488 sys.exit(1)
1277 # If we had repo_main, alias expansion would happen in main.py. 1489 if not repo_main:
1278 cmd, alias_args = _ExpandAlias(cmd) 1490 # Only expand aliases here since we'll be parsing the CLI ourselves.
1279 args = alias_args + args 1491 # If we had repo_main, alias expansion would happen in main.py.
1280 1492 cmd, alias_args = _ExpandAlias(cmd)
1281 if opt.help: 1493 args = alias_args + args
1282 _Usage() 1494
1283 if cmd == 'help': 1495 if opt.help:
1284 _Help(args) 1496 _Usage()
1285 if opt.version or cmd == 'version': 1497 if cmd == "help":
1286 _Version() 1498 _Help(args)
1287 if not cmd: 1499 if opt.version or cmd == "version":
1288 _NotInstalled() 1500 _Version()
1289 if cmd == 'init' or cmd == 'gitc-init': 1501 if not cmd:
1290 if my_git: 1502 _NotInstalled()
1291 _SetDefaultsTo(my_git) 1503 if cmd == "init" or cmd == "gitc-init":
1292 try: 1504 if my_git:
1293 _Init(args, gitc_init=(cmd == 'gitc-init')) 1505 _SetDefaultsTo(my_git)
1294 except CloneFailure: 1506 try:
1295 path = os.path.join(repodir, S_repo) 1507 _Init(args, gitc_init=(cmd == "gitc-init"))
1296 print("fatal: cloning the git-repo repository failed, will remove " 1508 except CloneFailure:
1297 "'%s' " % path, file=sys.stderr) 1509 path = os.path.join(repodir, S_repo)
1298 shutil.rmtree(path, ignore_errors=True) 1510 print(
1299 shutil.rmtree(path + '.tmp', ignore_errors=True) 1511 "fatal: cloning the git-repo repository failed, will remove "
1512 "'%s' " % path,
1513 file=sys.stderr,
1514 )
1515 shutil.rmtree(path, ignore_errors=True)
1516 shutil.rmtree(path + ".tmp", ignore_errors=True)
1517 sys.exit(1)
1518 repo_main, rel_repo_dir = _FindRepo()
1519 else:
1520 _NoCommands(cmd)
1521
1522 if my_main:
1523 repo_main = my_main
1524
1525 if not repo_main:
1526 print("fatal: unable to find repo entry point", file=sys.stderr)
1300 sys.exit(1) 1527 sys.exit(1)
1301 repo_main, rel_repo_dir = _FindRepo()
1302 else:
1303 _NoCommands(cmd)
1304
1305 if my_main:
1306 repo_main = my_main
1307
1308 if not repo_main:
1309 print("fatal: unable to find repo entry point", file=sys.stderr)
1310 sys.exit(1)
1311
1312 reqs = Requirements.from_dir(os.path.dirname(repo_main))
1313 if reqs:
1314 reqs.assert_all()
1315
1316 ver_str = '.'.join(map(str, VERSION))
1317 me = [sys.executable, repo_main,
1318 '--repo-dir=%s' % rel_repo_dir,
1319 '--wrapper-version=%s' % ver_str,
1320 '--wrapper-path=%s' % wrapper_path,
1321 '--']
1322 me.extend(orig_args)
1323 exec_command(me)
1324 print("fatal: unable to start %s" % repo_main, file=sys.stderr)
1325 sys.exit(148)
1326
1327 1528
1328if __name__ == '__main__': 1529 reqs = Requirements.from_dir(os.path.dirname(repo_main))
1329 main(sys.argv[1:]) 1530 if reqs:
1531 reqs.assert_all()
1532
1533 ver_str = ".".join(map(str, VERSION))
1534 me = [
1535 sys.executable,
1536 repo_main,
1537 "--repo-dir=%s" % rel_repo_dir,
1538 "--wrapper-version=%s" % ver_str,
1539 "--wrapper-path=%s" % wrapper_path,
1540 "--",
1541 ]
1542 me.extend(orig_args)
1543 exec_command(me)
1544 print("fatal: unable to start %s" % repo_main, file=sys.stderr)
1545 sys.exit(148)
1546
1547
1548if __name__ == "__main__":
1549 main(sys.argv[1:])
diff --git a/run_tests b/run_tests
index d61308f6..7307f827 100755
--- a/run_tests
+++ b/run_tests
@@ -27,8 +27,16 @@ ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
27 27
28def run_black(): 28def run_black():
29 """Returns the exit code from black.""" 29 """Returns the exit code from black."""
30 # Black by default only matches .py files. We have to list standalone
31 # scripts manually.
32 extra_programs = [
33 "repo",
34 "run_tests",
35 "release/update-manpages",
36 ]
30 return subprocess.run( 37 return subprocess.run(
31 [sys.executable, "-m", "black", "--check", ROOT_DIR], check=False 38 [sys.executable, "-m", "black", "--check", ROOT_DIR] + extra_programs,
39 check=False,
32 ).returncode 40 ).returncode
33 41
34 42