Chromium Code Reviews| 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()) |