Script for syncing all branches in a chain of CLs
Change-Id: I1852e22b043ce2c8ff4634d725157731686742ce
diff --git a/tools/git_sync_cl_chain.py b/tools/git_sync_cl_chain.py
new file mode 100755
index 0000000..97dcfb7
--- /dev/null
+++ b/tools/git_sync_cl_chain.py
@@ -0,0 +1,125 @@
+#!/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')
+ (options, args) = result.parse_args(argv)
+ print(options)
+ 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 True:
+ 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)
+ utils.RunCmd(['git', 'cl', 'upload', '-m', options.message], quiet=True)
+
+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:]))