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 | ||
