##// END OF EJS Templates
rebase: refactoring...
Stefano Tortarolo -
r10351:38fe86fb default
parent child Browse files
Show More
@@ -22,25 +22,6 from mercurial.lock import release
22 22 from mercurial.i18n import _
23 23 import os, errno
24 24
25 def rebasemerge(repo, rev, first=False):
26 'return the correct ancestor'
27 oldancestor = ancestor.ancestor
28
29 def newancestor(a, b, pfunc):
30 if b == rev:
31 return repo[rev].parents()[0].rev()
32 return oldancestor(a, b, pfunc)
33
34 if not first:
35 ancestor.ancestor = newancestor
36 else:
37 repo.ui.debug("first revision, do not change ancestor\n")
38 try:
39 stats = merge.update(repo, rev, True, True, False)
40 return stats
41 finally:
42 ancestor.ancestor = oldancestor
43
44 25 def rebase(ui, repo, **opts):
45 26 """move changeset (and descendants) to a different branch
46 27
@@ -55,6 +36,7 def rebase(ui, repo, **opts):
55 36 external = nullrev
56 37 state = {}
57 38 skipped = set()
39 targetancestors = set()
58 40
59 41 lock = wlock = None
60 42 try:
@@ -94,12 +76,16 def rebase(ui, repo, **opts):
94 76 raise error.ParseError('rebase', _('cannot specify both a '
95 77 'revision and a base'))
96 78 cmdutil.bail_if_changed(repo)
97 result = buildstate(repo, destf, srcf, basef, collapsef)
98 if result:
99 originalwd, target, state, external = result
100 else: # Empty state built, nothing to rebase
79 result = buildstate(repo, destf, srcf, basef)
80 if not result:
81 # Empty state built, nothing to rebase
101 82 ui.status(_('nothing to rebase\n'))
102 83 return
84 else:
85 originalwd, target, state = result
86 if collapsef:
87 targetancestors = set(repo.changelog.ancestors(target))
88 external = checkexternal(repo, state, targetancestors)
103 89
104 90 if keepbranchesf:
105 91 if extrafn:
@@ -109,22 +95,56 def rebase(ui, repo, **opts):
109 95 extra['branch'] = ctx.branch()
110 96
111 97 # Rebase
112 targetancestors = list(repo.changelog.ancestors(target))
113 targetancestors.append(target)
98 if not targetancestors:
99 targetancestors = set(repo.changelog.ancestors(target))
100 targetancestors.add(target)
114 101
115 102 for rev in sorted(state):
116 103 if state[rev] == -1:
104 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
117 105 storestatus(repo, originalwd, target, state, collapsef, keepf,
118 106 keepbranchesf, external)
119 rebasenode(repo, rev, target, state, skipped, targetancestors,
120 collapsef, extrafn)
107 p1, p2 = defineparents(repo, rev, target, state,
108 targetancestors)
109 if len(repo.parents()) == 2:
110 repo.ui.debug('resuming interrupted rebase\n')
111 else:
112 stats = rebasenode(repo, rev, p1, p2, state)
113 if stats and stats[3] > 0:
114 raise util.Abort(_('fix unresolved conflicts with hg '
115 'resolve then run hg rebase --continue'))
116 updatedirstate(repo, rev, target, p2)
117 if not collapsef:
118 extra = {'rebase_source': repo[rev].hex()}
119 if extrafn:
120 extrafn(repo[rev], extra)
121 newrev = concludenode(repo, rev, p1, p2, extra=extra)
122 else:
123 # Skip commit if we are collapsing
124 repo.dirstate.setparents(repo[p1].node())
125 newrev = None
126 # Update the state
127 if newrev is not None:
128 state[rev] = repo[newrev].rev()
129 else:
130 if not collapsef:
131 ui.note(_('no changes, revision %d skipped\n') % rev)
132 ui.debug('next revision set to %s\n' % p1)
133 skipped.add(rev)
134 state[rev] = p1
135
121 136 ui.note(_('rebase merging completed\n'))
122 137
123 138 if collapsef:
124 139 p1, p2 = defineparents(repo, min(state), target,
125 140 state, targetancestors)
126 concludenode(repo, rev, p1, external, state, collapsef,
127 last=True, skipped=skipped, extrafn=extrafn)
141 commitmsg = 'Collapsed revision'
142 for rebased in state:
143 if rebased not in skipped:
144 commitmsg += '\n* %s' % repo[rebased].description()
145 commitmsg = ui.edit(commitmsg, repo.ui.username())
146 concludenode(repo, rev, p1, external, commitmsg=commitmsg,
147 extra=extrafn)
128 148
129 149 if 'qtip' in repo.tags():
130 150 updatemq(repo, state, skipped, **opts)
@@ -146,37 +166,67 def rebase(ui, repo, **opts):
146 166 finally:
147 167 release(lock, wlock)
148 168
149 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped=None,
150 extrafn=None):
151 """Skip commit if collapsing has been required and rev is not the last
152 revision, commit otherwise
153 """
154 repo.ui.debug(" set parents\n")
155 if collapse and not last:
156 repo.dirstate.setparents(repo[p1].node())
157 return None
169 def rebasemerge(repo, rev, first=False):
170 'return the correct ancestor'
171 oldancestor = ancestor.ancestor
172
173 def newancestor(a, b, pfunc):
174 if b == rev:
175 return repo[rev].parents()[0].rev()
176 return oldancestor(a, b, pfunc)
158 177
159 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
160
161 if skipped is None:
162 skipped = set()
178 if not first:
179 ancestor.ancestor = newancestor
180 else:
181 repo.ui.debug("first revision, do not change ancestor\n")
182 try:
183 stats = merge.update(repo, rev, True, True, False)
184 return stats
185 finally:
186 ancestor.ancestor = oldancestor
163 187
164 # Commit, record the old nodeid
165 newrev = nullrev
188 def checkexternal(repo, state, targetancestors):
189 """Check whether one or more external revisions need to be taken in
190 consideration. In the latter case, abort.
191 """
192 external = nullrev
193 source = min(state)
194 for rev in state:
195 if rev == source:
196 continue
197 # Check externals and fail if there are more than one
198 for p in repo[rev].parents():
199 if (p.rev() not in state
200 and p.rev() not in targetancestors):
201 if external != nullrev:
202 raise util.Abort(_('unable to collapse, there is more '
203 'than one external parent'))
204 external = p.rev()
205 return external
206
207 def updatedirstate(repo, rev, p1, p2):
208 """Keep track of renamed files in the revision that is going to be rebased
209 """
210 # Here we simulate the copies and renames in the source changeset
211 cop, diver = copies.copies(repo, repo[rev], repo[p1], repo[p2], True)
212 m1 = repo[rev].manifest()
213 m2 = repo[p1].manifest()
214 for k, v in cop.iteritems():
215 if k in m1:
216 if v in m1 or v in m2:
217 repo.dirstate.copy(v, k)
218 if v in m2 and v not in m1:
219 repo.dirstate.remove(v)
220
221 def concludenode(repo, rev, p1, p2, commitmsg=None, extra=None):
222 'Commit the changes and store useful information in extra'
166 223 try:
167 if last:
168 # we don't translate commit messages
169 commitmsg = 'Collapsed revision'
170 for rebased in state:
171 if rebased not in skipped:
172 commitmsg += '\n* %s' % repo[rebased].description()
173 commitmsg = repo.ui.edit(commitmsg, repo.ui.username())
174 else:
224 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
225 if commitmsg is None:
175 226 commitmsg = repo[rev].description()
227 if extra is None:
228 extra = {}
176 229 # Commit might fail if unresolved files exist
177 extra = {'rebase_source': repo[rev].hex()}
178 if extrafn:
179 extrafn(repo[rev], extra)
180 230 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
181 231 date=repo[rev].date(), extra=extra)
182 232 repo.dirstate.setbranch(repo[newrev].branch())
@@ -186,18 +236,9 def concludenode(repo, rev, p1, p2, stat
186 236 repo.dirstate.invalidate()
187 237 raise
188 238
189 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
190 extrafn):
239 def rebasenode(repo, rev, p1, p2, state):
191 240 'Rebase a single revision'
192 repo.ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
193
194 p1, p2 = defineparents(repo, rev, target, state, targetancestors)
195
196 repo.ui.debug(" future parents are %d and %d\n" % (repo[p1].rev(),
197 repo[p2].rev()))
198
199 241 # Merge phase
200 if len(repo.parents()) != 2:
201 242 # Update to target and merge it with local
202 243 if repo['.'].rev() != repo[p1].rev():
203 244 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
@@ -208,37 +249,7 def rebasenode(repo, rev, target, state,
208 249 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
209 250 first = repo[rev].rev() == repo[min(state)].rev()
210 251 stats = rebasemerge(repo, rev, first)
211
212 if stats[3] > 0:
213 raise util.Abort(_('fix unresolved conflicts with hg resolve then '
214 'run hg rebase --continue'))
215 else: # we have an interrupted rebase
216 repo.ui.debug('resuming interrupted rebase\n')
217
218 # Keep track of renamed files in the revision that is going to be rebased
219 # Here we simulate the copies and renames in the source changeset
220 cop, diver = copies.copies(repo, repo[rev], repo[target], repo[p2], True)
221 m1 = repo[rev].manifest()
222 m2 = repo[target].manifest()
223 for k, v in cop.iteritems():
224 if k in m1:
225 if v in m1 or v in m2:
226 repo.dirstate.copy(v, k)
227 if v in m2 and v not in m1:
228 repo.dirstate.remove(v)
229
230 newrev = concludenode(repo, rev, p1, p2, state, collapse,
231 extrafn=extrafn)
232
233 # Update the state
234 if newrev is not None:
235 state[rev] = repo[newrev].rev()
236 else:
237 if not collapse:
238 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
239 repo.ui.debug('next revision set to %s\n' % p1)
240 skipped.add(rev)
241 state[rev] = p1
252 return stats
242 253
243 254 def defineparents(repo, rev, target, state, targetancestors):
244 255 'Return the new parent relationship of the revision that will be rebased'
@@ -267,6 +278,8 def defineparents(repo, rev, target, sta
267 278 raise util.Abort(_('cannot use revision %d as base, result '
268 279 'would have 3 parents') % rev)
269 280 p2 = P2n
281 repo.ui.debug(" future parents are %d and %d\n" %
282 (repo[p1].rev(), repo[p2].rev()))
270 283 return p1, p2
271 284
272 285 def isagitpatch(repo, patchname):
@@ -366,7 +379,7 def abort(repo, originalwd, target, stat
366 379 clearstatus(repo)
367 380 repo.ui.status(_('rebase aborted\n'))
368 381
369 def buildstate(repo, dest, src, base, collapse):
382 def buildstate(repo, dest, src, base):
370 383 'Define which revisions are going to be rebased and where'
371 384 targetancestors = set()
372 385
@@ -413,22 +426,8 def buildstate(repo, dest, src, base, co
413 426
414 427 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
415 428 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
416 external = nullrev
417 if collapse:
418 if not targetancestors:
419 targetancestors = set(repo.changelog.ancestors(dest))
420 for rev in state:
421 # Check externals and fail if there are more than one
422 for p in repo[rev].parents():
423 if (p.rev() not in state and p.rev() != source
424 and p.rev() not in targetancestors):
425 if external != nullrev:
426 raise util.Abort(_('unable to collapse, there is more '
427 'than one external parent'))
428 external = p.rev()
429
430 429 state[source] = nullrev
431 return repo['.'].rev(), repo[dest].rev(), state, external
430 return repo['.'].rev(), repo[dest].rev(), state
432 431
433 432 def pullrebase(orig, ui, repo, *args, **opts):
434 433 'Call rebase after pull if the latter has been invoked with --rebase'
General Comments 0
You need to be logged in to leave comments. Login now