diff options
-rwxr-xr-x | repo | 2202 | ||||
-rwxr-xr-x | run_tests | 10 |
2 files changed, 1220 insertions, 992 deletions
@@ -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. |
43 | class Trace(object): | 43 | class 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 | ||
59 | trace = Trace() | 59 | trace = Trace() |
60 | 60 | ||
61 | 61 | ||
62 | def exec_command(cmd): | 62 | def 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 | ||
75 | def check_python_version(): | 75 | def 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: | |
136 | if __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 | |||
160 | if __name__ == "__main__": | ||
161 | check_python_version() | ||
138 | 162 | ||
139 | 163 | ||
140 | # repo default configuration | 164 | # repo default configuration |
141 | # | 165 | # |
142 | REPO_URL = os.environ.get('REPO_URL', None) | 166 | REPO_URL = os.environ.get("REPO_URL", None) |
143 | if not REPO_URL: | 167 | if not REPO_URL: |
144 | REPO_URL = 'https://gerrit.googlesource.com/git-repo' | 168 | REPO_URL = "https://gerrit.googlesource.com/git-repo" |
145 | REPO_REV = os.environ.get('REPO_REV') | 169 | REPO_REV = os.environ.get("REPO_REV") |
146 | if not REPO_REV: | 170 | if 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. |
149 | BUG_URL = 'https://issues.gerritcodereview.com/issues/new?component=1370071' | 173 | BUG_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 |
152 | VERSION = (2, 36) | 176 | VERSION = (2, 36) |
@@ -231,19 +255,19 @@ cZ7aFsJF4PtcDrfdejyAxbtsSHI= | |||
231 | -----END PGP PUBLIC KEY BLOCK----- | 255 | -----END PGP PUBLIC KEY BLOCK----- |
232 | """ | 256 | """ |
233 | 257 | ||
234 | GIT = 'git' # our git command | 258 | GIT = "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. |
240 | MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version | 264 | MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version |
241 | repodir = '.repo' # name of repo's private directory | 265 | repodir = ".repo" # name of repo's private directory |
242 | S_repo = 'repo' # special repo repository | 266 | S_repo = "repo" # special repo repository |
243 | S_manifests = 'manifests' # special manifest repository | 267 | S_manifests = "manifests" # special manifest repository |
244 | REPO_MAIN = S_repo + '/main.py' # main script | 268 | REPO_MAIN = S_repo + "/main.py" # main script |
245 | GITC_CONFIG_FILE = '/gitc/.config' | 269 | GITC_CONFIG_FILE = "/gitc/.config" |
246 | GITC_FS_ROOT_DIR = '/gitc/manifest-rw/' | 270 | GITC_FS_ROOT_DIR = "/gitc/manifest-rw/" |
247 | 271 | ||
248 | 272 | ||
249 | import collections | 273 | import collections |
@@ -256,1074 +280,1270 @@ import stat | |||
256 | 280 | ||
257 | 281 | ||
258 | if sys.version_info[0] == 3: | 282 | if sys.version_info[0] == 3: |
259 | import urllib.error | 283 | import urllib.error |
260 | import urllib.request | 284 | import urllib.request |
261 | else: | 285 | else: |
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 | ||
270 | repo_config_dir = os.getenv('REPO_CONFIG_DIR', os.path.expanduser('~')) | 295 | repo_config_dir = os.getenv("REPO_CONFIG_DIR", os.path.expanduser("~")) |
271 | home_dot_repo = os.path.join(repo_config_dir, '.repoconfig') | 296 | home_dot_repo = os.path.join(repo_config_dir, ".repoconfig") |
272 | gpg_dir = os.path.join(home_dot_repo, 'gnupg') | 297 | gpg_dir = os.path.join(home_dot_repo, "gnupg") |
273 | 298 | ||
274 | 299 | ||
275 | def GetParser(gitc_init=False): | 300 | def 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 | ||
287 | def InitParser(parser): | 312 | def 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+. |
416 | RunResult = collections.namedtuple( | 547 | RunResult = collections.namedtuple( |
417 | 'RunResult', ('returncode', 'stdout', 'stderr')) | 548 | "RunResult", ("returncode", "stdout", "stderr") |
549 | ) | ||
418 | 550 | ||
419 | 551 | ||
420 | class RunError(Exception): | 552 | class RunError(Exception): |
421 | """Error when running a command failed.""" | 553 | """Error when running a command failed.""" |
422 | 554 | ||
423 | 555 | ||
424 | def run_command(cmd, **kwargs): | 556 | def 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 | ||
480 | def get_gitc_manifest_dir(): | 623 | def 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 | ||
495 | def gitc_parse_clientdir(gitc_fs_path): | 638 | def 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 | ||
520 | class CloneFailure(Exception): | 663 | class 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 | ||
526 | def check_repo_verify(repo_verify, quiet=False): | 668 | def 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 | ||
540 | def check_repo_rev(dst, rev, repo_verify=True, quiet=False): | 684 | def 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 | ||
554 | def _Init(args, gitc_init=False): | 701 | def _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 | ||
612 | def run_git(*args, **kwargs): | 767 | def 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. |
632 | GitVersion = collections.namedtuple( | 789 | GitVersion = collections.namedtuple( |
633 | 'GitVersion', ('major', 'minor', 'micro', 'full')) | 790 | "GitVersion", ("major", "minor", "micro", "full") |
791 | ) | ||
634 | 792 | ||
635 | 793 | ||
636 | def ParseGitVersion(ver_str=None): | 794 | def 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 | ||
656 | def _CheckGitVersion(): | 814 | def _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 | ||
669 | def SetGitTrace2ParentSid(env=None): | 829 | def 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 | ||
691 | def _setenv(key, value, env=None): | 851 | def _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 | ||
702 | def NeedSetupGnuPG(): | 862 | def 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 | ||
720 | def SetupGnuPG(quiet): | 880 | def 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 | ||
763 | def _SetConfig(cwd, name, value): | 938 | def _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 | ||
769 | def _GetRepoConfig(name): | 943 | def _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 | ||
787 | def _InitHttp(): | 963 | def _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 | ||
812 | def _Fetch(url, cwd, src, quiet, verbose): | 991 | def _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 | ||
827 | def _DownloadBundle(url, cwd, quiet, verbose): | 1006 | def _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 | ||
873 | def _ImportBundle(cwd): | 1053 | def _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 | ||
881 | def _Clone(url, cwd, clone_bundle, quiet, verbose): | 1061 | def _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 | ||
906 | def resolve_repo_rev(cwd, committish): | 1087 | def 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 | ||
971 | def verify_rev(cwd, remote_ref, rev, quiet): | 1163 | def 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 | ||
991 | def _Checkout(cwd, remote_ref, rev, quiet): | 1186 | def _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 | ||
1008 | def _FindRepo(): | 1202 | def _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 | ||
1024 | class _Options(object): | 1217 | class _Options(object): |
1025 | help = False | 1218 | help = False |
1026 | version = False | 1219 | version = False |
1027 | 1220 | ||
1028 | 1221 | ||
1029 | def _ExpandAlias(name): | 1222 | def _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 | ||
1048 | def _ParseArguments(args): | 1241 | def _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 | ||
1068 | class Requirements(object): | 1261 | class 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 | ||
1152 | def _Usage(): | 1351 | def _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 | ||
1160 | repo is not yet installed. Use "repo init" to install it here. | 1359 | repo is not yet installed. Use "repo init" to install it here. |
1161 | 1360 | ||
1162 | The most commonly used repo commands are: | 1361 | The 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 | ||
1168 | For access to the full online help, install repo ("repo init"). | 1368 | For 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 | ||
1174 | def _Help(args): | 1375 | def _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 | ||
1189 | def _Version(): | 1393 | def _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 | ||
1208 | def _NotInstalled(): | 1414 | def _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 | ||
1214 | def _NoCommands(cmd): | 1422 | def _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 | ||
1220 | def _RunSelf(wrapper_path): | 1432 | def _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 | ||
1235 | def _SetDefaultsTo(gitdir): | 1445 | def _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 | ||
1254 | def main(orig_args): | 1466 | def 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 | ||
1328 | if __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 | |||
1548 | if __name__ == "__main__": | ||
1549 | main(sys.argv[1:]) | ||
@@ -27,8 +27,16 @@ ROOT_DIR = os.path.dirname(os.path.realpath(__file__)) | |||
27 | 27 | ||
28 | def run_black(): | 28 | def 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 | ||