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.
--- a/hgext/rebase.py Fri Jan 18 23:21:32 2013 +0100
+++ b/hgext/rebase.py Fri Jan 18 23:41:48 2013 +0100
@@ -23,6 +23,7 @@
import os, errno
nullmerge = -2
+revignored = -3
cmdtable = {}
command = cmdutil.command(cmdtable)
@@ -392,6 +393,15 @@
# 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()
@@ -403,6 +413,10 @@
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
@@ -415,6 +429,11 @@
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
@@ -532,10 +551,10 @@
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:
@@ -658,6 +677,15 @@
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):
--- a/tests/test-rebase-obsolete.t Fri Jan 18 23:21:32 2013 +0100
+++ b/tests/test-rebase-obsolete.t Fri Jan 18 23:41:48 2013 +0100
@@ -366,11 +366,11 @@
$ 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
| |