[PATCH] rebase: use revset as soon as possible in internal logic

Pierre-Yves David pierre-yves.david at ens-lyon.org
Sat Oct 15 12:11:35 CDT 2011


# HG changeset patch
# User Pierre-Yves David <pierre-yves.david at ens-lyon.org>
# Date 1318698471 -7200
# Node ID f608548d5af3bec27d54ed650fc63646032368b1
# Parent  8bea39ca9acbab3211f40e3365ea2203bf164d41
rebase: use revset as soon as possible in internal logic

The buildstate function now take a set of revs. Logic related to --source and
--base option have been moved in the main rebase function.

In the process this fixes a bug where the wrong source changeset might be pick.
This explain the changes in hgext/rebase.py

diff --git a/hgext/rebase.py b/hgext/rebase.py
--- a/hgext/rebase.py
+++ b/hgext/rebase.py
@@ -165,7 +165,27 @@
                     raise util.Abort(_('cannot specify a base with detach'))
 
             cmdutil.bailifchanged(repo)
-            result = buildstate(repo, destf, srcf, basef, detachf)
+
+            if not destf:
+                # Destination defaults to the latest revision in the current branch
+                branch = repo[None].branch()
+                dest = repo[branch]
+            else:
+                dest = repo[destf]
+
+            if srcf:
+                revsetargs = ('(%s)::', srcf)
+            else:
+                base = basef or '.'
+                revsetargs = ('(children(ancestor(%s, %d)) and ::(%s))::',
+                             base, dest, base)
+
+            rebaseset = [c.rev() for c in repo.set(*revsetargs)]
+            if rebaseset:
+                result = buildstate(repo, dest, rebaseset, detachf)
+            else:
+                repo.ui.debug(_('base is ancestor of destination'))
+                result = None
             if not result:
                 # Empty state built, nothing to rebase
                 ui.status(_('nothing to rebase\n'))
@@ -507,71 +527,47 @@
         repo.ui.warn(_('rebase aborted\n'))
         return 0
 
-def buildstate(repo, dest, src, base, detach):
-    'Define which revisions are going to be rebased and where'
-    targetancestors = set()
-    detachset = set()
+def buildstate(repo, dest, rebaseset, detach):
+    '''Define which revisions are going to be rebased and where
 
-    if not dest:
-        # Destination defaults to the latest revision in the current branch
-        branch = repo[None].branch()
-        dest = repo[branch].rev()
-    else:
-        dest = repo[dest].rev()
+    repo: repo
+    dest: context
+    rebaseset: set of rev
+    detach: boolean'''
 
     # This check isn't strictly necessary, since mq detects commits over an
     # applied patch. But it prevents messing up the working directory when
     # a partially completed rebase is blocked by mq.
