rewriteutil: check for divergence
This code is adapted from the code in the evolve extension. It seems
to be equivalent as far as the evolve extension's test suite can tell
(the only impact when making their `precheck()` delegate to our
version is that error messages are less detailed).
I had to change the error message to work with "change branch of"
being inserted as the action.
Differential Revision: https://phab.mercurial-scm.org/D10518
--- a/hgext/rebase.py Wed Apr 28 08:48:10 2021 -0700
+++ b/hgext/rebase.py Tue Feb 23 10:28:42 2021 -0800
@@ -446,8 +446,15 @@
rebaseset = set(destmap.keys())
rebaseset -= set(self.obsolete_with_successor_in_destination)
rebaseset -= self.obsolete_with_successor_in_rebase_set
+ # We have our own divergence-checking in the rebase extension
+ overrides = {}
+ if obsolete.isenabled(self.repo, obsolete.createmarkersopt):
+ overrides = {
+ (b'experimental', b'evolution.allowdivergence'): b'true'
+ }
try:
- rewriteutil.precheck(self.repo, rebaseset, action=b'rebase')
+ with self.ui.configoverride(overrides):
+ rewriteutil.precheck(self.repo, rebaseset, action=b'rebase')
except error.Abort as e:
if e.hint is None:
e.hint = _(b'use --keep to keep original changesets')
--- a/mercurial/obsolete.py Wed Apr 28 08:48:10 2021 -0700
+++ b/mercurial/obsolete.py Tue Feb 23 10:28:42 2021 -0800
@@ -106,6 +106,7 @@
# Options for obsolescence
createmarkersopt = b'createmarkers'
allowunstableopt = b'allowunstable'
+allowdivergenceopt = b'allowdivergence'
exchangeopt = b'exchange'
@@ -144,10 +145,13 @@
createmarkersvalue = _getoptionvalue(repo, createmarkersopt)
unstablevalue = _getoptionvalue(repo, allowunstableopt)
+ divergencevalue = _getoptionvalue(repo, allowdivergenceopt)
exchangevalue = _getoptionvalue(repo, exchangeopt)
# createmarkers must be enabled if other options are enabled
- if (unstablevalue or exchangevalue) and not createmarkersvalue:
+ if (
+ unstablevalue or divergencevalue or exchangevalue
+ ) and not createmarkersvalue:
raise error.Abort(
_(
b"'createmarkers' obsolete option must be enabled "
@@ -158,6 +162,7 @@
return {
createmarkersopt: createmarkersvalue,
allowunstableopt: unstablevalue,
+ allowdivergenceopt: divergencevalue,
exchangeopt: exchangevalue,
}
--- a/mercurial/rewriteutil.py Wed Apr 28 08:48:10 2021 -0700
+++ b/mercurial/rewriteutil.py Tue Feb 23 10:28:42 2021 -0800
@@ -44,7 +44,9 @@
revs = (r.rev() for r in revs)
if len(repo[None].parents()) > 1:
- raise error.StateError(_(b"cannot %s changesets while merging") % action)
+ raise error.StateError(
+ _(b"cannot %s changesets while merging") % action
+ )
publicrevs = repo.revs(b'%ld and public()', revs)
if publicrevs:
@@ -59,6 +61,38 @@
_(b"cannot %s changeset with children") % action, hint=hint
)
+ if not obsolete.isenabled(repo, obsolete.allowdivergenceopt):
+ new_divergence = _find_new_divergence(repo, revs)
+ if new_divergence:
+ local_ctx, other_ctx, base_ctx = new_divergence
+ msg = _(
+ b'cannot %s %s, as that creates content-divergence with %s'
+ ) % (
+ action,
+ local_ctx,
+ other_ctx,
+ )
+ if local_ctx.rev() != base_ctx.rev():
+ msg += _(b', from %s') % base_ctx
+ if repo.ui.verbose:
+ if local_ctx.rev() != base_ctx.rev():
+ msg += _(
+ b'\n changeset %s is a successor of ' b'changeset %s'
+ ) % (local_ctx, base_ctx)
+ msg += _(
+ b'\n changeset %s already has a successor in '
+ b'changeset %s\n'
+ b' rewriting changeset %s would create '
+ b'"content-divergence"\n'
+ b' set experimental.evolution.allowdivergence=True to '
+ b'skip this check'
+ ) % (base_ctx, other_ctx, local_ctx)
+ raise error.InputError(msg)
+ else:
+ raise error.InputError(
+ msg, hint=_(b"add --verbose for details")
+ )
+
def disallowednewunstable(repo, revs):
"""Checks whether editing the revs will create new unstable changesets and
@@ -73,6 +107,40 @@
return repo.revs(b"(%ld::) - %ld", revs, revs)
+def _find_new_divergence(repo, revs):
+ obsrevs = repo.revs(b'%ld and obsolete()', revs)
+ for r in obsrevs:
+ div = find_new_divergence_from(repo, repo[r])
+ if div:
+ return (repo[r], repo[div[0]], repo[div[1]])
+ return None
+
+
+def find_new_divergence_from(repo, ctx):
+ """return divergent revision if rewriting an obsolete cset (ctx) will
+ create divergence
+
+ Returns (<other node>, <common ancestor node>) or None
+ """
+ if not ctx.obsolete():
+ return None
+ # We need to check two cases that can cause divergence:
+ # case 1: the rev being rewritten has a non-obsolete successor (easily
+ # detected by successorssets)
+ sset = obsutil.successorssets(repo, ctx.node())
+ if sset:
+ return (sset[0][0], ctx.node())
+ else:
+ # case 2: one of the precursors of the rev being revived has a
+ # non-obsolete successor (we need divergentsets for this)
+ divsets = obsutil.divergentsets(repo, ctx)
+ if divsets:
+ nsuccset = divsets[0][b'divergentnodes']
+ prec = divsets[0][b'commonpredecessor']
+ return (nsuccset[0], prec)
+ return None
+
+
def skip_empty_successor(ui, command):
empty_successor = ui.config(b'rewrite', b'empty-successor')
if empty_successor == b'skip':
--- a/tests/test-amend.t Wed Apr 28 08:48:10 2021 -0700
+++ b/tests/test-amend.t Tue Feb 23 10:28:42 2021 -0800
@@ -232,6 +232,17 @@
$ hg debugobsolete -r .
112478962961147124edd43549aedd1a335e44bf be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 16084da537dd8f84cfdb3055c633772269d62e1b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'note': 'adding bar', 'operation': 'amend', 'user': 'test'}
+
+Cannot cause divergence by default
+
+ $ hg co --hidden 1
+ 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+ $ hg amend -m divergent
+ abort: cannot amend 112478962961, as that creates content-divergence with 16084da537dd
+ (add --verbose for details)
+ [10]
+ $ hg amend -m divergent --config experimental.evolution.allowdivergence=true
+ 2 new content-divergent changesets
#endif
Cannot amend public changeset
--- a/tests/test-branch-change.t Wed Apr 28 08:48:10 2021 -0700
+++ b/tests/test-branch-change.t Tue Feb 23 10:28:42 2021 -0800
@@ -150,7 +150,8 @@
[255]
$ hg branch -r 4 --hidden foobar
- abort: cannot change branch of a obsolete changeset
+ abort: cannot change branch of 3938acfb5c0f, as that creates content-divergence with 7c1991464886
+ (add --verbose for details)
[10]
Make sure bookmark movement is correct
--- a/tests/test-obshistory.t Wed Apr 28 08:48:10 2021 -0700
+++ b/tests/test-obshistory.t Tue Feb 23 10:28:42 2021 -0800
@@ -13,6 +13,7 @@
> [experimental]
> evolution.createmarkers = yes
> evolution.effect-flags = yes
+ > evolution.allowdivergence=true
> EOF
Test output on amended commit
--- a/tests/test-obsmarker-template.t Wed Apr 28 08:48:10 2021 -0700
+++ b/tests/test-obsmarker-template.t Tue Feb 23 10:28:42 2021 -0800
@@ -11,6 +11,7 @@
> publish=False
> [experimental]
> evolution=true
+ > evolution.allowdivergence=true
> [templates]
> obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
> obsfateverb = "{obsfateverb(successors, markers)}"
--- a/tests/test-unamend.t Wed Apr 28 08:48:10 2021 -0700
+++ b/tests/test-unamend.t Tue Feb 23 10:28:42 2021 -0800
@@ -6,6 +6,7 @@
> glog = log -G -T '{rev}:{node|short} {desc}'
> [experimental]
> evolution = createmarkers, allowunstable
+ > evolution.allowdivergence = true
> [extensions]
> rebase =
> amend =