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: ...] ... |
Rico Wind | 1b52acf | 2021-03-21 12:36:55 +0100 | [diff] [blame] | 15 | # feature_prereq_a xxxxxxxxx [main: ...] ... |
| 16 | # main xxxxxxxxx [origin/main] ... |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 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() |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 43 | result.add_option('--delete', '-d', |
| 44 | help='Delete closed branches', |
| 45 | choices=['y', 'n', 'ask'], |
| 46 | default='ask') |
| 47 | result.add_option('--leave_upstream', '--leave-upstream', |
| 48 | help='To not update the upstream of the first open branch', |
| 49 | action='store_true') |
| 50 | result.add_option('--message', '-m', |
| 51 | help='Message for patchset', default='Sync') |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 52 | result.add_option('--rebase', |
| 53 | help='To use `git pull --rebase` instead of `git pull`', |
| 54 | action='store_true') |
Christoffer Quist Adamsen | a821281 | 2019-08-14 15:38:50 +0200 | [diff] [blame] | 55 | result.add_option('--no_upload', '--no-upload', |
| 56 | help='Disable uploading to Gerrit', action='store_true') |
Rico Wind | 1b52acf | 2021-03-21 12:36:55 +0100 | [diff] [blame] | 57 | result.add_option('--skip_main', '--skip-main', |
| 58 | help='Disable syncing for main', |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 59 | action='store_true') |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 60 | (options, args) = result.parse_args(argv) |
Christoffer Quist Adamsen | a821281 | 2019-08-14 15:38:50 +0200 | [diff] [blame] | 61 | options.upload = not options.no_upload |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 62 | assert options.delete != 'y' or not options.leave_upstream, ( |
| 63 | 'Inconsistent options: cannot leave the upstream of the first open ' + |
| 64 | 'branch (--leave_upstream) and delete the closed branches at the same ' + |
| 65 | 'time (--delete).') |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 66 | assert options.message, 'A message for the patchset is required.' |
| 67 | assert len(args) == 0 |
| 68 | return options |
| 69 | |
| 70 | def main(argv): |
| 71 | options = ParseOptions(argv) |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 72 | with utils.ChangedWorkingDirectory(REPO_ROOT, quiet=True): |
| 73 | branches = [ |
| 74 | parse(line) |
| 75 | for line in utils.RunCmd(['git', 'branch', '-vv'], quiet=True)] |
| 76 | |
| 77 | current_branch = None |
| 78 | for branch in branches: |
| 79 | if branch.is_current: |
| 80 | current_branch = branch |
| 81 | break |
| 82 | assert current_branch is not None |
| 83 | |
| 84 | if current_branch.upstream == None: |
| 85 | print('Nothing to sync') |
| 86 | return |
| 87 | |
| 88 | stack = [] |
Morten Krogh-Jespersen | 5b225f4 | 2019-08-14 15:26:06 +0200 | [diff] [blame] | 89 | while current_branch: |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 90 | stack.append(current_branch) |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 91 | if current_branch.upstream is None: |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 92 | break |
| 93 | current_branch = get_branch_with_name(current_branch.upstream, branches) |
| 94 | |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 95 | closed_branches = [] |
| 96 | has_seen_local_branch = False # A branch that is not uploaded. |
| 97 | has_seen_open_branch = False # A branch that is not closed. |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 98 | while len(stack) > 0: |
| 99 | branch = stack.pop() |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 100 | |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 101 | utils.RunCmd(['git', 'checkout', branch.name], quiet=True) |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 102 | |
| 103 | status = get_status_for_current_branch() |
| 104 | print('Syncing %s (status: %s)' % (branch.name, status)) |
| 105 | |
| 106 | pull_for_current_branch(branch, options) |
| 107 | |
Rico Wind | 1b52acf | 2021-03-21 12:36:55 +0100 | [diff] [blame] | 108 | if branch.name == 'main': |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 109 | continue |
| 110 | |
| 111 | if status == 'closed': |
| 112 | assert not has_seen_local_branch, ( |
| 113 | 'Unexpected closed branch %s after new branch' % branch.name) |
| 114 | assert not has_seen_open_branch, ( |
| 115 | 'Unexpected closed branch %s after open branch' % branch.name) |
| 116 | closed_branches.append(branch.name) |
| 117 | continue |
| 118 | |
| 119 | if not options.leave_upstream: |
| 120 | if not has_seen_open_branch and len(closed_branches) > 0: |
| 121 | print( |
Rico Wind | 1b52acf | 2021-03-21 12:36:55 +0100 | [diff] [blame] | 122 | 'Setting upstream for first open branch %s to main' |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 123 | % branch.name) |
Rico Wind | 1b52acf | 2021-03-21 12:36:55 +0100 | [diff] [blame] | 124 | set_upstream_for_current_branch_to_main() |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 125 | |
| 126 | has_seen_open_branch = True |
| 127 | has_seen_local_branch = has_seen_local_branch or (status == 'None') |
| 128 | |
Morten Krogh-Jespersen | 25b512f | 2020-11-10 11:52:26 +0100 | [diff] [blame] | 129 | if options.upload and status != 'closed': |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 130 | if has_seen_local_branch: |
| 131 | print( |
| 132 | 'Cannot upload branch %s since it comes after a local branch' |
| 133 | % branch.name) |
| 134 | else: |
| 135 | utils.RunCmd( |
| 136 | ['git', 'cl', 'upload', '-m', options.message], quiet=True) |
| 137 | |
Christoffer Quist Adamsen | d323044 | 2020-11-06 11:12:14 +0100 | [diff] [blame] | 138 | if get_delete_branches_option(closed_branches, options): |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 139 | delete_branches(closed_branches) |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 140 | |
Morten Krogh-Jespersen | c2be3f8 | 2019-08-14 15:30:55 +0200 | [diff] [blame] | 141 | utils.RunCmd(['git', 'cl', 'issue']) |
| 142 | |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 143 | def delete_branches(branches): |
| 144 | assert len(branches) > 0 |
Christoffer Quist Adamsen | 601aa3c | 2020-11-27 14:50:31 +0100 | [diff] [blame] | 145 | cmd = ['git', 'branch', '-D'] |
| 146 | cmd.extend(branches) |
| 147 | utils.RunCmd(cmd, quiet=True) |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 148 | |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 149 | def get_branch_with_name(name, branches): |
| 150 | for branch in branches: |
| 151 | if branch.name == name: |
| 152 | return branch |
| 153 | return None |
| 154 | |
Christoffer Quist Adamsen | d323044 | 2020-11-06 11:12:14 +0100 | [diff] [blame] | 155 | def get_delete_branches_option(closed_branches, options): |
| 156 | if len(closed_branches) == 0: |
| 157 | return False |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 158 | if options.leave_upstream: |
| 159 | return False |
| 160 | if options.delete == 'y': |
| 161 | return True |
| 162 | if options.delete == 'n': |
| 163 | return False |
| 164 | assert options.delete == 'ask' |
| 165 | print('Delete closed branches: %s (Y/N)?' % ", ".join(closed_branches)) |
| 166 | answer = sys.stdin.read(1) |
| 167 | return answer.lower() == 'y' |
| 168 | |
| 169 | def get_status_for_current_branch(): |
| 170 | return utils.RunCmd(['git', 'cl', 'status', '--field', 'status'], quiet=True)[0].strip() |
| 171 | |
| 172 | def pull_for_current_branch(branch, options): |
Rico Wind | 1b52acf | 2021-03-21 12:36:55 +0100 | [diff] [blame] | 173 | if branch.name == 'main' and options.skip_main: |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 174 | return |
| 175 | rebase_args = ['--rebase'] if options.rebase else [] |
| 176 | utils.RunCmd(['git', 'pull'] + rebase_args, quiet=True) |
| 177 | |
| 178 | |
Rico Wind | 1b52acf | 2021-03-21 12:36:55 +0100 | [diff] [blame] | 179 | def set_upstream_for_current_branch_to_main(): |
| 180 | utils.RunCmd(['git', 'cl', 'upstream', 'main'], quiet=True) |
Christoffer Quist Adamsen | 6fac89e | 2020-11-06 11:03:09 +0100 | [diff] [blame] | 181 | |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 182 | # Parses a line from the output of `git branch -vv`. |
| 183 | # |
| 184 | # Example output ('*' denotes the current branch): |
| 185 | # |
| 186 | # $ git branch -vv |
| 187 | # * feature_final xxxxxxxxx [feature_prereq_c: ...] ... |
| 188 | # feature_prereq_c xxxxxxxxx [feature_prereq_b: ...] ... |
| 189 | # feature_prereq_b xxxxxxxxx [feature_prereq_a: ...] ... |
Rico Wind | 1b52acf | 2021-03-21 12:36:55 +0100 | [diff] [blame] | 190 | # feature_prereq_a xxxxxxxxx [main: ...] ... |
| 191 | # main xxxxxxxxx [origin/main] ... |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 192 | def parse(line): |
| 193 | is_current = False |
| 194 | if line.startswith('*'): |
| 195 | is_current = True |
| 196 | line = line[1:].lstrip() |
| 197 | else: |
| 198 | line = line.lstrip() |
| 199 | |
| 200 | name_end_index = line.index(' ') |
| 201 | name = line[:name_end_index] |
| 202 | line = line[name_end_index:].lstrip() |
| 203 | |
Christoffer Quist Adamsen | 7a5deed | 2020-11-16 11:31:15 +0100 | [diff] [blame] | 204 | if '[' in line: |
| 205 | line = line[line.index('[')+1:] |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 206 | |
Christoffer Quist Adamsen | 7a5deed | 2020-11-16 11:31:15 +0100 | [diff] [blame] | 207 | if ':' in line: |
| 208 | upstream = line[:line.index(':')] |
| 209 | return Repo(name, is_current, upstream) |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 210 | |
Christoffer Quist Adamsen | 7a5deed | 2020-11-16 11:31:15 +0100 | [diff] [blame] | 211 | if ']' in line: |
| 212 | upstream = line[:line.index(']')] |
| 213 | return Repo(name, is_current, upstream) |
| 214 | |
| 215 | return Repo(name, is_current, None) |
Christoffer Quist Adamsen | 4bb82e9 | 2019-08-14 12:30:34 +0200 | [diff] [blame] | 216 | |
| 217 | if __name__ == '__main__': |
| 218 | sys.exit(main(sys.argv[1:])) |