-    if 'qtip' in repo.tags() and (repo[dest].node() in
+    if 'qtip' in repo.tags() and (dest.node() in
                             [s.node for s in repo.mq.applied]):
         raise util.Abort(_('cannot rebase onto an applied mq patch'))
 
-    if src:
-        commonbase = repo[src].ancestor(repo[dest])
-        if commonbase == repo[src]:
-            raise util.Abort(_('source is ancestor of destination'))
-        if commonbase == repo[dest]:
-            samebranch = repo[src].branch() == repo[dest].branch()
-            if samebranch and repo[src] in repo[dest].children():
-                raise util.Abort(_('source is a child of destination'))
-            # rebase on ancestor, force detach
-            detach = True
-        source = repo[src].rev()
-        if detach:
-            # We need to keep track of source's ancestors up to the common base
-            srcancestors = set(repo.changelog.ancestors(source))
-            baseancestors = set(repo.changelog.ancestors(commonbase.rev()))
-            detachset = srcancestors - baseancestors
-            detachset.discard(commonbase.rev())
-    else:
-        if base:
-            cwd = repo[base].rev()
-        else:
-            cwd = repo['.'].rev()
+    detachset = set()
+    roots = list(repo.set('roots(%ld)', rebaseset))
+    if not roots:
+        raise util.Abort( _('no matching revisions'))
+    if len(roots) > 1:
+        raise util.Abort( _("Can't multiple roots"))
+    root = roots[0]
 
-        if cwd == dest:
-            repo.ui.debug('source and destination are the same\n')
-            return None
+    commonbase = root.ancestor(dest)
+    if commonbase == root:
+        raise util.Abort(_('source is ancestor of destination'))
+    if commonbase == dest:
+        samebranch = root.branch() == dest.branch()
+        if samebranch and root in dest.children():
+           repo.ui.debug(_('source is a child of destination'))
+           return None
+        # rebase on ancestor, force detach
+        detach = True
+    if detach:
+        detachset = [c.rev() for c in repo.set('::%d - ::%d - %d',
+                                                root, commonbase, root)]
 
-        targetancestors = set(repo.changelog.ancestors(dest))
-        if cwd in targetancestors:
-            repo.ui.debug('source is ancestor of destination\n')
-            return None
-
-        cwdancestors = set(repo.changelog.ancestors(cwd))
-        if dest in cwdancestors:
-            repo.ui.debug('source is descendant of destination\n')
-            return None
-
-        cwdancestors.add(cwd)
-        rebasingbranch = cwdancestors - targetancestors
-        source = min(rebasingbranch)
-
-    repo.ui.debug('rebase onto %d starting from %d\n' % (dest, source))
-    state = dict.fromkeys(repo.changelog.descendants(source), nullrev)
+    repo.ui.debug('rebase onto %d starting from %d\n' % (dest, root))
+    state = dict.fromkeys(rebaseset, nullrev)
     state.update(dict.fromkeys(detachset, nullmerge))
-    state[source] = nullrev
-    return repo['.'].rev(), repo[dest].rev(), state
+    return repo['.'].rev(), dest.rev(), state
 
 def pullrebase(orig, ui, repo, *args, **opts):
     'Call rebase after pull if the latter has been invoked with --rebase'
diff --git a/tests/test-rebase-collapse.t b/tests/test-rebase-collapse.t
--- a/tests/test-rebase-collapse.t
+++ b/tests/test-rebase-collapse.t
@@ -74,12 +74,12 @@
   $ cd ..
 
 
-Rebasing G onto H:
+Rebasing E onto H:
 
   $ hg clone -q -u . a a2
   $ cd a2
 
-  $ hg rebase --base 6 --collapse
+  $ hg rebase --source 4 --collapse
   saved backup bundle to $TESTTMP/a2/.hg/strip-backup/*-backup.hg (glob)
 
   $ hg tglog
@@ -115,7 +115,7 @@
   abort: message can only be specified with collapse
   [255]
 
-  $ hg rebase --base 6 --collapse -m 'custom message'
+  $ hg rebase --source 4 --collapse -m 'custom message'
   saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
 
   $ hg tglog
diff --git a/tests/test-rebase-parameters.t b/tests/test-rebase-parameters.t
--- a/tests/test-rebase-parameters.t
+++ b/tests/test-rebase-parameters.t
@@ -51,8 +51,8 @@
   $ cd a1
 
   $ hg rebase -s 8 -d 7
-  abort: source is a child of destination
-  [255]
+  nothing to rebase
+  [1]
 
   $ hg rebase --continue --abort
   abort: cannot use both abort and continue
@@ -76,7 +76,7 @@
 
   $ hg up -q 7
 
-  $ hg rebase
+  $ hg rebase --traceback
   nothing to rebase
   [1]
 
diff --git a/tests/test-rebase-scenario-global.t b/tests/test-rebase-scenario-global.t
--- a/tests/test-rebase-scenario-global.t
+++ b/tests/test-rebase-scenario-global.t
@@ -212,8 +212,8 @@
   $ cd a7
 
   $ hg rebase -s 6 -d 5
-  abort: source is a child of destination
-  [255]
+  nothing to rebase
+  [1]
 
 F onto G - rebase onto a descendant:
 


More information about the Mercurial-devel mailing list