|  | #!/usr/bin/env python | 
|  | # Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file | 
|  | # for details. All rights reserved. Use of this source code is governed by a | 
|  | # BSD-style license that can be found in the LICENSE file. | 
|  |  | 
|  | # Script that automatically pulls and uploads all upstream direct and indirect | 
|  | # branches into the current branch. | 
|  | # | 
|  | # Example: | 
|  | # | 
|  | #   $ git branch -vv | 
|  | #   * feature_final    xxxxxxxxx [feature_prereq_c: ...] ... | 
|  | #     feature_prereq_c xxxxxxxxx [feature_prereq_b: ...] ... | 
|  | #     feature_prereq_b xxxxxxxxx [feature_prereq_a: ...] ... | 
|  | #     feature_prereq_a xxxxxxxxx [master: ...] ... | 
|  | #     master           xxxxxxxxx [origin/master] ... | 
|  | # | 
|  | # Executing `git_sync_cl_chain.py -m <message>` causes the following chain of | 
|  | # commands to be executed: | 
|  | # | 
|  | #   $ git checkout feature_prereq_a; git pull; git cl upload -m <message> | 
|  | #   $ git checkout feature_prereq_b; git pull; git cl upload -m <message> | 
|  | #   $ git checkout feature_prereq_c; git pull; git cl upload -m <message> | 
|  | #   $ git checkout feature_final;    git pull; git cl upload -m <message> | 
|  |  | 
|  | import optparse | 
|  | import os | 
|  | import sys | 
|  |  | 
|  | import defines | 
|  | import utils | 
|  |  | 
|  | REPO_ROOT = defines.REPO_ROOT | 
|  |  | 
|  | class Repo(object): | 
|  | def __init__(self, name, is_current, upstream): | 
|  | self.name = name | 
|  | self.is_current = is_current | 
|  | self.upstream = upstream | 
|  |  | 
|  | def ParseOptions(argv): | 
|  | result = optparse.OptionParser() | 
|  | result.add_option('--message', '-m', help='Message for patchset') | 
|  | result.add_option('--rebase', | 
|  | help='To use `git pull --rebase` instead of `git pull`', | 
|  | action='store_true') | 
|  | result.add_option('--no_upload', '--no-upload', | 
|  | help='Disable uploading to Gerrit', action='store_true') | 
|  | (options, args) = result.parse_args(argv) | 
|  | options.upload = not options.no_upload | 
|  | assert options.message, 'A message for the patchset is required.' | 
|  | assert len(args) == 0 | 
|  | return options | 
|  |  | 
|  | def main(argv): | 
|  | options = ParseOptions(argv) | 
|  | rebase_args = ['--rebase'] if options.rebase else [] | 
|  | with utils.ChangedWorkingDirectory(REPO_ROOT, quiet=True): | 
|  | branches = [ | 
|  | parse(line) | 
|  | for line in utils.RunCmd(['git', 'branch', '-vv'], quiet=True)] | 
|  |  | 
|  | current_branch = None | 
|  | for branch in branches: | 
|  | if branch.is_current: | 
|  | current_branch = branch | 
|  | break | 
|  | assert current_branch is not None | 
|  |  | 
|  | if current_branch.upstream == None: | 
|  | print('Nothing to sync') | 
|  | return | 
|  |  | 
|  | stack = [] | 
|  | while current_branch: | 
|  | stack.append(current_branch) | 
|  | if current_branch.upstream is None or current_branch.upstream == 'master': | 
|  | break | 
|  | current_branch = get_branch_with_name(current_branch.upstream, branches) | 
|  |  | 
|  | while len(stack) > 0: | 
|  | branch = stack.pop() | 
|  | print('Syncing ' + branch.name) | 
|  | utils.RunCmd(['git', 'checkout', branch.name], quiet=True) | 
|  | utils.RunCmd(['git', 'pull'] + rebase_args, quiet=True) | 
|  | if options.upload: | 
|  | utils.RunCmd(['git', 'cl', 'upload', '-m', options.message], quiet=True) | 
|  |  | 
|  | utils.RunCmd(['git', 'cl', 'issue']) | 
|  |  | 
|  | def get_branch_with_name(name, branches): | 
|  | for branch in branches: | 
|  | if branch.name == name: | 
|  | return branch | 
|  | return None | 
|  |  | 
|  | # Parses a line from the output of `git branch -vv`. | 
|  | # | 
|  | # Example output ('*' denotes the current branch): | 
|  | # | 
|  | #   $ git branch -vv | 
|  | #   * feature_final    xxxxxxxxx [feature_prereq_c: ...] ... | 
|  | #     feature_prereq_c xxxxxxxxx [feature_prereq_b: ...] ... | 
|  | #     feature_prereq_b xxxxxxxxx [feature_prereq_a: ...] ... | 
|  | #     feature_prereq_a xxxxxxxxx [master: ...] ... | 
|  | #     master           xxxxxxxxx [origin/master] ... | 
|  | def parse(line): | 
|  | is_current = False | 
|  | if line.startswith('*'): | 
|  | is_current = True | 
|  | line = line[1:].lstrip() | 
|  | else: | 
|  | line = line.lstrip() | 
|  |  | 
|  | name_end_index = line.index(' ') | 
|  | name = line[:name_end_index] | 
|  | line = line[name_end_index:].lstrip() | 
|  |  | 
|  | if ('[') not in line or ':' not in line: | 
|  | return Repo(name, is_current, None) | 
|  |  | 
|  | upstream_start_index = line.index('[') | 
|  | line = line[upstream_start_index+1:] | 
|  | upstream_end_index = line.index(':') | 
|  | upstream = line[:upstream_end_index] | 
|  |  | 
|  | return Repo(name, is_current, upstream) | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | sys.exit(main(sys.argv[1:])) |