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
--- 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: