diff --git a/hgext/rebase.py b/hgext/rebase.py --- a/hgext/rebase.py +++ b/hgext/rebase.py @@ -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') diff --git a/mercurial/obsolete.py b/mercurial/obsolete.py --- a/mercurial/obsolete.py +++ b/mercurial/obsolete.py @@ -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, } diff --git a/mercurial/rewriteutil.py b/mercurial/rewriteutil.py --- a/mercurial/rewriteutil.py +++ b/mercurial/rewriteutil.py @@ -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 (, ) 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': diff --git a/tests/test-amend.t b/tests/test-amend.t --- a/tests/test-amend.t +++ b/tests/test-amend.t @@ -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 diff --git a/tests/test-branch-change.t b/tests/test-branch-change.t --- a/tests/test-branch-change.t +++ b/tests/test-branch-change.t @@ -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 diff --git a/tests/test-obshistory.t b/tests/test-obshistory.t --- a/tests/test-obshistory.t +++ b/tests/test-obshistory.t @@ -13,6 +13,7 @@ > [experimental] > evolution.createmarkers = yes > evolution.effect-flags = yes + > evolution.allowdivergence=true > EOF Test output on amended commit diff --git a/tests/test-obsmarker-template.t b/tests/test-obsmarker-template.t --- a/tests/test-obsmarker-template.t +++ b/tests/test-obsmarker-template.t @@ -11,6 +11,7 @@ > publish=False > [experimental] > evolution=true + > evolution.allowdivergence=true > [templates] > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}" > obsfateverb = "{obsfateverb(successors, markers)}" diff --git a/tests/test-unamend.t b/tests/test-unamend.t --- a/tests/test-unamend.t +++ b/tests/test-unamend.t @@ -6,6 +6,7 @@ > glog = log -G -T '{rev}:{node|short} {desc}' > [experimental] > evolution = createmarkers, allowunstable + > evolution.allowdivergence = true > [extensions] > rebase = > amend =