Mercurial > hg
diff hgext/rebase.py @ 26349:92409f8dff5d
rebase: don't rebase obsolete commit whose successor is already rebased
This patch avoids unnecessary conflicts to resolve during rebase for the users
of changeset evolution.
This patch modifies rebase to skip obsolete commits if they are being rebased on
their successors.
It introduces a new rebase state 'revprecursor' for these revisions that are
being skipped and a new message to inform the user of what is happening.
This feature is gated behind the config flag experimental.rebaseskipobsolete
When an obsolete commit is skipped, the output is:
not rebasing 14:9ad579b4a5de "I", already in destination as 17:fc37a630c901 "K"
author | Laurent Charignon <lcharignon@fb.com> |
---|---|
date | Mon, 14 Sep 2015 17:31:48 -0700 |
parents | 3f8c5c284c86 |
children | b2415e94b2f5 |
line wrap: on
line diff
--- a/hgext/rebase.py Thu Sep 24 00:34:15 2015 -0700 +++ b/hgext/rebase.py Mon Sep 14 17:31:48 2015 -0700 @@ -26,6 +26,7 @@ revtodo = -1 nullmerge = -2 revignored = -3 +revprecursor = -4 cmdtable = {} command = cmdutil.command(cmdtable) @@ -333,7 +334,20 @@ " unrebased descendants"), hint=_('use --keep to keep original changesets')) - result = buildstate(repo, dest, rebaseset, collapsef) + obsoletenotrebased = {} + if ui.configbool('experimental', 'rebaseskipobsolete'): + rebasesetrevs = set(rebaseset) + obsoletenotrebased = _computeobsoletenotrebased(repo, + rebasesetrevs, + dest) + + # - plain prune (no successor) changesets are rebased + # - split changesets are not rebased if at least one of the + # changeset resulting from the split is an ancestor of dest + rebaseset = rebasesetrevs - set(obsoletenotrebased) + result = buildstate(repo, dest, rebaseset, collapsef, + obsoletenotrebased) + if not result: # Empty state built, nothing to rebase ui.status(_('nothing to rebase\n')) @@ -439,6 +453,12 @@ ui.debug('ignoring null merge rebase of %s\n' % rev) elif state[rev] == revignored: ui.status(_('not rebasing ignored %s\n') % desc) + elif state[rev] == revprecursor: + targetctx = repo[obsoletenotrebased[rev]] + desctarget = '%d:%s "%s"' % (targetctx.rev(), targetctx, + targetctx.description().split('\n', 1)[0]) + msg = _('note: not rebasing %s, already in destination as %s\n') + ui.status(msg % (desc, desctarget)) else: ui.status(_('already rebased %s as %s\n') % (desc, repo[state[rev]])) @@ -620,7 +640,7 @@ elif p1n in state: if state[p1n] == nullmerge: p1 = target - elif state[p1n] == revignored: + elif state[p1n] in (revignored, revprecursor): p1 = nearestrebased(repo, p1n, state) if p1 is None: p1 = target @@ -636,7 +656,7 @@ if p2n in state: if p1 == target: # p1n in targetancestors or external p1 = state[p2n] - elif state[p2n] == revignored: + elif state[p2n] in (revignored, revprecursor): p2 = nearestrebased(repo, p2n, state) if p2 is None: # no ancestors rebased yet, detach @@ -824,7 +844,8 @@ activebookmark = l else: oldrev, newrev = l.split(':') - if newrev in (str(nullmerge), str(revignored)): + if newrev in (str(nullmerge), str(revignored), + str(revprecursor)): state[repo[oldrev].rev()] = int(newrev) elif newrev == nullid: state[repo[oldrev].rev()] = revtodo @@ -912,7 +933,7 @@ repo.ui.warn(_('rebase aborted\n')) return 0 -def buildstate(repo, dest, rebaseset, collapse): +def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased): '''Define which revisions are going to be rebased and where repo: repo @@ -999,6 +1020,8 @@ rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset)) for ignored in set(rebasedomain) - set(rebaseset): state[ignored] = revignored + for r in obsoletenotrebased: + state[r] = revprecursor return repo['.'].rev(), dest.rev(), state def clearrebased(ui, repo, state, skipped, collapsedas=None): @@ -1107,6 +1130,32 @@ blockers.update(getattr(repo, '_rebaseset', ())) return blockers +def _computeobsoletenotrebased(repo, rebasesetrevs, dest): + """return a mapping obsolete => successor for all obsolete nodes to be + rebased that have a successors in the destination""" + obsoletenotrebased = {} + + # Build a mapping succesor => obsolete nodes for the obsolete + # nodes to be rebased + allsuccessors = {} + for r in rebasesetrevs: + n = repo[r] + if n.obsolete(): + node = repo.changelog.node(r) + for s in obsolete.allsuccessors(repo.obsstore, [node]): + allsuccessors[repo.changelog.rev(s)] = repo.changelog.rev(node) + + if allsuccessors: + # Look for successors of obsolete nodes to be rebased among + # the ancestors of dest + ancs = repo.changelog.ancestors([repo[dest].rev()], + stoprev=min(allsuccessors), + inclusive=True) + for s in allsuccessors: + if s in ancs: + obsoletenotrebased[allsuccessors[s]] = s + return obsoletenotrebased + def summaryhook(ui, repo): if not os.path.exists(repo.join('rebasestate')): return