rebase: use revset as soon as possible in internal logic
authorPierre-Yves David <pierre-yves.david@ens-lyon.org>
Sat, 15 Oct 2011 19:07:51 +0200
changeset 15267 3bfdfefea2fc
parent 15266 8bea39ca9acb
child 15268 bd5103819c2e
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
hgext/rebase.py
tests/test-rebase-collapse.t
tests/test-rebase-parameters.t
tests/test-rebase-scenario-global.t
--- a/hgext/rebase.py	Sat Oct 15 10:20:08 2011 -0500
+++ b/hgext/rebase.py	Sat Oct 15 19:07:51 2011 +0200
@@ -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 rebase multiple roots"))
+    root = roots[0]
 
-        if cwd == dest:
-            repo.ui.debug('source and destination are the same\n')
-            return None
-
-        targetancestors = set(repo.changelog.ancestors(dest))
-        if cwd in targetancestors:
-            repo.ui.debug('source is ancestor of destination\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)]
 
-        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'
--- a/tests/test-rebase-collapse.t	Sat Oct 15 10:20:08 2011 -0500
+++ b/tests/test-rebase-collapse.t	Sat Oct 15 19:07:51 2011 +0200
@@ -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
--- a/tests/test-rebase-parameters.t	Sat Oct 15 10:20:08 2011 -0500
+++ b/tests/test-rebase-parameters.t	Sat Oct 15 19:07:51 2011 +0200
@@ -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]
 
--- a/tests/test-rebase-scenario-global.t	Sat Oct 15 10:20:08 2011 -0500
+++ b/tests/test-rebase-scenario-global.t	Sat Oct 15 19:07:51 2011 +0200
@@ -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: