|
[ Source navigation ] [ Diff markup ] [ Identifier search ] |
|||
|
Warning, /repo is written in an unsupported language. File is not indexed.
0001 #!/usr/bin/env python3 0002 # -*- coding:utf-8 -*- 0003 # 0004 # Copyright (C) 2008 The Android Open Source Project 0005 # 0006 # Licensed under the Apache License, Version 2.0 (the "License"); 0007 # you may not use this file except in compliance with the License. 0008 # You may obtain a copy of the License at 0009 # 0010 # http://www.apache.org/licenses/LICENSE-2.0 0011 # 0012 # Unless required by applicable law or agreed to in writing, software 0013 # distributed under the License is distributed on an "AS IS" BASIS, 0014 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 0015 # See the License for the specific language governing permissions and 0016 # limitations under the License. 0017 0018 """Repo launcher. 0019 0020 This is a standalone tool that people may copy to anywhere in their system. 0021 It is used to get an initial repo client checkout, and after that it runs the 0022 copy of repo in the checkout. 0023 """ 0024 0025 from __future__ import print_function 0026 0027 import datetime 0028 import os 0029 import platform 0030 import shlex 0031 import subprocess 0032 import sys 0033 0034 0035 # Keep basic logic in sync with repo_trace.py. 0036 class Trace(object): 0037 """Trace helper logic.""" 0038 0039 REPO_TRACE = 'REPO_TRACE' 0040 0041 def __init__(self): 0042 self.set(os.environ.get(self.REPO_TRACE) == '1') 0043 0044 def set(self, value): 0045 self.enabled = bool(value) 0046 0047 def print(self, *args, **kwargs): 0048 if self.enabled: 0049 print(*args, **kwargs) 0050 0051 0052 trace = Trace() 0053 0054 0055 def exec_command(cmd): 0056 """Execute |cmd| or return None on failure.""" 0057 trace.print(':', ' '.join(cmd)) 0058 try: 0059 if platform.system() == 'Windows': 0060 ret = subprocess.call(cmd) 0061 sys.exit(ret) 0062 else: 0063 os.execvp(cmd[0], cmd) 0064 except Exception: 0065 pass 0066 0067 0068 def check_python_version(): 0069 """Make sure the active Python version is recent enough.""" 0070 def reexec(prog): 0071 exec_command([prog] + sys.argv) 0072 0073 MIN_PYTHON_VERSION = (3, 6) 0074 0075 ver = sys.version_info 0076 major = ver.major 0077 minor = ver.minor 0078 0079 # Abort on very old Python 2 versions. 0080 if (major, minor) < (2, 7): 0081 print('repo: error: Your Python version is too old. ' 0082 'Please use Python {}.{} or newer instead.'.format( 0083 *MIN_PYTHON_VERSION), file=sys.stderr) 0084 sys.exit(1) 0085 0086 # Try to re-exec the version specific Python 3 if needed. 0087 if (major, minor) < MIN_PYTHON_VERSION: 0088 # Python makes releases ~once a year, so try our min version +10 to help 0089 # bridge the gap. This is the fallback anyways so perf isn't critical. 0090 min_major, min_minor = MIN_PYTHON_VERSION 0091 for inc in range(0, 10): 0092 reexec('python{}.{}'.format(min_major, min_minor + inc)) 0093 0094 # Try the generic Python 3 wrapper, but only if it's new enough. We don't 0095 # want to go from (still supported) Python 2.7 to (unsupported) Python 3.5. 0096 try: 0097 proc = subprocess.Popen( 0098 ['python3', '-c', 'import sys; ' 0099 'print(sys.version_info.major, sys.version_info.minor)'], 0100 stdout=subprocess.PIPE, stderr=subprocess.PIPE) 0101 (output, _) = proc.communicate() 0102 python3_ver = tuple(int(x) for x in output.decode('utf-8').split()) 0103 except (OSError, subprocess.CalledProcessError): 0104 python3_ver = None 0105 0106 # The python3 version looks like it's new enough, so give it a try. 0107 if python3_ver and python3_ver >= MIN_PYTHON_VERSION: 0108 reexec('python3') 0109 0110 # We're still here, so diagnose things for the user. 0111 if major < 3: 0112 print('repo: warning: Python 2 is no longer supported; ' 0113 'Please upgrade to Python {}.{}+.'.format(*MIN_PYTHON_VERSION), 0114 file=sys.stderr) 0115 else: 0116 print('repo: error: Python 3 version is too old; ' 0117 'Please use Python {}.{} or newer.'.format(*MIN_PYTHON_VERSION), 0118 file=sys.stderr) 0119 sys.exit(1) 0120 0121 0122 if __name__ == '__main__': 0123 check_python_version() 0124 0125 0126 # repo default configuration 0127 # 0128 REPO_URL = os.environ.get('REPO_URL', None) 0129 if not REPO_URL: 0130 REPO_URL = 'https://gitee.com/oschina/repo.git' 0131 REPO_REV = os.environ.get('REPO_REV') 0132 if not REPO_REV: 0133 REPO_REV = 'fork_flow' 0134 0135 # increment this whenever we make important changes to this script 0136 VERSION = (2, 8) 0137 0138 # increment this if the MAINTAINER_KEYS block is modified 0139 KEYRING_VERSION = (2, 3) 0140 0141 # Each individual key entry is created by using: 0142 # gpg --armor --export keyid 0143 MAINTAINER_KEYS = """ 0144 0145 Repo Maintainer <repo@android.kernel.org> 0146 -----BEGIN PGP PUBLIC KEY BLOCK----- 0147 0148 -----END PGP PUBLIC KEY BLOCK----- 0149 """ 0150 0151 GIT = 'git' # our git command 0152 # NB: The version of git that the repo launcher requires may be much older than 0153 # the version of git that the main repo source tree requires. Keeping this at 0154 # an older version also makes it easier for users to upgrade/rollback as needed. 0155 # 0156 # git-1.7 is in (EOL) Ubuntu Precise. 0157 MIN_GIT_VERSION = (1, 7, 2) # minimum supported git version 0158 repodir = '.repo' # name of repo's private directory 0159 S_repo = 'repo' # special repo repository 0160 S_manifests = 'manifests' # special manifest repository 0161 REPO_MAIN = S_repo + '/main.py' # main script 0162 GITC_CONFIG_FILE = '/gitc/.config' 0163 GITC_FS_ROOT_DIR = '/gitc/manifest-rw/' 0164 0165 0166 import collections 0167 import errno 0168 import optparse 0169 import re 0170 import shutil 0171 import stat 0172 0173 if sys.version_info[0] == 3: 0174 import urllib.request 0175 import urllib.error 0176 else: 0177 import imp 0178 import urllib2 0179 urllib = imp.new_module('urllib') 0180 urllib.request = urllib2 0181 urllib.error = urllib2 0182 0183 0184 home_dot_repo = os.path.expanduser('~/.repoconfig') 0185 gpg_dir = os.path.join(home_dot_repo, 'gnupg') 0186 0187 0188 def GetParser(gitc_init=False): 0189 """Setup the CLI parser.""" 0190 if gitc_init: 0191 usage = 'repo gitc-init -u url -c client [options]' 0192 else: 0193 usage = 'repo init -u url [options]' 0194 0195 parser = optparse.OptionParser(usage=usage) 0196 0197 # Logging. 0198 group = parser.add_option_group('Logging options') 0199 group.add_option('-v', '--verbose', 0200 dest='output_mode', action='store_true', 0201 help='show all output') 0202 group.add_option('-q', '--quiet', 0203 dest='output_mode', action='store_false', 0204 help='only show errors') 0205 0206 # Manifest. 0207 group = parser.add_option_group('Manifest options') 0208 group.add_option('-u', '--manifest-url', 0209 help='manifest repository location', metavar='URL') 0210 group.add_option('-b', '--manifest-branch', 0211 help='manifest branch or revision', metavar='REVISION') 0212 group.add_option('-m', '--manifest-name', 0213 help='initial manifest file', metavar='NAME.xml') 0214 cbr_opts = ['--current-branch'] 0215 # The gitc-init subcommand allocates -c itself, but a lot of init users 0216 # want -c, so try to satisfy both as best we can. 0217 if not gitc_init: 0218 cbr_opts += ['-c'] 0219 group.add_option(*cbr_opts, 0220 dest='current_branch_only', action='store_true', 0221 help='fetch only current manifest branch from server') 0222 group.add_option('--mirror', action='store_true', 0223 help='create a replica of the remote repositories ' 0224 'rather than a client working directory') 0225 group.add_option('--reference', 0226 help='location of mirror directory', metavar='DIR') 0227 group.add_option('--dissociate', action='store_true', 0228 help='dissociate from reference mirrors after clone') 0229 group.add_option('--depth', type='int', default=None, 0230 help='create a shallow clone with given depth; ' 0231 'see git clone') 0232 group.add_option('--partial-clone', action='store_true', 0233 help='perform partial clone (https://git-scm.com/' 0234 'docs/gitrepository-layout#_code_partialclone_code)') 0235 group.add_option('--clone-filter', action='store', default='blob:none', 0236 help='filter for use with --partial-clone ' 0237 '[default: %default]') 0238 group.add_option('--worktree', action='store_true', 0239 help=optparse.SUPPRESS_HELP) 0240 group.add_option('--archive', action='store_true', 0241 help='checkout an archive instead of a git repository for ' 0242 'each project. See git archive.') 0243 group.add_option('--submodules', action='store_true', 0244 help='sync any submodules associated with the manifest repo') 0245 group.add_option('-g', '--groups', default='default', 0246 help='restrict manifest projects to ones with specified ' 0247 'group(s) [default|all|G1,G2,G3|G4,-G5,-G6]', 0248 metavar='GROUP') 0249 group.add_option('-p', '--platform', default='auto', 0250 help='restrict manifest projects to ones with a specified ' 0251 'platform group [auto|all|none|linux|darwin|...]', 0252 metavar='PLATFORM') 0253 group.add_option('--clone-bundle', action='store_false', 0254 help='enable use of /clone.bundle on HTTP/HTTPS (default if not --partial-clone). ' 0255 'WARNING: Not currently supported') 0256 group.add_option('--no-clone-bundle', 0257 dest='clone_bundle', action='store_false', default=False, 0258 help='disable use of /clone.bundle on HTTP/HTTPS (default if --partial-clone). ' 0259 'WARNING: Not currently supported') 0260 group.add_option('--no-tags', 0261 dest='tags', default=True, action='store_false', 0262 help="don't fetch tags in the manifest") 0263 0264 # Tool. 0265 group = parser.add_option_group('repo Version options') 0266 group.add_option('--repo-url', metavar='URL', 0267 help='repo repository location ($REPO_URL)') 0268 group.add_option('--repo-rev', metavar='REV', 0269 help='repo branch or revision ($REPO_REV)') 0270 group.add_option('--repo-branch', dest='repo_rev', 0271 help=optparse.SUPPRESS_HELP) 0272 group.add_option('--no-repo-verify', 0273 dest='repo_verify', default=False, action='store_true', 0274 help='do not verify repo source code') 0275 0276 # Other. 0277 group = parser.add_option_group('Other options') 0278 group.add_option('--config-name', 0279 action='store_true', default=False, 0280 help='Always prompt for name/e-mail') 0281 0282 # gitc-init specific settings. 0283 if gitc_init: 0284 group = parser.add_option_group('GITC options') 0285 group.add_option('-f', '--manifest-file', 0286 help='Optional manifest file to use for this GITC client.') 0287 group.add_option('-c', '--gitc-client', 0288 help='Name of the gitc_client instance to create or modify.') 0289 0290 return parser 0291 0292 0293 # This is a poor replacement for subprocess.run until we require Python 3.6+. 0294 RunResult = collections.namedtuple( 0295 'RunResult', ('returncode', 'stdout', 'stderr')) 0296 0297 0298 class RunError(Exception): 0299 """Error when running a command failed.""" 0300 0301 0302 def run_command(cmd, **kwargs): 0303 """Run |cmd| and return its output.""" 0304 check = kwargs.pop('check', False) 0305 if kwargs.pop('capture_output', False): 0306 kwargs.setdefault('stdout', subprocess.PIPE) 0307 kwargs.setdefault('stderr', subprocess.PIPE) 0308 cmd_input = kwargs.pop('input', None) 0309 0310 def decode(output): 0311 """Decode |output| to text.""" 0312 if output is None: 0313 return output 0314 try: 0315 return output.decode('utf-8') 0316 except UnicodeError: 0317 print('repo: warning: Invalid UTF-8 output:\ncmd: %r\n%r' % (cmd, output), 0318 file=sys.stderr) 0319 # TODO(vapier): Once we require Python 3, use 'backslashreplace'. 0320 return output.decode('utf-8', 'replace') 0321 0322 # Run & package the results. 0323 proc = subprocess.Popen(cmd, **kwargs) 0324 (stdout, stderr) = proc.communicate(input=cmd_input) 0325 dbg = ': ' + ' '.join(cmd) 0326 if cmd_input is not None: 0327 dbg += ' 0<|' 0328 if stdout == subprocess.PIPE: 0329 dbg += ' 1>|' 0330 if stderr == subprocess.PIPE: 0331 dbg += ' 2>|' 0332 elif stderr == subprocess.STDOUT: 0333 dbg += ' 2>&1' 0334 trace.print(dbg) 0335 ret = RunResult(proc.returncode, decode(stdout), decode(stderr)) 0336 0337 # If things failed, print useful debugging output. 0338 if check and ret.returncode: 0339 print('repo: error: "%s" failed with exit status %s' % 0340 (cmd[0], ret.returncode), file=sys.stderr) 0341 print(' cwd: %s\n cmd: %r' % 0342 (kwargs.get('cwd', os.getcwd()), cmd), file=sys.stderr) 0343 0344 def _print_output(name, output): 0345 if output: 0346 print(' %s:\n >> %s' % (name, '\n >> '.join(output.splitlines())), 0347 file=sys.stderr) 0348 0349 _print_output('stdout', ret.stdout) 0350 _print_output('stderr', ret.stderr) 0351 raise RunError(ret) 0352 0353 return ret 0354 0355 0356 _gitc_manifest_dir = None 0357 0358 0359 def get_gitc_manifest_dir(): 0360 global _gitc_manifest_dir 0361 if _gitc_manifest_dir is None: 0362 _gitc_manifest_dir = '' 0363 try: 0364 with open(GITC_CONFIG_FILE, 'r') as gitc_config: 0365 for line in gitc_config: 0366 match = re.match('gitc_dir=(?P<gitc_manifest_dir>.*)', line) 0367 if match: 0368 _gitc_manifest_dir = match.group('gitc_manifest_dir') 0369 except IOError: 0370 pass 0371 return _gitc_manifest_dir 0372 0373 0374 def gitc_parse_clientdir(gitc_fs_path): 0375 """Parse a path in the GITC FS and return its client name. 0376 0377 @param gitc_fs_path: A subdirectory path within the GITC_FS_ROOT_DIR. 0378 0379 @returns: The GITC client name 0380 """ 0381 if gitc_fs_path == GITC_FS_ROOT_DIR: 0382 return None 0383 if not gitc_fs_path.startswith(GITC_FS_ROOT_DIR): 0384 manifest_dir = get_gitc_manifest_dir() 0385 if manifest_dir == '': 0386 return None 0387 if manifest_dir[-1] != '/': 0388 manifest_dir += '/' 0389 if gitc_fs_path == manifest_dir: 0390 return None 0391 if not gitc_fs_path.startswith(manifest_dir): 0392 return None 0393 return gitc_fs_path.split(manifest_dir)[1].split('/')[0] 0394 return gitc_fs_path.split(GITC_FS_ROOT_DIR)[1].split('/')[0] 0395 0396 0397 class CloneFailure(Exception): 0398 0399 """Indicate the remote clone of repo itself failed. 0400 """ 0401 0402 0403 def check_repo_verify(repo_verify, quiet=False): 0404 """Check the --repo-verify state.""" 0405 if not repo_verify: 0406 print('repo: warning: verification of repo code has been disabled;\n' 0407 'repo will not be able to verify the integrity of itself.\n', 0408 file=sys.stderr) 0409 return False 0410 0411 if NeedSetupGnuPG(): 0412 return SetupGnuPG(quiet) 0413 0414 return True 0415 0416 0417 def check_repo_rev(dst, rev, repo_verify=False, quiet=False): 0418 """Check that |rev| is valid.""" 0419 do_verify = check_repo_verify(repo_verify, quiet=quiet) 0420 remote_ref, local_rev = resolve_repo_rev(dst, rev) 0421 if not quiet and not remote_ref.startswith('refs/heads/'): 0422 print('warning: repo is not tracking a remote branch, so it will not ' 0423 'receive updates', file=sys.stderr) 0424 if do_verify: 0425 rev = verify_rev(dst, remote_ref, local_rev, quiet) 0426 else: 0427 rev = local_rev 0428 return (remote_ref, rev) 0429 0430 0431 def _Init(args, gitc_init=False): 0432 """Installs repo by cloning it over the network. 0433 """ 0434 parser = GetParser(gitc_init=gitc_init) 0435 opt, args = parser.parse_args(args) 0436 if args: 0437 parser.print_usage() 0438 sys.exit(1) 0439 opt.quiet = opt.output_mode is False 0440 opt.verbose = opt.output_mode is True 0441 0442 if opt.clone_bundle is None: 0443 opt.clone_bundle = False if opt.partial_clone else True 0444 0445 url = opt.repo_url or REPO_URL 0446 rev = opt.repo_rev or REPO_REV 0447 0448 try: 0449 if gitc_init: 0450 gitc_manifest_dir = get_gitc_manifest_dir() 0451 if not gitc_manifest_dir: 0452 print('fatal: GITC filesystem is not available. Exiting...', 0453 file=sys.stderr) 0454 sys.exit(1) 0455 gitc_client = opt.gitc_client 0456 if not gitc_client: 0457 gitc_client = gitc_parse_clientdir(os.getcwd()) 0458 if not gitc_client: 0459 print('fatal: GITC client (-c) is required.', file=sys.stderr) 0460 sys.exit(1) 0461 client_dir = os.path.join(gitc_manifest_dir, gitc_client) 0462 if not os.path.exists(client_dir): 0463 os.makedirs(client_dir) 0464 os.chdir(client_dir) 0465 if os.path.exists(repodir): 0466 # This GITC Client has already initialized repo so continue. 0467 return 0468 0469 os.mkdir(repodir) 0470 except OSError as e: 0471 if e.errno != errno.EEXIST: 0472 print('fatal: cannot make %s directory: %s' 0473 % (repodir, e.strerror), file=sys.stderr) 0474 # Don't raise CloneFailure; that would delete the 0475 # name. Instead exit immediately. 0476 # 0477 sys.exit(1) 0478 0479 _CheckGitVersion() 0480 try: 0481 if not opt.quiet: 0482 print('Downloading Repo source from', url) 0483 dst = os.path.abspath(os.path.join(repodir, S_repo)) 0484 _Clone(url, dst, opt.clone_bundle, opt.quiet, opt.verbose) 0485 0486 remote_ref, rev = check_repo_rev(dst, rev, False, quiet=opt.quiet) 0487 _Checkout(dst, remote_ref, rev, opt.quiet) 0488 0489 if not os.path.isfile(os.path.join(dst, 'repo')): 0490 print("warning: '%s' does not look like a git-repo repository, is " 0491 "REPO_URL set correctly?" % url, file=sys.stderr) 0492 0493 except CloneFailure: 0494 if opt.quiet: 0495 print('fatal: repo init failed; run without --quiet to see why', 0496 file=sys.stderr) 0497 raise 0498 0499 0500 def run_git(*args, **kwargs): 0501 """Run git and return execution details.""" 0502 kwargs.setdefault('capture_output', True) 0503 kwargs.setdefault('check', True) 0504 try: 0505 return run_command([GIT] + list(args), **kwargs) 0506 except OSError as e: 0507 print(file=sys.stderr) 0508 print('repo: error: "%s" is not available' % GIT, file=sys.stderr) 0509 print('repo: error: %s' % e, file=sys.stderr) 0510 print(file=sys.stderr) 0511 print('Please make sure %s is installed and in your path.' % GIT, 0512 file=sys.stderr) 0513 sys.exit(1) 0514 except RunError: 0515 raise CloneFailure() 0516 0517 def _CheckRequests(): 0518 try: 0519 import requests 0520 except: 0521 print('No module named requests') 0522 print('confirm installation requests[y/N]? ', end='') 0523 sys.stdout.flush() 0524 a = sys.stdin.readline().strip().lower() 0525 if a in ('yes', 'y', 't', 'true'): 0526 _InstallRequests('-i' ,'https://pypi.tuna.tsinghua.edu.cn/simple') 0527 sys.exit(1) 0528 0529 0530 class InstallRequestsError(Exception): 0531 "pip install requests error" 0532 0533 def _InstallRequests(*args, **kwargs): 0534 """Run pip install requests and return execution details.""" 0535 # kwargs.setdefault('capture_output', True) 0536 kwargs.setdefault('check', True) 0537 pip, version = ('pip3', '2.24.0') if sys.version_info[0] == 3 else ('pip', '2.18.4') 0538 try: 0539 return run_command([pip, 'install', 'requests==%s' % version] + list(args), **kwargs) 0540 except OSError as e: 0541 print(file=sys.stderr) 0542 print('repo: error: "%s" is not available' % pip, file=sys.stderr) 0543 print('repo: error: %s' % e, file=sys.stderr) 0544 print(file=sys.stderr) 0545 print('Please make sure %s is installed and in your path.' % pip, 0546 file=sys.stderr) 0547 sys.exit(1) 0548 except RunError: 0549 raise InstallRequestsError() 0550 0551 0552 # The git version info broken down into components for easy analysis. 0553 # Similar to Python's sys.version_info. 0554 GitVersion = collections.namedtuple( 0555 'GitVersion', ('major', 'minor', 'micro', 'full')) 0556 0557 0558 def ParseGitVersion(ver_str=None): 0559 if ver_str is None: 0560 # Load the version ourselves. 0561 ver_str = run_git('--version').stdout 0562 0563 if not ver_str.startswith('git version '): 0564 return None 0565 0566 full_version = ver_str[len('git version '):].strip() 0567 num_ver_str = full_version.split('-')[0] 0568 to_tuple = [] 0569 for num_str in num_ver_str.split('.')[:3]: 0570 if num_str.isdigit(): 0571 to_tuple.append(int(num_str)) 0572 else: 0573 to_tuple.append(0) 0574 to_tuple.append(full_version) 0575 return GitVersion(*to_tuple) 0576 0577 0578 def _CheckGitVersion(): 0579 ver_act = ParseGitVersion() 0580 if ver_act is None: 0581 print('fatal: unable to detect git version', file=sys.stderr) 0582 raise CloneFailure() 0583 0584 if ver_act < MIN_GIT_VERSION: 0585 need = '.'.join(map(str, MIN_GIT_VERSION)) 0586 print('fatal: git %s or later required; found %s' % (need, ver_act.full), 0587 file=sys.stderr) 0588 raise CloneFailure() 0589 0590 0591 def SetGitTrace2ParentSid(env=None): 0592 """Set up GIT_TRACE2_PARENT_SID for git tracing.""" 0593 # We roughly follow the format git itself uses in trace2/tr2_sid.c. 0594 # (1) Be unique (2) be valid filename (3) be fixed length. 0595 # 0596 # Since we always export this variable, we try to avoid more expensive calls. 0597 # e.g. We don't attempt hostname lookups or hashing the results. 0598 if env is None: 0599 env = os.environ 0600 0601 KEY = 'GIT_TRACE2_PARENT_SID' 0602 0603 now = datetime.datetime.utcnow() 0604 value = 'repo-%s-P%08x' % (now.strftime('%Y%m%dT%H%M%SZ'), os.getpid()) 0605 0606 # If it's already set, then append ourselves. 0607 if KEY in env: 0608 value = env[KEY] + '/' + value 0609 0610 _setenv(KEY, value, env=env) 0611 0612 0613 def _setenv(key, value, env=None): 0614 """Set |key| in the OS environment |env| to |value|.""" 0615 if env is None: 0616 env = os.environ 0617 # Environment handling across systems is messy. 0618 try: 0619 env[key] = value 0620 except UnicodeEncodeError: 0621 env[key] = value.encode() 0622 0623 0624 def NeedSetupGnuPG(): 0625 if not os.path.isdir(home_dot_repo): 0626 return True 0627 0628 kv = os.path.join(home_dot_repo, 'keyring-version') 0629 if not os.path.exists(kv): 0630 return True 0631 0632 kv = open(kv).read() 0633 if not kv: 0634 return True 0635 0636 kv = tuple(map(int, kv.split('.'))) 0637 if kv < KEYRING_VERSION: 0638 return True 0639 return False 0640 0641 0642 def SetupGnuPG(quiet): 0643 try: 0644 os.mkdir(home_dot_repo) 0645 except OSError as e: 0646 if e.errno != errno.EEXIST: 0647 print('fatal: cannot make %s directory: %s' 0648 % (home_dot_repo, e.strerror), file=sys.stderr) 0649 sys.exit(1) 0650 0651 try: 0652 os.mkdir(gpg_dir, stat.S_IRWXU) 0653 except OSError as e: 0654 if e.errno != errno.EEXIST: 0655 print('fatal: cannot make %s directory: %s' % (gpg_dir, e.strerror), 0656 file=sys.stderr) 0657 sys.exit(1) 0658 0659 if not quiet: 0660 print('repo: Updating release signing keys to keyset ver %s' % 0661 ('.'.join(str(x) for x in KEYRING_VERSION),)) 0662 # NB: We use --homedir (and cwd below) because some environments (Windows) do 0663 # not correctly handle full native paths. We avoid the issue by changing to 0664 # the right dir with cwd=gpg_dir before executing gpg, and then telling gpg to 0665 # use the cwd (.) as its homedir which leaves the path resolution logic to it. 0666 cmd = ['gpg', '--homedir', '.', '--import'] 0667 try: 0668 # gpg can be pretty chatty. Always capture the output and if something goes 0669 # wrong, the builtin check failure will dump stdout & stderr for debugging. 0670 run_command(cmd, stdin=subprocess.PIPE, capture_output=True, 0671 cwd=gpg_dir, check=True, 0672 input=MAINTAINER_KEYS.encode('utf-8')) 0673 except OSError: 0674 if not quiet: 0675 print('warning: gpg (GnuPG) is not available.', file=sys.stderr) 0676 print('warning: Installing it is strongly encouraged.', file=sys.stderr) 0677 print(file=sys.stderr) 0678 return False 0679 0680 with open(os.path.join(home_dot_repo, 'keyring-version'), 'w') as fd: 0681 fd.write('.'.join(map(str, KEYRING_VERSION)) + '\n') 0682 return True 0683 0684 0685 def _SetConfig(cwd, name, value): 0686 """Set a git configuration option to the specified value. 0687 """ 0688 run_git('config', name, value, cwd=cwd) 0689 0690 0691 def _GetRepoConfig(name): 0692 """Read a repo configuration option.""" 0693 config = os.path.join(home_dot_repo, 'config') 0694 if not os.path.exists(config): 0695 return None 0696 0697 cmd = ['config', '--file', config, '--get', name] 0698 ret = run_git(*cmd, check=False) 0699 if ret.returncode == 0: 0700 return ret.stdout 0701 elif ret.returncode == 1: 0702 return None 0703 else: 0704 print('repo: error: git %s failed:\n%s' % (' '.join(cmd), ret.stderr), 0705 file=sys.stderr) 0706 raise RunError() 0707 0708 0709 def _InitHttp(): 0710 handlers = [] 0711 0712 mgr = urllib.request.HTTPPasswordMgrWithDefaultRealm() 0713 try: 0714 import netrc 0715 n = netrc.netrc() 0716 for host in n.hosts: 0717 p = n.hosts[host] 0718 mgr.add_password(p[1], 'http://%s/' % host, p[0], p[2]) 0719 mgr.add_password(p[1], 'https://%s/' % host, p[0], p[2]) 0720 except Exception: 0721 pass 0722 handlers.append(urllib.request.HTTPBasicAuthHandler(mgr)) 0723 handlers.append(urllib.request.HTTPDigestAuthHandler(mgr)) 0724 0725 if 'http_proxy' in os.environ: 0726 url = os.environ['http_proxy'] 0727 handlers.append(urllib.request.ProxyHandler({'http': url, 'https': url})) 0728 if 'REPO_CURL_VERBOSE' in os.environ: 0729 handlers.append(urllib.request.HTTPHandler(debuglevel=1)) 0730 handlers.append(urllib.request.HTTPSHandler(debuglevel=1)) 0731 urllib.request.install_opener(urllib.request.build_opener(*handlers)) 0732 0733 0734 def _Fetch(url, cwd, src, quiet, verbose): 0735 cmd = ['fetch'] 0736 if not verbose: 0737 cmd.append('--quiet') 0738 err = None 0739 if not quiet and sys.stdout.isatty(): 0740 cmd.append('--progress') 0741 elif not verbose: 0742 err = subprocess.PIPE 0743 cmd.append(src) 0744 cmd.append('+refs/heads/*:refs/remotes/origin/*') 0745 cmd.append('+refs/tags/*:refs/tags/*') 0746 run_git(*cmd, stderr=err, capture_output=False, cwd=cwd) 0747 0748 0749 def _DownloadBundle(url, cwd, quiet, verbose): 0750 if not url.endswith('/'): 0751 url += '/' 0752 url += 'clone.bundle' 0753 0754 ret = run_git('config', '--get-regexp', 'url.*.insteadof', cwd=cwd, 0755 check=False) 0756 for line in ret.stdout.splitlines(): 0757 m = re.compile(r'^url\.(.*)\.insteadof (.*)$').match(line) 0758 if m: 0759 new_url = m.group(1) 0760 old_url = m.group(2) 0761 if url.startswith(old_url): 0762 url = new_url + url[len(old_url):] 0763 break 0764 0765 if not url.startswith('http:') and not url.startswith('https:'): 0766 return False 0767 0768 dest = open(os.path.join(cwd, '.git', 'clone.bundle'), 'w+b') 0769 try: 0770 try: 0771 r = urllib.request.urlopen(url) 0772 except urllib.error.HTTPError as e: 0773 if e.code in [401, 403, 404, 501]: 0774 return False 0775 print('fatal: Cannot get %s' % url, file=sys.stderr) 0776 print('fatal: HTTP error %s' % e.code, file=sys.stderr) 0777 raise CloneFailure() 0778 except urllib.error.URLError as e: 0779 print('fatal: Cannot get %s' % url, file=sys.stderr) 0780 print('fatal: error %s' % e.reason, file=sys.stderr) 0781 raise CloneFailure() 0782 try: 0783 if verbose: 0784 print('Downloading clone bundle %s' % url, file=sys.stderr) 0785 while True: 0786 buf = r.read(8192) 0787 if not buf: 0788 return True 0789 dest.write(buf) 0790 finally: 0791 r.close() 0792 finally: 0793 dest.close() 0794 0795 0796 def _ImportBundle(cwd): 0797 path = os.path.join(cwd, '.git', 'clone.bundle') 0798 try: 0799 _Fetch(cwd, cwd, path, True, False) 0800 finally: 0801 os.remove(path) 0802 0803 0804 def _Clone(url, cwd, clone_bundle, quiet, verbose): 0805 """Clones a git repository to a new subdirectory of repodir 0806 """ 0807 if verbose: 0808 print('Cloning git repository', url) 0809 0810 try: 0811 os.mkdir(cwd) 0812 except OSError as e: 0813 print('fatal: cannot make %s directory: %s' % (cwd, e.strerror), 0814 file=sys.stderr) 0815 raise CloneFailure() 0816 0817 run_git('init', '--quiet', cwd=cwd) 0818 0819 _InitHttp() 0820 _SetConfig(cwd, 'remote.origin.url', url) 0821 _SetConfig(cwd, 0822 'remote.origin.fetch', 0823 '+refs/heads/*:refs/remotes/origin/*') 0824 if clone_bundle and _DownloadBundle(url, cwd, quiet, verbose): 0825 _ImportBundle(cwd) 0826 _Fetch(url, cwd, 'origin', quiet, verbose) 0827 0828 0829 def resolve_repo_rev(cwd, committish): 0830 """Figure out what REPO_REV represents. 0831 0832 We support: 0833 * refs/heads/xxx: Branch. 0834 * refs/tags/xxx: Tag. 0835 * xxx: Branch or tag or commit. 0836 0837 Args: 0838 cwd: The git checkout to run in. 0839 committish: The REPO_REV argument to resolve. 0840 0841 Returns: 0842 A tuple of (remote ref, commit) as makes sense for the committish. 0843 For branches, this will look like ('refs/heads/stable', <revision>). 0844 For tags, this will look like ('refs/tags/v1.0', <revision>). 0845 For commits, this will be (<revision>, <revision>). 0846 """ 0847 def resolve(committish): 0848 ret = run_git('rev-parse', '--verify', '%s^{commit}' % (committish,), 0849 cwd=cwd, check=False) 0850 return None if ret.returncode else ret.stdout.strip() 0851 0852 # An explicit branch. 0853 if committish.startswith('refs/heads/'): 0854 remote_ref = committish 0855 committish = committish[len('refs/heads/'):] 0856 rev = resolve('refs/remotes/origin/%s' % committish) 0857 if rev is None: 0858 print('repo: error: unknown branch "%s"' % (committish,), 0859 file=sys.stderr) 0860 raise CloneFailure() 0861 return (remote_ref, rev) 0862 0863 # An explicit tag. 0864 if committish.startswith('refs/tags/'): 0865 remote_ref = committish 0866 committish = committish[len('refs/tags/'):] 0867 rev = resolve(remote_ref) 0868 if rev is None: 0869 print('repo: error: unknown tag "%s"' % (committish,), 0870 file=sys.stderr) 0871 raise CloneFailure() 0872 return (remote_ref, rev) 0873 0874 # See if it's a short branch name. 0875 rev = resolve('refs/remotes/origin/%s' % committish) 0876 if rev: 0877 return ('refs/heads/%s' % (committish,), rev) 0878 0879 # See if it's a tag. 0880 rev = resolve('refs/tags/%s' % committish) 0881 if rev: 0882 return ('refs/tags/%s' % (committish,), rev) 0883 0884 # See if it's a commit. 0885 rev = resolve(committish) 0886 if rev and rev.lower().startswith(committish.lower()): 0887 return (rev, rev) 0888 0889 # Give up! 0890 print('repo: error: unable to resolve "%s"' % (committish,), file=sys.stderr) 0891 raise CloneFailure() 0892 0893 0894 def verify_rev(cwd, remote_ref, rev, quiet): 0895 """Verify the commit has been signed by a tag.""" 0896 ret = run_git('describe', rev, cwd=cwd) 0897 cur = ret.stdout.strip() 0898 0899 m = re.compile(r'^(.*)-[0-9]{1,}-g[0-9a-f]{1,}$').match(cur) 0900 if m: 0901 cur = m.group(1) 0902 if not quiet: 0903 print(file=sys.stderr) 0904 print("warning: '%s' is not signed; falling back to signed release '%s'" 0905 % (remote_ref, cur), file=sys.stderr) 0906 print(file=sys.stderr) 0907 0908 env = os.environ.copy() 0909 _setenv('GNUPGHOME', gpg_dir, env) 0910 run_git('tag', '-v', cur, cwd=cwd, env=env) 0911 return '%s^0' % cur 0912 0913 0914 def _Checkout(cwd, remote_ref, rev, quiet): 0915 """Checkout an upstream branch into the repository and track it. 0916 """ 0917 run_git('update-ref', 'refs/heads/default', rev, cwd=cwd) 0918 0919 _SetConfig(cwd, 'branch.default.remote', 'origin') 0920 _SetConfig(cwd, 'branch.default.merge', remote_ref) 0921 0922 run_git('symbolic-ref', 'HEAD', 'refs/heads/default', cwd=cwd) 0923 0924 cmd = ['read-tree', '--reset', '-u'] 0925 if not quiet: 0926 cmd.append('-v') 0927 cmd.append('HEAD') 0928 run_git(*cmd, cwd=cwd) 0929 0930 0931 def _FindRepo(): 0932 """Look for a repo installation, starting at the current directory. 0933 """ 0934 curdir = os.getcwd() 0935 repo = None 0936 0937 olddir = None 0938 while curdir != '/' \ 0939 and curdir != olddir \ 0940 and not repo: 0941 repo = os.path.join(curdir, repodir, REPO_MAIN) 0942 if not os.path.isfile(repo): 0943 repo = None 0944 olddir = curdir 0945 curdir = os.path.dirname(curdir) 0946 return (repo, os.path.join(curdir, repodir)) 0947 0948 0949 class _Options(object): 0950 help = False 0951 version = False 0952 0953 0954 def _ExpandAlias(name): 0955 """Look up user registered aliases.""" 0956 # We don't resolve aliases for existing subcommands. This matches git. 0957 if name in {'gitc-init', 'help', 'init'}: 0958 return name, [] 0959 0960 alias = _GetRepoConfig('alias.%s' % (name,)) 0961 if alias is None: 0962 return name, [] 0963 0964 args = alias.strip().split(' ', 1) 0965 name = args[0] 0966 if len(args) == 2: 0967 args = shlex.split(args[1]) 0968 else: 0969 args = [] 0970 return name, args 0971 0972 0973 def _ParseArguments(args): 0974 cmd = None 0975 opt = _Options() 0976 arg = [] 0977 0978 for i in range(len(args)): 0979 a = args[i] 0980 if a == '-h' or a == '--help': 0981 opt.help = True 0982 elif a == '--version': 0983 opt.version = True 0984 elif a == '--trace': 0985 trace.set(True) 0986 elif not a.startswith('-'): 0987 cmd = a 0988 arg = args[i + 1:] 0989 break 0990 return cmd, opt, arg 0991 0992 0993 def _Usage(): 0994 gitc_usage = "" 0995 if get_gitc_manifest_dir(): 0996 gitc_usage = " gitc-init Initialize a GITC Client.\n" 0997 0998 print( 0999 """usage: repo COMMAND [ARGS] 1000 1001 repo is not yet installed. Use "repo init" to install it here. 1002 1003 The most commonly used repo commands are: 1004 1005 init Install repo in the current working directory 1006 """ + gitc_usage + 1007 """ help Display detailed help on a command 1008 1009 For access to the full online help, install repo ("repo init"). 1010 """) 1011 sys.exit(0) 1012 1013 1014 def _Help(args): 1015 if args: 1016 if args[0] in {'init', 'gitc-init'}: 1017 parser = GetParser(gitc_init=args[0] == 'gitc-init') 1018 parser.print_help() 1019 sys.exit(0) 1020 else: 1021 print("error: '%s' is not a bootstrap command.\n" 1022 ' For access to online help, install repo ("repo init").' 1023 % args[0], file=sys.stderr) 1024 else: 1025 _Usage() 1026 sys.exit(1) 1027 1028 1029 def _Version(): 1030 """Show version information.""" 1031 print('<repo not installed>') 1032 print('repo launcher version %s' % ('.'.join(str(x) for x in VERSION),)) 1033 print(' (from %s)' % (__file__,)) 1034 print('git %s' % (ParseGitVersion().full,)) 1035 print('Python %s' % sys.version) 1036 uname = platform.uname() 1037 if sys.version_info.major < 3: 1038 # Python 3 returns a named tuple, but Python 2 is simpler. 1039 print(uname) 1040 else: 1041 print('OS %s %s (%s)' % (uname.system, uname.release, uname.version)) 1042 print('CPU %s (%s)' % 1043 (uname.machine, uname.processor if uname.processor else 'unknown')) 1044 sys.exit(0) 1045 1046 1047 def _NotInstalled(): 1048 print('error: repo is not installed. Use "repo init" to install it here.', 1049 file=sys.stderr) 1050 sys.exit(1) 1051 1052 1053 def _NoCommands(cmd): 1054 print("""error: command '%s' requires repo to be installed first. 1055 Use "repo init" to install it here.""" % cmd, file=sys.stderr) 1056 sys.exit(1) 1057 1058 1059 def _RunSelf(wrapper_path): 1060 my_dir = os.path.dirname(wrapper_path) 1061 my_main = os.path.join(my_dir, 'main.py') 1062 my_git = os.path.join(my_dir, '.git') 1063 1064 if os.path.isfile(my_main) and os.path.isdir(my_git): 1065 for name in ['git_config.py', 1066 'project.py', 1067 'subcmds']: 1068 if not os.path.exists(os.path.join(my_dir, name)): 1069 return None, None 1070 return my_main, my_git 1071 return None, None 1072 1073 1074 def _SetDefaultsTo(gitdir): 1075 global REPO_URL 1076 global REPO_REV 1077 1078 REPO_URL = gitdir 1079 ret = run_git('--git-dir=%s' % gitdir, 'symbolic-ref', 'HEAD', check=False) 1080 if ret.returncode: 1081 # If we're not tracking a branch (bisect/etc...), then fall back to commit. 1082 print('repo: warning: %s has no current branch; using HEAD' % gitdir, 1083 file=sys.stderr) 1084 try: 1085 ret = run_git('rev-parse', 'HEAD', cwd=gitdir) 1086 except CloneFailure: 1087 print('fatal: %s has invalid HEAD' % gitdir, file=sys.stderr) 1088 sys.exit(1) 1089 1090 REPO_REV = ret.stdout.strip() 1091 1092 1093 def main(orig_args): 1094 _CheckRequests() 1095 cmd, opt, args = _ParseArguments(orig_args) 1096 # We run this early as we run some git commands ourselves. 1097 SetGitTrace2ParentSid() 1098 1099 repo_main, rel_repo_dir = None, None 1100 # Don't use the local repo copy, make sure to switch to the gitc client first. 1101 if cmd != 'gitc-init': 1102 repo_main, rel_repo_dir = _FindRepo() 1103 1104 wrapper_path = os.path.abspath(__file__) 1105 my_main, my_git = _RunSelf(wrapper_path) 1106 1107 cwd = os.getcwd() 1108 if get_gitc_manifest_dir() and cwd.startswith(get_gitc_manifest_dir()): 1109 print('error: repo cannot be used in the GITC local manifest directory.' 1110 '\nIf you want to work on this GITC client please rerun this ' 1111 'command from the corresponding client under /gitc/', 1112 file=sys.stderr) 1113 sys.exit(1) 1114 if not repo_main: 1115 # Only expand aliases here since we'll be parsing the CLI ourselves. 1116 # If we had repo_main, alias expansion would happen in main.py. 1117 cmd, alias_args = _ExpandAlias(cmd) 1118 args = alias_args + args 1119 1120 if opt.help: 1121 _Usage() 1122 if cmd == 'help': 1123 _Help(args) 1124 if opt.version or cmd == 'version': 1125 _Version() 1126 if not cmd: 1127 _NotInstalled() 1128 if cmd == 'init' or cmd == 'gitc-init': 1129 if my_git: 1130 _SetDefaultsTo(my_git) 1131 try: 1132 _Init(args, gitc_init=(cmd == 'gitc-init')) 1133 except CloneFailure: 1134 path = os.path.join(repodir, S_repo) 1135 print("fatal: cloning the git-repo repository failed, will remove " 1136 "'%s' " % path, file=sys.stderr) 1137 shutil.rmtree(path, ignore_errors=True) 1138 sys.exit(1) 1139 repo_main, rel_repo_dir = _FindRepo() 1140 else: 1141 _NoCommands(cmd) 1142 1143 if my_main: 1144 repo_main = my_main 1145 1146 if not repo_main: 1147 print("fatal: unable to find repo entry point", file=sys.stderr) 1148 sys.exit(1) 1149 1150 ver_str = '.'.join(map(str, VERSION)) 1151 me = [sys.executable, repo_main, 1152 '--repo-dir=%s' % rel_repo_dir, 1153 '--wrapper-version=%s' % ver_str, 1154 '--wrapper-path=%s' % wrapper_path, 1155 '--'] 1156 me.extend(orig_args) 1157 exec_command(me) 1158 print("fatal: unable to start %s" % repo_main, file=sys.stderr) 1159 sys.exit(148) 1160 1161 1162 if __name__ == '__main__': 1163 main(sys.argv[1:])
[ Source navigation ] | [ Diff markup ] | [ Identifier search ] |
本网站由冷钦街制作 主页 |
蜀ICP备2022028677 |