rebase: allow rebase even if some revisions need no rebase (BC) (issue5422)
This allows you to do e.g. "hg rebase -d @ -r 'draft()'" even if some
drafts are already based off of @. You'd still need to exclude
obsolete and troubled revisions, though. We will deal with those cases
later.
Implemented by treating state[rev]==rev as "no need to rebase". I
considered adding another fake revision number like revdone=-6. That
would make the code clearer in a few places, but would add extra code
in other places.
I moved the existing test out of test-rebase-base.t and into a new
file and added more tests there, since not all are using --base.
--- a/hgext/rebase.py Wed May 10 11:55:22 2017 -0700
+++ b/hgext/rebase.py Thu May 11 11:37:18 2017 -0700
@@ -384,7 +384,9 @@
names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
if names:
desc += ' (%s)' % ' '.join(names)
- if self.state[rev] == revtodo:
+ if self.state[rev] == rev:
+ ui.status(_('already rebased %s\n') % desc)
+ elif self.state[rev] == revtodo:
pos += 1
ui.status(_('rebasing %s\n') % desc)
ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
@@ -508,7 +510,7 @@
# Nodeids are needed to reset bookmarks
nstate = {}
for k, v in self.state.iteritems():
- if v > nullmerge:
+ if v > nullmerge and v != k:
nstate[repo[k].node()] = repo[v].node()
elif v == revprecursor:
succ = self.obsoletenotrebased[k]
@@ -1248,6 +1250,7 @@
roots.sort()
state = dict.fromkeys(rebaseset, revtodo)
detachset = set()
+ emptyrebase = True
for root in roots:
commonbase = root.ancestor(dest)
if commonbase == root:
@@ -1260,9 +1263,13 @@
else:
samebranch = root.branch() == dest.branch()
if not collapse and samebranch and root in dest.children():
+ # mark the revision as done by setting its new revision
+ # equal to its old (current) revisions
+ state[root.rev()] = root.rev()
repo.ui.debug('source is a child of destination\n')
- return None
+ continue
+ emptyrebase = False
repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
# Rebase tries to turn <dest> into a parent of <root> while
# preserving the number of parents of rebased changesets:
@@ -1305,6 +1312,13 @@
# ancestors of <root> not ancestors of <dest>
detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
[root.rev()]))
+ if emptyrebase:
+ return None
+ for rev in sorted(state):
+ parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
+ # if all parents of this revision are done, then so is this revision
+ if parents and all((state.get(p) == p for p in parents)):
+ state[rev] = rev
for r in detachset:
if r not in state:
state[r] = nullmerge
@@ -1332,7 +1346,7 @@
if obsolete.isenabled(repo, obsolete.createmarkersopt):
markers = []
for rev, newrev in sorted(state.items()):
- if newrev >= 0:
+ if newrev >= 0 and newrev != rev:
if rev in skipped:
succs = ()
elif collapsedas is not None:
@@ -1343,7 +1357,8 @@
if markers:
obsolete.createmarkers(repo, markers)
else:
- rebased = [rev for rev in state if state[rev] > nullmerge]
+ rebased = [rev for rev in state
+ if state[rev] > nullmerge and state[rev] != rev]
if rebased:
stripped = []
for root in repo.set('roots(%ld)', rebased):
--- a/tests/test-rebase-base.t Wed May 10 11:55:22 2017 -0700
+++ b/tests/test-rebase-base.t Thu May 11 11:37:18 2017 -0700
@@ -298,18 +298,6 @@
|
o 0: M0
-Mixed rebasable and non-rebasable bases (unresolved, issue5422):
-
- $ rebasewithdag -b C+D -d B <<'EOS'
- > D
- > /
- > B C
- > |/
- > A
- > EOS
- nothing to rebase
- [1]
-
Disconnected graph:
$ rebasewithdag -b B -d Z <<'EOS'
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-partial.t Thu May 11 11:37:18 2017 -0700
@@ -0,0 +1,95 @@
+Tests rebasing with part of the rebase set already in the
+destination (issue5422)
+
+ $ cat >> $HGRCPATH <<EOF
+ > [extensions]
+ > rebase=
+ > drawdag=$TESTDIR/drawdag.py
+ >
+ > [experimental]
+ > evolution=createmarkers,allowunstable
+ >
+ > [alias]
+ > tglog = log -G --template "{rev}: {desc}"
+ > EOF
+
+ $ rebasewithdag() {
+ > N=`$PYTHON -c "print($N+1)"`
+ > hg init repo$N && cd repo$N
+ > hg debugdrawdag
+ > hg rebase "$@" > _rebasetmp
+ > r=$?
+ > grep -v 'saved backup bundle' _rebasetmp
+ > [ $r -eq 0 ] && hg tglog
+ > cd ..
+ > return $r
+ > }
+
+Rebase two commits, of which one is already in the right place
+
+ $ rebasewithdag -r C+D -d B <<EOF
+ > C
+ > |
+ > B D
+ > |/
+ > A
+ > EOF
+ rebasing 2:b18e25de2cf5 "D" (D)
+ already rebased 3:26805aba1e60 "C" (C tip)
+ o 4: D
+ |
+ | o 3: C
+ |/
+ | x 2: D
+ | |
+ o | 1: B
+ |/
+ o 0: A
+
+Can collapse commits even if one is already in the right place
+
+ $ rebasewithdag --collapse -r C+D -d B <<EOF
+ > C
+ > |
+ > B D
+ > |/
+ > A
+ > EOF
+ rebasing 2:b18e25de2cf5 "D" (D)
+ rebasing 3:26805aba1e60 "C" (C tip)
+ o 4: Collapsed revision
+ | * D
+ | * C
+ | x 3: C
+ |/
+ | x 2: D
+ | |
+ o | 1: B
+ |/
+ o 0: A
+
+Rebase with "holes". The commits after the hole should end up on the parent of
+the hole (B below), not on top of the destination (A).
+
+ $ rebasewithdag -r B+D -d A <<EOF
+ > D
+ > |
+ > C
+ > |
+ > B
+ > |
+ > A
+ > EOF
+ already rebased 1:112478962961 "B" (B)
+ not rebasing ignored 2:26805aba1e60 "C" (C)
+ rebasing 3:f585351a92f8 "D" (D tip)
+ o 4: D
+ |
+ | x 3: D
+ | |
+ | o 2: C
+ |/
+ o 1: B
+ |
+ o 0: A
+