changeset 3462:e147c18ed064

evolvecmd: move more functions from __init__.py to evolvecmd.py If things are looking ugly, hold on.
author Pulkit Goyal <7895pulkit@gmail.com>
date Fri, 19 Jan 2018 15:04:12 +0530
parents 6475d2046f87
children f994c480cea9
files hgext3rd/evolve/__init__.py hgext3rd/evolve/evolvecmd.py
diffstat 2 files changed, 347 insertions(+), 325 deletions(-) [+]
line wrap: on
line diff
--- a/hgext3rd/evolve/__init__.py	Fri Jan 19 14:42:46 2018 +0530
+++ b/hgext3rd/evolve/__init__.py	Fri Jan 19 15:04:12 2018 +0530
@@ -285,7 +285,6 @@
     cmdutil,
     commands,
     context,
-    copies,
     dirstate,
     error,
     extensions,
@@ -1701,329 +1700,6 @@
                 result.add(unstable)
     return sorted(result - target)
 
-def _solveunstable(ui, repo, orig, dryrun=False, confirm=False,
-                   progresscb=None):
-    """ Tries to stabilize the changeset orig which is orphan.
-
-    returns a tuple (bool, newnode) where,
-        bool: a boolean value indicating whether the instability was solved
-        newnode: if bool is True, then the newnode of the resultant commit
-                 formed. newnode can be node, when resolution led to no new
-                 commit. If bool is False, this is ''.
-    """
-    pctx = orig.p1()
-    keepbranch = orig.p1().branch() != orig.branch()
-    if len(orig.parents()) == 2:
-        if not pctx.obsolete():
-            pctx = orig.p2()  # second parent is obsolete ?
-            keepbranch = orig.p2().branch() != orig.branch()
-        elif orig.p2().obsolete():
-            hint = _("Redo the merge (%s) and use `hg prune <old> "
-                     "--succ <new>` to obsolete the old one") % orig.hex()[:12]
-            ui.warn(_("warning: no support for evolving merge changesets "
-                      "with two obsolete parents yet\n") +
-                    _("(%s)\n") % hint)
-            return (False, '')
-
-    if not pctx.obsolete():
-        ui.warn(_("cannot solve instability of %s, skipping\n") % orig)
-        return (False, '')
-    obs = pctx
-    newer = compat.successorssets(repo, obs.node())
-    # search of a parent which is not killed
-    while not newer or newer == [()]:
-        ui.debug("stabilize target %s is plain dead,"
-                 " trying to stabilize on its parent\n" %
-                 obs)
-        obs = obs.parents()[0]
-        newer = compat.successorssets(repo, obs.node())
-    if len(newer) > 1:
-        msg = _("skipping %s: divergent rewriting. can't choose "
-                "destination\n") % obs
-        ui.write_err(msg)
-        return (False, '')
-    targets = newer[0]
-    assert targets
-    if len(targets) > 1:
-        # split target, figure out which one to pick, are they all in line?
-        targetrevs = [repo[r].rev() for r in targets]
-        roots = repo.revs('roots(%ld)', targetrevs)
-        heads = repo.revs('heads(%ld)', targetrevs)
-        if len(roots) > 1 or len(heads) > 1:
-            msg = "cannot solve split across two branches\n"
-            ui.write_err(msg)
-            return (False, '')
-        target = repo[heads.first()]
-    else:
-        target = targets[0]
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    target = repo[target]
-    if not ui.quiet or confirm:
-        repo.ui.write(_('move:'))
-        displayer.show(orig)
-        repo.ui.write(_('atop:'))
-        displayer.show(target)
-    if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
-            raise error.Abort(_('evolve aborted by user'))
-    if progresscb:
-        progresscb()
-    todo = 'hg rebase -r %s -d %s\n' % (orig, target)
-    if dryrun:
-        repo.ui.write(todo)
-        return (False, '')
-    else:
-        repo.ui.note(todo)
-        if progresscb:
-            progresscb()
-        try:
-            newid = relocate(repo, orig, target, pctx, keepbranch)
-            return (True, newid)
-        except MergeFailure:
-            ops = {'current': orig.node()}
-            evolvestate = state.cmdstate(repo, opts=ops)
-            evolvestate.save()
-            repo.ui.write_err(_('evolve failed!\n'))
-            repo.ui.write_err(
-                _("fix conflict and run 'hg evolve --continue'"
-                  " or use 'hg update -C .' to abort\n"))
-            raise
-
-def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False,
-                 progresscb=None):
-    """Stabilize a bumped changeset
-
-    returns a tuple (bool, newnode) where,
-        bool: a boolean value indicating whether the instability was solved
-        newnode: if bool is True, then the newnode of the resultant commit
-                 formed. newnode can be node, when resolution led to no new
-                 commit. If bool is False, this is ''.
-    """
-    repo = repo.unfiltered()
-    bumped = repo[bumped.rev()]
-    # For now we deny bumped merge
-    if len(bumped.parents()) > 1:
-        msg = _('skipping %s : we do not handle merge yet\n') % bumped
-        ui.write_err(msg)
-        return (False, '')
-    prec = repo.set('last(allprecursors(%d) and public())', bumped).next()
-    # For now we deny target merge
-    if len(prec.parents()) > 1:
-        msg = _('skipping: %s: public version is a merge, '
-                'this is not handled yet\n') % prec
-        ui.write_err(msg)
-        return (False, '')
-
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    if not ui.quiet or confirm:
-        repo.ui.write(_('recreate:'))
-        displayer.show(bumped)
-        repo.ui.write(_('atop:'))
-        displayer.show(prec)
-    if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
-        raise error.Abort(_('evolve aborted by user'))
-    if dryrun:
-        todo = 'hg rebase --rev %s --dest %s;\n' % (bumped, prec.p1())
-        repo.ui.write(todo)
-        repo.ui.write(('hg update %s;\n' % prec))
-        repo.ui.write(('hg revert --all --rev %s;\n' % bumped))
-        repo.ui.write(('hg commit --msg "%s update to %s"\n' %
-                       (TROUBLES['PHASEDIVERGENT'], bumped)))
-        return (False, '')
-    if progresscb:
-        progresscb()
-    newid = tmpctx = None
-    tmpctx = bumped
-    # Basic check for common parent. Far too complicated and fragile
-    tr = repo.currenttransaction()
-    assert tr is not None
-    bmupdate = _bookmarksupdater(repo, bumped.node(), tr)
-    if not list(repo.set('parents(%d) and parents(%d)', bumped, prec)):
-        # Need to rebase the changeset at the right place
-        repo.ui.status(
-            _('rebasing to destination parent: %s\n') % prec.p1())
-        try:
-            tmpid = relocate(repo, bumped, prec.p1())
-            if tmpid is not None:
-                tmpctx = repo[tmpid]
-                obsolete.createmarkers(repo, [(bumped, (tmpctx,))])
-        except MergeFailure:
-            repo.vfs.write('graftstate', bumped.hex() + '\n')
-            repo.ui.write_err(_('evolution failed!\n'))
-            msg = _("fix conflict and run 'hg evolve --continue'\n")
-            repo.ui.write_err(msg)
-            raise
-    # Create the new commit context
-    repo.ui.status(_('computing new diff\n'))
-    files = set()
-    copied = copies.pathcopies(prec, bumped)
-    precmanifest = prec.manifest().copy()
-    # 3.3.2 needs a list.
-    # future 3.4 don't detect the size change during iteration
-    # this is fishy
-    for key, val in list(bumped.manifest().iteritems()):
-        precvalue = precmanifest.get(key, None)
-        if precvalue is not None:
-            del precmanifest[key]
-        if precvalue != val:
-            files.add(key)
-    files.update(precmanifest)  # add missing files
-    # commit it
-    if files: # something to commit!
-        def filectxfn(repo, ctx, path):
-            if path in bumped:
-                fctx = bumped[path]
-                flags = fctx.flags()
-                mctx = compat.memfilectx(repo, ctx, fctx, flags, copied, path)
-                return mctx
-            return None
-        text = '%s update to %s:\n\n' % (TROUBLES['PHASEDIVERGENT'], prec)
-        text += bumped.description()
-
-        new = context.memctx(repo,
-                             parents=[prec.node(), node.nullid],
-                             text=text,
-                             files=files,
-                             filectxfn=filectxfn,
-                             user=bumped.user(),
-                             date=bumped.date(),
-                             extra=bumped.extra())
-
-        newid = repo.commitctx(new)
-    if newid is None:
-        obsolete.createmarkers(repo, [(tmpctx, ())])
-        newid = prec.node()
-    else:
-        phases.retractboundary(repo, tr, bumped.phase(), [newid])
-        obsolete.createmarkers(repo, [(tmpctx, (repo[newid],))],
-                               flag=obsolete.bumpedfix)
-    bmupdate(newid)
-    repo.ui.status(_('committed as %s\n') % node.short(newid))
-    # reroute the working copy parent to the new changeset
-    with repo.dirstate.parentchange():
-        repo.dirstate.setparents(newid, node.nullid)
-    return (True, newid)
-
-def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False,
-                    progresscb=None):
-    """tries to solve content-divergence of a changeset
-
-    returns a tuple (bool, newnode) where,
-        bool: a boolean value indicating whether the instability was solved
-        newnode: if bool is True, then the newnode of the resultant commit
-                 formed. newnode can be node, when resolution led to no new
-                 commit. If bool is False, this is ''.
-    """
-    repo = repo.unfiltered()
-    divergent = repo[divergent.rev()]
-    base, others = divergentdata(divergent)
-    if len(others) > 1:
-        othersstr = "[%s]" % (','.join([str(i) for i in others]))
-        msg = _("skipping %d:%s with a changeset that got split"
-                " into multiple ones:\n"
-                "|[%s]\n"
-                "| This is not handled by automatic evolution yet\n"
-                "| You have to fallback to manual handling with commands "
-                "such as:\n"
-                "| - hg touch -D\n"
-                "| - hg prune\n"
-                "| \n"
-                "| You should contact your local evolution Guru for help.\n"
-                ) % (divergent, TROUBLES['CONTENTDIVERGENT'], othersstr)
-        ui.write_err(msg)
-        return (False, '')
-    other = others[0]
-    if len(other.parents()) > 1:
-        msg = _("skipping %s: %s changeset can't be "
-                "a merge (yet)\n") % (divergent, TROUBLES['CONTENTDIVERGENT'])
-        ui.write_err(msg)
-        hint = _("You have to fallback to solving this by hand...\n"
-                 "| This probably means redoing the merge and using \n"
-                 "| `hg prune` to kill older version.\n")
-        ui.write_err(hint)
-        return (False, '')
-    if other.p1() not in divergent.parents():
-        msg = _("skipping %s: have a different parent than %s "
-                "(not handled yet)\n") % (divergent, other)
-        hint = _("| %(d)s, %(o)s are not based on the same changeset.\n"
-                 "| With the current state of its implementation, \n"
-                 "| evolve does not work in that case.\n"
-                 "| rebase one of them next to the other and run \n"
-                 "| this command again.\n"
-                 "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n"
-                 "| - or:     hg rebase --dest 'p1(%(o)s)' -r %(d)s\n"
-                 ) % {'d': divergent, 'o': other}
-        ui.write_err(msg)
-        ui.write_err(hint)
-        return (False, '')
-
-    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
-    if not ui.quiet or confirm:
-        ui.write(_('merge:'))
-        displayer.show(divergent)
-        ui.write(_('with: '))
-        displayer.show(other)
-        ui.write(_('base: '))
-        displayer.show(base)
-    if confirm and ui.prompt(_('perform evolve? [Ny]'), 'n') != 'y':
-        raise error.Abort(_('evolve aborted by user'))
-    if dryrun:
-        ui.write(('hg update -c %s &&\n' % divergent))
-        ui.write(('hg merge %s &&\n' % other))
-        ui.write(('hg commit -m "auto merge resolving conflict between '
-                 '%s and %s"&&\n' % (divergent, other)))
-        ui.write(('hg up -C %s &&\n' % base))
-        ui.write(('hg revert --all --rev tip &&\n'))
-        ui.write(('hg commit -m "`hg log -r %s --template={desc}`";\n'
-                 % divergent))
-        return (False, '')
-    if divergent not in repo[None].parents():
-        repo.ui.status(_('updating to "local" conflict\n'))
-        hg.update(repo, divergent.rev())
-    repo.ui.note(_('merging %s changeset\n') % TROUBLES['CONTENTDIVERGENT'])
-    if progresscb:
-        progresscb()
-    stats = merge.update(repo,
-                         other.node(),
-                         branchmerge=True,
-                         force=False,
-                         ancestor=base.node(),
-                         mergeancestor=True)
-    hg._showstats(repo, stats)
-    if stats[3]:
-        repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
-                         "or 'hg update -C .' to abort\n"))
-    if stats[3] > 0:
-        raise error.Abort('merge conflict between several amendments '
-                          '(this is not automated yet)',
-                          hint="""/!\ You can try:
-/!\ * manual merge + resolve => new cset X
-/!\ * hg up to the parent of the amended changeset (which are named W and Z)
-/!\ * hg revert --all -r X
-/!\ * hg ci -m "same message as the amended changeset" => new cset Y
-/!\ * hg prune -n Y W Z
-""")
-    if progresscb:
-        progresscb()
-    emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit')
-    tr = repo.currenttransaction()
-    assert tr is not None
-    try:
-        repo.ui.setconfig('ui', 'allowemptycommit', True, 'evolve')
-        with repo.dirstate.parentchange():
-            repo.dirstate.setparents(divergent.node(), node.nullid)
-        oldlen = len(repo)
-        cmdrewrite.amend(ui, repo, message='', logfile='')
-        if oldlen == len(repo):
-            new = divergent
-            # no changes
-        else:
-            new = repo['.']
-        obsolete.createmarkers(repo, [(other, (new,))])
-        phases.retractboundary(repo, tr, other.phase(), [new.node()])
-        return (True, new.node())
-    finally:
-        repo.ui.restoreconfig(emtpycommitallowed)
-
 def divergentdata(ctx):
     """return base, other part of a conflict
 
--- a/hgext3rd/evolve/evolvecmd.py	Fri Jan 19 14:42:46 2018 +0530
+++ b/hgext3rd/evolve/evolvecmd.py	Fri Jan 19 15:04:12 2018 +0530
@@ -9,10 +9,33 @@
 """logic related to hg evolve command"""
 
 from mercurial import (
+    cmdutil,
+    context,
+    copies,
+    error,
+    hg,
     lock as lockmod,
+    merge,
+    node,
+    obsolete,
+    phases,
 )
 
-from . import _solveunstable, _solvebumped, _solvedivergent
+from mercurial.i18n import _
+
+from . import (
+    cmdrewrite,
+    compat,
+    rewriteutil,
+    state,
+    utility,
+)
+
+TROUBLES = compat.TROUBLES
+shorttemplate = utility.shorttemplate
+_bookmarksupdater = rewriteutil.bookmarksupdater
+
+from . import relocate, divergentdata, MergeFailure
 
 def _solveone(ui, repo, ctx, dryrun, confirm, progresscb, category):
     """Resolve the troubles affecting one revision
