+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""Wrapper for syncing CrOS code.
+
+Actions:
+ depot_tools clone depot_tools tree
+ int switch to internal tree
+ ext switch to external tree
+
+Operates on the repo in the cwd.
+"""
+
+import argparse
+import fnmatch
+import os
+import subprocess
+import sys
+
+
+REPO_URL = 'https://chromium.googlesource.com/external/repo'
+INT_MANIFEST = 'https://chrome-internal.googlesource.com/chromeos/manifest-internal'
+EXT_MANIFEST = 'https://chromium.googlesource.com/chromiumos/manifest'
+
+
+def run(cmd, **kwargs):
+ """Run with command tracing."""
+ # Python 3.6 doesn't support capture_output.
+ if sys.version_info < (3, 7):
+ capture_output = kwargs.pop('capture_output', None)
+ if capture_output:
+ assert 'stdout' not in kwargs and 'stderr' not in kwargs
+ kwargs['stdout'] = subprocess.PIPE
+ kwargs['stderr'] = subprocess.PIPE
+
+ print(kwargs.get('cwd', os.getcwd()))
+ print(' '.join(cmd))
+ return subprocess.run(cmd, **kwargs)
+
+
+def expand_branch(opts):
+ """Support branch shortnames."""
+ url = INT_MANIFEST if opts.action == 'int' else EXT_MANIFEST
+ ret = run(['git', 'ls-remote', url, 'refs/heads/*'], capture_output=True, encoding='utf-8')
+ branches = [x.split()[-1].split('/')[-1] for x in ret.stdout.splitlines()]
+ ret = []
+ for branch in branches:
+ if branch.lower() == opts.branch.lower():
+ ret = [branch]
+ break
+ elif fnmatch.fnmatch(branch.lower(), f'*{opts.branch.lower()}*'):
+ ret.append(branch)
+ if not ret:
+ print(f'error: could not match branch {opts.branch}', file=sys.stderr)
+ print(f'Available branches:\n{" ".join(sorted(branches))}', file=sys.stderr)
+ sys.exit(1)
+ if len(ret) > 1:
+ print(f'error: too many branches match {opts.branch}:\n{" ".join(sorted(ret))}',
+ file=sys.stderr)
+ sys.exit(1)
+ print(f'Expanded branch {opts.branch} into {ret[0]}')
+ return ret[0]
+
+
+def get_repo_topdir(opts):
+ """Find the top dir of this repo client checkout."""
+ topdir = os.getcwd()
+ while True:
+ rdir = os.path.join(topdir, '.repo')
+ if os.path.exists(rdir):
+ break
+ topdir = os.path.dirname(topdir)
+ assert topdir != '/'
+ return topdir
+
+
+def set_git_config(opts):
+ """Set .git/config settings in all the repos."""
+ topdir = get_repo_topdir(opts)
+ rdir = os.path.join(topdir, '.repo')
+
+ def gcfg(path, *args):
+ cmd = ['git', f'--git-dir={path}', 'config', 'user.email'] + list(args)
+ ret = run(cmd, capture_output=True, encoding='utf-8')
+ return ret.stdout.strip()
+
+ current_email = gcfg(os.path.join(rdir, 'manifests.git'))
+ if current_email == opts.email:
+ return
+
+ print(f'Setting e-mail to {opts.email}')
+ for root, dirs, files in os.walk(rdir):
+ if root.endswith('.git') and not os.path.islink(os.path.join(root, 'config')):
+ gcfg(root, opts.email)
+ del dirs[:]
+
+
+def init_repo(opts):
+ """Initialize CrOS checkout."""
+ cmd = ['repo', 'init']
+
+ if opts.action == 'int':
+ cmd += ['-u', INT_MANIFEST]
+ cmd += ['--repo-url', REPO_URL]
+ elif opts.action == 'ext':
+ cmd += ['-u', EXT_MANIFEST]
+ cmd += ['--repo-url', REPO_URL]
+
+ if opts.ref and os.path.realpath(opts.ref) != os.path.realpath(get_repo_topdir(opts)):
+ cmd += ['--reference', opts.ref]
+
+ if opts.manifest:
+ cmd += ['-m', opts.manifest]
+
+ if opts.group:
+ cmd += ['-g', opts.group]
+
+ if opts.branch:
+ branch = expand_branch(opts)
+ cmd += ['-b', branch]
+
+ ret = run(cmd)
+ if ret.returncode:
+ return ret.returncode
+
+ set_git_config(opts)
+
+ print('\ncros-repo: all done')
+ return 0
+
+
+def clone_depot_tools():
+ """Clone depot_tools repo."""
+ ret = run(['git', 'clone', 'https://chromium.googlesource.com/chromium/tools/depot_tools'])
+ return ret.returncode
+
+
+def get_parser():
+ """Get command line parser."""
+ parser = argparse.ArgumentParser(
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument('-b', '--branch',
+ help='Switch branches (use "master" to get to ToT)')
+ parser.add_argument('-r', '--ref', '--reference', default='~/chromiumos/',
+ help='Patch to reference repo (default: %(default)s)')
+ parser.add_argument('-g', '--group',
+ help='Manifest group to use (e.g. "minilayout")')
+ parser.add_argument('-m', '--manifest',
+ help='Manifest file name to use (e.g. "full.xml")')
+ parser.add_argument('-e', '--email', default='vapier@chromium.org',
+ help='E-mail address to force (default: %(default)s)')
+ parser.add_argument('action', nargs='?',
+ choices={'depot_tools', 'dt', 'int', 'ext'},
+ help='What to do!')
+ return parser
+
+
+def main(argv):
+ """The main func."""
+ parser = get_parser()
+ opts = parser.parse_args(argv)
+ if opts.manifest:
+ if '/' in opts.manifest:
+ parser.error('manifest should be basename like "full.xml"')
+ if opts.manifest.endswith('.xml'):
+ opts.manifest = opts.manifest[:-4]
+ opts.manifest += '.xml'
+ if opts.ref:
+ opts.ref = os.path.expanduser(opts.ref)
+
+ if opts.action in {'depot_tools', 'dt'}:
+ return clone_depot_tools()
+ else:
+ return init_repo(opts)
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1:]))