Index: git_rebase_update.py |
diff --git a/git_rebase_update.py b/git_rebase_update.py |
new file mode 100755 |
index 0000000000000000000000000000000000000000..c2b07cf44eba18c9e59cda9ab69ea6d7868e5c01 |
--- /dev/null |
+++ b/git_rebase_update.py |
@@ -0,0 +1,157 @@ |
+#!/usr/bin/python |
+# Copyright (c) 2014 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+import logging |
Ryan Tseng
2014/02/28 21:22:05
2 lines before line 5 + sort
|
+import sys |
+import collections |
+ |
+from pprint import pformat |
+ |
+from subprocess2 import CalledProcessError |
+ |
+from git_squash_branch import squash |
+ |
agable
2014/02/28 20:14:16
No newline here, and put git_squash_branch bellow
|
+from git_common import run, root, upstream |
+from git_common import branches, current_branch, get_or_create_merge_base_tag |
+from git_common import clean_refs, hash_one |
+ |
+RebaseRet = collections.namedtuple('TryRebaseRet', 'success message') |
+ |
+ |
+def bclean(): |
Ryan Tseng
2014/02/28 21:22:05
What does this stand for?
|
+ merged = list(branches('--merged', root())) |
+ |
+ logging.debug('merged: %s', merged) |
+ |
+ upstreams = {} |
+ downstreams = collections.defaultdict(list) |
+ for branch in branches(): |
+ try: |
+ parent = upstream(branch) |
+ upstreams[branch] = parent |
+ downstreams[parent].append(branch) |
+ except CalledProcessError: |
+ pass |
+ |
+ logging.debug('upstreams: %s', upstreams) |
+ logging.debug('downstreams: %s', downstreams) |
+ |
+ if current_branch() in merged: |
+ run('checkout', root()) |
+ for branch in merged: |
+ for down in downstreams[branch]: |
+ if down not in merged: |
+ run('branch', '--set-upstream-to', upstreams[branch], down) |
+ print ('Reparented %s to track %s (was tracking %s)' |
+ % (down, upstreams[branch], branch)) |
+ print run('branch', '-d', branch) |
+ |
+ return 0 |
+ |
+ |
+def rebase(parent, start, branch, abort=False): |
+ try: |
+ run('rebase', '--onto', parent, start, branch) |
+ return RebaseRet(True, '') |
+ except CalledProcessError as cpe: |
+ if abort: |
+ run('rebase', '--abort') |
+ return RebaseRet(False, cpe.output) |
+ |
+ |
+def clean_branch(branch, parent, starting_ref): |
+ 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
|
+ and hash_one(branch) != hash_one(starting_ref)): |
+ print 'Rebasing:', branch |
+ |
+ # Try a plain rebase first |
+ if rebase(parent, starting_ref, branch, abort=True).success: |
+ return |
+ |
+ # Maybe squashing will help? |
+ print "Failed! Attempting to squash", branch, "...", |
+ squash_branch = branch+"_squash_attempt" |
+ run('checkout', '-b', squash_branch) |
+ squash() |
+ |
+ squash_ret = rebase(parent, starting_ref, squash_branch, abort=True) |
+ run('checkout', branch) |
+ run('branch', '-D', squash_branch) |
+ |
+ if squash_ret.success: |
+ print 'Success!' |
+ squash() |
+ final_rebase = rebase(parent, starting_ref, branch) |
+ assert final_rebase.success == squash_ret.success |
agable
2014/02/28 20:14:16
Add error message here.
|
+ |
+ if not squash_ret.success: |
+ print squash_ret.message |
+ print 'Failure :(' |
+ 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
|
+ print 'run `git reup` again.' |
+ sys.exit(1) |
+ |
+ |
+def main(): |
+ # 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
|
+ # TODO(iannucci): have a way to set log level |
+ if '--clean' in sys.argv: |
+ clean_refs() |
+ return 0 |
+ |
+ # TODO(iannucci): check for a clean working dir |
+ # TODO(iannucci): Tag merge bases more intelligently |
+ # TODO(iannucci): Find collapsible branches in a smarter way |
+ |
+ # TODO(iannucci): Allow for root() to be a tag |
+ # * update all remotes + tags |
+ # * rebase to tag instead of to branch |
+ # * when creating a new branch, use the tag as the ref |
+ # * still need 'upstream' to be origin/master? |
+ # * configurable? |
+ |
+ orig_branch = current_branch() |
+ if orig_branch == 'HEAD': |
+ orig_branch = hash_one('HEAD') |
+ |
+ if 'origin' in run('remote').splitlines(): |
Ryan Tseng
2014/02/28 21:22:05
splitlines() is probably unnecessary.
|
+ run('fetch', 'origin', stderr=None) |
Ryan Tseng
2014/02/28 21:22:05
This might take a while, can we stream the output?
|
+ else: |
+ run('svn', 'fetch', stderr=None) |
+ branch_tree = {} |
+ for branch in branches(): |
+ parent = upstream(branch) |
+ if not parent: |
+ print 'Skipping %s: No upstream specified' % branch |
+ continue |
+ branch_tree[branch] = parent |
+ |
+ starting_refs = {} |
+ for branch, parent in branch_tree.iteritems(): |
+ starting_refs[branch] = get_or_create_merge_base_tag(branch, parent) |
+ |
+ logging.debug('branch_tree: %s', pformat(branch_tree)) |
+ logging.debug('starting_refs: %s', pformat(starting_refs)) |
+ |
+ # XXX There is a more efficient way to do this, but for now... |
Ryan Tseng
2014/02/28 21:22:05
XXX?
|
+ while branch_tree: |
+ this_pass = [i for i in branch_tree.items() if i[1] not in branch_tree] |
+ for branch, parent in this_pass: |
+ clean_branch(branch, parent, starting_refs[branch]) |
+ del branch_tree[branch] |
+ |
+ clean_refs() |
+ |
+ bclean() |
+ |
+ if orig_branch in branches(): |
+ run('checkout', orig_branch) |
+ else: |
+ run('checkout', root()) |
+ |
+ return 0 |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main()) |