58 # The following constants are used throughout the rebase module. The ordering of |
57 # The following constants are used throughout the rebase module. The ordering of |
59 # their values must be maintained. |
58 # their values must be maintained. |
60 |
59 |
61 # Indicates that a revision needs to be rebased |
60 # Indicates that a revision needs to be rebased |
62 revtodo = -1 |
61 revtodo = -1 |
|
62 revtodostr = '-1' |
63 |
63 |
64 # legacy revstates no longer needed in current code |
64 # legacy revstates no longer needed in current code |
65 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned |
65 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned |
66 legacystates = {'-2', '-3', '-4', '-5'} |
66 legacystates = {'-2', '-3', '-4', '-5'} |
67 |
67 |
144 # Mapping between the old revision id and either what is the new rebased |
144 # Mapping between the old revision id and either what is the new rebased |
145 # revision or what needs to be done with the old revision. The state |
145 # revision or what needs to be done with the old revision. The state |
146 # dict will be what contains most of the rebase progress state. |
146 # dict will be what contains most of the rebase progress state. |
147 self.state = {} |
147 self.state = {} |
148 self.activebookmark = None |
148 self.activebookmark = None |
149 self.dest = None |
149 self.destmap = {} |
150 self.skipped = set() |
150 self.skipped = set() |
151 |
151 |
152 self.collapsef = opts.get('collapse', False) |
152 self.collapsef = opts.get('collapse', False) |
153 self.collapsemsg = cmdutil.logmessage(ui, opts) |
153 self.collapsemsg = cmdutil.logmessage(ui, opts) |
154 self.date = opts.get('date', None) |
154 self.date = opts.get('date', None) |
175 self._writestatus(f) |
175 self._writestatus(f) |
176 |
176 |
177 def _writestatus(self, f): |
177 def _writestatus(self, f): |
178 repo = self.repo.unfiltered() |
178 repo = self.repo.unfiltered() |
179 f.write(repo[self.originalwd].hex() + '\n') |
179 f.write(repo[self.originalwd].hex() + '\n') |
180 f.write(repo[self.dest].hex() + '\n') |
180 # was "dest". we now write dest per src root below. |
|
181 f.write('\n') |
181 f.write(repo[self.external].hex() + '\n') |
182 f.write(repo[self.external].hex() + '\n') |
182 f.write('%d\n' % int(self.collapsef)) |
183 f.write('%d\n' % int(self.collapsef)) |
183 f.write('%d\n' % int(self.keepf)) |
184 f.write('%d\n' % int(self.keepf)) |
184 f.write('%d\n' % int(self.keepbranchesf)) |
185 f.write('%d\n' % int(self.keepbranchesf)) |
185 f.write('%s\n' % (self.activebookmark or '')) |
186 f.write('%s\n' % (self.activebookmark or '')) |
|
187 destmap = self.destmap |
186 for d, v in self.state.iteritems(): |
188 for d, v in self.state.iteritems(): |
187 oldrev = repo[d].hex() |
189 oldrev = repo[d].hex() |
188 if v >= 0: |
190 if v >= 0: |
189 newrev = repo[v].hex() |
191 newrev = repo[v].hex() |
190 elif v == revtodo: |
|
191 # To maintain format compatibility, we have to use nullid. |
|
192 # Please do remove this special case when upgrading the format. |
|
193 newrev = hex(nullid) |
|
194 else: |
192 else: |
195 newrev = v |
193 newrev = v |
196 f.write("%s:%s\n" % (oldrev, newrev)) |
194 destnode = repo[destmap[d]].hex() |
|
195 f.write("%s:%s:%s\n" % (oldrev, newrev, destnode)) |
197 repo.ui.debug('rebase status stored\n') |
196 repo.ui.debug('rebase status stored\n') |
198 |
197 |
199 def restorestatus(self): |
198 def restorestatus(self): |
200 """Restore a previously stored status""" |
199 """Restore a previously stored status""" |
201 repo = self.repo |
200 repo = self.repo |
202 keepbranches = None |
201 keepbranches = None |
203 dest = None |
202 legacydest = None |
204 collapse = False |
203 collapse = False |
205 external = nullrev |
204 external = nullrev |
206 activebookmark = None |
205 activebookmark = None |
207 state = {} |
206 state = {} |
|
207 destmap = {} |
208 |
208 |
209 try: |
209 try: |
210 f = repo.vfs("rebasestate") |
210 f = repo.vfs("rebasestate") |
211 for i, l in enumerate(f.read().splitlines()): |
211 for i, l in enumerate(f.read().splitlines()): |
212 if i == 0: |
212 if i == 0: |
213 originalwd = repo[l].rev() |
213 originalwd = repo[l].rev() |
214 elif i == 1: |
214 elif i == 1: |
215 dest = repo[l].rev() |
215 # this line should be empty in newer version. but legacy |
|
216 # clients may still use it |
|
217 if l: |
|
218 legacydest = repo[l].rev() |
216 elif i == 2: |
219 elif i == 2: |
217 external = repo[l].rev() |
220 external = repo[l].rev() |
218 elif i == 3: |
221 elif i == 3: |
219 collapse = bool(int(l)) |
222 collapse = bool(int(l)) |
220 elif i == 4: |
223 elif i == 4: |
225 # line 6 is a recent addition, so for backwards |
228 # line 6 is a recent addition, so for backwards |
226 # compatibility check that the line doesn't look like the |
229 # compatibility check that the line doesn't look like the |
227 # oldrev:newrev lines |
230 # oldrev:newrev lines |
228 activebookmark = l |
231 activebookmark = l |
229 else: |
232 else: |
230 oldrev, newrev = l.split(':') |
233 args = l.split(':') |
|
234 oldrev = args[0] |
|
235 newrev = args[1] |
231 if newrev in legacystates: |
236 if newrev in legacystates: |
232 continue |
237 continue |
233 elif newrev == nullid: |
238 if len(args) > 2: |
|
239 destnode = args[2] |
|
240 else: |
|
241 destnode = legacydest |
|
242 destmap[repo[oldrev].rev()] = repo[destnode].rev() |
|
243 if newrev in (nullid, revtodostr): |
234 state[repo[oldrev].rev()] = revtodo |
244 state[repo[oldrev].rev()] = revtodo |
235 # Legacy compat special case |
245 # Legacy compat special case |
236 else: |
246 else: |
237 state[repo[oldrev].rev()] = repo[newrev].rev() |
247 state[repo[oldrev].rev()] = repo[newrev].rev() |
238 |
248 |
245 raise error.Abort(_('.hg/rebasestate is incomplete')) |
255 raise error.Abort(_('.hg/rebasestate is incomplete')) |
246 |
256 |
247 skipped = set() |
257 skipped = set() |
248 # recompute the set of skipped revs |
258 # recompute the set of skipped revs |
249 if not collapse: |
259 if not collapse: |
250 seen = {dest} |
260 seen = set(destmap.values()) |
251 for old, new in sorted(state.items()): |
261 for old, new in sorted(state.items()): |
252 if new != revtodo and new in seen: |
262 if new != revtodo and new in seen: |
253 skipped.add(old) |
263 skipped.add(old) |
254 seen.add(new) |
264 seen.add(new) |
255 repo.ui.debug('computed skipped revs: %s\n' % |
265 repo.ui.debug('computed skipped revs: %s\n' % |
256 (' '.join(str(r) for r in sorted(skipped)) or None)) |
266 (' '.join(str(r) for r in sorted(skipped)) or None)) |
257 repo.ui.debug('rebase status resumed\n') |
267 repo.ui.debug('rebase status resumed\n') |
258 _setrebasesetvisibility(repo, set(state.keys()) | {originalwd}) |
268 _setrebasesetvisibility(repo, set(state.keys()) | {originalwd}) |
259 |
269 |
260 self.originalwd = originalwd |
270 self.originalwd = originalwd |
261 self.dest = dest |
271 self.destmap = destmap |
262 self.state = state |
272 self.state = state |
263 self.skipped = skipped |
273 self.skipped = skipped |
264 self.collapsef = collapse |
274 self.collapsef = collapse |
265 self.keepf = keep |
275 self.keepf = keep |
266 self.keepbranchesf = keepbranches |
276 self.keepbranchesf = keepbranches |
267 self.external = external |
277 self.external = external |
268 self.activebookmark = activebookmark |
278 self.activebookmark = activebookmark |
269 |
279 |
270 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest): |
280 def _handleskippingobsolete(self, obsoleterevs, destmap): |
271 """Compute structures necessary for skipping obsolete revisions |
281 """Compute structures necessary for skipping obsolete revisions |
272 |
282 |
273 rebaserevs: iterable of all revisions that are to be rebased |
|
274 obsoleterevs: iterable of all obsolete revisions in rebaseset |
283 obsoleterevs: iterable of all obsolete revisions in rebaseset |
275 dest: a destination revision for the rebase operation |
284 destmap: {srcrev: destrev} destination revisions |
276 """ |
285 """ |
277 self.obsoletenotrebased = {} |
286 self.obsoletenotrebased = {} |
278 if not self.ui.configbool('experimental', 'rebaseskipobsolete', |
287 if not self.ui.configbool('experimental', 'rebaseskipobsolete', |
279 default=True): |
288 default=True): |
280 return |
289 return |
281 obsoleteset = set(obsoleterevs) |
290 obsoleteset = set(obsoleterevs) |
282 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo, |
291 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo, |
283 obsoleteset, dest) |
292 obsoleteset, destmap) |
284 skippedset = set(self.obsoletenotrebased) |
293 skippedset = set(self.obsoletenotrebased) |
285 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset) |
294 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset) |
286 |
295 |
287 def _prepareabortorcontinue(self, isabort): |
296 def _prepareabortorcontinue(self, isabort): |
288 try: |
297 try: |
298 else: |
307 else: |
299 msg = _('cannot continue inconsistent rebase') |
308 msg = _('cannot continue inconsistent rebase') |
300 hint = _('use "hg rebase --abort" to clear broken state') |
309 hint = _('use "hg rebase --abort" to clear broken state') |
301 raise error.Abort(msg, hint=hint) |
310 raise error.Abort(msg, hint=hint) |
302 if isabort: |
311 if isabort: |
303 return abort(self.repo, self.originalwd, self.dest, |
312 return abort(self.repo, self.originalwd, self.destmap, |
304 self.state, activebookmark=self.activebookmark) |
313 self.state, activebookmark=self.activebookmark) |
305 |
314 |
306 def _preparenewrebase(self, dest, rebaseset): |
315 def _preparenewrebase(self, destmap): |
307 if dest is None: |
316 if not destmap: |
308 return _nothingtorebase() |
317 return _nothingtorebase() |
309 |
318 |
|
319 rebaseset = destmap.keys() |
310 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt) |
320 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt) |
311 if (not (self.keepf or allowunstable) |
321 if (not (self.keepf or allowunstable) |
312 and self.repo.revs('first(children(%ld) - %ld)', |
322 and self.repo.revs('first(children(%ld) - %ld)', |
313 rebaseset, rebaseset)): |
323 rebaseset, rebaseset)): |
314 raise error.Abort( |
324 raise error.Abort( |
315 _("can't remove original changesets with" |
325 _("can't remove original changesets with" |
316 " unrebased descendants"), |
326 " unrebased descendants"), |
317 hint=_('use --keep to keep original changesets')) |
327 hint=_('use --keep to keep original changesets')) |
318 |
328 |
319 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset)) |
329 obsrevs = _filterobsoleterevs(self.repo, rebaseset) |
320 self._handleskippingobsolete(rebaseset, obsrevs, dest.rev()) |
330 self._handleskippingobsolete(obsrevs, destmap) |
321 |
331 |
322 result = buildstate(self.repo, dest, rebaseset, self.collapsef, |
332 result = buildstate(self.repo, destmap, self.collapsef, |
323 self.obsoletenotrebased) |
333 self.obsoletenotrebased) |
324 |
334 |
325 if not result: |
335 if not result: |
326 # Empty state built, nothing to rebase |
336 # Empty state built, nothing to rebase |
327 self.ui.status(_('nothing to rebase\n')) |
337 self.ui.status(_('nothing to rebase\n')) |
331 if not self.keepf and not root.mutable(): |
341 if not self.keepf and not root.mutable(): |
332 raise error.Abort(_("can't rebase public changeset %s") |
342 raise error.Abort(_("can't rebase public changeset %s") |
333 % root, |
343 % root, |
334 hint=_("see 'hg help phases' for details")) |
344 hint=_("see 'hg help phases' for details")) |
335 |
345 |
336 (self.originalwd, self.dest, self.state) = result |
346 (self.originalwd, self.destmap, self.state) = result |
337 if self.collapsef: |
347 if self.collapsef: |
338 destancestors = self.repo.changelog.ancestors([self.dest], |
348 dests = set(self.destmap.values()) |
|
349 if len(dests) != 1: |
|
350 raise error.Abort( |
|
351 _('--collapse does not work with multiple destinations')) |
|
352 destrev = next(iter(dests)) |
|
353 destancestors = self.repo.changelog.ancestors([destrev], |
339 inclusive=True) |
354 inclusive=True) |
340 self.external = externalparent(self.repo, self.state, destancestors) |
355 self.external = externalparent(self.repo, self.state, destancestors) |
341 |
356 |
342 if dest.closesbranch() and not self.keepbranchesf: |
357 for destrev in sorted(set(destmap.values())): |
343 self.ui.status(_('reopening closed branch head %s\n') % dest) |
358 dest = self.repo[destrev] |
|
359 if dest.closesbranch() and not self.keepbranchesf: |
|
360 self.ui.status(_('reopening closed branch head %s\n') % dest) |
344 |
361 |
345 def _performrebase(self, tr): |
362 def _performrebase(self, tr): |
346 repo, ui, opts = self.repo, self.ui, self.opts |
363 repo, ui, opts = self.repo, self.ui, self.opts |
347 if self.keepbranchesf: |
364 if self.keepbranchesf: |
348 # insert _savebranch at the start of extrafns so if |
365 # insert _savebranch at the start of extrafns so if |
369 sortedrevs = repo.revs('sort(%ld, -topo)', self.state) |
386 sortedrevs = repo.revs('sort(%ld, -topo)', self.state) |
370 cands = [k for k, v in self.state.iteritems() if v == revtodo] |
387 cands = [k for k, v in self.state.iteritems() if v == revtodo] |
371 total = len(cands) |
388 total = len(cands) |
372 pos = 0 |
389 pos = 0 |
373 for rev in sortedrevs: |
390 for rev in sortedrevs: |
|
391 dest = self.destmap[rev] |
374 ctx = repo[rev] |
392 ctx = repo[rev] |
375 desc = _ctxdesc(ctx) |
393 desc = _ctxdesc(ctx) |
376 if self.state[rev] == rev: |
394 if self.state[rev] == rev: |
377 ui.status(_('already rebased %s\n') % desc) |
395 ui.status(_('already rebased %s\n') % desc) |
378 elif self.state[rev] == revtodo: |
396 elif self.state[rev] == revtodo: |
379 pos += 1 |
397 pos += 1 |
380 ui.status(_('rebasing %s\n') % desc) |
398 ui.status(_('rebasing %s\n') % desc) |
381 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)), |
399 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)), |
382 _('changesets'), total) |
400 _('changesets'), total) |
383 p1, p2, base = defineparents(repo, rev, self.dest, self.state) |
401 p1, p2, base = defineparents(repo, rev, self.destmap, |
|
402 self.state) |
384 self.storestatus(tr=tr) |
403 self.storestatus(tr=tr) |
385 storecollapsemsg(repo, self.collapsemsg) |
404 storecollapsemsg(repo, self.collapsemsg) |
386 if len(repo[None].parents()) == 2: |
405 if len(repo[None].parents()) == 2: |
387 repo.ui.debug('resuming interrupted rebase\n') |
406 repo.ui.debug('resuming interrupted rebase\n') |
388 else: |
407 else: |
389 try: |
408 try: |
390 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), |
409 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), |
391 'rebase') |
410 'rebase') |
392 stats = rebasenode(repo, rev, p1, base, self.state, |
411 stats = rebasenode(repo, rev, p1, base, self.state, |
393 self.collapsef, self.dest) |
412 self.collapsef, dest) |
394 if stats and stats[3] > 0: |
413 if stats and stats[3] > 0: |
395 raise error.InterventionRequired( |
414 raise error.InterventionRequired( |
396 _('unresolved conflicts (see hg ' |
415 _('unresolved conflicts (see hg ' |
397 'resolve, then hg rebase --continue)')) |
416 'resolve, then hg rebase --continue)')) |
398 finally: |
417 finally: |
434 ui.note(_('rebase merging completed\n')) |
453 ui.note(_('rebase merging completed\n')) |
435 |
454 |
436 def _finishrebase(self): |
455 def _finishrebase(self): |
437 repo, ui, opts = self.repo, self.ui, self.opts |
456 repo, ui, opts = self.repo, self.ui, self.opts |
438 if self.collapsef and not self.keepopen: |
457 if self.collapsef and not self.keepopen: |
439 p1, p2, _base = defineparents(repo, min(self.state), |
458 p1, p2, _base = defineparents(repo, min(self.state), self.destmap, |
440 self.dest, self.state) |
459 self.state) |
441 editopt = opts.get('edit') |
460 editopt = opts.get('edit') |
442 editform = 'rebase.collapse' |
461 editform = 'rebase.collapse' |
443 if self.collapsemsg: |
462 if self.collapsemsg: |
444 commitmsg = self.collapsemsg |
463 commitmsg = self.collapsemsg |
445 else: |
464 else: |
673 |
692 |
674 retcode = rbsrt._prepareabortorcontinue(abortf) |
693 retcode = rbsrt._prepareabortorcontinue(abortf) |
675 if retcode is not None: |
694 if retcode is not None: |
676 return retcode |
695 return retcode |
677 else: |
696 else: |
678 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf, |
697 destmap = _definedestmap(ui, repo, destf, srcf, basef, revf, |
679 destspace=destspace) |
698 destspace=destspace) |
680 retcode = rbsrt._preparenewrebase(dest, rebaseset) |
699 retcode = rbsrt._preparenewrebase(destmap) |
681 if retcode is not None: |
700 if retcode is not None: |
682 return retcode |
701 return retcode |
683 |
702 |
684 tr = None |
703 tr = None |
685 dsguard = None |
704 dsguard = None |
693 with util.acceptintervention(dsguard): |
712 with util.acceptintervention(dsguard): |
694 rbsrt._performrebase(tr) |
713 rbsrt._performrebase(tr) |
695 |
714 |
696 rbsrt._finishrebase() |
715 rbsrt._finishrebase() |
697 |
716 |
698 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None, |
717 def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None, |
699 destspace=None): |
718 destspace=None): |
700 """use revisions argument to define destination and rebase set |
719 """use revisions argument to define destmap {srcrev: destrev}""" |
701 """ |
|
702 if revf is None: |
720 if revf is None: |
703 revf = [] |
721 revf = [] |
704 |
722 |
705 # destspace is here to work around issues with `hg pull --rebase` see |
723 # destspace is here to work around issues with `hg pull --rebase` see |
706 # issue5214 for details |
724 # issue5214 for details |
723 |
741 |
724 if revf: |
742 if revf: |
725 rebaseset = scmutil.revrange(repo, revf) |
743 rebaseset = scmutil.revrange(repo, revf) |
726 if not rebaseset: |
744 if not rebaseset: |
727 ui.status(_('empty "rev" revision set - nothing to rebase\n')) |
745 ui.status(_('empty "rev" revision set - nothing to rebase\n')) |
728 return None, None |
746 return None |
729 elif srcf: |
747 elif srcf: |
730 src = scmutil.revrange(repo, [srcf]) |
748 src = scmutil.revrange(repo, [srcf]) |
731 if not src: |
749 if not src: |
732 ui.status(_('empty "source" revision set - nothing to rebase\n')) |
750 ui.status(_('empty "source" revision set - nothing to rebase\n')) |
733 return None, None |
751 return None |
734 rebaseset = repo.revs('(%ld)::', src) |
752 rebaseset = repo.revs('(%ld)::', src) |
735 assert rebaseset |
753 assert rebaseset |
736 else: |
754 else: |
737 base = scmutil.revrange(repo, [basef or '.']) |
755 base = scmutil.revrange(repo, [basef or '.']) |
738 if not base: |
756 if not base: |
739 ui.status(_('empty "base" revision set - ' |
757 ui.status(_('empty "base" revision set - ' |
740 "can't compute rebase set\n")) |
758 "can't compute rebase set\n")) |
741 return None, None |
759 return None |
742 if not destf: |
760 if not destf: |
743 dest = repo[_destrebase(repo, base, destspace=destspace)] |
761 dest = repo[_destrebase(repo, base, destspace=destspace)] |
744 destf = str(dest) |
762 destf = str(dest) |
745 |
763 |
746 roots = [] # selected children of branching points |
764 roots = [] # selected children of branching points |
780 'directory parent is already an ' |
798 'directory parent is already an ' |
781 'ancestor of destination %s\n') % dest) |
799 'ancestor of destination %s\n') % dest) |
782 else: # can it happen? |
800 else: # can it happen? |
783 ui.status(_('nothing to rebase from %s to %s\n') % |
801 ui.status(_('nothing to rebase from %s to %s\n') % |
784 ('+'.join(str(repo[r]) for r in base), dest)) |
802 ('+'.join(str(repo[r]) for r in base), dest)) |
785 return None, None |
803 return None |
786 |
804 |
787 if not destf: |
805 if not destf: |
788 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)] |
806 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)] |
789 destf = str(dest) |
807 destf = str(dest) |
790 |
808 |
791 return dest, rebaseset |
809 # assign dest to each rev in rebaseset |
|
810 destrev = dest.rev() |
|
811 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev} |
|
812 |
|
813 return destmap |
792 |
814 |
793 def externalparent(repo, state, destancestors): |
815 def externalparent(repo, state, destancestors): |
794 """Return the revision that should be used as the second parent |
816 """Return the revision that should be used as the second parent |
795 when the revisions in state is collapsed on top of destancestors. |
817 when the revisions in state is collapsed on top of destancestors. |
796 Abort if there is more than one parent. |
818 Abort if there is more than one parent. |
872 # performed in the destination. |
894 # performed in the destination. |
873 p1rev = repo[rev].p1().rev() |
895 p1rev = repo[rev].p1().rev() |
874 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest) |
896 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest) |
875 return stats |
897 return stats |
876 |
898 |
877 def adjustdest(repo, rev, dest, state): |
899 def adjustdest(repo, rev, destmap, state): |
878 """adjust rebase destination given the current rebase state |
900 """adjust rebase destination given the current rebase state |
879 |
901 |
880 rev is what is being rebased. Return a list of two revs, which are the |
902 rev is what is being rebased. Return a list of two revs, which are the |
881 adjusted destinations for rev's p1 and p2, respectively. If a parent is |
903 adjusted destinations for rev's p1 and p2, respectively. If a parent is |
882 nullrev, return dest without adjustment for it. |
904 nullrev, return dest without adjustment for it. |
955 nodemap = unfi.changelog.nodemap |
978 nodemap = unfi.changelog.nodemap |
956 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]): |
979 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]): |
957 if s in nodemap: |
980 if s in nodemap: |
958 yield nodemap[s] |
981 yield nodemap[s] |
959 |
982 |
960 def defineparents(repo, rev, dest, state): |
983 def defineparents(repo, rev, destmap, state): |
961 """Return new parents and optionally a merge base for rev being rebased |
984 """Return new parents and optionally a merge base for rev being rebased |
962 |
985 |
963 The destination specified by "dest" cannot always be used directly because |
986 The destination specified by "dest" cannot always be used directly because |
964 previously rebase result could affect destination. For example, |
987 previously rebase result could affect destination. For example, |
965 |
988 |
979 return True |
1002 return True |
980 elif a > b: |
1003 elif a > b: |
981 return False |
1004 return False |
982 return cl.isancestor(cl.node(a), cl.node(b)) |
1005 return cl.isancestor(cl.node(a), cl.node(b)) |
983 |
1006 |
|
1007 dest = destmap[rev] |
984 oldps = repo.changelog.parentrevs(rev) # old parents |
1008 oldps = repo.changelog.parentrevs(rev) # old parents |
985 newps = [nullrev, nullrev] # new parents |
1009 newps = [nullrev, nullrev] # new parents |
986 dests = adjustdest(repo, rev, dest, state) # adjusted destinations |
1010 dests = adjustdest(repo, rev, destmap, state) # adjusted destinations |
987 bases = list(oldps) # merge base candidates, initially just old parents |
1011 bases = list(oldps) # merge base candidates, initially just old parents |
988 |
1012 |
989 if all(r == nullrev for r in oldps[1:]): |
1013 if all(r == nullrev for r in oldps[1:]): |
990 # For non-merge changeset, just move p to adjusted dest as requested. |
1014 # For non-merge changeset, just move p to adjusted dest as requested. |
991 newps[0] = dests[0] |
1015 newps[0] = dests[0] |
1236 if firstunrebased in parents: |
1260 if firstunrebased in parents: |
1237 return True |
1261 return True |
1238 |
1262 |
1239 return False |
1263 return False |
1240 |
1264 |
1241 def abort(repo, originalwd, dest, state, activebookmark=None): |
1265 def abort(repo, originalwd, destmap, state, activebookmark=None): |
1242 '''Restore the repository to its original state. Additional args: |
1266 '''Restore the repository to its original state. Additional args: |
1243 |
1267 |
1244 activebookmark: the name of the bookmark that should be active after the |
1268 activebookmark: the name of the bookmark that should be active after the |
1245 restore''' |
1269 restore''' |
1246 |
1270 |
1247 try: |
1271 try: |
1248 # If the first commits in the rebased set get skipped during the rebase, |
1272 # If the first commits in the rebased set get skipped during the rebase, |
1249 # their values within the state mapping will be the dest rev id. The |
1273 # their values within the state mapping will be the dest rev id. The |
1250 # dstates list must must not contain the dest rev (issue4896) |
1274 # dstates list must must not contain the dest rev (issue4896) |
1251 dstates = [s for s in state.values() if s >= 0 and s != dest] |
1275 dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]] |
1252 immutable = [d for d in dstates if not repo[d].mutable()] |
1276 immutable = [d for d in dstates if not repo[d].mutable()] |
1253 cleanup = True |
1277 cleanup = True |
1254 if immutable: |
1278 if immutable: |
1255 repo.ui.warn(_("warning: can't clean up public changesets %s\n") |
1279 repo.ui.warn(_("warning: can't clean up public changesets %s\n") |
1256 % ', '.join(str(repo[r]) for r in immutable), |
1280 % ', '.join(str(repo[r]) for r in immutable), |
1265 "branch, can't strip\n")) |
1289 "branch, can't strip\n")) |
1266 cleanup = False |
1290 cleanup = False |
1267 |
1291 |
1268 if cleanup: |
1292 if cleanup: |
1269 shouldupdate = False |
1293 shouldupdate = False |
1270 rebased = filter(lambda x: x >= 0 and x != dest, state.values()) |
1294 rebased = [s for r, s in state.items() |
|
1295 if s >= 0 and s != destmap[r]] |
1271 if rebased: |
1296 if rebased: |
1272 strippoints = [ |
1297 strippoints = [ |
1273 c.node() for c in repo.set('roots(%ld)', rebased)] |
1298 c.node() for c in repo.set('roots(%ld)', rebased)] |
1274 |
1299 |
1275 updateifonnodes = set(rebased) |
1300 updateifonnodes = set(rebased) |
1276 updateifonnodes.add(dest) |
1301 updateifonnodes.update(destmap.values()) |
1277 updateifonnodes.add(originalwd) |
1302 updateifonnodes.add(originalwd) |
1278 shouldupdate = repo['.'].rev() in updateifonnodes |
1303 shouldupdate = repo['.'].rev() in updateifonnodes |
1279 |
1304 |
1280 # Update away from the rebase if necessary |
1305 # Update away from the rebase if necessary |
1281 if shouldupdate or needupdate(repo, state): |
1306 if shouldupdate or needupdate(repo, state): |
1293 clearstatus(repo) |
1318 clearstatus(repo) |
1294 clearcollapsemsg(repo) |
1319 clearcollapsemsg(repo) |
1295 repo.ui.warn(_('rebase aborted\n')) |
1320 repo.ui.warn(_('rebase aborted\n')) |
1296 return 0 |
1321 return 0 |
1297 |
1322 |
1298 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased): |
1323 def buildstate(repo, destmap, collapse, obsoletenotrebased): |
1299 '''Define which revisions are going to be rebased and where |
1324 '''Define which revisions are going to be rebased and where |
1300 |
1325 |
1301 repo: repo |
1326 repo: repo |
1302 dest: context |
1327 destmap: {srcrev: destrev} |
1303 rebaseset: set of rev |
|
1304 ''' |
1328 ''' |
|
1329 rebaseset = destmap.keys() |
1305 originalwd = repo['.'].rev() |
1330 originalwd = repo['.'].rev() |
1306 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd}) |
1331 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd}) |
1307 |
1332 |
1308 # This check isn't strictly necessary, since mq detects commits over an |
1333 # This check isn't strictly necessary, since mq detects commits over an |
1309 # applied patch. But it prevents messing up the working directory when |
1334 # applied patch. But it prevents messing up the working directory when |
1310 # a partially completed rebase is blocked by mq. |
1335 # a partially completed rebase is blocked by mq. |
1311 if 'qtip' in repo.tags() and (dest.node() in |
1336 if 'qtip' in repo.tags(): |
1312 [s.node for s in repo.mq.applied]): |
1337 mqapplied = set(repo[s.node].rev() for s in repo.mq.applied) |
1313 raise error.Abort(_('cannot rebase onto an applied mq patch')) |
1338 if set(destmap.values()) & mqapplied: |
|
1339 raise error.Abort(_('cannot rebase onto an applied mq patch')) |
1314 |
1340 |
1315 roots = list(repo.set('roots(%ld)', rebaseset)) |
1341 roots = list(repo.set('roots(%ld)', rebaseset)) |
1316 if not roots: |
1342 if not roots: |
1317 raise error.Abort(_('no matching revisions')) |
1343 raise error.Abort(_('no matching revisions')) |
1318 roots.sort() |
1344 roots.sort() |
1319 state = dict.fromkeys(rebaseset, revtodo) |
1345 state = dict.fromkeys(rebaseset, revtodo) |
1320 emptyrebase = True |
1346 emptyrebase = True |
1321 for root in roots: |
1347 for root in roots: |
|
1348 dest = repo[destmap[root.rev()]] |
1322 commonbase = root.ancestor(dest) |
1349 commonbase = root.ancestor(dest) |
1323 if commonbase == root: |
1350 if commonbase == root: |
1324 raise error.Abort(_('source is ancestor of destination')) |
1351 raise error.Abort(_('source is ancestor of destination')) |
1325 if commonbase == dest: |
1352 if commonbase == dest: |
1326 wctx = repo[None] |
1353 wctx = repo[None] |
1350 desc = _ctxdesc(unfi[r]) |
1377 desc = _ctxdesc(unfi[r]) |
1351 succ = obsoletenotrebased[r] |
1378 succ = obsoletenotrebased[r] |
1352 if succ is None: |
1379 if succ is None: |
1353 msg = _('note: not rebasing %s, it has no successor\n') % desc |
1380 msg = _('note: not rebasing %s, it has no successor\n') % desc |
1354 del state[r] |
1381 del state[r] |
|
1382 del destmap[r] |
1355 else: |
1383 else: |
1356 destctx = unfi[succ] |
1384 destctx = unfi[succ] |
1357 destdesc = '%d:%s "%s"' % (destctx.rev(), destctx, |
1385 destdesc = '%d:%s "%s"' % (destctx.rev(), destctx, |
1358 destctx.description().split('\n', 1)[0]) |
1386 destctx.description().split('\n', 1)[0]) |
1359 msg = (_('note: not rebasing %s, already in destination as %s\n') |
1387 msg = (_('note: not rebasing %s, already in destination as %s\n') |
1360 % (desc, destdesc)) |
1388 % (desc, destdesc)) |
1361 del state[r] |
1389 del state[r] |
|
1390 del destmap[r] |
1362 repo.ui.status(msg) |
1391 repo.ui.status(msg) |
1363 return originalwd, dest.rev(), state |
1392 return originalwd, destmap, state |
1364 |
1393 |
1365 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None): |
1394 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None): |
1366 """dispose of rebased revision at the end of the rebase |
1395 """dispose of rebased revision at the end of the rebase |
1367 |
1396 |
1368 If `collapsedas` is not None, the rebase was a collapse whose result if the |
1397 If `collapsedas` is not None, the rebase was a collapse whose result if the |
1369 `collapsedas` node.""" |
1398 `collapsedas` node.""" |
1370 tonode = repo.changelog.node |
1399 tonode = repo.changelog.node |
1371 # Move bookmark of skipped nodes to destination. This cannot be handled |
1400 # Move bookmark of skipped nodes to destination. This cannot be handled |
1372 # by scmutil.cleanupnodes since it will treat rev as removed (no successor) |
1401 # by scmutil.cleanupnodes since it will treat rev as removed (no successor) |
1373 # and move bookmark backwards. |
1402 # and move bookmark backwards. |
1374 bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state)))) |
1403 bmchanges = [(name, tonode(max(adjustdest(repo, rev, destmap, state)))) |
1375 for rev in skipped |
1404 for rev in skipped |
1376 for name in repo.nodebookmarks(tonode(rev))] |
1405 for name in repo.nodebookmarks(tonode(rev))] |
1377 if bmchanges: |
1406 if bmchanges: |
1378 with repo.transaction('rebase') as tr: |
1407 with repo.transaction('rebase') as tr: |
1379 repo._bookmarks.applychanges(repo, tr, bmchanges) |
1408 repo._bookmarks.applychanges(repo, tr, bmchanges) |
1475 |
1504 |
1476 def _filterobsoleterevs(repo, revs): |
1505 def _filterobsoleterevs(repo, revs): |
1477 """returns a set of the obsolete revisions in revs""" |
1506 """returns a set of the obsolete revisions in revs""" |
1478 return set(r for r in revs if repo[r].obsolete()) |
1507 return set(r for r in revs if repo[r].obsolete()) |
1479 |
1508 |
1480 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest): |
1509 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap): |
1481 """return a mapping obsolete => successor for all obsolete nodes to be |
1510 """return a mapping obsolete => successor for all obsolete nodes to be |
1482 rebased that have a successors in the destination |
1511 rebased that have a successors in the destination |
1483 |
1512 |
1484 obsolete => None entries in the mapping indicate nodes with no successor""" |
1513 obsolete => None entries in the mapping indicate nodes with no successor""" |
1485 obsoletenotrebased = {} |
1514 obsoletenotrebased = {} |
1486 |
1515 |
1487 cl = repo.unfiltered().changelog |
1516 cl = repo.unfiltered().changelog |
1488 nodemap = cl.nodemap |
1517 nodemap = cl.nodemap |
1489 destnode = cl.node(dest) |
|
1490 for srcrev in rebaseobsrevs: |
1518 for srcrev in rebaseobsrevs: |
1491 srcnode = cl.node(srcrev) |
1519 srcnode = cl.node(srcrev) |
|
1520 destnode = cl.node(destmap[srcrev]) |
1492 # XXX: more advanced APIs are required to handle split correctly |
1521 # XXX: more advanced APIs are required to handle split correctly |
1493 successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode])) |
1522 successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode])) |
1494 if len(successors) == 1: |
1523 if len(successors) == 1: |
1495 # obsutil.allsuccessors includes node itself. When the list only |
1524 # obsutil.allsuccessors includes node itself. When the list only |
1496 # contains one element, it means there are no successors. |
1525 # contains one element, it means there are no successors. |