Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file |
| 3 | # for details. All rights reserved. Use of this source code is governed by a |
| 4 | # BSD-style license that can be found in the LICENSE file. |
| 5 | |
| 6 | # Script that automatically pulls and uploads all upstream direct and indirect |
| 7 | # branches into the current branch. |
| 8 | # |
| 9 | # Example: |
| 10 | # |
| 11 | # $ git branch -vv |
| 12 | # * feature_final xxxxxxxxx [feature_prereq_c: ...] ... |
| 13 | # feature_prereq_c xxxxxxxxx [feature_prereq_b: ...] ... |
| 14 | # feature_prereq_b xxxxxxxxx [feature_prereq_a: ...] ... |
| 15 | # feature_prereq_a xxxxxxxxx [master: ...] ... |
| 16 | # master xxxxxxxxx [origin/master] ... |
| 17 | # |
| 18 | # Executing `git_sync_cl_chain.py -m <message>` causes the following chain of |
| 19 | # commands to be executed: |
| 20 | # |
| 21 | # $ git checkout feature_prereq_a; git pull; git cl upload -m <message> |
| 22 | # $ git checkout feature_prereq_b; git pull; git cl upload -m <message> |
| 23 | # $ git checkout feature_prereq_c; git pull; git cl upload -m <message> |
| 24 | # $ git checkout feature_final; git pull; git cl upload -m <message> |
| 25 | |
| 26 | import optparse |
| 27 | import os |
| 28 | import sys |
| 29 | |
| 30 | import defines |
| 31 | import utils |
| 32 | |
| 33 | REPO_ROOT = defines.REPO_ROOT |
| 34 | |
| 35 | class Repo(object): |
| 36 | def __init__(self, name, is_current, upstream): |
| 37 | self.name = name |
| 38 | self.is_current = is_current |
| 39 | self.upstream = upstream |
| 40 | |
| 41 | def ParseOptions(argv): |
| 42 | result = optparse.OptionParser() |
| 43 | result.add_option('--message', '-m', help='Message for patchset') |
| 44 | result.add_option('--rebase', |
| 45 | help='To use `git pull --rebase` instead of `git pull`', |
| 46 | action='store_true') |
Christoffer Quist Adamsen | a821281 | 2019-08-14 15:38:50 +0200 | [diff] [blame] | 47 | result.add_option('--no_upload', '--no-upload', |
| 48 | help='Disable uploading to Gerrit', action='store_true') |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 49 | (options, args) = result.parse_args(argv) |
Christoffer Quist Adamsen | a821281 | 2019-08-14 15:38:50 +0200 | [diff] [blame] | 50 | options.upload = not options.no_upload |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 51 | assert options.message, 'A message for the patchset is required.' |
| 52 | assert len(args) == 0 |
| 53 | return options |
| 54 | |
| 55 | def main(argv): |
| 56 | options = ParseOptions(argv) |
| 57 | rebase_args = ['--rebase'] if options.rebase else [] |
| 58 | with utils.ChangedWorkingDirectory(REPO_ROOT, quiet=True): |
| 59 | branches = [ |
| 60 | parse(line) |
| 61 | for line in utils.RunCmd(['git', 'branch', '-vv'], quiet=True)] |
| 62 | |
| 63 | current_branch = None |
| 64 | for branch in branches: |
| 65 | if branch.is_current: |
| 66 | current_branch = branch |
| 67 | break |
| 68 | assert current_branch is not None |
| 69 | |
| 70 | if current_branch.upstream == None: |
| 71 | print('Nothing to sync') |
| 72 | return |
| 73 | |
| 74 | stack = [] |
Morten Krogh-Jespersen | 5b225f4 | 2019-08-14 15:26:06 +0200 | [diff] [blame] | 75 | while current_branch: |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 76 | stack.append(current_branch) |
| 77 | if current_branch.upstream is None or current_branch.upstream == 'master': |
| 78 | break |
| 79 | current_branch = get_branch_with_name(current_branch.upstream, branches) |
| 80 | |
| 81 | while len(stack) > 0: |
| 82 | branch = stack.pop() |
| 83 | print('Syncing ' + branch.name) |
| 84 | utils.RunCmd(['git', 'checkout', branch.name], quiet=True) |
| 85 | utils.RunCmd(['git', 'pull'] + rebase_args, quiet=True) |
Christoffer Quist Adamsen | a821281 | 2019-08-14 15:38:50 +0200 | [diff] [blame] | 86 | if options.upload: |
| 87 | utils.RunCmd(['git', 'cl', 'upload', '-m', options.message], quiet=True) |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 88 | |
Morten Krogh-Jespersen | c2be3f8 | 2019-08-14 15:30:55 +0200 | [diff] [blame] | 89 | utils.RunCmd(['git', 'cl', 'issue']) |
| 90 | |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 91 | def get_branch_with_name(name, branches): |
| 92 | for branch in branches: |
| 93 | if branch.name == name: |
| 94 | return branch |
| 95 | return None |
| 96 | |
| 97 | # Parses a line from the output of `git branch -vv`. |
| 98 | # |
| 99 | # Example output ('*' denotes the current branch): |
| 100 | # |
| 101 | # $ git branch -vv |
| 102 | # * feature_final xxxxxxxxx [feature_prereq_c: ...] ... |
| 103 | # feature_prereq_c xxxxxxxxx [feature_prereq_b: ...] ... |
| 104 | # feature_prereq_b xxxxxxxxx [feature_prereq_a: ...] ... |
| 105 | # feature_prereq_a xxxxxxxxx [master: ...] ... |
| 106 | # master xxxxxxxxx [origin/master] ... |
| 107 | def parse(line): |
| 108 | is_current = False |
| 109 | if line.startswith('*'): |
| 110 | is_current = True |
| 111 | line = line[1:].lstrip() |
| 112 | else: |
| 113 | line = line.lstrip() |
| 114 | |
| 115 | name_end_index = line.index(' ') |
| 116 | name = line[:name_end_index] |
| 117 | line = line[name_end_index:].lstrip() |
| 118 | |
| 119 | if ('[') not in line or ':' not in line: |
| 120 | return Repo(name, is_current, None) |
| 121 | |
| 122 | upstream_start_index = line.index('[') |
| 123 | line = line[upstream_start_index+1:] |
| 124 | upstream_end_index = line.index(':') |
| 125 | upstream = line[:upstream_end_index] |
| 126 | |
| 127 | return Repo(name, is_current, upstream) |
| 128 | |
| 129 | if __name__ == '__main__': |
| 130 | sys.exit(main(sys.argv[1:])) |