comparison hgext/rebase.py @ 34008:9422107a6b64

rebase: move working parent and bookmark for obsoleted revs (BC) Previously, obsoleted revs with successors in destination are completely ignored. That caused some inconvenience when working copy is obsoleted. Most commands avoid working copy being obsoleted, but `hg pull` is an exception. This patch makes rebase able to move bookmarks or working parent for those obsoleted revs. It does so by keeping the obsoleted revs in `state` and marking them as "skipped, rebased to desired destination" during run-time. This reverts part of the behavior change of 3b7cb3d17137 and D24. Differential Revision: https://phab.mercurial-scm.org/D527
author Jun Wu <quark@fb.com>
date Sun, 27 Aug 2017 02:47:47 -0700
parents 06b0cc2588de
children 79ab5369d55a
comparison
equal deleted inserted replaced
34007:06b0cc2588de 34008:9422107a6b64
325 raise error.Abort( 325 raise error.Abort(
326 _("can't remove original changesets with" 326 _("can't remove original changesets with"
327 " unrebased descendants"), 327 " unrebased descendants"),
328 hint=_('use --keep to keep original changesets')) 328 hint=_('use --keep to keep original changesets'))
329 329
330 obsrevs = _filterobsoleterevs(self.repo, rebaseset) 330 result = buildstate(self.repo, destmap, self.collapsef)
331 self._handleskippingobsolete(obsrevs, destmap)
332
333 result = buildstate(self.repo, destmap, self.collapsef,
334 self.obsoletenotrebased)
335 331
336 if not result: 332 if not result:
337 # Empty state built, nothing to rebase 333 # Empty state built, nothing to rebase
338 self.ui.status(_('nothing to rebase\n')) 334 self.ui.status(_('nothing to rebase\n'))
339 return _nothingtorebase() 335 return _nothingtorebase()
373 branches.add(repo[rev].branch()) 369 branches.add(repo[rev].branch())
374 if len(branches) > 1: 370 if len(branches) > 1:
375 raise error.Abort(_('cannot collapse multiple named ' 371 raise error.Abort(_('cannot collapse multiple named '
376 'branches')) 372 'branches'))
377 373
374 # Calculate self.obsoletenotrebased
375 obsrevs = _filterobsoleterevs(self.repo, self.state)
376 self._handleskippingobsolete(obsrevs, self.destmap)
377
378 # Keep track of the active bookmarks in order to reset them later 378 # Keep track of the active bookmarks in order to reset them later
379 self.activebookmark = self.activebookmark or repo._activebookmark 379 self.activebookmark = self.activebookmark or repo._activebookmark
380 if self.activebookmark: 380 if self.activebookmark:
381 bookmarks.deactivate(repo) 381 bookmarks.deactivate(repo)
382 382
399 dest = self.destmap[rev] 399 dest = self.destmap[rev]
400 ctx = repo[rev] 400 ctx = repo[rev]
401 desc = _ctxdesc(ctx) 401 desc = _ctxdesc(ctx)
402 if self.state[rev] == rev: 402 if self.state[rev] == rev:
403 ui.status(_('already rebased %s\n') % desc) 403 ui.status(_('already rebased %s\n') % desc)
404 elif rev in self.obsoletenotrebased:
405 succ = self.obsoletenotrebased[rev]
406 if succ is None:
407 msg = _('note: not rebasing %s, it has no '
408 'successor\n') % desc
409 else:
410 succctx = repo[succ]
411 succdesc = '%d:%s "%s"' % (
412 succctx.rev(), succctx,
413 succctx.description().split('\n', 1)[0])
414 msg = (_('note: not rebasing %s, already in '
415 'destination as %s\n') % (desc, succdesc))
416 repo.ui.status(msg)
417 # Make clearrebased aware state[rev] is not a true successor
418 self.skipped.add(rev)
419 # Record rev as moved to its desired destination in self.state.
420 # This helps bookmark and working parent movement.
421 dest = max(adjustdest(repo, rev, self.destmap, self.state,
422 self.skipped))
423 self.state[rev] = dest
404 elif self.state[rev] == revtodo: 424 elif self.state[rev] == revtodo:
405 pos += 1 425 pos += 1
406 ui.status(_('rebasing %s\n') % desc) 426 ui.status(_('rebasing %s\n') % desc)
407 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)), 427 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
408 _('changesets'), total) 428 _('changesets'), total)
409 p1, p2, base = defineparents(repo, rev, self.destmap, 429 p1, p2, base = defineparents(repo, rev, self.destmap,
410 self.state) 430 self.state, self.skipped,
431 self.obsoletenotrebased)
411 self.storestatus(tr=tr) 432 self.storestatus(tr=tr)
412 storecollapsemsg(repo, self.collapsemsg) 433 storecollapsemsg(repo, self.collapsemsg)
413 if len(repo[None].parents()) == 2: 434 if len(repo[None].parents()) == 2:
414 repo.ui.debug('resuming interrupted rebase\n') 435 repo.ui.debug('resuming interrupted rebase\n')
415 else: 436 else:
460 481
461 def _finishrebase(self): 482 def _finishrebase(self):
462 repo, ui, opts = self.repo, self.ui, self.opts 483 repo, ui, opts = self.repo, self.ui, self.opts
463 if self.collapsef and not self.keepopen: 484 if self.collapsef and not self.keepopen:
464 p1, p2, _base = defineparents(repo, min(self.state), self.destmap, 485 p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
465 self.state) 486 self.state, self.skipped,
487 self.obsoletenotrebased)
466 editopt = opts.get('edit') 488 editopt = opts.get('edit')
467 editform = 'rebase.collapse' 489 editform = 'rebase.collapse'
468 if self.collapsemsg: 490 if self.collapsemsg:
469 commitmsg = self.collapsemsg 491 commitmsg = self.collapsemsg
470 else: 492 else:
933 # performed in the destination. 955 # performed in the destination.
934 p1rev = repo[rev].p1().rev() 956 p1rev = repo[rev].p1().rev()
935 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest) 957 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
936 return stats 958 return stats
937 959
938 def adjustdest(repo, rev, destmap, state): 960 def adjustdest(repo, rev, destmap, state, skipped):
939 """adjust rebase destination given the current rebase state 961 """adjust rebase destination given the current rebase state
940 962
941 rev is what is being rebased. Return a list of two revs, which are the 963 rev is what is being rebased. Return a list of two revs, which are the
942 adjusted destinations for rev's p1 and p2, respectively. If a parent is 964 adjusted destinations for rev's p1 and p2, respectively. If a parent is
943 nullrev, return dest without adjustment for it. 965 nullrev, return dest without adjustment for it.
987 \ / 1009 \ /
988 A 1010 A
989 """ 1011 """
990 # pick already rebased revs with same dest from state as interesting source 1012 # pick already rebased revs with same dest from state as interesting source
991 dest = destmap[rev] 1013 dest = destmap[rev]
992 source = [s for s, d in state.items() if d > 0 and destmap[s] == dest] 1014 source = [s for s, d in state.items()
1015 if d > 0 and destmap[s] == dest and s not in skipped]
993 1016
994 result = [] 1017 result = []
995 for prev in repo.changelog.parentrevs(rev): 1018 for prev in repo.changelog.parentrevs(rev):
996 adjusted = dest 1019 adjusted = dest
997 if prev != nullrev: 1020 if prev != nullrev:
1035 nodemap = unfi.changelog.nodemap 1058 nodemap = unfi.changelog.nodemap
1036 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]): 1059 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1037 if s in nodemap: 1060 if s in nodemap:
1038 yield nodemap[s] 1061 yield nodemap[s]
1039 1062
1040 def defineparents(repo, rev, destmap, state): 1063 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1041 """Return new parents and optionally a merge base for rev being rebased 1064 """Return new parents and optionally a merge base for rev being rebased
1042 1065
1043 The destination specified by "dest" cannot always be used directly because 1066 The destination specified by "dest" cannot always be used directly because
1044 previously rebase result could affect destination. For example, 1067 previously rebase result could affect destination. For example,
1045 1068
1062 return cl.isancestor(cl.node(a), cl.node(b)) 1085 return cl.isancestor(cl.node(a), cl.node(b))
1063 1086
1064 dest = destmap[rev] 1087 dest = destmap[rev]
1065 oldps = repo.changelog.parentrevs(rev) # old parents 1088 oldps = repo.changelog.parentrevs(rev) # old parents
1066 newps = [nullrev, nullrev] # new parents 1089 newps = [nullrev, nullrev] # new parents
1067 dests = adjustdest(repo, rev, destmap, state) # adjusted destinations 1090 dests = adjustdest(repo, rev, destmap, state, skipped)
1068 bases = list(oldps) # merge base candidates, initially just old parents 1091 bases = list(oldps) # merge base candidates, initially just old parents
1069 1092
1070 if all(r == nullrev for r in oldps[1:]): 1093 if all(r == nullrev for r in oldps[1:]):
1071 # For non-merge changeset, just move p to adjusted dest as requested. 1094 # For non-merge changeset, just move p to adjusted dest as requested.
1072 newps[0] = dests[0] 1095 newps[0] = dests[0]
1189 bases, base, base, dest)) 1212 bases, base, base, dest))
1190 1213
1191 # If those revisions are covered by rebaseset, the result is good. 1214 # If those revisions are covered by rebaseset, the result is good.
1192 # A merge in rebaseset would be considered to cover its ancestors. 1215 # A merge in rebaseset would be considered to cover its ancestors.
1193 if siderevs: 1216 if siderevs:
1194 rebaseset = [r for r, d in state.items() if d > 0] 1217 rebaseset = [r for r, d in state.items()
1218 if d > 0 and r not in obsskipped]
1195 merges = [r for r in rebaseset 1219 merges = [r for r in rebaseset
1196 if cl.parentrevs(r)[1] != nullrev] 1220 if cl.parentrevs(r)[1] != nullrev]
1197 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld', 1221 unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
1198 siderevs, merges, rebaseset)) 1222 siderevs, merges, rebaseset))
1199 1223
1406 if not result: 1430 if not result:
1407 raise error.Abort(_('source and destination form a cycle')) 1431 raise error.Abort(_('source and destination form a cycle'))
1408 srcset -= set(result) 1432 srcset -= set(result)
1409 yield result 1433 yield result
1410 1434
1411 def buildstate(repo, destmap, collapse, obsoletenotrebased): 1435 def buildstate(repo, destmap, collapse):
1412 '''Define which revisions are going to be rebased and where 1436 '''Define which revisions are going to be rebased and where
1413 1437
1414 repo: repo 1438 repo: repo
1415 destmap: {srcrev: destrev} 1439 destmap: {srcrev: destrev}
1416 ''' 1440 '''
1467 for rev in sorted(state): 1491 for rev in sorted(state):
1468 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev] 1492 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1469 # if all parents of this revision are done, then so is this revision 1493 # if all parents of this revision are done, then so is this revision
1470 if parents and all((state.get(p) == p for p in parents)): 1494 if parents and all((state.get(p) == p for p in parents)):
1471 state[rev] = rev 1495 state[rev] = rev
1472 unfi = repo.unfiltered()
1473 for r in obsoletenotrebased:
1474 desc = _ctxdesc(unfi[r])
1475 succ = obsoletenotrebased[r]
1476 if succ is None:
1477 msg = _('note: not rebasing %s, it has no successor\n') % desc
1478 del state[r]
1479 del destmap[r]
1480 else:
1481 destctx = unfi[succ]
1482 destdesc = '%d:%s "%s"' % (destctx.rev(), destctx,
1483 destctx.description().split('\n', 1)[0])
1484 msg = (_('note: not rebasing %s, already in destination as %s\n')
1485 % (desc, destdesc))
1486 del state[r]
1487 del destmap[r]
1488 repo.ui.status(msg)
1489 return originalwd, destmap, state 1496 return originalwd, destmap, state
1490 1497
1491 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None): 1498 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None):
1492 """dispose of rebased revision at the end of the rebase 1499 """dispose of rebased revision at the end of the rebase
1493 1500