@@ -41,3 +64,326 @@
         return result
     finally:
         lockmod.release(tr, lock, wlock)
+
+def _solveunstable(ui, repo, orig, dryrun=False, confirm=False,
+                   progresscb=None):
+    """ Tries to stabilize the changeset orig which is orphan.
+
+    returns a tuple (bool, newnode) where,
+        bool: a boolean value indicating whether the instability was solved
+        newnode: if bool is True, then the newnode of the resultant commit
+                 formed. newnode can be node, when resolution led to no new
+                 commit. If bool is False, this is ''.
+    """
+    pctx = orig.p1()
+    keepbranch = orig.p1().branch() != orig.branch()
+    if len(orig.parents()) == 2:
+        if not pctx.obsolete():
+            pctx = orig.p2()  # second parent is obsolete ?
+            keepbranch = orig.p2().branch() != orig.branch()
+        elif orig.p2().obsolete():
+            hint = _("Redo the merge (%s) and use `hg prune <old> "
+                     "--succ <new>` to obsolete the old one") % orig.hex()[:12]
+            ui.warn(_("warning: no support for evolving merge changesets "
+                      "with two obsolete parents yet\n") +
+                    _("(%s)\n") % hint)
+            return (False, '')
+
+    if not pctx.obsolete():
+        ui.warn(_("cannot solve instability of %s, skipping\n") % orig)
+        return (False, '')
+    obs = pctx
+    newer = compat.successorssets(repo, obs.node())
+    # search of a parent which is not killed
+    while not newer or newer == [()]:
+        ui.debug("stabilize target %s is plain dead,"
+                 " trying to stabilize on its parent\n" %
+                 obs)
+        obs = obs.parents()[0]
+        newer = compat.successorssets(repo, obs.node())
+    if len(newer) > 1:
+        msg = _("skipping %s: divergent rewriting. can't choose "
+                "destination\n") % obs
+        ui.write_err(msg)
+        return (False, '')
+    targets = newer[0]
+    assert targets
+    if len(targets) > 1:
+        # split target, figure out which one to pick, are they all in line?
+        targetrevs = [repo[r].rev() for r in targets]
+        roots = repo.revs('roots(%ld)', targetrevs)
+        heads = repo.revs('heads(%ld)', targetrevs)
+        if len(roots) > 1 or len(heads) > 1:
+            msg = "cannot solve split across two branches\n"
+            ui.write_err(msg)
+            return (False, '')
+        target = repo[heads.first()]
+    else:
+        target = targets[0]
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    target = repo[target]
+    if not ui.quiet or confirm:
+        repo.ui.write(_('move:'))
+        displayer.show(orig)
+        repo.ui.write(_('atop:'))
+        displayer.show(target)
+    if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
+            raise error.Abort(_('evolve aborted by user'))
+    if progresscb:
+        progresscb()
+    todo = 'hg rebase -r %s -d %s\n' % (orig, target)
+    if dryrun:
+        repo.ui.write(todo)
+        return (False, '')
+    else:
+        repo.ui.note(todo)
+        if progresscb:
+            progresscb()
+        try:
+            newid = relocate(repo, orig, target, pctx, keepbranch)
+            return (True, newid)
+        except MergeFailure:
+            ops = {'current': orig.node()}
+            evolvestate = state.cmdstate(repo, opts=ops)
+            evolvestate.save()
+            repo.ui.write_err(_('evolve failed!\n'))
+            repo.ui.write_err(
+                _("fix conflict and run 'hg evolve --continue'"
+                  " or use 'hg update -C .' to abort\n"))
+            raise
+
+def _solvebumped(ui, repo, bumped, dryrun=False, confirm=False,
+                 progresscb=None):
+    """Stabilize a bumped changeset
+
+    returns a tuple (bool, newnode) where,
+        bool: a boolean value indicating whether the instability was solved
+        newnode: if bool is True, then the newnode of the resultant commit
+                 formed. newnode can be node, when resolution led to no new
+                 commit. If bool is False, this is ''.
+    """
+    repo = repo.unfiltered()
+    bumped = repo[bumped.rev()]
+    # For now we deny bumped merge
+    if len(bumped.parents()) > 1:
+        msg = _('skipping %s : we do not handle merge yet\n') % bumped
+        ui.write_err(msg)
+        return (False, '')
+    prec = repo.set('last(allprecursors(%d) and public())', bumped).next()
+    # For now we deny target merge
+    if len(prec.parents()) > 1:
+        msg = _('skipping: %s: public version is a merge, '
+                'this is not handled yet\n') % prec
+        ui.write_err(msg)
+        return (False, '')
+
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    if not ui.quiet or confirm:
+        repo.ui.write(_('recreate:'))
+        displayer.show(bumped)
+        repo.ui.write(_('atop:'))
+        displayer.show(prec)
+    if confirm and ui.prompt('perform evolve? [Ny]', 'n') != 'y':
+        raise error.Abort(_('evolve aborted by user'))
+    if dryrun:
+        todo = 'hg rebase --rev %s --dest %s;\n' % (bumped, prec.p1())
+        repo.ui.write(todo)
+        repo.ui.write(('hg update %s;\n' % prec))
+        repo.ui.write(('hg revert --all --rev %s;\n' % bumped))
+        repo.ui.write(('hg commit --msg "%s update to %s"\n' %
+                       (TROUBLES['PHASEDIVERGENT'], bumped)))
+        return (False, '')
+    if progresscb:
+        progresscb()
+    newid = tmpctx = None
+    tmpctx = bumped
+    # Basic check for common parent. Far too complicated and fragile
+    tr = repo.currenttransaction()
+    assert tr is not None
+    bmupdate = _bookmarksupdater(repo, bumped.node(), tr)
+    if not list(repo.set('parents(%d) and parents(%d)', bumped, prec)):
+        # Need to rebase the changeset at the right place
+        repo.ui.status(
+            _('rebasing to destination parent: %s\n') % prec.p1())
+        try:
+            tmpid = relocate(repo, bumped, prec.p1())
+            if tmpid is not None:
+                tmpctx = repo[tmpid]
+                obsolete.createmarkers(repo, [(bumped, (tmpctx,))])
+        except MergeFailure:
+            repo.vfs.write('graftstate', bumped.hex() + '\n')
+            repo.ui.write_err(_('evolution failed!\n'))
+            msg = _("fix conflict and run 'hg evolve --continue'\n")
+            repo.ui.write_err(msg)
+            raise
+    # Create the new commit context
+    repo.ui.status(_('computing new diff\n'))
+    files = set()
+    copied = copies.pathcopies(prec, bumped)
+    precmanifest = prec.manifest().copy()
+    # 3.3.2 needs a list.
+    # future 3.4 don't detect the size change during iteration
+    # this is fishy
+    for key, val in list(bumped.manifest().iteritems()):
+        precvalue = precmanifest.get(key, None)
+        if precvalue is not None:
+            del precmanifest[key]
+        if precvalue != val:
+            files.add(key)
+    files.update(precmanifest)  # add missing files
+    # commit it
+    if files: # something to commit!
+        def filectxfn(repo, ctx, path):
+            if path in bumped:
+                fctx = bumped[path]
+                flags = fctx.flags()
+                mctx = compat.memfilectx(repo, ctx, fctx, flags, copied, path)
+                return mctx
+            return None
+        text = '%s update to %s:\n\n' % (TROUBLES['PHASEDIVERGENT'], prec)
+        text += bumped.description()
+
+        new = context.memctx(repo,
+                             parents=[prec.node(), node.nullid],
+                             text=text,
+                             files=files,
+                             filectxfn=filectxfn,
+                             user=bumped.user(),
+                             date=bumped.date(),
+                             extra=bumped.extra())
+
+        newid = repo.commitctx(new)
+    if newid is None:
+        obsolete.createmarkers(repo, [(tmpctx, ())])
+        newid = prec.node()
+    else:
+        phases.retractboundary(repo, tr, bumped.phase(), [newid])
+        obsolete.createmarkers(repo, [(tmpctx, (repo[newid],))],
+                               flag=obsolete.bumpedfix)
+    bmupdate(newid)
+    repo.ui.status(_('committed as %s\n') % node.short(newid))
+    # reroute the working copy parent to the new changeset
+    with repo.dirstate.parentchange():
+        repo.dirstate.setparents(newid, node.nullid)
+    return (True, newid)
+
+def _solvedivergent(ui, repo, divergent, dryrun=False, confirm=False,
+                    progresscb=None):
+    """tries to solve content-divergence of a changeset
+
+    returns a tuple (bool, newnode) where,
+        bool: a boolean value indicating whether the instability was solved
+        newnode: if bool is True, then the newnode of the resultant commit
+                 formed. newnode can be node, when resolution led to no new
+                 commit. If bool is False, this is ''.
+    """
+    repo = repo.unfiltered()
+    divergent = repo[divergent.rev()]
+    base, others = divergentdata(divergent)
+    if len(others) > 1:
+        othersstr = "[%s]" % (','.join([str(i) for i in others]))
+        msg = _("skipping %d:%s with a changeset that got split"
+                " into multiple ones:\n"
+                "|[%s]\n"
+                "| This is not handled by automatic evolution yet\n"
+                "| You have to fallback to manual handling with commands "
+                "such as:\n"
+                "| - hg touch -D\n"
+                "| - hg prune\n"
+                "| \n"
+                "| You should contact your local evolution Guru for help.\n"
+                ) % (divergent, TROUBLES['CONTENTDIVERGENT'], othersstr)
+        ui.write_err(msg)
+        return (False, '')
+    other = others[0]
+    if len(other.parents()) > 1:
+        msg = _("skipping %s: %s changeset can't be "
+                "a merge (yet)\n") % (divergent, TROUBLES['CONTENTDIVERGENT'])
+        ui.write_err(msg)
+        hint = _("You have to fallback to solving this by hand...\n"
+                 "| This probably means redoing the merge and using \n"
+                 "| `hg prune` to kill older version.\n")
+        ui.write_err(hint)
+        return (False, '')
+    if other.p1() not in divergent.parents():
+        msg = _("skipping %s: have a different parent than %s "
+                "(not handled yet)\n") % (divergent, other)
+        hint = _("| %(d)s, %(o)s are not based on the same changeset.\n"
+                 "| With the current state of its implementation, \n"
+                 "| evolve does not work in that case.\n"
+                 "| rebase one of them next to the other and run \n"
+                 "| this command again.\n"
+                 "| - either: hg rebase --dest 'p1(%(d)s)' -r %(o)s\n"
+                 "| - or:     hg rebase --dest 'p1(%(o)s)' -r %(d)s\n"
+                 ) % {'d': divergent, 'o': other}
+        ui.write_err(msg)
+        ui.write_err(hint)
+        return (False, '')
+
+    displayer = cmdutil.show_changeset(ui, repo, {'template': shorttemplate})
+    if not ui.quiet or confirm:
+        ui.write(_('merge:'))
+        displayer.show(divergent)
+        ui.write(_('with: '))
+        displayer.show(other)
+        ui.write(_('base: '))
+        displayer.show(base)
+    if confirm and ui.prompt(_('perform evolve? [Ny]'), 'n') != 'y':
+        raise error.Abort(_('evolve aborted by user'))
+    if dryrun:
+        ui.write(('hg update -c %s &&\n' % divergent))
+        ui.write(('hg merge %s &&\n' % other))
+        ui.write(('hg commit -m "auto merge resolving conflict between '
+                 '%s and %s"&&\n' % (divergent, other)))
+        ui.write(('hg up -C %s &&\n' % base))
+        ui.write(('hg revert --all --rev tip &&\n'))
+        ui.write(('hg commit -m "`hg log -r %s --template={desc}`";\n'
+                 % divergent))
+        return (False, '')
+    if divergent not in repo[None].parents():
+        repo.ui.status(_('updating to "local" conflict\n'))
+        hg.update(repo, divergent.rev())
+    repo.ui.note(_('merging %s changeset\n') % TROUBLES['CONTENTDIVERGENT'])
+    if progresscb:
+        progresscb()
+    stats = merge.update(repo,
+                         other.node(),
+                         branchmerge=True,
+                         force=False,
+                         ancestor=base.node(),
+                         mergeancestor=True)
+    hg._showstats(repo, stats)
+    if stats[3]:
+        repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
+                         "or 'hg update -C .' to abort\n"))
+    if stats[3] > 0:
+        raise error.Abort('merge conflict between several amendments '
+                          '(this is not automated yet)',
+                          hint="""/!\ You can try:
+/!\ * manual merge + resolve => new cset X
+/!\ * hg up to the parent of the amended changeset (which are named W and Z)
+/!\ * hg revert --all -r X
+/!\ * hg ci -m "same message as the amended changeset" => new cset Y
+/!\ * hg prune -n Y W Z
+""")
+    if progresscb:
+        progresscb()
+    emtpycommitallowed = repo.ui.backupconfig('ui', 'allowemptycommit')
+    tr = repo.currenttransaction()
+    assert tr is not None
+    try:
+        repo.ui.setconfig('ui', 'allowemptycommit', True, 'evolve')
+        with repo.dirstate.parentchange():
+            repo.dirstate.setparents(divergent.node(), node.nullid)
+        oldlen = len(repo)
+        cmdrewrite.amend(ui, repo, message='', logfile='')
+        if oldlen == len(repo):
+            new = divergent
+            # no changes
+        else:
+            new = repo['.']
+        obsolete.createmarkers(repo, [(other, (new,))])
+        phases.retractboundary(repo, tr, other.phase(), [new.node()])
+        return (True, new.node())
+    finally:
+        repo.ui.restoreconfig(emtpycommitallowed)