Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(228)

Side by Side Diff: git_rebase_update.py

Issue 184253003: Add git-reup and friends (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@freeze_thaw
Patch Set: one more argparse Created 6 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
agable 2014/03/21 01:14:21 docstrong
6 import argparse
7 import collections
8 import logging
9 import sys
10
11 from pprint import pformat
12
13 from git_common import run, root, tags, topo_iter, once, hash_one, rebase
agable 2014/03/21 01:14:21 Honestly might be more reasonable to do import git
14 from git_common import get_or_create_merge_base, squash_current_branch, run_both
15 from git_common import get_branch_tree, branches, remove_merge_base
16 from git_common import current_branch, set_config, config
17
18 from git_freezer import freeze, thaw
19
20
21 def find_return_branch():
22 """Finds the branch which we should return to after rebase-update completes
23 its entire action without any conflicts.
24 """
25 STARTING_BRANCH_KEY = 'depot-tools.rebase-update.starting-branch'
26 return_branch = config(STARTING_BRANCH_KEY)
27 if return_branch is None:
28 return_branch = current_branch()
29 if return_branch != 'HEAD':
30 set_config(STARTING_BRANCH_KEY, return_branch)
31
32 return return_branch
33
34
35 def check_for_rebase():
36 from git_common import in_rebase
37
38 if in_rebase():
39 # TODO(iannucci): Be able to resume rebase with flags like --continue,
40 # etc.
41 print (
42 'Rebase in progress. Please complete the rebase before running '
43 '`git rebase-update`.'
44 )
45 sys.exit(1)
46
47
48 def fetch_remotes(branch_tree):
49 """Fetches all remotes which are needed to update |branch_tree|."""
50
51 fetch_tags = False
52 remotes = set()
53 tag_set = tags()
54 for parent in branch_tree.itervalues():
55 if parent in tag_set:
56 fetch_tags = True
57 else:
58 full_ref = run('rev-parse', '--symbolic-full-name', parent)
59 if full_ref.startswith('refs/remotes'):
60 parts = full_ref.split('/')
61 remote_name = parts[2]
62 remotes.add(remote_name)
63
64 fetch_args = []
65 if fetch_tags:
66 fetch_args.append('--tags')
67 fetch_args.append('--multiple')
68 fetch_args.extend(remotes)
69 # TODO(iannucci): Should we fetch git-svn?
70
71 if not fetch_args: # pragma: no cover
72 print 'Nothing to fetch.'
73 else:
74 out, err = run_both('fetch', *fetch_args)
75 for data, stream in zip((out, err), (sys.stdout, sys.stderr)):
76 if data:
77 print >> stream, data
78
79
80 def remove_empty_branches(branch_tree):
81 tag_set = tags()
82 ensure_root_checkout = once(lambda: run('checkout', root()))
83
84 downstreams = collections.defaultdict(list)
85 for branch, parent in topo_iter(branch_tree, top_down=False):
86 downstreams[parent].append(branch)
87
88 if hash_one(branch) == hash_one(parent):
89 ensure_root_checkout()
90
91 logging.debug('branch %s merged to %s', branch, parent)
92
93 for down in downstreams[branch]:
94 if parent in tag_set:
95 set_config('branch.%s.remote', '.')
96 set_config('branch.%s.merge', 'refs/tags/parent')
97 print ('Reparented %s to track %s [tag] (was tracking %s)'
98 % (down, parent, branch))
99 else:
100 run('branch', '--set-upstream-to', parent, down)
101 print ('Reparented %s to track %s (was tracking %s)'
102 % (down, parent, branch))
103
104 print run('branch', '-d', branch)
105
106
107 def rebase_branch(branch, parent, start_hash):
108 logging.debug('considering %s(%s) -> %s(%s) : %s',
109 branch, hash_one(branch), parent, hash_one(parent), start_hash)
110
111 # If parent has FROZEN commits, don't base branch on top of them. Instead,
112 # base branch on top of whatever commit is before them.
113 back_ups = 0
114 orig_parent = parent
115 while run('log', '-n1', '--format=%s', parent).startswith('FREEZE'):
116 back_ups += 1
117 parent = run('rev-parse', parent+'~')
118
119 if back_ups:
120 logging.debug('Backed parent up by %d from %s to %s',
121 back_ups, orig_parent, parent)
122
123 if hash_one(parent) != start_hash:
124 # Try a plain rebase first
125 print 'Rebasing:', branch
126 if not rebase(parent, start_hash, branch, abort=True).success:
127 # TODO(iannucci): Find collapsible branches in a smarter way?
128 # Maybe squashing will help?
129 print "Failed! Attempting to squash", branch, "...",
130 squash_branch = branch+"_squash_attempt"
131 run('checkout', '-b', squash_branch)
132 squash_current_branch(merge_base=start_hash)
133
134 # Try to rebase the branch_squash_attempt branch to see if it's empty.
135 squash_ret = rebase(parent, start_hash, squash_branch, abort=True)
136 empty_rebase = hash_one(squash_branch) == hash_one(parent)
137 run('checkout', branch)
138 run('branch', '-D', squash_branch)
139 if squash_ret.success and empty_rebase:
140 print 'Success!'
141 squash_current_branch(merge_base=start_hash)
142 rebase(parent, start_hash, branch)
143 else:
144 # rebase and leave in mid-rebase state.
145
146 # TODO(iannucci): Allow user to mark branch as 'dormant' so that
147 # rebase-update can skip it.
148 rebase(parent, start_hash, branch)
149 print squash_ret.message
150 print 'Failure :('
151 print 'Your working copy is in mid-rebase. Please completely resolve '
152 print 'and run `git rebase-update` again.'
153 sys.exit(1)
154 else:
155 print '%s up-to-date' % branch
156
157 remove_merge_base(branch)
158 get_or_create_merge_base(branch)
159
160
161 def main(args=None):
162 try:
163 parser = argparse.ArgumentParser()
164 parser.add_argument('--verbose', '-v', action='store_true')
165 opts = parser.parse_args(args)
166
167 if opts.verbose: # pragma: no cover
168 logging.getLogger().setLevel(logging.DEBUG)
169
170 # TODO(iannucci): snapshot all branches somehow, so we can implement
171 # `git rebase-update --undo`.
172 # * Perhaps just copy packed-refs + refs/ + logs/ to the side?
173 # * commit them to a secret ref?
174 # * Then we could view a summary of each run as a
175 # `diff --stat` on that secret ref.
176
177 check_for_rebase()
178
179 return_branch = find_return_branch()
180
181 if current_branch() == 'HEAD':
182 if run('status', '--porcelain'):
183 print 'Cannot rebase-update with detached head + uncommitted changes.'
184 return 1
185 else:
186 freeze() # just in case there are any local changes.
187
188 skipped, branch_tree = get_branch_tree()
189 for branch in skipped:
190 print 'Skipping %s: No upstream specified' % branch
191
192 fetch_remotes(branch_tree)
193
194 branch_to_merge_base = {}
195 for branch, parent in branch_tree.iteritems():
196 branch_to_merge_base[branch] = get_or_create_merge_base(branch, parent)
197
198 print 'branch_tree: %s' % pformat(branch_tree)
199 print 'branch_to_merge_base: %s' % pformat(branch_to_merge_base)
200
201 # Rebase each branch starting with the root-most branches and working
202 # towards the leaves.
203 for branch, parent in topo_iter(branch_tree):
204 rebase_branch(branch, parent, branch_to_merge_base[branch])
205
206 remove_empty_branches(branch_tree)
207
208 # return_branch may not be there any more.
209 if return_branch in branches():
210 run('checkout', return_branch)
211 thaw()
212 else:
213 root_branch = root()
214 if return_branch != 'HEAD':
215 print (
216 "%r was merged with its parent, checking out %r instead."
217 % (return_branch, root_branch)
218 )
219 run('checkout', root_branch)
220 except SystemExit as se:
221 return se.code
222
223 return 0
224
225
226 if __name__ == '__main__': # pragma: no cover
227 sys.exit(main(sys.argv[1:]))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698