##// END OF EJS Templates
rebase: refactoring...
Stefano Tortarolo -
r10351:38fe86fb default
parent child Browse files
Show More
@@ -22,25 +22,6 b' from mercurial.lock import release'
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23 import os, errno
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 def rebase(ui, repo, **opts):
25 def rebase(ui, repo, **opts):
45 """move changeset (and descendants) to a different branch
26 """move changeset (and descendants) to a different branch
46
27
@@ -55,6 +36,7 b' def rebase(ui, repo, **opts):'
55 external = nullrev
36 external = nullrev
56 state = {}
37 state = {}
57 skipped = set()
38 skipped = set()
39 targetancestors = set()
58
40
59 lock = wlock = None
41 lock = wlock = None
60 try:
42 try:
@@ -94,12 +76,16 b' def rebase(ui, repo, **opts):'
94 raise error.ParseError('rebase', _('cannot specify both a '
76 raise error.ParseError('rebase', _('cannot specify both a '
95 'revision and a base'))
77 'revision and a base'))
96 cmdutil.bail_if_changed(repo)
78 cmdutil.bail_if_changed(repo)
97 result = buildstate(repo, destf, srcf, basef, collapsef)
79 result = buildstate(repo, destf, srcf, basef)
98 if result:
80 if not result:
99 originalwd, target, state, external = result
81 # Empty state built, nothing to rebase
100 else: # Empty state built, nothing to rebase
101 ui.status(_('nothing to rebase\n'))
82 ui.status(_('nothing to rebase\n'))
102 return
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 if keepbranchesf:
90 if keepbranchesf:
105 if extrafn:
91 if extrafn:
@@ -109,22 +95,56 b' def rebase(ui, repo, **opts):'
109 extra['branch'] = ctx.branch()
95 extra['branch'] = ctx.branch()
110
96
111 # Rebase
97 # Rebase
112 targetancestors = list(repo.changelog.ancestors(target))
98 if not targetancestors:
113 targetancestors.append(target)
99 targetancestors = set(repo.changelog.ancestors(target))
100 targetancestors.add(target)
114
101
115 for rev in sorted(state):
102 for rev in sorted(state):
116 if state[rev] == -1:
103 if state[rev] == -1:
104 ui.debug("rebasing %d:%s\n" % (rev, repo[rev]))
117 storestatus(repo, originalwd, target, state, collapsef, keepf,
105 storestatus(repo, originalwd, target, state, collapsef, keepf,
118 keepbranchesf, external)
106 keepbranchesf, external)
119 rebasenode(repo, rev, target, state, skipped, targetancestors,
107 p1, p2 = defineparents(repo, rev, target, state,
120 collapsef, extrafn)
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 ui.note(_('rebase merging completed\n'))
136 ui.note(_('rebase merging completed\n'))
122
137
123 if collapsef:
138 if collapsef:
124 p1, p2 = defineparents(repo, min(state), target,
139 p1, p2 = defineparents(repo, min(state), target,
125 state, targetancestors)
140 state, targetancestors)
126 concludenode(repo, rev, p1, external, state, collapsef,
141 commitmsg = 'Collapsed revision'
127 last=True, skipped=skipped, extrafn=extrafn)
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 if 'qtip' in repo.tags():
149 if 'qtip' in repo.tags():
130 updatemq(repo, state, skipped, **opts)
150 updatemq(repo, state, skipped, **opts)
@@ -146,37 +166,67 b' def rebase(ui, repo, **opts):'
146 finally:
166 finally:
147 release(lock, wlock)
167 release(lock, wlock)
148
168
149 def concludenode(repo, rev, p1, p2, state, collapse, last=False, skipped=None,
169 def rebasemerge(repo, rev, first=False):
150 extrafn=None):
170 'return the correct ancestor'
151 """Skip commit if collapsing has been required and rev is not the last
171 oldancestor = ancestor.ancestor
152 revision, commit otherwise
172
153 """
173 def newancestor(a, b, pfunc):
154 repo.ui.debug(" set parents\n")
174 if b == rev:
155 if collapse and not last:
175 return repo[rev].parents()[0].rev()
156 repo.dirstate.setparents(repo[p1].node())
176 return oldancestor(a, b, pfunc)
157 return None
158
177
159 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
178 if not first:
160
179 ancestor.ancestor = newancestor
161 if skipped is None:
180 else:
162 skipped = set()
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
188 def checkexternal(repo, state, targetancestors):
165 newrev = nullrev
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 try:
223 try:
167 if last:
224 repo.dirstate.setparents(repo[p1].node(), repo[p2].node())
168 # we don't translate commit messages
225 if commitmsg is None:
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:
175 commitmsg = repo[rev].description()
226 commitmsg = repo[rev].description()
227 if extra is None:
228 extra = {}
176 # Commit might fail if unresolved files exist
229 # Commit might fail if unresolved files exist
177 extra = {'rebase_source': repo[rev].hex()}
178 if extrafn:
179 extrafn(repo[rev], extra)
180 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
230 newrev = repo.commit(text=commitmsg, user=repo[rev].user(),
181 date=repo[rev].date(), extra=extra)
231 date=repo[rev].date(), extra=extra)
182 repo.dirstate.setbranch(repo[newrev].branch())
232 repo.dirstate.setbranch(repo[newrev].branch())
@@ -186,59 +236,20 b' def concludenode(repo, rev, p1, p2, stat'
186 repo.dirstate.invalidate()
236 repo.dirstate.invalidate()
187 raise
237 raise
188
238
189 def rebasenode(repo, rev, target, state, skipped, targetancestors, collapse,
239 def rebasenode(repo, rev, p1, p2, state):
190 extrafn):
191 'Rebase a single revision'
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 # Merge phase
241 # Merge phase
200 if len(repo.parents()) != 2:
242 # Update to target and merge it with local
201 # Update to target and merge it with local
243 if repo['.'].rev() != repo[p1].rev():
202 if repo['.'].rev() != repo[p1].rev():
244 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
203 repo.ui.debug(" update to %d:%s\n" % (repo[p1].rev(), repo[p1]))
245 merge.update(repo, p1, False, True, False)
204 merge.update(repo, p1, False, True, False)
205 else:
206 repo.ui.debug(" already in target\n")
207 repo.dirstate.write()
208 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
209 first = repo[rev].rev() == repo[min(state)].rev()
210 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:
246 else:
237 if not collapse:
247 repo.ui.debug(" already in target\n")
238 repo.ui.note(_('no changes, revision %d skipped\n') % rev)
248 repo.dirstate.write()
239 repo.ui.debug('next revision set to %s\n' % p1)
249 repo.ui.debug(" merge against %d:%s\n" % (repo[rev].rev(), repo[rev]))
240 skipped.add(rev)
250 first = repo[rev].rev() == repo[min(state)].rev()
241 state[rev] = p1
251 stats = rebasemerge(repo, rev, first)
252 return stats
242
253
243 def defineparents(repo, rev, target, state, targetancestors):
254 def defineparents(repo, rev, target, state, targetancestors):
244 'Return the new parent relationship of the revision that will be rebased'
255 'Return the new parent relationship of the revision that will be rebased'
@@ -267,6 +278,8 b' def defineparents(repo, rev, target, sta'
267 raise util.Abort(_('cannot use revision %d as base, result '
278 raise util.Abort(_('cannot use revision %d as base, result '
268 'would have 3 parents') % rev)
279 'would have 3 parents') % rev)
269 p2 = P2n
280 p2 = P2n
281 repo.ui.debug(" future parents are %d and %d\n" %
282 (repo[p1].rev(), repo[p2].rev()))
270 return p1, p2
283 return p1, p2
271
284
272 def isagitpatch(repo, patchname):
285 def isagitpatch(repo, patchname):
@@ -366,7 +379,7 b' def abort(repo, originalwd, target, stat'
366 clearstatus(repo)
379 clearstatus(repo)
367 repo.ui.status(_('rebase aborted\n'))
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 'Define which revisions are going to be rebased and where'
383 'Define which revisions are going to be rebased and where'
371 targetancestors = set()
384 targetancestors = set()
372
385
@@ -413,22 +426,8 b' def buildstate(repo, dest, src, base, co'
413
426
414 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
427 repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
415 state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
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 state[source] = nullrev
429 state[source] = nullrev
431 return repo['.'].rev(), repo[dest].rev(), state, external
430 return repo['.'].rev(), repo[dest].rev(), state
432
431
433 def pullrebase(orig, ui, repo, *args, **opts):
432 def pullrebase(orig, ui, repo, *args, **opts):
434 'Call rebase after pull if the latter has been invoked with --rebase'
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