[PATCH 3 of 3 two-rebase-fixes] rebase: properly handle unrebased revision between rebased one

Pierre-Yves David pierre-yves.david at ens-lyon.org
Fri Jan 18 16:50:59 CST 2013


# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at ens-lyon.org>
# Date 1358548908 -3600
# Node ID 6e0c775e67c12b22a58dfc771e4e00784eaf1523
# Parent  124ad8551b284a700309b2c4cbcffb6b36385605
rebase: properly handle unrebased revision between rebased one

With rebase taking multiple roots it is possible to have revision in the "rebase
domain" not rebased themself. We do not want rebased revision above them to be
detached. We want such revision to be rebased on the nearest rebased ancestors.
This allows to preserve the topology of the rebase set as much a possible

To achieve this we introduce a new state `revignored` which informs
`defineparents` of the situation.

The test in `test-rebase-obsolete.t` was actually wrote and his now fixed.

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -21,10 +21,11 @@ from mercurial.node import nullrev
 from mercurial.lock import release
 from mercurial.i18n import _
 import os, errno
 
 nullmerge = -2
+revignored = -3
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
 testedwith = 'internal'
 
@@ -390,10 +391,19 @@ def rebasenode(repo, rev, p1, state, col
         base = repo[rev].p1().node()
     # When collapsing in-place, the parent is the common ancestor, we
     # have to allow merging with it.
     return merge.update(repo, rev, True, True, False, base, collapse)
 
+def nearestrebased(repo, rev, state):
+    """return the nearest ancestors of rev in the rebase result"""
+    rebased = [r for r in state if state[r] > nullmerge]
+    candidates = repo.revs('max(%ld  and (::%d))', rebased, rev)
+    if candidates:
+        return state[candidates[0]]
+    else:
+        return None
+
 def defineparents(repo, rev, target, state, targetancestors):
     'Return the new parent relationship of the revision that will be rebased'
     parents = repo[rev].parents()
     p1 = p2 = nullrev
 
@@ -401,10 +411,14 @@ def defineparents(repo, rev, target, sta
     if P1n in targetancestors:
         p1 = target
     elif P1n in state:
         if state[P1n] == nullmerge:
             p1 = target
+        elif state[P1n] == revignored:
+            p1 = nearestrebased(repo, P1n, state)
+            if p1 is None:
+                p1 = target
         else:
             p1 = state[P1n]
     else: # P1n external
         p1 = target
         p2 = P1n
@@ -413,10 +427,15 @@ def defineparents(repo, rev, target, sta
         P2n = parents[1].rev()
         # interesting second parent
         if P2n in state:
             if p1 == target: # P1n in targetancestors or external
                 p1 = state[P2n]
+            elif state[P2n] == revignored:
+                p2 = nearestrebased(repo, P2n, state)
+                if p2 is None:
+                    # no ancestors rebased yet, detach
+                    p2 = target
             else:
                 p2 = state[P2n]
         else: # P2n external
             if p2 != nullrev: # P1n external too => rev is a merged revision
                 raise util.Abort(_('cannot use revision %d as base, result '
@@ -530,14 +549,14 @@ def restorestatus(repo):
                 keep = bool(int(l))
             elif i == 5:
                 keepbranches = bool(int(l))
             else:
                 oldrev, newrev = l.split(':')
-                if newrev != str(nullmerge):
+                if newrev in (str(nullmerge), str(revignored)):
+                    state[repo[oldrev].rev()] = int(newrev)
+                else:
                     state[repo[oldrev].rev()] = repo[newrev].rev()
-                else:
-                    state[repo[oldrev].rev()] = int(newrev)
         skipped = set()
         # recompute the set of skipped revs
         if not collapse:
             seen = set([target])
             for old, new in sorted(state.items()):
@@ -656,10 +675,19 @@ def buildstate(repo, dest, rebaseset, co
             detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
                                                             [root.rev()]))
     for r in detachset:
         if r not in state:
             state[r] = nullmerge
+    if len(roots) > 1:
+        # If we have multiple roots, we may have "hole" in the rebase set.
+        # Rebase roots that descend from those "hole" should not be detached as
+        # other root are. We use the special `revignored` to inform rebase that
+        # the revision should be ignored but that `defineparent` should search
+        # a rebase destination that make sense regarding rebaset topology.
+        rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
+        for ignored in set(rebasedomain) - set(rebaseset):
+            state[ignored] = revignored
     return repo['.'].rev(), dest.rev(), state
 
 def clearrebased(ui, repo, state, skipped, collapsedas=None):
     """dispose of rebased revision at the end of the rebase
 
diff --git a/tests/test-rebase-obsolete.t b/tests/test-rebase-obsolete.t
--- a/tests/test-rebase-obsolete.t
+++ b/tests/test-rebase-obsolete.t
@@ -364,15 +364,15 @@ Test that rewriting leaving instability 
 Test multiple root handling
 ------------------------------------
 
   $ hg rebase --dest 4 --rev '7+11+9'
   $ hg log -G
-  @  14:00891d85fcfc C
+  @  14:1e8370e38cca C
   |
   | o  13:102b4c1d889b D
-  |/
-  | o  12:bfe264faf697 H
+  | |
+  o |  12:bfe264faf697 H
   |/
   | o  10:7c6027df6a99 B
   | |
   | x  7:02de42196ebe H
   | |


More information about the Mercurial-devel mailing list