blob: ef10a73ebcfd978b1bf26ebf7aeec701b73c9863 [file] [log] [blame]
Christoffer Quist Adamsen4bb82e92019-08-14 12:30:34 +02001#!/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
26import optparse
27import os
28import sys
29
30import defines
31import utils
32
33REPO_ROOT = defines.REPO_ROOT
34
35class 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
41def 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 Adamsena8212812019-08-14 15:38:50 +020047 result.add_option('--no_upload', '--no-upload',
48 help='Disable uploading to Gerrit', action='store_true')
Christoffer Quist Adamsen4bb82e92019-08-14 12:30:34 +020049 (options, args) = result.parse_args(argv)
Christoffer Quist Adamsena8212812019-08-14 15:38:50 +020050 options.upload = not options.no_upload
Christoffer Quist Adamsen4bb82e92019-08-14 12:30:34 +020051 assert options.message, 'A message for the patchset is required.'
52 assert len(args) == 0
53 return options
54
55def 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-Jespersen5b225f42019-08-14 15:26:06 +020075 while current_branch:
Christoffer Quist Adamsen4bb82e92019-08-14 12:30:34 +020076 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 Adamsena8212812019-08-14 15:38:50 +020086 if options.upload:
87 utils.RunCmd(['git', 'cl', 'upload', '-m', options.message], quiet=True)
Christoffer Quist Adamsen4bb82e92019-08-14 12:30:34 +020088
Morten Krogh-Jespersenc2be3f82019-08-14 15:30:55 +020089 utils.RunCmd(['git', 'cl', 'issue'])
90
Christoffer Quist Adamsen4bb82e92019-08-14 12:30:34 +020091def 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] ...
107def 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
129if __name__ == '__main__':
130 sys.exit(main(sys.argv[1:]))