diff --git a/tools/repo_pull/README.md b/tools/repo_pull/README.md index 1fa0dffd6..c4723ea41 100644 --- a/tools/repo_pull/README.md +++ b/tools/repo_pull/README.md @@ -72,8 +72,8 @@ These are common queries: * `-g` or `--gerrit` specifies the URL of the Gerrit Code Review website. *(required)* -* `-b` or `--branch` specifies the local topic branch name that will be passed - to `repo start`. +* `-b` or `--branch` specifies the local branch name that will be passed to + `repo start`. * `-j` or `--parallel` specifies the number of parallel threads while pulling change lists. @@ -81,6 +81,19 @@ These are common queries: * `-n` or `--limits` specifies the maximum number of change lists. (default: 1000) +* `-m` or `--merge` specifies the method to pick the merge commits. (default: + `merge-ff-only`) + +* `-p` or `--pick` specifies the method to pick the non-merge commits. + (default: `pick`) + + * `pick` maps to `git cherry-pick --allow-empty` + * `merge` maps to `git merge --no-edit` + * `merge-ff-only` maps to `git merge --no-edit --ff-only` + * `merge-no-ff` maps to `git merge --no-edit --no-ff` + * `reset` maps to `git reset --hard` + * `checkout` maps to `git checkout` + ## Examples diff --git a/tools/repo_pull/repo_pull.py b/tools/repo_pull/repo_pull.py index 5a04d5543..3c40da644 100755 --- a/tools/repo_pull/repo_pull.py +++ b/tools/repo_pull/repo_pull.py @@ -40,6 +40,11 @@ try: except ImportError: from urllib2 import HTTPBasicAuthHandler, build_opener # PY2 +try: + from __builtin__ import raw_input as input # PY2 +except ImportError: + pass + try: from shlex import quote as _sh_quote # PY3.3 except ImportError: @@ -96,6 +101,18 @@ else: file.buffer.write(data) +def _confirm(question, default, file=sys.stderr): + """Prompt a yes/no question and convert the answer to a boolean value.""" + answers = {'': default, 'y': True, 'yes': True, 'n': False, 'no': False} + suffix = '[Y/n] ' if default else ' [y/N] ' + while True: + file.write(question + suffix) + file.flush() + ans = answers.get(input().lower()) + if ans is not None: + return ans + + class ChangeList(object): """A ChangeList to be checked out.""" @@ -260,20 +277,33 @@ _MERGE_COMMANDS = { 'merge-ff-only': ['git', 'merge', '--no-edit', '--ff-only'], 'merge-no-ff': ['git', 'merge', '--no-edit', '--no-ff'], 'reset': ['git', 'reset', '--hard'], + 'checkout': ['git', 'checkout'], } -def build_pull_commands(change, branch_name, merge_opt): +# Git commands for non-merge commits +_PICK_COMMANDS = { + 'pick': ['git', 'cherry-pick', '--allow-empty'], + 'merge': ['git', 'merge', '--no-edit'], + 'merge-ff-only': ['git', 'merge', '--no-edit', '--ff-only'], + 'merge-no-ff': ['git', 'merge', '--no-edit', '--no-ff'], + 'reset': ['git', 'reset', '--hard'], + 'checkout': ['git', 'checkout'], +} + + +def build_pull_commands(change, branch_name, merge_opt, pick_opt): """Build command lines for each change. The command lines will be passed to subprocess.run().""" cmds = [] - cmds.append(['repo', 'start', branch_name]) + if branch_name is not None: + cmds.append(['repo', 'start', branch_name]) cmds.append(['git', 'fetch', change.fetch_url, change.fetch_ref]) if change.is_merge(): cmds.append(_MERGE_COMMANDS[merge_opt] + ['FETCH_HEAD']) else: - cmds.append(['git', 'cherry-pick', '--allow-empty', 'FETCH_HEAD']) + cmds.append(_PICK_COMMANDS[pick_opt] + ['FETCH_HEAD']) return cmds @@ -292,7 +322,7 @@ def _sh_quote_commands(cmds): def _main_bash(args): """Print the bash command to pull the change lists.""" - branch_name = _get_topic_branch_from_args(args) + branch_name = _get_local_branch_name_from_args(args) manifest_path = _get_manifest_xml_from_args(args) project_dirs = build_project_name_to_directory_dict(manifest_path) @@ -304,7 +334,8 @@ def _main_bash(args): for change in changes: cmds = [] cmds.append(['pushd', change.project_dir]) - cmds.extend(build_pull_commands(change, branch_name, args.merge)) + cmds.extend(build_pull_commands( + change, branch_name, args.merge, args.pick)) cmds.append(['popd']) print(_sh_quote_commands(cmds)) @@ -315,11 +346,13 @@ def _do_pull_change_lists_for_project(task): branch_name = task_opts['branch_name'] merge_opt = task_opts['merge_opt'] + pick_opt = task_opts['pick_opt'] for i, change in enumerate(changes): cwd = change.project_dir print(change.commit_sha1[0:10], i + 1, cwd) - for cmd in build_pull_commands(change, branch_name, merge_opt): + cmds = build_pull_commands(change, branch_name, merge_opt, pick_opt) + for cmd in cmds: proc = run(cmd, cwd=cwd, stderr=PIPE) if proc.returncode != 0: return (change, changes[i + 1:], cmd, proc.stderr) @@ -329,7 +362,7 @@ def _do_pull_change_lists_for_project(task): def _main_pull(args): """Pull the change lists.""" - branch_name = _get_topic_branch_from_args(args) + branch_name = _get_local_branch_name_from_args(args) manifest_path = _get_manifest_xml_from_args(args) project_dirs = build_project_name_to_directory_dict(manifest_path) @@ -342,6 +375,7 @@ def _main_pull(args): task_opts = { 'branch_name': branch_name, 'merge_opt': args.merge, + 'pick_opt': args.pick, } # Run the commands to pull the change lists @@ -391,12 +425,17 @@ def _parse_args(): help='Max number of change lists') parser.add_argument('-m', '--merge', - choices=_MERGE_COMMANDS.keys(), + choices=sorted(_MERGE_COMMANDS.keys()), default='merge-ff-only', help='Method to pull merge commits') + parser.add_argument('-p', '--pick', + choices=sorted(_PICK_COMMANDS.keys()), + default='pick', + help='Method to pull merge commits') + parser.add_argument('-b', '--branch', - help='Topic branch name to start with') + help='Local branch name for `repo start`') parser.add_argument('-j', '--parallel', default=1, type=int, help='Number of parallel running commands') @@ -418,10 +457,11 @@ def _get_change_lists_from_args(args): args.limits) -def _get_topic_branch_from_args(args): - """Get the topic branch name from args.""" - if not args.branch: - print('error: --branch must be specified') +def _get_local_branch_name_from_args(args): + """Get the local branch name from args.""" + if not args.branch and not _confirm( + 'Do you want to continue without local branch name?', False): + print('error: `-b` or `--branch` must be specified', file=sys.stderr) sys.exit(1) return args.branch