# HG changeset patch # User Martin von Zweigbergk # Date 1494527838 25200 # Node ID 78496ac300255e9996b3e282086661afc08af37c # Parent 6096d27dc1193070fd56ea94b5227da1ef69e5f3 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. diff -r 6096d27dc119 -r 78496ac30025 hgext/rebase.py --- 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 into a parent of while # preserving the number of parents of rebased changesets: @@ -1305,6 +1312,13 @@ # ancestors of not ancestors of 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): diff -r 6096d27dc119 -r 78496ac30025 tests/test-rebase-base.t --- 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' diff -r 6096d27dc119 -r 78496ac30025 tests/test-rebase-partial.t --- /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 < [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 < 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 < 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 < 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 +