blob: ef10a73ebcfd978b1bf26ebf7aeec701b73c9863 [file] [log] [blame]
#!/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:]))