OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/python | |
2 # Copyright (c) 2014 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 import logging | |
Ryan Tseng
2014/02/28 21:22:05
2 lines before line 5 + sort
| |
6 import sys | |
7 import collections | |
8 | |
9 from pprint import pformat | |
10 | |
11 from subprocess2 import CalledProcessError | |
12 | |
13 from git_squash_branch import squash | |
14 | |
agable
2014/02/28 20:14:16
No newline here, and put git_squash_branch bellow
| |
15 from git_common import run, root, upstream | |
16 from git_common import branches, current_branch, get_or_create_merge_base_tag | |
17 from git_common import clean_refs, hash_one | |
18 | |
19 RebaseRet = collections.namedtuple('TryRebaseRet', 'success message') | |
20 | |
21 | |
22 def bclean(): | |
Ryan Tseng
2014/02/28 21:22:05
What does this stand for?
| |
23 merged = list(branches('--merged', root())) | |
24 | |
25 logging.debug('merged: %s', merged) | |
26 | |
27 upstreams = {} | |
28 downstreams = collections.defaultdict(list) | |
29 for branch in branches(): | |
30 try: | |
31 parent = upstream(branch) | |
32 upstreams[branch] = parent | |
33 downstreams[parent].append(branch) | |
34 except CalledProcessError: | |
35 pass | |
36 | |
37 logging.debug('upstreams: %s', upstreams) | |
38 logging.debug('downstreams: %s', downstreams) | |
39 | |
40 if current_branch() in merged: | |
41 run('checkout', root()) | |
42 for branch in merged: | |
43 for down in downstreams[branch]: | |
44 if down not in merged: | |
45 run('branch', '--set-upstream-to', upstreams[branch], down) | |
46 print ('Reparented %s to track %s (was tracking %s)' | |
47 % (down, upstreams[branch], branch)) | |
48 print run('branch', '-d', branch) | |
49 | |
50 return 0 | |
51 | |
52 | |
53 def rebase(parent, start, branch, abort=False): | |
54 try: | |
55 run('rebase', '--onto', parent, start, branch) | |
56 return RebaseRet(True, '') | |
57 except CalledProcessError as cpe: | |
58 if abort: | |
59 run('rebase', '--abort') | |
60 return RebaseRet(False, cpe.output) | |
61 | |
62 | |
63 def clean_branch(branch, parent, starting_ref): | |
64 if (hash_one(parent) != hash_one(starting_ref) | |
agable
2014/02/28 20:14:16
Main line of code should be at top level. Reverse
| |
65 and hash_one(branch) != hash_one(starting_ref)): | |
66 print 'Rebasing:', branch | |
67 | |
68 # Try a plain rebase first | |
69 if rebase(parent, starting_ref, branch, abort=True).success: | |
70 return | |
71 | |
72 # Maybe squashing will help? | |
73 print "Failed! Attempting to squash", branch, "...", | |
74 squash_branch = branch+"_squash_attempt" | |
75 run('checkout', '-b', squash_branch) | |
76 squash() | |
77 | |
78 squash_ret = rebase(parent, starting_ref, squash_branch, abort=True) | |
79 run('checkout', branch) | |
80 run('branch', '-D', squash_branch) | |
81 | |
82 if squash_ret.success: | |
83 print 'Success!' | |
84 squash() | |
85 final_rebase = rebase(parent, starting_ref, branch) | |
86 assert final_rebase.success == squash_ret.success | |
agable
2014/02/28 20:14:16
Add error message here.
| |
87 | |
88 if not squash_ret.success: | |
89 print squash_ret.message | |
90 print 'Failure :(' | |
91 print 'Your working copy is in mid-rebase. Please completely resolve and' | |
Ryan Tseng
2014/02/28 21:22:05
Does this leave HEAD mid rebase?
I think we'll nee
iannucci
2014/03/20 10:59:37
It also prints the standard git rebase boilerplate
| |
92 print 'run `git reup` again.' | |
93 sys.exit(1) | |
94 | |
95 | |
96 def main(): | |
97 # TODO(iannucci): use argparse | |
agable
2014/02/28 20:14:16
Probably do this now (less important than the othe
Ryan Tseng
2014/02/28 21:22:05
We at least need a usage string behind --help. "r
| |
98 # TODO(iannucci): have a way to set log level | |
99 if '--clean' in sys.argv: | |
100 clean_refs() | |
101 return 0 | |
102 | |
103 # TODO(iannucci): check for a clean working dir | |
104 # TODO(iannucci): Tag merge bases more intelligently | |
105 # TODO(iannucci): Find collapsible branches in a smarter way | |
106 | |
107 # TODO(iannucci): Allow for root() to be a tag | |
108 # * update all remotes + tags | |
109 # * rebase to tag instead of to branch | |
110 # * when creating a new branch, use the tag as the ref | |
111 # * still need 'upstream' to be origin/master? | |
112 # * configurable? | |
113 | |
114 orig_branch = current_branch() | |
115 if orig_branch == 'HEAD': | |
116 orig_branch = hash_one('HEAD') | |
117 | |
118 if 'origin' in run('remote').splitlines(): | |
Ryan Tseng
2014/02/28 21:22:05
splitlines() is probably unnecessary.
| |
119 run('fetch', 'origin', stderr=None) | |
Ryan Tseng
2014/02/28 21:22:05
This might take a while, can we stream the output?
| |
120 else: | |
121 run('svn', 'fetch', stderr=None) | |
122 branch_tree = {} | |
123 for branch in branches(): | |
124 parent = upstream(branch) | |
125 if not parent: | |
126 print 'Skipping %s: No upstream specified' % branch | |
127 continue | |
128 branch_tree[branch] = parent | |
129 | |
130 starting_refs = {} | |
131 for branch, parent in branch_tree.iteritems(): | |
132 starting_refs[branch] = get_or_create_merge_base_tag(branch, parent) | |
133 | |
134 logging.debug('branch_tree: %s', pformat(branch_tree)) | |
135 logging.debug('starting_refs: %s', pformat(starting_refs)) | |
136 | |
137 # XXX There is a more efficient way to do this, but for now... | |
Ryan Tseng
2014/02/28 21:22:05
XXX?
| |
138 while branch_tree: | |
139 this_pass = [i for i in branch_tree.items() if i[1] not in branch_tree] | |
140 for branch, parent in this_pass: | |
141 clean_branch(branch, parent, starting_refs[branch]) | |
142 del branch_tree[branch] | |
143 | |
144 clean_refs() | |
145 | |
146 bclean() | |
147 | |
148 if orig_branch in branches(): | |
149 run('checkout', orig_branch) | |
150 else: | |
151 run('checkout', root()) | |
152 | |
153 return 0 | |
154 | |
155 | |
156 if __name__ == '__main__': | |
157 sys.exit(main()) | |
OLD | NEW |