# HG changeset patch # User Pierre-Yves David # Date 2013-01-18 22:41:48 # Node ID 7159426c8d13647679e47f4692d5aeb7cdf31baa # Parent c83d36b81df131640281c20e7100e6e8a33bc009 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 @@ -23,6 +23,7 @@ from mercurial.i18n import _ import os, errno nullmerge = -2 +revignored = -3 cmdtable = {} command = cmdutil.command(cmdtable) @@ -392,6 +393,15 @@ def rebasenode(repo, rev, p1, state, col # 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 @@ def defineparents(repo, rev, target, sta 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 @@ def defineparents(repo, rev, target, sta 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 @@ def restorestatus(repo): 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 @@ def buildstate(repo, dest, rebaseset, co 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): 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 @@ -366,11 +366,11 @@ 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 | |