changeset 5035:c5efcbbd0dc4

branching: merge with stable
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Thu, 26 Dec 2019 21:23:30 +0100
parents 7cc3d96eb589 (diff) 1015a1dbaf7c (current diff)
children f583d9eedbce
files CHANGELOG
diffstat 32 files changed, 1290 insertions(+), 735 deletions(-) [+]
line wrap: on
line diff
--- a/.gitlab-ci.yml	Thu Dec 26 12:22:49 2019 +0700
+++ b/.gitlab-ci.yml	Thu Dec 26 21:23:30 2019 +0100
@@ -34,3 +34,14 @@
         - hg_rev=$(tests/testlib/map-hg-rev.sh "$(hg log -r . -T '{branch}')")
         - hg -R /ci/repos/mercurial/ update "$hg_rev"
         - (cd tests; python3 /ci/repos/mercurial/tests/run-tests.py --color=always --pure)
+
+doc:
+    image: octobus/ci-py2-evolve-doc
+    script:
+        - cd docs/
+        - make
+    variables:
+        LANG: en_us.UTF-8
+    artifacts:
+        paths:
+            - html/*
--- a/CHANGELOG	Thu Dec 26 12:22:49 2019 +0700
+++ b/CHANGELOG	Thu Dec 26 21:23:30 2019 +0100
@@ -1,6 +1,15 @@
 Changelog
 =========
 
+9.3.0 - in progress
+-------------------
+
+  * exchange: dropped more bundle-1 related dead code
+  * help: categorizing evolve and topic commands
+  * obslog: make templatable
+  * compat: cleanup some compatibility code for mercurial < 4.5
+  * compat: compatibility with some changes of the upcoming Mercurial 5.3
+
 9.2.2 - in progress
 -------------------
 
--- a/hgext3rd/evolve/__init__.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/__init__.py	Thu Dec 26 21:23:30 2019 +0100
@@ -465,7 +465,8 @@
     _alias, statuscmd = cmdutil.findcmd(b'status', commands.table)
     pstatusopts = [o for o in statuscmd[1] if o[1] != b'rev']
 
-    @eh.command(b'pstatus', pstatusopts)
+    @eh.command(b'pstatus', pstatusopts,
+                **compat.helpcategorykwargs('CATEGORY_WORKING_DIRECTORY'))
     def pstatus(ui, repo, *args, **kwargs):
         """show status combining committed and uncommited changes
 
@@ -480,7 +481,8 @@
     _alias, diffcmd = cmdutil.findcmd(b'diff', commands.table)
     pdiffopts = [o for o in diffcmd[1] if o[1] != b'rev']
 
-    @eh.command(b'pdiff', pdiffopts)
+    @eh.command(b'pdiff', pdiffopts,
+                **compat.helpcategorykwargs('CATEGORY_WORKING_DIRECTORY'))
     def pdiff(ui, repo, *args, **kwargs):
         """show diff combining committed and uncommited changes
 
@@ -947,7 +949,7 @@
 def _findprevtarget(repo, displayer, movebookmark=False, topic=True):
     target = bookmark = None
     wkctx = repo[None]
-    p1 = wkctx.parents()[0]
+    p1 = wkctx.p1()
     parents = p1.parents()
     currenttopic = _getcurrenttopic(repo)
 
@@ -989,7 +991,8 @@
      (b'n', b'dry-run', False,
         _(b'do not perform actions, just print what would be done'))],
     b'[OPTION]...',
-    helpbasic=True)
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_WORKING_DIRECTORY'))
 def cmdprevious(ui, repo, **opts):
     """update to parent revision
 
@@ -1023,15 +1026,14 @@
         target, bookmark = _findprevtarget(repo, displayer,
                                            opts.get('move_bookmark'), topic)
         if target is not None:
-            backup = repo.ui.backupconfig(b'_internal', b'keep-topic')
-            try:
-                if topic and _getcurrenttopic(repo) != _gettopic(target):
-                    repo.ui.setconfig(b'_internal', b'keep-topic', b'yes',
-                                      source=b'topic-extension')
+            configoverride = util.nullcontextmanager()
+            if topic and _getcurrenttopic(repo) != _gettopic(target):
+                configoverride = repo.ui.configoverride({
+                    (b'_internal', b'keep-topic'): b'yes'
+                }, source=b'topic-extension')
+            with configoverride:
                 _prevupdate(repo, displayer, target, bookmark, dryrunopt,
                             mergeopt)
-            finally:
-                repo.ui.restoreconfig(backup)
             return 0
         else:
             return 1
@@ -1048,7 +1050,8 @@
      (b'n', b'dry-run', False,
       _(b'do not perform actions, just print what would be done'))],
     b'[OPTION]...',
-    helpbasic=True)
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_WORKING_DIRECTORY'))
 def cmdnext(ui, repo, **opts):
     """update to next child revision
 
@@ -1339,8 +1342,16 @@
         if entry[0] == b"evolution":
             break
     else:
-        help.helptable.append(([b"evolution"], _(b"Safely Rewriting History"),
-                               _helploader))
+        if util.safehasattr(help, 'TOPIC_CATEGORY_CONCEPTS'):
+            help.helptable.append(([b"evolution"],
+                                   _(b"Safely Rewriting History"),
+                                   _helploader,
+                                   help.TOPIC_CATEGORY_CONCEPTS))
+        else:
+            # hg <= 4.7 (c303d65d2e34)
+            help.helptable.append(([b"evolution"],
+                                   _(b"Safely Rewriting History"),
+                                   _helploader))
         help.helptable.sort()
 
 evolvestateversion = 0
--- a/hgext3rd/evolve/cmdrewrite.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/cmdrewrite.py	Thu Dec 26 21:23:30 2019 +0100
@@ -109,7 +109,8 @@
      (b'n', b'note', b'', _(b'store a note on amend'), _(b'TEXT')),
      ] + walkopts + commitopts + commitopts2 + commitopts3 + interactiveopt,
     _(b'[OPTION]... [FILE]...'),
-    helpbasic=True)
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_COMMITTING'))
 def amend(ui, repo, *pats, **opts):
     """combine a changeset with updates and replace it with a new one
 
@@ -291,8 +292,10 @@
 
 def _touchedbetween(repo, source, dest, match=None):
     touched = set()
-    for files in repo.status(source, dest, match=match)[:3]:
-        touched.update(files)
+    st = repo.status(source, dest, match=match)
+    touched.update(st.modified)
+    touched.update(st.added)
+    touched.update(st.removed)
     return touched
 
 def _commitfiltered(repo, ctx, match, target=None, message=None, user=None,
@@ -364,8 +367,8 @@
         # and considering only the files which are changed between oldctx and
         # ctx, and the status of what changed between oldctx and ctx will help
         # us in defining the exact behavior
-        m, a, r = repo.status(oldctx, ctx, match=match)[:3]
-        for f in m:
+        st = repo.status(oldctx, ctx, match=match)
+        for f in st.modified:
             # These are files which are modified between oldctx and ctx which
             # contains two cases: 1) Were modified in oldctx and some
             # modifications are uncommitted
@@ -381,7 +384,7 @@
                 continue
             ds.normallookup(f)
 
-        for f in a:
+        for f in st.added:
             # These are the files which are added between oldctx and ctx(new
             # one), which means the files which were removed in oldctx
             # but uncommitted completely while making the ctx
@@ -394,7 +397,7 @@
                 continue
             ds.remove(f)
 
-        for f in r:
+        for f in st.removed:
             # These are files which are removed between oldctx and ctx, which
             # means the files which were added in oldctx and were completely
             # uncommitted in ctx. If a added file is partially uncommitted, that
@@ -408,21 +411,21 @@
                 continue
             ds.add(f)
     else:
-        m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
-        for f in m:
+        st = repo.status(oldctx.p1(), oldctx, match=match)
+        for f in st.modified:
             if ds[f] == b'r':
                 # modified + removed -> removed
                 continue
             ds.normallookup(f)
 
-        for f in a:
+        for f in st.added:
             if ds[f] == b'r':
                 # added + removed -> unknown
                 ds.drop(f)
             elif ds[f] != b'a':
                 ds.add(f)
 
-        for f in r:
+        for f in st.removed:
             if ds[f] == b'a':
                 # removed + added -> normal
                 ds.normallookup(f)
@@ -434,8 +437,8 @@
     if interactive:
         # Interactive had different meaning of the variables so restoring the
         # original meaning to use them
-        m, a, r = repo.status(oldctx.p1(), oldctx, match=match)[:3]
-    for f in (m + a):
+        st = repo.status(oldctx.p1(), oldctx, match=match)
+    for f in (st.modified + st.added):
         src = oldctx[f].renamed()
         if src:
             oldcopies[f] = src[0]
@@ -456,7 +459,8 @@
      (b'', b'revert', False, _(b'discard working directory changes after uncommit')),
      (b'n', b'note', b'', _(b'store a note on uncommit'), _(b'TEXT')),
      ] + commands.walkopts + commitopts + commitopts2 + commitopts3,
-    _(b'[OPTION]... [FILE]...'))
+    _(b'[OPTION]... [FILE]...'),
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT'))
 def uncommit(ui, repo, *pats, **opts):
     """move changes from parent revision to working directory
 
@@ -692,7 +696,8 @@
      (b'n', b'note', b'', _(b'store a note on fold'), _(b'TEXT')),
      ] + commitopts + commitopts2 + commitopts3,
     _(b'hg fold [OPTION]... [-r] REV...'),
-    helpbasic=True)
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT'))
 def fold(ui, repo, *revs, **opts):
     """fold multiple revisions into a single one
 
@@ -822,7 +827,8 @@
      (b'', b'fold', None, _(b"also fold specified revisions into one")),
      (b'n', b'note', b'', _(b'store a note on metaedit'), _(b'TEXT')),
      ] + commitopts + commitopts2 + commitopts3,
-    _(b'hg metaedit [OPTION]... [[-r] REV]...'))
+    _(b'hg metaedit [OPTION]... [[-r] REV]...'),
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT'))
 def metaedit(ui, repo, *revs, **opts):
     """edit commit information
 
@@ -980,7 +986,8 @@
      (b'B', b'bookmark', [], _(b"remove revs only reachable from given"
                                b" bookmark"), _(b'BOOKMARK'))] + metadataopts,
     _(b'[OPTION]... [-r] REV...'),
-    helpbasic=True)
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT'))
 # XXX -U  --noupdate option to prevent wc update and or bookmarks update ?
 def cmdprune(ui, repo, *revs, **opts):
     """mark changesets as obsolete or succeeded by another changeset
@@ -1090,7 +1097,7 @@
                 newnode = wdp
 
                 while newnode in precs or newnode.obsolete():
-                    newnode = newnode.parents()[0]
+                    newnode = newnode.p1()
         else:
             # no need to update anywhere as wdp is not related to revs
             # being pruned
@@ -1174,7 +1181,8 @@
      (b'n', b'note', b'', _(b"store a note on split"), _(b'TEXT')),
      ] + commitopts + commitopts2 + commitopts3,
     _(b'hg split [OPTION]... [-r REV] [FILE]...'),
-    helpbasic=True)
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT'))
 def cmdsplit(ui, repo, *pats, **opts):
     """split a changeset into smaller changesets
 
@@ -1230,8 +1238,8 @@
         rewriteutil.presplitupdate(repo, ui, prev, ctx)
 
         def haschanges(matcher=None):
-            modified, added, removed, deleted = repo.status(match=matcher)[:4]
-            return modified or added or removed or deleted
+            st = repo.status(match=matcher)
+            return st.modified or st.added or st.removed or st.deleted
         msg = (b"HG: This is the original pre-split commit message. "
                b"Edit it as appropriate.\n\n")
         msg += ctx.description()
@@ -1294,10 +1302,12 @@
                         ui.status(_(b'discarding remaining changes\n'))
                         target = newcommits[0]
                         if pats:
-                            status = repo.status(match=matcher)[:4]
+                            status = repo.status(match=matcher)
                             dirty = set()
-                            for i in status:
-                                dirty.update(i)
+                            dirty.update(status.modified)
+                            dirty.update(status.added)
+                            dirty.update(status.removed)
+                            dirty.update(status.deleted)
                             dirty = sorted(dirty)
                             cmdutil.revert(ui, repo, repo[target],
                                            (target, node.nullid), *dirty)
@@ -1349,7 +1359,8 @@
       b'mark the new revision as successor of the old one potentially creating '
       b'divergence')],
     # allow to choose the seed ?
-    _(b'[OPTION]... [-r] REV...'))
+    _(b'[OPTION]... [-r] REV...'),
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT'))
 def touch(ui, repo, *revs, **opts):
     """create successors identical to their predecessors but the changeset ID
 
@@ -1450,7 +1461,8 @@
      (b'c', b'continue', False, b'continue interrupted pick'),
      (b'a', b'abort', False, b'abort interrupted pick'),
      ] + mergetoolopts,
-    _(b'[OPTION]... [-r] REV'))
+    _(b'[OPTION]... [-r] REV'),
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT'))
 def cmdpick(ui, repo, *revs, **opts):
     """move a commit on the top of working directory parent and updates to it."""
 
--- a/hgext3rd/evolve/compat.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/compat.py	Thu Dec 26 21:23:30 2019 +0100
@@ -17,6 +17,7 @@
     obsolete,
     obsutil,
     pycompat,
+    registrar,
     repair,
     scmutil,
     util,
@@ -523,6 +524,15 @@
 
     return sorted(operations)
 
+# help category compatibility
+# hg <= 4.7 (c303d65d2e34)
+def helpcategorykwargs(categoryname):
+    """Backwards-compatible specification of the helpategory argument."""
+    category = getattr(registrar.command, categoryname, None)
+    if not category:
+        return {}
+    return {'helpcategory': category}
+
 # nodemap.get and index.[has_node|rev|get_rev]
 # hg <= 5.3 (02802fa87b74)
 def getgetrev(cl):
--- a/hgext3rd/evolve/evolvecmd.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/evolvecmd.py	Thu Dec 26 21:23:30 2019 +0100
@@ -16,7 +16,6 @@
     cmdutil,
     commands,
     context,
-    copies,
     error,
     encoding,
     hg,
@@ -62,7 +61,7 @@
     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
+                 formed. newnode can be None, when resolution led to no new
                  commit. If bool is False, this is ".".
     """
     tr = repo.currenttransaction()
@@ -125,11 +124,11 @@
     obs = pctx
     newer = obsutil.successorssets(repo, obs.node())
     # search of a parent which is not killed, but also isn't the orig
-    while not newer or newer == [()] or newer[0][0] == orig.node():
+    while not newer or newer[0][0] == orig.node():
         ui.debug(b"stabilize target %s is plain dead,"
                  b" trying to stabilize on its parent\n" %
                  obs)
-        obs = obs.parents()[0]
+        obs = obs.p1()
         newer = obsutil.successorssets(repo, obs.node())
     if len(newer) > 1:
         msg = _(b"skipping %s: divergent rewriting. can't choose "
@@ -179,8 +178,8 @@
         if progresscb:
             progresscb()
         with state.saver(evolvestate, {b'current': orig.node()}):
-            newid = relocate(repo, orig, target, evolvestate, pctx,
-                             keepbranch, b'orphan')
+            newid = _relocate(repo, orig, target, evolvestate, pctx,
+                              keepbranch, b'orphan')
             return (True, newid)
 
 def _solvephasedivergence(ui, repo, bumped, evolvestate, displayer,
@@ -238,8 +237,8 @@
             _(b'rebasing to destination parent: %s\n') % prec.p1())
         with state.saver(evolvestate, {b'current': bumped.hex(),
                                        b'precursor': prec.hex()}):
-            newnode = relocate(repo, bumped, prec.p1(), evolvestate,
-                               category=b'phasedivergent')
+            newnode = _relocate(repo, bumped, prec.p1(), evolvestate,
+                                category=b'phasedivergent')
             if newnode is not None:
                 new = repo[newnode]
                 obsolete.createmarkers(repo, [(bumped, (new,))],
@@ -299,17 +298,9 @@
         repo.dirstate.setparents(newid, nodemod.nullid)
     return (True, replacementnode)
 
-def _solvedivergent(ui, repo, divergent, evolvestate, displayer, 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()
+def _prepcontentdivresolution(ui, repo, divergent, other, evolvestate):
+    """ if relocation required, decide which divergent cset will be relocated
+    to the other side"""
     divergent = repo[divergent.rev()]
     evolvestate[b'divergent'] = divergent.node()
     evolvestate[b'orig-divergent'] = divergent.node()
@@ -319,32 +310,15 @@
     # strip that relocated commit. However if `--all` is passed, we need to
     # reset this value for each content-divergence resolution which we are doing
     # below.
-    evolvestate[b'relocated'] = None
-    evolvestate[b'relocating'] = False
+    evolvestate[b'relocated-other'] = None
+    evolvestate[b'relocating-other'] = False
+    evolvestate[b'relocated-div'] = None
+    evolvestate[b'relocating-div'] = False
+    evolvestate[b'relocation-req'] = False
     # in case or relocation we get a new other node, we need to store the old
     # other for purposes like `--abort` or `--stop`
     evolvestate[b'old-other'] = None
-    base, others = divergentdata(divergent)
-
-    # we don't handle split in content-divergence yet
-    if len(others) > 1:
-        othersstr = b"[%s]" % (b','.join([bytes(i) for i in others]))
-        msg = _(b"skipping %s: %s with a changeset that got split"
-                b" into multiple ones:\n"
-                b"|[%s]\n"
-                b"| This is not handled by automatic evolution yet\n"
-                b"| You have to fallback to manual handling with commands "
-                b"such as:\n"
-                b"| - hg touch -D\n"
-                b"| - hg prune\n"
-                b"| \n"
-                b"| You should contact your local evolution Guru for help.\n"
-                ) % (divergent, TROUBLES['CONTENTDIVERGENT'], othersstr)
-        ui.write_err(msg)
-        return (False, b".")
-    other = others[0]
-    evolvestate[b'other-divergent'] = other.node()
-    evolvestate[b'base'] = base.node()
+    evolvestate[b'old-divergent'] = None
 
     def swapnodes(div, other):
         div, other = other, div
@@ -363,6 +337,7 @@
         else:
             publicdiv = divergent
         evolvestate[b'public-divergent'] = publicdiv.node()
+
     # we don't handle merge content-divergent changesets yet
     if len(other.parents()) > 1:
         msg = _(b"skipping %s: %s changeset can't be "
@@ -374,32 +349,32 @@
         ui.write_err(hint)
         return (False, b".")
 
-    otherp1 = other.p1().rev()
-    divp1 = divergent.p1().rev()
+    otherp1 = succsotherp1 = other.p1().rev()
+    divp1 = succsdivp1 = divergent.p1().rev()
 
     # finding single successors of otherp1 and divp1
     try:
-        otherp1 = utility._singlesuccessor(repo, other.p1())
+        succsotherp1 = utility._singlesuccessor(repo, other.p1())
     except utility.MultipleSuccessorsError:
         pass
 
     try:
-        divp1 = utility._singlesuccessor(repo, divergent.p1())
+        succsdivp1 = utility._singlesuccessor(repo, divergent.p1())
     except utility.MultipleSuccessorsError:
         pass
 
     # the changeset on which resolution changeset will be based on
-    resolutionparent = repo[divp1].node()
+    resolutionparent = repo[succsdivp1].node()
 
     # the nullrev has to be handled specially because -1 is overloaded to both
     # mean nullrev (this meaning is used for the result of changectx.rev(), as
     # called above) and the tipmost revision (this meaning is used for the %d
     # format specifier, as used below)
-    if nodemod.nullrev in (otherp1, divp1):
-        # nullrev is the only possible ancestor if otherp1 or divp1 is nullrev
+    if nodemod.nullrev in (succsotherp1, succsdivp1):
+        # nullrev is the only possible ancestor if succsotherp1 or succsdivp1 is nullrev
         gca = [nodemod.nullrev]
     else:
-        gca = repo.revs(b"ancestor(%d, %d)" % (otherp1, divp1))
+        gca = repo.revs(b"ancestor(%d, %d)" % (succsotherp1, succsdivp1))
     # divonly: non-obsolete csets which are topological ancestor of "divergent"
     # but not "other"
     divonly = repo.revs(b"only(%d, %d) - obsolete()" % (divergent.rev(),
@@ -427,32 +402,31 @@
     # for 2) we will relocate one which is behind to the parent of ahead one and
     # then solve the content-divergence the way we solve 1)
     # for 3) and 4), we still have to decide
-    if otherp1 in gca and divp1 in gca:
-        if otherp1 == other.p1().rev() and divp1 == divergent.p1().rev():
-            # both are on the same parents
+    if otherp1 == divp1:
+        # both are on the same parents
+        pass
+    elif succsotherp1 in gca and succsdivp1 in gca:
+        # both are not on the same parent but have same parents's succs.
+        if otheronly and divonly:
+            # case: we have visible csets on both side diverging from
+            # tca of "divergent" and "other". We still need to decide what
+            # to do in this case
             pass
+        if otheronly:
+            relocatereq = True
+            if not haspubdiv:
+                # can't swap when public divergence, as public can't move
+                divergent, other = swapnodes(divergent, other)
+                resolutionparent = repo[succsotherp1].node()
+        elif divonly:
+            relocatereq = True
         else:
-            # both are not on the same parent but have same parents's succs.
-            if otheronly and divonly:
-                # case: we have visible csets on both side diverging from
-                # tca of "divergent" and "other". We still need to decide what
-                # to do in this case
-                pass
-            if otheronly:
-                relocatereq = True
-                if not haspubdiv:
-                    # can't swap when public divergence, as public can't move
-                    divergent, other = swapnodes(divergent, other)
-                    resolutionparent = repo[otherp1].node()
-            elif divonly:
-                relocatereq = True
-            else:
-                # no extra cset on either side; so not considering relocation
-                pass
-    elif otherp1 in gca and divp1 not in gca:
+            # no extra cset on either side; so not considering relocation
+            pass
+    elif succsotherp1 in gca and succsdivp1 not in gca:
         relocatereq = True
         pass
-    elif divp1 in gca and otherp1 not in gca:
+    elif succsdivp1 in gca and succsotherp1 not in gca:
         relocatereq = True
 
         # When public branch is behind to the mutable branch, for now we
@@ -481,6 +455,52 @@
         ui.write_err(hint)
         return (False, b".")
 
+    return (True, divergent, other, resolutionparent, relocatereq)
+
+def _solvedivergent(ui, repo, divergent, evolvestate, displayer, 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()
+    base, others = divergentdata(divergent)
+
+    # we don't handle split in content-divergence yet
+    if len(others) > 1:
+        othersstr = b"[%s]" % (b','.join([bytes(i) for i in others]))
+        msg = _(b"skipping %s: %s with a changeset that got split"
+                b" into multiple ones:\n"
+                b"|[%s]\n"
+                b"| This is not handled by automatic evolution yet\n"
+                b"| You have to fallback to manual handling with commands "
+                b"such as:\n"
+                b"| - hg touch -D\n"
+                b"| - hg prune\n"
+                b"| \n"
+                b"| You should contact your local evolution Guru for help.\n"
+                ) % (divergent, TROUBLES['CONTENTDIVERGENT'], othersstr)
+        ui.write_err(msg)
+        return (False, b".")
+    other = others[0]
+    evolvestate[b'other-divergent'] = other.node()
+    evolvestate[b'base'] = base.node()
+
+    # setup everything before merging two content-divergent csets
+    datatoproceed = _prepcontentdivresolution(ui, repo, divergent, other,
+                                              evolvestate)
+    if not datatoproceed[0]:
+        return (False, b".")
+    divergent, other, resolutionparent, relocatereq = datatoproceed[1:]
+
+    if relocatereq:
+        evolvestate['relocation-req'] = True
+    evolvestate[b'resolutionparent'] = resolutionparent
+
     if not ui.quiet or confirm:
         ui.write(_(b'merge:'), label=b'evolve.operation')
         displayer.show(divergent)
@@ -501,19 +521,26 @@
                   % divergent))
         return (False, b".")
 
+    if relocatereq:
+        evolvestate[b'relocation-req'] = True
+    evolvestate[b'resolutionparent'] = resolutionparent
     try:
         succsdivp1 = utility._singlesuccessor(repo, divergent.p1())
     except utility.MultipleSuccessorsError:
-        msg = _(b"ambiguous orphan resolution parent for %b")
+        msg = _(b"ambiguous orphan resolution parent for %s")
         raise error.Abort(msg % divergent.hex()[:12])
     # relocate divergent cset to its obsolete parent's successsor
     if succsdivp1 != divergent.p1().rev():
+        evolvestate[b'relocating-div'] = True
         ui.status(_(b'rebasing "divergent" content-divergent changeset %s on'
                     b' %s\n' % (divergent, repo[succsdivp1])))
         with state.saver(evolvestate, {b'current': divergent.node()}):
-            newdivergent = relocate(repo, divergent, repo[succsdivp1],
-                                    evolvestate, keepbranch=True)
+            newdivergent = _relocate(repo, divergent, repo[succsdivp1],
+                                     evolvestate, keepbranch=True)
+        evolvestate[b'old-divergent'] = divergent.node()
         divergent = repo[newdivergent]
+        evolvestate[b'relocating-div'] = False
+        evolvestate[b'relocated-div'] = divergent.node()
         evolvestate[b'temprevs'].append(divergent.node())
         evolvestate[b'divergent'] = divergent.node()
 
@@ -521,22 +548,21 @@
     if relocatereq and other == divergent.p1():
         relocatereq = False
 
-    evolvestate[b'resolutionparent'] = resolutionparent
     # relocate the other divergent if required
     if relocatereq:
         # relocating will help us understand during the time of conflicts that
         # whether conflicts occur at reloacting or they occured at merging
         # content divergent changesets
-        evolvestate[b'relocating'] = True
+        evolvestate[b'relocating-other'] = True
         ui.status(_(b'rebasing "other" content-divergent changeset %s on'
                     b' %s\n' % (other, divergent.p1())))
         with state.saver(evolvestate, {b'current': other.node()}):
-            newother = relocate(repo, other, divergent.p1(), evolvestate,
-                                keepbranch=True)
+            newother = _relocate(repo, other, divergent.p1(), evolvestate,
+                                 keepbranch=True)
         evolvestate[b'old-other'] = other.node()
         other = repo[newother]
-        evolvestate[b'relocating'] = False
-        evolvestate[b'relocated'] = other.node()
+        evolvestate[b'relocating-other'] = False
+        evolvestate[b'relocated-other'] = other.node()
         evolvestate[b'temprevs'].append(other.node())
         evolvestate[b'other-divergent'] = other.node()
 
@@ -544,9 +570,11 @@
                             evolvestate)
     res, newnode = _completecontentdivergent(ui, repo, progresscb, divergent,
                                              other, base, evolvestate)
+    haspubdiv = not (divergent.mutable() and other.mutable())
     if not haspubdiv:
         return (res, newnode)
     else:
+        publicdiv = repo[evolvestate[b'public-divergent']]
         # we have content-divergence with a public cset:
         # after performing content divergence resolution steps, possbile cases:
         # 1) merging results in a new node:
@@ -606,7 +634,6 @@
     # resume resolution
     if progresscb:
         progresscb()
-    emtpycommitallowed = repo.ui.backupconfig(b'ui', b'allowemptycommit')
     tr = repo.currenttransaction()
     assert tr is not None
     # whether to store the obsmarker in the evolvestate
@@ -634,68 +661,64 @@
             obsolete.createmarkers(repo, [(otherdiv, (publicdiv,))],
                                    operation=b'evolve')
             return (True, publicnode)
-    try:
-        with repo.dirstate.parentchange():
-            repo.dirstate.setparents(resparent, nodemod.nullid)
-
-        dirstatedance(repo, divergent, resparent, None)
 
-        # merge the branches
-        mergebranches(repo, divergent, other, base)
-        # merge the commit messages
-        desc = mergecommitmessages(ui, base.description(),
-                                   divergent.description(),
-                                   other.description())
-        user = utility.mergeusers(ui, base, divergent, other)
+    with repo.dirstate.parentchange():
+        repo.dirstate.setparents(resparent, nodemod.nullid)
+
+    dirstatedance(repo, divergent, resparent, None)
 
-        # new node if any formed as the replacement
-        newnode = None
+    # merge the branches
+    mergebranches(repo, divergent, other, base)
+    # merge the commit messages
+    desc = mergecommitmessages(ui, base.description(),
+                               divergent.description(),
+                               other.description())
+    user = utility.mergeusers(ui, base, divergent, other)
 
-        mergehook(repo, base, divergent, other)
+    mergehook(repo, base, divergent, other)
 
-        date = divergent.date()
-        if other.date() != divergent.date():
-            basedate = base.date()
-            if other.date() == basedate:
-                date = divergent.date()
-            elif divergent.date() == basedate:
-                date = other.date()
-            else:
-                date = max(divergent.date(), other.date())
+    date = divergent.date()
+    if other.date() != divergent.date():
+        basedate = base.date()
+        if other.date() == basedate:
+            date = divergent.date()
+        elif divergent.date() == basedate:
+            date = other.date()
+        else:
+            date = max(divergent.date(), other.date())
 
-        newnode = repo.commit(text=desc, user=user, date=date)
-        if newnode == divergent.node() or newnode is None:
-            # no changes
-            new = divergent
-            storemarker = True
-            repo.ui.status(_(b"nothing changed\n"))
-            hg.updaterepo(repo, divergent.rev(), False)
+    # new node if any formed as the replacement
+    newnode = repo.commit(text=desc, user=user, date=date)
+    if newnode == divergent.node() or newnode is None:
+        # no changes
+        new = divergent
+        storemarker = True
+        repo.ui.status(_(b"nothing changed\n"))
+        hg.updaterepo(repo, divergent.rev(), False)
+    else:
+        new = repo[newnode]
+        newnode = new.node()
+        hg.updaterepo(repo, new.rev(), False)
+        if haspubdiv and publicdiv == divergent:
+            bypassphase(repo, (divergent, new), operation=b'evolve')
         else:
-            new = repo[newnode]
-            newnode = new.node()
-            hg.updaterepo(repo, new.rev(), False)
-            if haspubdiv and publicdiv == divergent:
-                bypassphase(repo, (divergent, new), operation=b'evolve')
-            else:
-                obsolete.createmarkers(repo, [(divergent, (new,))],
-                                       operation=b'evolve')
+            obsolete.createmarkers(repo, [(divergent, (new,))],
+                                   operation=b'evolve')
 
-        # creating markers and moving phases post-resolution
-        if haspubdiv and publicdiv == other:
-            bypassphase(repo, (other, new), operation=b'evolve')
-        else:
-            obsolete.createmarkers(repo, [(other, (new,))], operation=b'evolve')
-        if storemarker:
-            # storing the marker in the evolvestate
-            # we just store the precursors and successor pair for now, we might
-            # want to store more data and serialize obsmarker in a better way in
-            # future
-            evolvestate[b'obsmarkers'].append((other.node(), new.node()))
+    # creating markers and moving phases post-resolution
+    if haspubdiv and publicdiv == other:
+        bypassphase(repo, (other, new), operation=b'evolve')
+    else:
+        obsolete.createmarkers(repo, [(other, (new,))], operation=b'evolve')
+    if storemarker:
+        # storing the marker in the evolvestate
+        # we just store the precursors and successor pair for now, we might
+        # want to store more data and serialize obsmarker in a better way in
+        # future
+        evolvestate[b'obsmarkers'].append((other.node(), new.node()))
 
-        phases.retractboundary(repo, tr, other.phase(), [new.node()])
-        return (True, newnode)
-    finally:
-        repo.ui.restoreconfig(emtpycommitallowed)
+    phases.retractboundary(repo, tr, other.phase(), [new.node()])
+    return (True, newnode)
 
 def warnmetadataloss(repo, local, other):
     """warn the user for the metadata being lost while resolving
@@ -953,8 +976,8 @@
     ordering.extend(sorted(dependencies))
     return ordering
 
-def relocate(repo, orig, dest, evolvestate, pctx=None, keepbranch=False,
-             category=None):
+def _relocate(repo, orig, dest, evolvestate, pctx=None, keepbranch=False,
+              category=None):
     """rewrites the orig rev on dest rev
 
     returns the node of new commit which is formed
@@ -1008,10 +1031,6 @@
         with repo.dirstate.parentchange():
             repo.setparents(dest.node(), orig.node())
             repo.dirstate.write(tr)
-            # fix up dirstate for copies and renames
-            copies.duplicatecopies(repo, repo[None], dest.rev(),
-                                   orig.p1().rev())
-            dirstatedance(repo, dest, orig.node(), None)
         hint = _(b"see 'hg help evolve.interrupted'")
         raise error.InterventionRequired(_(b"unresolved merge conflicts"),
                                          hint=hint)
@@ -1027,16 +1046,14 @@
         del extra[b'branch']
     extra[b'rebase_source'] = orig.hex()
 
-    backup = repo.ui.backupconfig(b'phases', b'new-commit')
-    try:
-        targetphase = max(orig.phase(), phases.draft)
-        repo.ui.setconfig(b'phases', b'new-commit', targetphase, b'evolve')
+    targetphase = max(orig.phase(), phases.draft)
+    configoverride = repo.ui.configoverride({
+        (b'phases', b'new-commit'): targetphase
+    }, source=b'evolve')
+    with configoverride:
         # Commit might fail if unresolved files exist
-        nodenew = repo.commit(text=commitmsg, user=orig.user(),
-                              date=orig.date(), extra=extra)
-    finally:
-        repo.ui.restoreconfig(backup)
-    return nodenew
+        return repo.commit(text=commitmsg, user=orig.user(),
+                           date=orig.date(), extra=extra)
 
 def _finalizerelocate(repo, orig, dest, nodenew, tr, category, evolvestate):
     destbookmarks = repo.nodebookmarks(dest.node())
@@ -1045,7 +1062,7 @@
     bmchanges = []
 
     if nodenew is not None:
-        obsolete.createmarkers(repo, [(repo[nodesrc], (repo[nodenew],))],
+        obsolete.createmarkers(repo, [(orig, (repo[nodenew],))],
                                operation=b'evolve')
         for book in oldbookmarks:
             bmchanges.append((book, nodenew))
@@ -1054,7 +1071,7 @@
         if category == b'orphan':
             repo.ui.status(_(b"evolution of %d:%s created no changes "
                              b"to commit\n") % (orig.rev(), orig))
-        obsolete.createmarkers(repo, [(repo[nodesrc], ())], operation=b'evolve')
+        obsolete.createmarkers(repo, [(orig, ())], operation=b'evolve')
         # Behave like rebase, move bookmarks to dest
         for book in oldbookmarks:
             evolvestate[b'bookmarkchanges'].append((book, nodesrc))
@@ -1539,7 +1556,8 @@
                               b' in the repo')),
      ] + mergetoolopts,
     _(b'[OPTIONS]...'),
-    helpbasic=True
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT')
 )
 def evolve(ui, repo, **opts):
     """solve troubled changesets in your repository
@@ -1842,15 +1860,29 @@
     """logic for handling of `hg evolve --stop`"""
     updated = False
     pctx = None
+    divrelocated = evolvestate.get(b'relocated-div')
+    otherrelocated = evolvestate.get(b'relocated-other')
+    strips = []
+    if divrelocated:
+        strips.append(divrelocated)
+    if otherrelocated:
+        strips.append(otherrelocated)
     if (evolvestate[b'command'] == b'evolve'
         and evolvestate[b'category'] == b'contentdivergent'
-        and evolvestate[b'relocated']):
+        and strips):
         oldother = evolvestate[b'old-other']
-        if oldother:
+        olddiv = evolvestate[b'old-divergent']
+        if olddiv:
+            with repo.wlock(), repo.lock():
+                repo = repo.unfiltered()
+                hg.updaterepo(repo, olddiv, True)
+                repair.strip(ui, repo, strips, False)
+                updated = True
+                pctx = repo[olddiv]
+        elif oldother:
             with repo.wlock(), repo.lock():
                 repo = repo.unfiltered()
                 hg.updaterepo(repo, oldother, True)
-                strips = [evolvestate[b'relocated']]
                 repair.strip(ui, repo, strips, False)
                 updated = True
                 pctx = repo[oldother]
@@ -2024,13 +2056,47 @@
         divergent = evolvestate[b'divergent']
         base = evolvestate[b'base']
         repo = repo.unfiltered()
-        if evolvestate[b'relocating']:
+        if evolvestate[b'relocating-div']:
+            newdiv = _completerelocation(ui, repo, evolvestate)
+            current = repo[evolvestate[b'current']]
+            obsolete.createmarkers(repo, [(current, (repo[newdiv],))],
+                                   operation=b'evolve')
+            evolvestate[b'old-divergent'] = repo[divergent].node()
+            evolvestate[b'relocating-div'] = False
+            evolvestate[b'relocated-div'] = newdiv
+            evolvestate[b'temprevs'].append(newdiv)
+            evolvestate[b'divergent'] = newdiv
+
+            relocatereq = evolvestate[b'relocation-req']
+            if relocatereq:
+                divergent = repo[evolvestate[b'divergent']]
+                other = repo[evolvestate[b'other-divergent']]
+                evolvestate[b'relocating-other'] = True
+                ui.status(_(b'rebasing "other" content-divergent changeset %s on'
+                            b' %s\n' % (other, divergent.p1())))
+                with state.saver(evolvestate, {b'current': other.node()}):
+                    newother = _relocate(repo, other, divergent.p1(),
+                                         evolvestate, keepbranch=True)
+                evolvestate[b'old-other'] = other.node()
+                other = repo[newother]
+                evolvestate[b'relocating-other'] = False
+                evolvestate[b'relocated-other'] = other.node()
+                evolvestate[b'temprevs'].append(other.node())
+                evolvestate[b'other-divergent'] = other.node()
+                # continue the resolution by merging the content-divergence
+                _mergecontentdivergents(repo, progresscb,
+                                        divergent,
+                                        repo[newother],
+                                        repo[base],
+                                        evolvestate)
+
+        if evolvestate[b'relocating-other']:
             newother = _completerelocation(ui, repo, evolvestate)
             current = repo[evolvestate[b'current']]
             obsolete.createmarkers(repo, [(current, (repo[newother],))],
                                    operation=b'evolve')
-            evolvestate[b'relocating'] = False
-            evolvestate[b'relocated'] = newother
+            evolvestate[b'relocating-other'] = False
+            evolvestate[b'relocated-other'] = newother
             evolvestate[b'temprevs'].append(newother)
             evolvestate[b'other-divergent'] = newother
             # continue the resolution by merging the content-divergence
@@ -2040,18 +2106,19 @@
                                     repo[base],
                                     evolvestate)
 
+        divergent = evolvestate[b'divergent']
         other = evolvestate[b'other-divergent']
-        ret = _completecontentdivergent(ui, repo, progresscb,
-                                        repo[divergent],
-                                        repo[other],
-                                        repo[base],
-                                        evolvestate)
+        res, newnode = _completecontentdivergent(ui, repo, progresscb,
+                                                 repo[divergent],
+                                                 repo[other],
+                                                 repo[base],
+                                                 evolvestate)
         origdivergent = evolvestate[b'orig-divergent']
-        evolvestate[b'replacements'][origdivergent] = ret[1]
+        evolvestate[b'replacements'][origdivergent] = newnode
+        ret = (res, newnode)
         # logic to continue the public content-divergent
         publicnode = evolvestate.get(b'public-divergent')
         if publicnode:
-            res, newnode = ret
             if not res:
                 # no need to proceed for phase divergence resolution step
                 pass
@@ -2136,7 +2203,7 @@
 
     ctxparents = orig.parents()
     if len(ctxparents) == 2:
-        currentp1 = repo.dirstate.parents()[0]
+        currentp1 = repo.dirstate.p1()
         p1obs = ctxparents[0].obsolete()
         p2obs = ctxparents[1].obsolete()
         # asumming that the parent of current wdir is successor of one
@@ -2166,7 +2233,7 @@
             pass
     else:
         with repo.dirstate.parentchange():
-            repo.dirstate.setparents(repo.dirstate.parents()[0], nodemod.nullid)
+            repo.dirstate.setparents(repo.dirstate.p1(), nodemod.nullid)
 
     with repo.ui.configoverride(overrides, b'evolve-continue'):
         node = repo.commit(text=message, user=user,
--- a/hgext3rd/evolve/exthelper.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/exthelper.py	Thu Dec 26 21:23:30 2019 +0100
@@ -50,7 +50,7 @@
         @eh.command('mynewcommand',
             [('r', 'rev', [], _('operate on these revisions'))],
             _('-r REV...'),
-            helpcategory=command.CATEGORY_XXX)
+            **compat.helpcategorykwargs('CATEGORY_XXX'))
         def newcommand(ui, repo, *revs, **opts):
             # implementation goes here
 
--- a/hgext3rd/evolve/genericcaches.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/genericcaches.py	Thu Dec 26 21:23:30 2019 +0100
@@ -14,7 +14,7 @@
     util,
 )
 
-class incrementalcachebase(object):
+class incrementalcachebase(object):  # pytype: disable=ignored-metaclass
     """base class for incremental cache from append only source
 
     There are multiple append only data source we might want to cache
@@ -133,7 +133,7 @@
         """read the cachekey from bytes"""
         return self._cachekeystruct.unpack(data)
 
-class changelogsourcebase(incrementalcachebase):
+class changelogsourcebase(incrementalcachebase):  # pytype: disable=ignored-metaclass
     """an abstract class for cache sourcing data from the changelog
 
     For this purpose it use a cache key covering changelog content.
--- a/hgext3rd/evolve/metadata.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/metadata.py	Thu Dec 26 21:23:30 2019 +0100
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-__version__ = b'9.2.2.dev'
+__version__ = b'9.3.0.dev'
 testedwith = b'4.5.2 4.6.2 4.7 4.8 4.9 5.0 5.1'
 minimumhgversion = b'4.5'
 buglink = b'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obsexchange.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/obsexchange.py	Thu Dec 26 21:23:30 2019 +0100
@@ -7,17 +7,11 @@
 
 from __future__ import absolute_import
 
-try:
-    from StringIO import StringIO
-except ImportError:
-    from io import StringIO
-
 from mercurial import (
     bundle2,
     error,
     exchange,
     extensions,
-    lock as lockmod,
     node,
     obsolete,
     pushkey,
@@ -35,7 +29,6 @@
 eh = exthelper.exthelper()
 eh.merge(obsdiscovery.eh)
 obsexcmsg = utility.obsexcmsg
-obsexcprg = utility.obsexcprg
 
 eh.configitem(b'experimental', b'verbose-obsolescence-exchange', False)
 
@@ -156,82 +149,6 @@
         return _obscommon_capabilities(oldcap, repo, proto)
     wireprotov1server.commands[b'capabilities'] = (newcap, args)
 
-def _pushobsmarkers(repo, data):
-    tr = lock = None
-    try:
-        lock = repo.lock()
-        tr = repo.transaction(b'pushkey: obsolete markers')
-        new = repo.obsstore.mergemarkers(tr, data)
-        if new is not None:
-            obsexcmsg(repo.ui, b"%i obsolescence markers added\n" % new, True)
-        tr.close()
-    finally:
-        lockmod.release(tr, lock)
-    repo.hook(b'evolve_pushobsmarkers')
-
-def srv_pushobsmarkers(repo, proto):
-    """wireprotocol command"""
-    fp = StringIO()
-    proto.redirect()
-    proto.getfile(fp)
-    data = fp.getvalue()
-    fp.close()
-    _pushobsmarkers(repo, data)
-    try:
-        from mercurial import wireprototypes
-        wireprototypes.pushres # force demandimport
-    except (ImportError, AttributeError):
-        from mercurial import wireproto as wireprototypes
-    return wireprototypes.pushres(0)
-
-def _getobsmarkersstream(repo, heads=None, common=None):
-    """Get a binary stream for all markers relevant to `::<heads> - ::<common>`
-    """
-    revset = b''
-    args = []
-    repo = repo.unfiltered()
-    if heads is None:
-        revset = b'all()'
-    elif heads:
-        revset += b"(::%ln)"
-        args.append(heads)
-    else:
-        assert False, b'pulling no heads?'
-    if common:
-        revset += b' - (::%ln)'
-        args.append(common)
-    nodes = [c.node() for c in repo.set(revset, *args)]
-    markers = repo.obsstore.relevantmarkers(nodes)
-    obsdata = StringIO()
-    for chunk in obsolete.encodemarkers(markers, True):
-        obsdata.write(chunk)
-    obsdata.seek(0)
-    return obsdata
-
-def srv_pullobsmarkers(repo, proto, others):
-    """serves a binary stream of markers.
-
-    Serves relevant to changeset between heads and common. The stream is prefix
-    by a -string- representation of an integer. This integer is the size of the
-    stream."""
-    try:
-        from mercurial import wireprototypes, wireprotov1server
-        wireprototypes.pushres # force demandimport
-    except (ImportError, AttributeError):
-        from mercurial import wireproto as wireprototypes
-        wireprotov1server = wireprototypes
-    opts = wireprotov1server.options(b'', [b'heads', b'common'], others)
-    for k, v in opts.items():
-        if k in (b'heads', b'common'):
-            opts[k] = wireprototypes.decodelist(v)
-    obsdata = _getobsmarkersstream(repo, **opts)
-    finaldata = StringIO()
-    obsdata = obsdata.getvalue()
-    finaldata.write(b'%20i' % len(obsdata))
-    finaldata.write(obsdata)
-    finaldata.seek(0)
-    return wireprototypes.streamres(reader=finaldata, v1compressible=True)
-
 abortmsg = b"won't exchange obsmarkers through pushkey"
 hint = b"upgrade your client or server to use the bundle2 protocol"
 
--- a/hgext3rd/evolve/obshistory.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/obshistory.py	Thu Dec 26 21:23:30 2019 +0100
@@ -50,7 +50,8 @@
      (b'p', b'patch', False, _(b'show the patch between two obs versions')),
      (b'f', b'filternonlocal', False, _(b'filter out non local commits')),
      ] + commands.formatteropts,
-    _(b'hg olog [OPTION]... [[-r] REV]...'))
+    _(b'hg olog [OPTION]... [[-r] REV]...'),
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_NAVIGATION'))
 def cmdobshistory(ui, repo, *revs, **opts):
     """show the obsolescence history of the specified revisions
 
@@ -85,6 +86,13 @@
         revs = [b'.']
     revs = scmutil.revrange(repo, revs)
 
+    # Use the default template unless the user provided one, but not if
+    # -f was given, because that doesn't work with templates yet. Note
+    # that --no-graph doesn't support -f (it ignores it), so we also
+    # don't use templating with --no-graph.
+    if not opts['template'] and not (opts['filternonlocal'] and opts['graph']):
+        opts['template'] = DEFAULT_TEMPLATE
+
     if opts['graph']:
         return _debugobshistorygraph(ui, repo, revs, opts)
 
@@ -135,6 +143,35 @@
 
     return values
 
+TEMPLATE_MISSING_NODE = b"""{label("evolve.node evolve.missing_change_ctx", node|short)}"""
+TEMPLATE_PRESENT_NODE = b"""{label("evolve.node", node|short)} {label("evolve.rev", "({rev})")} {label("evolve.short_description", desc|firstline)}"""
+TEMPLATE_FIRST_LINE = b"""{if(rev, "%(presentnode)s", "%(missingnode)s")}""" % {
+    b"presentnode": TEMPLATE_PRESENT_NODE,
+    b"missingnode": TEMPLATE_MISSING_NODE
+}
+TEMPLATE_VERB = b"""{label("evolve.verb", verb)}"""
+TEMPLATE_SUCCNODES = b"""{label("evolve.node", join(succnodes % "{succnode|short}", ", "))}"""
+TEMPLATE_REWRITE = b"""{if(succnodes, "%(verb)s{if(effects, "({join(effects, ", ")})")} as %(succnodes)s", "pruned")}""" % {
+    b"verb": TEMPLATE_VERB,
+    b"succnodes": TEMPLATE_SUCCNODES
+}
+TEMPLATE_OPERATION = b"""{if(operation, "using {label("evolve.operation", operation)}")}"""
+TEMPLATE_USER = b"""by {label("evolve.user", user)}"""
+TEMPLATE_DATE = b"""{label("evolve.date", "({date(date, "%a %b %d %H:%M:%S %Y %1%2")})")}"""
+TEMPLATE_NOTE = b"""{if(note, "\n    note: {label("evolve.note", note)}")}"""
+TEMPLATE_PATCH = b"""{if(patch, "{patch}")}{if(nopatchreason, "\n(No patch available, {nopatchreason})")}"""
+DEFAULT_TEMPLATE = (b"""%(firstline)s
+{markers %% "  {separate(" ", "%(rewrite)s", "%(operation)s", "%(user)s", "%(date)s")}%(note)s{indent(descdiff, "    ")}{indent("%(patch)s", "    ")}\n"}
+""") % {
+    b"firstline": TEMPLATE_FIRST_LINE,
+    b"rewrite": TEMPLATE_REWRITE,
+    b"operation": TEMPLATE_OPERATION,
+    b"user": TEMPLATE_USER,
+    b"date": TEMPLATE_DATE,
+    b"note": TEMPLATE_NOTE,
+    b"patch": TEMPLATE_PATCH,
+}
+
 class obsmarker_printer(compat.changesetprinter):
     """show (available) information about a node
 
@@ -178,7 +215,7 @@
                 succs = sorted(succs)
 
                 for successor in succs:
-                    _debugobshistorydisplaymarker(markerfm, successor,
+                    _debugobshistorydisplaymarker(self.ui, markerfm, successor,
                                                   ctx.node(), self.repo,
                                                   self._includediff)
 
@@ -190,11 +227,11 @@
                     if not markers:
                         continue
                     successors = succset[b"successors"]
-                    _debugobshistorydisplaysuccsandmarkers(markerfm, successors, markers, ctx.node(), self.repo, self._includediff)
+                    _debugobshistorydisplaysuccsandmarkers(self.ui, markerfm, successors, markers, ctx.node(), self.repo, self._includediff)
 
             markerfm.end()
 
-            markerfm.plain(b'\n')
+            fm.plain(b'\n')
             fm.end()
 
             self.hunk[ctx.node()] = self.ui.popbuffer()
@@ -454,8 +491,9 @@
         markerfm = fm.nested(b"markers")
         for successor in sorted(succs):
             includediff = opts and opts.get("patch")
-            _debugobshistorydisplaymarker(markerfm, successor, ctxnode, unfi, includediff)
+            _debugobshistorydisplaymarker(ui, markerfm, successor, ctxnode, unfi, includediff)
         markerfm.end()
+        fm.plain(b'\n')
 
         precs = precursors.get(ctxnode, ())
         for p in sorted(precs):
@@ -477,12 +515,12 @@
         shortdescription = shortdescription.splitlines()[0]
 
     fm.startitem()
-    fm.write(b'node', b'%s', bytes(ctx),
-             label=b"evolve.node")
+    fm.context(ctx=ctx)
+    fm.data(node=ctx.hex())
+    fm.plain(b'%s' % bytes(ctx), label=b"evolve.node")
     fm.plain(b' ')
 
-    fm.write(b'rev', b'(%d)', ctx.rev(),
-             label=b"evolve.rev")
+    fm.plain(b'(%d)' % ctx.rev(), label=b"evolve.rev")
     fm.plain(b' ')
 
     fm.write(b'shortdescription', b'%s', shortdescription,
@@ -490,19 +528,18 @@
     fm.plain(b'\n')
 
 def _debugobshistorydisplaymissingctx(fm, nodewithoutctx):
-    hexnode = nodemod.short(nodewithoutctx)
     fm.startitem()
-    fm.write(b'node', b'%s', hexnode,
+    fm.data(node=nodemod.hex(nodewithoutctx))
+    fm.plain(nodemod.short(nodewithoutctx),
              label=b"evolve.node evolve.missing_change_ctx")
     fm.plain(b'\n')
 
-def _debugobshistorydisplaymarker(fm, marker, node, repo, includediff=False):
+def _debugobshistorydisplaymarker(ui, fm, marker, node, repo, includediff=False):
     succnodes = marker[1]
     date = marker[4]
     metadata = dict(marker[3])
 
     fm.startitem()
-    fm.plain(b'  ')
 
     # Detect pruned revisions
     if len(succnodes) == 0:
@@ -510,8 +547,7 @@
     else:
         verb = b'rewritten'
 
-    fm.write(b'verb', b'%s', verb,
-             label=b"evolve.verb")
+    fm.data(verb=verb)
 
     effectflag = metadata.get(b'ef1')
     if effectflag is not None:
@@ -520,54 +556,44 @@
         except ValueError:
             effectflag = None
     if effectflag:
-        effect = []
+        effects = []
 
         # XXX should be a dict
         if effectflag & DESCCHANGED:
-            effect.append(b'description')
+            effects.append(b'description')
         if effectflag & METACHANGED:
-            effect.append(b'meta')
+            effects.append(b'meta')
         if effectflag & USERCHANGED:
-            effect.append(b'user')
+            effects.append(b'user')
         if effectflag & DATECHANGED:
-            effect.append(b'date')
+            effects.append(b'date')
         if effectflag & BRANCHCHANGED:
-            effect.append(b'branch')
+            effects.append(b'branch')
         if effectflag & PARENTCHANGED:
-            effect.append(b'parent')
+            effects.append(b'parent')
         if effectflag & DIFFCHANGED:
-            effect.append(b'content')
+            effects.append(b'content')
 
-        if effect:
-            fmteffect = fm.formatlist(effect, b'effect', sep=b', ')
-            fm.write(b'effect', b'(%s)', fmteffect)
+        if effects:
+            fmteffect = fm.formatlist(effects, b'effect')
+            fm.write(b'effects', b'(%s)', fmteffect)
 
     if len(succnodes) > 0:
-        fm.plain(b' as ')
-
-        shortsnodes = (nodemod.short(succnode) for succnode in sorted(succnodes))
-        nodes = fm.formatlist(shortsnodes, b'succnodes', sep=b', ')
-        fm.write(b'succnodes', b'%s', nodes,
-                 label=b"evolve.node")
+        hexnodes = (nodemod.hex(succnode) for succnode in sorted(succnodes))
+        nodes = fm.formatlist(hexnodes, b'succnode')
+        fm.write(b'succnodes', b'%s', nodes)
 
     operation = metadata.get(b'operation')
     if operation:
-        fm.plain(b' using ')
-        fm.write(b'operation', b'%s', operation, label=b"evolve.operation")
-
-    fm.plain(b' by ')
+        fm.data(operation=operation)
 
-    fm.write(b'user', b'%s', metadata[b'user'],
-             label=b"evolve.user")
-    fm.plain(b' ')
+    fm.data(user=metadata[b'user'])
 
-    fm.write(b'date', b'(%s)', fm.formatdate(date),
-             label=b"evolve.date")
+    fm.data(date=date)
 
     # initial support for showing note
     if metadata.get(b'note'):
-        fm.plain(b'\n    note: ')
-        fm.write(b'note', b"%s", metadata[b'note'], label=b"evolve.note")
+        fm.data(note=metadata[b'note'])
 
     # Patch display
     if includediff is True:
@@ -592,15 +618,16 @@
                 def tolist(text):
                     return [text]
 
-                fm.plain(b"\n")
+                ui.pushbuffer(labeled=True)
+                ui.write(b"\n")
 
                 for chunk, label in patch.difflabel(tolist, descriptionpatch):
                     chunk = chunk.strip(b'\t')
-                    if chunk and chunk != b'\n':
-                        fm.plain(b'    ')
-                    fm.write(b'desc-diff', b'%s', chunk, label=label)
+                    ui.write(chunk, label=label)
+                fm.write(b'descdiff', b'%s', ui.popbuffer())
 
             # Content patch
+            ui.pushbuffer(labeled=True)
             diffopts = patch.diffallopts(repo.ui, {})
             matchfn = scmutil.matchall(repo)
             firstline = True
@@ -608,23 +635,18 @@
             for chunk, label in patch.diffui(repo, node, succ, matchfn,
                                              opts=diffopts):
                 if firstline:
-                    fm.plain(b'\n')
+                    ui.write(b'\n')
                     firstline = False
                 if linestart:
-                    fm.plain(b'    ')
                     linestart = False
                 if chunk == b'\n':
                     linestart = True
-                fm.write(b'patch', b'%s', chunk, label=label)
+                ui.write(chunk, label=label)
+            fm.data(patch=ui.popbuffer())
         else:
-            nopatch = b"    (No patch available, %s)" % _patchavailable[1]
-            fm.plain(b"\n")
-            # TODO: should be in json too
-            fm.plain(nopatch)
+            fm.data(nopatchreason=_patchavailable[1])
 
-    fm.plain(b"\n")
-
-def _debugobshistorydisplaysuccsandmarkers(fm, succnodes, markers, node, repo, includediff=False):
+def _debugobshistorydisplaysuccsandmarkers(ui, fm, succnodes, markers, node, repo, includediff=False):
     """
     This function is a duplication of _debugobshistorydisplaymarker modified
     to accept multiple markers as input.
@@ -648,33 +670,33 @@
             effectflag |= int(ef)
 
     if effectflag:
-        effect = []
+        effects = []
 
         # XXX should be a dict
         if effectflag & DESCCHANGED:
-            effect.append(b'description')
+            effects.append(b'description')
         if effectflag & METACHANGED:
-            effect.append(b'meta')
+            effects.append(b'meta')
         if effectflag & USERCHANGED:
-            effect.append(b'user')
+            effects.append(b'user')
         if effectflag & DATECHANGED:
-            effect.append(b'date')
+            effects.append(b'date')
         if effectflag & BRANCHCHANGED:
-            effect.append(b'branch')
+            effects.append(b'branch')
         if effectflag & PARENTCHANGED:
-            effect.append(b'parent')
+            effects.append(b'parent')
         if effectflag & DIFFCHANGED:
-            effect.append(b'content')
+            effects.append(b'content')
 
-        if effect:
-            fmteffect = fm.formatlist(effect, b'effect', sep=b', ')
-            fm.write(b'effect', b'(%s)', fmteffect)
+        if effects:
+            fmteffect = fm.formatlist(effects, b'effect', sep=b', ')
+            fm.write(b'effects', b'(%s)', fmteffect)
 
     if len(succnodes) > 0:
         fm.plain(b' as ')
 
         shortsnodes = (nodemod.short(succnode) for succnode in sorted(succnodes))
-        nodes = fm.formatlist(shortsnodes, b'succnodes', sep=b', ')
+        nodes = fm.formatlist(shortsnodes, b'succnode', sep=b', ')
         fm.write(b'succnodes', b'%s', nodes,
                  label=b"evolve.node")
 
@@ -732,15 +754,18 @@
                 def tolist(text):
                     return [text]
 
-                fm.plain(b"\n")
+                ui.pushbuffer(labeled=True)
+                ui.write(b"\n")
 
                 for chunk, label in patch.difflabel(tolist, descriptionpatch):
                     chunk = chunk.strip(b'\t')
                     if chunk and chunk != b'\n':
-                        fm.plain(b'    ')
-                    fm.write(b'desc-diff', b'%s', chunk, label=label)
+                        ui.write(b'    ')
+                    ui.write(chunk, label=label)
+                fm.write(b'descdiff', b'%s', ui.popbuffer())
 
             # Content patch
+            ui.pushbuffer(labeled=True)
             diffopts = patch.diffallopts(repo.ui, {})
             matchfn = scmutil.matchall(repo)
             firstline = True
@@ -748,19 +773,18 @@
             for chunk, label in patch.diffui(repo, node, succ, matchfn,
                                              opts=diffopts):
                 if firstline:
-                    fm.plain(b'\n')
+                    ui.write(b'\n')
                     firstline = False
                 if linestart:
-                    fm.plain(b'    ')
+                    ui.write(b'    ')
                     linestart = False
                 if chunk == b'\n':
                     linestart = True
-                fm.write(b'patch', b'%s', chunk, label=label)
+                ui.write(chunk, label=label)
+            fm.write(b'patch', b'%s', ui.popbuffer())
         else:
-            nopatch = b"    (No patch available, %s)" % _patchavailable[1]
-            fm.plain(b"\n")
-            # TODO: should be in json too
-            fm.plain(nopatch)
+            fm.write(b'nopatchreason', b"\n    (No patch available, %s)",
+                     _patchavailable[1])
 
     fm.plain(b"\n")
 
--- a/hgext3rd/evolve/rewind.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/rewind.py	Thu Dec 26 21:23:30 2019 +0100
@@ -36,7 +36,8 @@
       _(b"do not modify working directory during rewind")),
      ],
     _(b'[--as-divergence] [--exact] [--keep] [--to REV]... [--from REV]...'),
-    helpbasic=True)
+    helpbasic=True,
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_MANAGEMENT'))
 def rewind(ui, repo, **opts):
     """rewind a stack of changesets to a previous state
 
--- a/hgext3rd/evolve/utility.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/evolve/utility.py	Thu Dec 26 21:23:30 2019 +0100
@@ -13,10 +13,6 @@
 
 from mercurial.node import nullrev
 
-from . import (
-    compat,
-)
-
 shorttemplate = b"[{label('evolve.rev', rev)}] {desc|firstline}\n"
 stacktemplate = b"""[{label('evolve.rev', if(topicidx, "s{topicidx}", rev))}] {desc|firstline}\n"""
 
@@ -27,12 +23,6 @@
     if important or verbose:
         ui.status(message)
 
-def obsexcprg(ui, *args, **kwargs):
-    topic = b'obsmarkers exchange'
-    if ui.configbool(b'experimental', b'verbose-obsolescence-exchange'):
-        topic = b'OBSEXC'
-    compat.progress(ui, topic, *args, **kwargs)
-
 def filterparents(parents):
     """filter nullrev parents
 
@@ -126,7 +116,7 @@
         ui.debug(b"stabilize target %s is plain dead,"
                  b" trying to stabilize on its parent\n" %
                  obs)
-        obs = obs.parents()[0]
+        obs = obs.p1()
         newer = obsutil.successorssets(repo, obs.node())
     if len(newer) > 1 or len(newer[0]) > 1:
         raise MultipleSuccessorsError(newer)
--- a/hgext3rd/topic/__init__.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/topic/__init__.py	Thu Dec 26 21:23:30 2019 +0100
@@ -135,6 +135,7 @@
     namespaces,
     node,
     obsolete,
+    obsutil,
     patch,
     phases,
     pycompat,
@@ -187,7 +188,7 @@
               b'topic.active': b'green',
               }
 
-__version__ = b'0.17.2.dev'
+__version__ = b'0.18.0.dev'
 
 testedwith = b'4.5.2 4.6.2 4.7 4.8 4.9 5.0 5.1'
 minimumhgversion = b'4.5'
@@ -384,14 +385,14 @@
             return caps
 
         def commit(self, *args, **kwargs):
-            backup = self.ui.backupconfig(b'ui', b'allowemptycommit')
-            try:
-                if self.currenttopic != self[b'.'].topic():
-                    # bypass the core "nothing changed" logic
-                    self.ui.setconfig(b'ui', b'allowemptycommit', True)
+            configoverride = util.nullcontextmanager()
+            if self.currenttopic != self[b'.'].topic():
+                # bypass the core "nothing changed" logic
+                configoverride = self.ui.configoverride({
+                    (b'ui', b'allowemptycommit'): True
+                })
+            with configoverride:
                 return super(topicrepo, self).commit(*args, **kwargs)
-            finally:
-                self.ui.restoreconfig(backup)
 
         def commitctx(self, ctx, *args, **kwargs):
             topicfilter = topicmap.topicfilter(self.filtername)
@@ -634,7 +635,8 @@
         (b'', b'age', False, b'show when you last touched the topics'),
         (b'', b'current', None, b'display the current topic only'),
     ] + commands.formatteropts,
-    _(b'hg topics [OPTION]... [-r REV]... [TOPIC]'))
+    _(b'hg topics [OPTION]... [-r REV]... [TOPIC]'),
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_ORGANIZATION'))
 def topics(ui, repo, topic=None, **opts):
     """View current topic, set current topic, change topic for a set of revisions, or see all topics.
 
@@ -777,7 +779,8 @@
         (b'c', b'children', None,
             _(b'display data about children outside of the stack'))
     ] + commands.formatteropts,
-    _(b'hg stack [TOPIC]'))
+    _(b'hg stack [TOPIC]'),
+    **compat.helpcategorykwargs('CATEGORY_CHANGE_NAVIGATION'))
 def cmdstack(ui, repo, topic=b'', **opts):
     """list all changesets in a topic and other information
 
@@ -1115,7 +1118,7 @@
                 user = repo[revs].user()
             # looking on the markers also to get more information and accurate
             # last touch time.
-            obsmarkers = compat.getmarkers(repo, [repo[revs].node()])
+            obsmarkers = obsutil.getmarkers(repo, [repo[revs].node()])
             for marker in obsmarkers:
                 rt = marker.date()
                 if rt[0] > maxtime[0]:
@@ -1258,14 +1261,13 @@
 def checkt0(orig, ui, repo, node=None, rev=None, *args, **kwargs):
 
     thezeros = set([b't0', b'b0', b's0'])
-    backup = repo.ui.backupconfig(b'_internal', b'keep-topic')
-    try:
-        if node in thezeros or rev in thezeros:
-            repo.ui.setconfig(b'_internal', b'keep-topic', b'yes',
-                              source=b'topic-extension')
+    configoverride = util.nullcontextmanager()
+    if node in thezeros or rev in thezeros:
+        configoverride = repo.ui.configoverride({
+            (b'_internal', b'keep-topic'): b'yes'
+        }, source=b'topic-extension')
+    with configoverride:
         return orig(ui, repo, node=node, rev=rev, *args, **kwargs)
-    finally:
-        repo.ui.restoreconfig(backup)
 
 def _fixrebase(loaded):
     if not loaded:
--- a/hgext3rd/topic/compat.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/topic/compat.py	Thu Dec 26 21:23:30 2019 +0100
@@ -8,25 +8,11 @@
 from __future__ import absolute_import
 
 from mercurial import (
-    obsolete,
     pycompat,
+    registrar,
     util,
 )
 
-getmarkers = None
-successorssets = None
-try:
-    from mercurial import obsutil
-    getmarkers = getattr(obsutil, 'getmarkers', None)
-    successorssets = getattr(obsutil, 'successorssets', None)
-except ImportError:
-    pass
-
-if getmarkers is None:
-    getmarkers = obsolete.getmarkers
-if successorssets is None:
-    successorssets = obsolete.successorssets
-
 if pycompat.ispy3:
     def branchmapitems(branchmap):
         return branchmap.items()
@@ -36,6 +22,15 @@
         return branchmap.iteritems()
     # py3-transform: on
 
+# help category compatibility
+# hg <= 4.7 (c303d65d2e34)
+def helpcategorykwargs(categoryname):
+    """Backwards-compatible specification of the helpategory argument."""
+    category = getattr(registrar.command, categoryname, None)
+    if not category:
+        return {}
+    return {'helpcategory': category}
+
 # nodemap.get and index.[has_node|rev|get_rev]
 # hg <= 5.3 (02802fa87b74)
 def getgetrev(cl):
--- a/hgext3rd/topic/evolvebits.py	Thu Dec 26 12:22:49 2019 +0700
+++ b/hgext3rd/topic/evolvebits.py	Thu Dec 26 21:23:30 2019 +0100
@@ -81,7 +81,7 @@
         ui.debug(b"stabilize target %s is plain dead,"
                  b" trying to stabilize on its parent\n" %
                  obs)
-        obs = obs.parents()[0]
+        obs = obs.p1()
         newer = obsutil.successorssets(repo, obs.node())
     if 1 < len(newer):
         # divergence case
--- a/tests/test-evolve-content-divergent-meta.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-content-divergent-meta.t	Thu Dec 26 21:23:30 2019 +0100
@@ -14,6 +14,7 @@
   > publish = False
   > [extensions]
   > rebase =
+  > strip =
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
 
@@ -157,3 +158,213 @@
   user:        baruser, foouser
 
   $ cd ..
+
+Test the content-divergence resolution involving date update
+------------------------------------------------------------
+
+  $ hg init divergingdate
+  $ cd divergingdate
+  $ unset HGUSER
+  $ echo "[ui]" >> ./.hg/hgrc
+  $ echo "username = test" >> ./.hg/hgrc
+
+  $ echo hi > r0
+  $ hg ci -qAm 'add r0'
+  $ echo hi > foo.txt
+  $ hg ci -qAm 'add foo.txt'
+  $ hg metaedit -r . -d '0 2'
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+date: updated on both side to the same value
+
+  $ echo hi > bar.txt
+  $ hg add -q bar.txt
+  $ hg amend -q
+  $ hg metaedit -r 1 -d '0 1' --hidden
+  2 new content-divergent changesets
+  $ hg log -G
+  *  changeset:   4:c17bf400a278
+  |  tag:         tip
+  |  parent:      0:a24ed8ad918c
+  |  user:        test
+  |  date:        Wed Dec 31 23:59:59 1969 -0000
+  |  instability: content-divergent
+  |  summary:     add foo.txt
+  |
+  | @  changeset:   3:a25dd7af6cf6
+  |/   parent:      0:a24ed8ad918c
+  |    user:        test
+  |    date:        Wed Dec 31 23:59:58 1969 -0000
+  |    instability: content-divergent
+  |    summary:     add foo.txt
+  |
+  o  changeset:   0:a24ed8ad918c
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add r0
+  
+  $ hg evolve --list --rev .
+  a25dd7af6cf6: add foo.txt
+    content-divergent: c17bf400a278 (draft) (precursor cc71ffbc7c00)
+  
+  $ hg log --hidden -r cc71ffbc7c00 -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  1 cc71ffbc7c00 1970-01-01 00:00 +0000: date-changed using metaedit as 4:c17bf400a278; date-changed using metaedit as 2:0065551bd38f
+  $ hg log -r 'desc("add foo.txt")' -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  3 a25dd7af6cf6 1969-12-31 23:59 -0000: 
+  4 c17bf400a278 1969-12-31 23:59 -0000: 
+  $ hg evolve --content-divergent
+  merge:[3] add foo.txt
+  with: [4] add foo.txt
+  base: [1] add foo.txt
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 6c144bb30333
+  $ hg log -r 'desc("add foo.txt")' -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  5 6c144bb30333 1969-12-31 23:59 -0000: 
+
+date: updated one one side to an older value
+
+  $ hg strip .
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/divergingdate/.hg/strip-backup/6c144bb30333-72e26b88-backup.hg
+  2 new content-divergent changesets
+  $ hg up tip
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg amend --date "0 3"
+  $ hg log -G
+  @  changeset:   5:6189a9adfff0
+  |  tag:         tip
+  |  parent:      0:a24ed8ad918c
+  |  user:        test
+  |  date:        Wed Dec 31 23:59:57 1969 -0000
+  |  instability: content-divergent
+  |  summary:     add foo.txt
+  |
+  | *  changeset:   3:a25dd7af6cf6
+  |/   parent:      0:a24ed8ad918c
+  |    user:        test
+  |    date:        Wed Dec 31 23:59:58 1969 -0000
+  |    instability: content-divergent
+  |    summary:     add foo.txt
+  |
+  o  changeset:   0:a24ed8ad918c
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add r0
+  
+  $ hg evolve --list -r .
+  6189a9adfff0: add foo.txt
+    content-divergent: a25dd7af6cf6 (draft) (precursor cc71ffbc7c00)
+  
+  $ hg log -r cc71ffbc7c00+6189a9adfff0+a25dd7af6cf6 --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  1 cc71ffbc7c00 1970-01-01 00:00 +0000: date-changed using metaedit as 4:c17bf400a278; date-changed using metaedit as 2:0065551bd38f
+  5 6189a9adfff0 1969-12-31 23:59 -0000: 
+  3 a25dd7af6cf6 1969-12-31 23:59 -0000: 
+  $ hg evolve --content-divergent
+  merge:[3] add foo.txt
+  with: [5] add foo.txt
+  base: [1] add foo.txt
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 806d0024c04d
+  $ hg log -r . --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  6 806d0024c04d 1969-12-31 23:59 -0000: 
+
+date: updated one side to an newer value
+
+  $ hg strip .
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/divergingdate/.hg/strip-backup/806d0024c04d-24cb28ad-backup.hg
+  2 new content-divergent changesets
+  $ hg update a25dd7af6cf6 --hidden
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg amend --date "120 0"
+  $ hg log -G
+  @  changeset:   6:5199d0bc13d4
+  |  tag:         tip
+  |  parent:      0:a24ed8ad918c
+  |  user:        test
+  |  date:        Thu Jan 01 00:02:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     add foo.txt
+  |
+  | *  changeset:   5:6189a9adfff0
+  |/   parent:      0:a24ed8ad918c
+  |    user:        test
+  |    date:        Wed Dec 31 23:59:57 1969 -0000
+  |    instability: content-divergent
+  |    summary:     add foo.txt
+  |
+  o  changeset:   0:a24ed8ad918c
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add r0
+  
+  $ hg evolve --list -r .
+  5199d0bc13d4: add foo.txt
+    content-divergent: 6189a9adfff0 (draft) (precursor cc71ffbc7c00)
+  
+  $ hg up 6189a9adfff0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg log -r cc71ffbc7c00+6189a9adfff0+5199d0bc13d4 --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  1 cc71ffbc7c00 1970-01-01 00:00 +0000: date-changed using metaedit as 4:c17bf400a278; date-changed using metaedit as 2:0065551bd38f
+  5 6189a9adfff0 1969-12-31 23:59 -0000: 
+  6 5199d0bc13d4 1970-01-01 00:02 +0000: 
+  $ hg evolve --content-divergent
+  merge:[5] add foo.txt
+  with: [6] add foo.txt
+  base: [1] add foo.txt
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 51e08ac59670
+  $ hg log -r . --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  7 51e08ac59670 1970-01-01 00:02 +0000: 
+
+date: updated each side to a different value, newer should win
+
+  $ hg strip .
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/divergingdate/.hg/strip-backup/51e08ac59670-d8a3c2ca-backup.hg
+  2 new content-divergent changesets
+  $ hg up tip
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg amend --date "235 0"
+  $ hg update 6189a9adfff0 --hidden
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg amend --date "784 0"
+  $ hg log -G
+  @  changeset:   8:75254fb3164b
+  |  tag:         tip
+  |  parent:      0:a24ed8ad918c
+  |  user:        test
+  |  date:        Thu Jan 01 00:13:04 1970 +0000
+  |  instability: content-divergent
+  |  summary:     add foo.txt
+  |
+  | *  changeset:   7:5421a7efcc6e
+  |/   parent:      0:a24ed8ad918c
+  |    user:        test
+  |    date:        Thu Jan 01 00:03:55 1970 +0000
+  |    instability: content-divergent
+  |    summary:     add foo.txt
+  |
+  o  changeset:   0:a24ed8ad918c
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add r0
+  
+  $ hg evolve --list -r .
+  75254fb3164b: add foo.txt
+    content-divergent: 5421a7efcc6e (draft) (precursor cc71ffbc7c00)
+  
+  $ hg log -r 6189a9adfff0+5421a7efcc6e+75254fb3164b --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  5 6189a9adfff0 1969-12-31 23:59 -0000: date-changed using amend as 8:75254fb3164b
+  7 5421a7efcc6e 1970-01-01 00:03 +0000: 
+  8 75254fb3164b 1970-01-01 00:13 +0000: 
+  $ hg evolve --content-divergent
+  merge:[7] add foo.txt
+  with: [8] add foo.txt
+  base: [1] add foo.txt
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at ab7c0a425dc9
+  $ hg log -r . --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
+  9 ab7c0a425dc9 1970-01-01 00:13 +0000: 
+
+  $ cd ..
--- a/tests/test-evolve-content-divergent-stack.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-content-divergent-stack.t	Thu Dec 26 21:23:30 2019 +0100
@@ -10,6 +10,7 @@
   > [phases]
   > publish = False
   > [extensions]
+  > strip =
   > rebase =
   > EOF
   $ echo "evolve=$(echo $(dirname $TESTDIR))/hgext3rd/evolve/" >> $HGRCPATH
@@ -348,6 +349,386 @@
   |   () [default] draft
   o  0:8fa14d15e168 added hgignore
       () [default] draft
+
+when "divergent" and "other" both hit merge conflict in relocating
+------------------------------------------------------------------
+
+  $ hg strip 14: --hidden
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/stackrepo1/.hg/strip-backup/74fbf3e6a0b6-f3612603-backup.hg
+  8 new content-divergent changesets
+
+Prepare repo to have merge conflicts
+  $ hg up -r "max(desc('added a'))"
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg evolve -r . --content-divergent
+  merge:[10] added a
+  with: [5] watbar to a
+  base: [1] added a
+  rebasing "other" content-divergent changeset 8e222f257bbf on 2228e3b74514
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  6 new orphan changesets
+  working directory is now at 74fbf3e6a0b6
+  $ echo b_conflict > b
+  $ hg amend -A
+  adding b
+
+Let's try to evolve stack
+  $ hg evolve --content-divergent
+  merge:[11] added b
+  with: [6] added b
+  base: [2] added b
+  rebasing "divergent" content-divergent changeset 6eb54b5af3fb on 119989a4317e
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+
+  $ echo b > b
+  $ hg res -m
+  (no more unresolved files)
+  continue: hg evolve --continue
+  $ hg evolve --continue
+  evolving 11:6eb54b5af3fb "added b"
+  rebasing "other" content-divergent changeset d5f148423c16 on 119989a4317e
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+
+  $ echo b > b
+  $ hg res -m
+  (no more unresolved files)
+  continue: hg evolve --continue
+  $ hg evolve --continue
+  evolving 6:d5f148423c16 "added b"
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merge:[12] added c
+  with: [7] added c
+  base: [3] added c
+  rebasing "divergent" content-divergent changeset 8ed612937375 on 646bd3372ee7
+  rebasing "other" content-divergent changeset 3ce4be6d8e5e on 646bd3372ee7
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merge:[13] added d
+  with: [8] added d
+  base: [4] added d
+  rebasing "divergent" content-divergent changeset d45f050514c2 on 67abc597e636
+  rebasing "other" content-divergent changeset c72d2885eb51 on 67abc597e636
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 119989a4317e
+  $ hg glog
+  o  25:5e2572194f59 added d
+  |   () [default] draft
+  o  22:67abc597e636 added c
+  |   () [default] draft
+  o  19:646bd3372ee7 added b
+  |   () [default] draft
+  @  16:119989a4317e watbar to a
+  |   () [default] draft
+  o  9:2228e3b74514 add newfile
+  |   () [default] draft
+  o  0:8fa14d15e168 added hgignore
+      () [default] draft
+
+when relocating "other" hit merge conflict but not "divergent"
+--------------------------------------------------------------
+  $ hg strip 14: --hidden
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/stackrepo1/.hg/strip-backup/74fbf3e6a0b6-15474722-backup.hg
+  8 new content-divergent changesets
+
+Insert conflicting changes in between the stack of content-div csets
+  $ hg up -r "max(desc('added b'))"
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo b_diverging_local > b
+  $ hg amend
+  2 new orphan changesets
+  $ hg evolve
+  move:[12] added c
+  atop:[14] added b
+  move:[13] added d
+  $ hg up -r d5f148423c16
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo b_diverging_other > b
+  $ hg amend
+  2 new orphan changesets
+  $ hg evolve
+  move:[7] added c
+  atop:[17] added b
+  move:[8] added d
+
+  $ hg log -r tip
+  changeset:   19:c351be27f199
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  instability: content-divergent
+  summary:     added d
+  
+Now let's try to evolve stack
+  $ hg evolve --content-divergent
+  merge:[10] added a
+  with: [5] watbar to a
+  base: [1] added a
+  rebasing "other" content-divergent changeset 8e222f257bbf on 2228e3b74514
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merge:[14] added b
+  with: [17] added b
+  base: [2] added b
+  rebasing "divergent" content-divergent changeset 2a955e808c53 on 74fbf3e6a0b6
+  rebasing "other" content-divergent changeset 509103439e5e on 74fbf3e6a0b6
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  4 new orphan changesets
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+
+As now we have interrupted evolution of stack of content-divergent cset (when
+relocation of "divergent" also included) let's test --abort and --stop 
+test --abort:
+  $ hg evolve --abort
+  2 new content-divergent changesets
+  evolve aborted
+  working directory is now at 509103439e5e
+
+confirm that tip is same as it was before we started --content-div resolution
+  $ hg log -r tip
+  changeset:   19:c351be27f199
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  instability: content-divergent
+  summary:     added d
+  
+test --stop:
+  $ hg log -G
+  *  changeset:   19:c351be27f199
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     added d
+  |
+  *  changeset:   18:eaf34afe4df3
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     added c
+  |
+  @  changeset:   17:509103439e5e
+  |  parent:      5:8e222f257bbf
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     added b
+  |
+  | *  changeset:   16:91c8ccb9c241
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: content-divergent
+  | |  summary:     added d
+  | |
+  | *  changeset:   15:48b0f803817a
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: content-divergent
+  | |  summary:     added c
+  | |
+  | *  changeset:   14:2a955e808c53
+  | |  parent:      10:c04ff147ef79
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: content-divergent
+  | |  summary:     added b
+  | |
+  | *  changeset:   10:c04ff147ef79
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: content-divergent
+  | |  summary:     added a
+  | |
+  | o  changeset:   9:2228e3b74514
+  | |  parent:      0:8fa14d15e168
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     add newfile
+  | |
+  * |  changeset:   5:8e222f257bbf
+  |/   parent:      0:8fa14d15e168
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    instability: content-divergent
+  |    summary:     watbar to a
+  |
+  o  changeset:   0:8fa14d15e168
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     added hgignore
+  
+  $ hg evolve --content-divergent
+  merge:[10] added a
+  with: [5] watbar to a
+  base: [1] added a
+  rebasing "other" content-divergent changeset 8e222f257bbf on 2228e3b74514
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merge:[14] added b
+  with: [17] added b
+  base: [2] added b
+  rebasing "divergent" content-divergent changeset 2a955e808c53 on 74fbf3e6a0b6
+  rebasing "other" content-divergent changeset 509103439e5e on 74fbf3e6a0b6
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  4 new orphan changesets
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+
+  $ hg evolve --stop
+  2 new orphan changesets
+  stopped the interrupted evolve
+  working directory is now at 2a955e808c53
+  $ hg log -G
+  o  changeset:   21:74fbf3e6a0b6
+  |  tag:         tip
+  |  parent:      9:2228e3b74514
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     watbar to a
+  |
+  | *  changeset:   19:c351be27f199
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: orphan, content-divergent
+  | |  summary:     added d
+  | |
+  | *  changeset:   18:eaf34afe4df3
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: orphan, content-divergent
+  | |  summary:     added c
+  | |
+  | *  changeset:   17:509103439e5e
+  | |  parent:      5:8e222f257bbf
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: orphan, content-divergent
+  | |  summary:     added b
+  | |
+  | | *  changeset:   16:91c8ccb9c241
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  instability: orphan, content-divergent
+  | | |  summary:     added d
+  | | |
+  | | *  changeset:   15:48b0f803817a
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  instability: orphan, content-divergent
+  | | |  summary:     added c
+  | | |
+  | | @  changeset:   14:2a955e808c53
+  | | |  parent:      10:c04ff147ef79
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  instability: orphan, content-divergent
+  | | |  summary:     added b
+  | | |
+  +---x  changeset:   10:c04ff147ef79
+  | |    user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    obsolete:    rewritten using evolve as 21:74fbf3e6a0b6
+  | |    summary:     added a
+  | |
+  o |  changeset:   9:2228e3b74514
+  | |  parent:      0:8fa14d15e168
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     add newfile
+  | |
+  | x  changeset:   5:8e222f257bbf
+  |/   parent:      0:8fa14d15e168
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rebased using evolve as 21:74fbf3e6a0b6
+  |    summary:     watbar to a
+  |
+  o  changeset:   0:8fa14d15e168
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     added hgignore
+  
+  $ hg obslog -r 'desc("watbar to a")' --all
+  o    74fbf3e6a0b6 (21) watbar to a
+  |\
+  x |  186bdc2cdfa2 (20) watbar to a
+  | |    rewritten as 74fbf3e6a0b6 using evolve by test (Thu Jan 01 00:00:00 1970 +0000)
+  | |
+  | x  c04ff147ef79 (10) added a
+  | |    rewritten(description, content) as 74fbf3e6a0b6 using evolve by test (Thu Jan 01 00:00:00 1970 +0000)
+  | |
+  x |  8e222f257bbf (5) watbar to a
+  |/     rewritten(parent) as 186bdc2cdfa2 using evolve by test (Thu Jan 01 00:00:00 1970 +0000)
+  |
+  x  c7586e2a9264 (1) added a
+       rewritten(description, content) as 8e222f257bbf using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+       rewritten(parent) as c04ff147ef79 using rebase by test (Thu Jan 01 00:00:00 1970 +0000)
+  
+  $ hg obslog -r 'desc("added b")' --all
+  @  2a955e808c53 (14) added b
+  |
+  | *  509103439e5e (17) added b
+  | |
+  x |  6eb54b5af3fb (11) added b
+  | |    rewritten(content) as 2a955e808c53 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+  | |
+  | x  d5f148423c16 (6) added b
+  |/     rewritten(content) as 509103439e5e using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+  |
+  x  b1661037fa25 (2) added b
+       rewritten(parent) as 6eb54b5af3fb using rebase by test (Thu Jan 01 00:00:00 1970 +0000)
+       rewritten(parent) as d5f148423c16 using evolve by test (Thu Jan 01 00:00:00 1970 +0000)
+  
+
+Again, let's evolve the stack
+  $ hg evolve --content-divergent
+  merge:[14] added b
+  with: [17] added b
+  base: [2] added b
+  rebasing "divergent" content-divergent changeset 2a955e808c53 on 74fbf3e6a0b6
+  rebasing "other" content-divergent changeset 509103439e5e on 74fbf3e6a0b6
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+
+  $ echo foo > b
+  $ hg res -m
+  (no more unresolved files)
+  continue: hg evolve --continue
+  $ hg evolve --continue
+  merge:[15] added c
+  with: [18] added c
+  base: [3] added c
+  rebasing "divergent" content-divergent changeset 48b0f803817a on 4e29776e83a5
+  rebasing "other" content-divergent changeset eaf34afe4df3 on 4e29776e83a5
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merge:[16] added d
+  with: [19] added d
+  base: [4] added d
+  rebasing "divergent" content-divergent changeset 91c8ccb9c241 on 77126af93a25
+  rebasing "other" content-divergent changeset c351be27f199 on 77126af93a25
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  working directory is now at 4e29776e83a5
+
+  $ hg evolve -l
+
   $ cd ..
 
 Make sure that content-divergent resolution doesn't undo a change (issue6203)
--- a/tests/test-evolve-continue.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-continue.t	Thu Dec 26 21:23:30 2019 +0100
@@ -434,3 +434,31 @@
   M c
   A d
   ? c.orig
+
+  $ cd ..
+  $ hg init transitive-renames
+  $ cd transitive-renames
+  $ echo 1 > a
+  $ echo 1 > b
+  $ hg ci -Aqm initial
+  $ echo 2 > a
+  $ hg mv b c
+  $ hg ci -m 'rename b to c'
+  $ echo 3 > a
+  $ hg mv c d
+  $ hg ci -m 'rename c to d'
+  $ hg prev -q
+  $ echo 2b > a
+  $ hg amend -q
+  1 new orphan changesets
+  $ hg ev -q
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  unresolved merge conflicts
+  (see 'hg help evolve.interrupted')
+  [1]
+  $ hg st -C
+  M a
+  A d
+    c
+  R c
+  ? a.orig
--- a/tests/test-evolve-cycles.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-cycles.t	Thu Dec 26 21:23:30 2019 +0100
@@ -300,21 +300,20 @@
                       *, (glob)
                       0
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
                   "operation": "prune",
                   "succnodes": [
-                      "0da815c333f6"
+                      "0da815c333f6364b46c86b0a897c00eb617397b6"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "868d2e0eb19c",
-          "rev": 4,
+          "node": "868d2e0eb19c2b55a2894d37e1c435c221384d48",
           "shortdescription": "D"
       },
       {
@@ -324,21 +323,20 @@
                       *, (glob)
                       0
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
                   "operation": "prune",
                   "succnodes": [
-                      "868d2e0eb19c"
+                      "868d2e0eb19c2b55a2894d37e1c435c221384d48"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "d9f908fde1a1",
-          "rev": 6,
+          "node": "d9f908fde1a10ad198a462a3ec8b440bb397fc9c",
           "shortdescription": "F"
       },
       {
@@ -348,21 +346,20 @@
                       *, (glob)
                       0
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
                   "operation": "prune",
                   "succnodes": [
-                      "d9f908fde1a1"
+                      "d9f908fde1a10ad198a462a3ec8b440bb397fc9c"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "0da815c333f6",
-          "rev": 5,
+          "node": "0da815c333f6364b46c86b0a897c00eb617397b6",
           "shortdescription": "E"
       },
       {
@@ -372,22 +369,21 @@
                       *, (glob)
                       0
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
                   "operation": "prune",
                   "succnodes": [
-                      "2a34000d3544",
-                      "868d2e0eb19c"
+                      "2a34000d35446022104f7a091c06fe21ff2b5912",
+                      "868d2e0eb19c2b55a2894d37e1c435c221384d48"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "a8df460dbbfe",
-          "rev": 3,
+          "node": "a8df460dbbfe9ef0c1e5ab4fff02e9514672e379",
           "shortdescription": "C"
       },
       {
@@ -397,21 +393,20 @@
                       *, (glob)
                       0
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
                   "operation": "prune",
                   "succnodes": [
-                      "a8df460dbbfe"
+                      "a8df460dbbfe9ef0c1e5ab4fff02e9514672e379"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "c473644ee0e9",
-          "rev": 2,
+          "node": "c473644ee0e988d7f537e31423831bbc409f12f7",
           "shortdescription": "B"
       },
       {
@@ -421,21 +416,20 @@
                       *, (glob)
                       0
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
                   "operation": "prune",
                   "succnodes": [
-                      "c473644ee0e9"
+                      "c473644ee0e988d7f537e31423831bbc409f12f7"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "2a34000d3544",
-          "rev": 1,
+          "node": "2a34000d35446022104f7a091c06fe21ff2b5912",
           "shortdescription": "A"
       }
   ]
--- a/tests/test-evolve-issue5958.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-issue5958.t	Thu Dec 26 21:23:30 2019 +0100
@@ -22,8 +22,6 @@
 (Make changes in unrelated files so that we don't have any merge conflicts
 during the rebase, but the two touched revisions aren't identical)
 
-date: updated on both side to the same value
-
   $ echo hi > bar.txt
   $ hg add -q bar.txt
   $ hg amend -q
@@ -81,15 +79,6 @@
        rewritten(date) as 0065551bd38f using metaedit by test (Thu Jan 01 00:00:00 1970 +0000)
        rewritten(date) as c17bf400a278 using metaedit by test (Thu Jan 01 00:00:00 1970 +0000)
   
-  $ hg evolve --list --rev .
-  08bc7ba82799: add foo.txt
-    content-divergent: c17bf400a278 (draft) (precursor cc71ffbc7c00)
-  
-  $ hg log --hidden -r cc71ffbc7c00 -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  1 cc71ffbc7c00 1970-01-01 00:00 +0000: date-changed using metaedit as 4:c17bf400a278; date-changed using metaedit as 2:0065551bd38f
-  $ hg log -r 'desc("add foo.txt")' -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  4 c17bf400a278 1969-12-31 23:59 -0000: 
-  6 08bc7ba82799 1969-12-31 23:59 -0000: 
   $ hg evolve --content-divergent
   merge:[6] add foo.txt
   with: [4] add foo.txt
@@ -97,160 +86,3 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 new orphan changesets
   working directory is now at 459c64f7eaad
-  $ hg log -r 'desc("add foo.txt")' -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  4 c17bf400a278 1969-12-31 23:59 -0000: rewritten using evolve as 7:459c64f7eaad
-  7 459c64f7eaad 1969-12-31 23:59 -0000: 
-
-date: updated one one side to an older value
-
-  $ hg evolve -r .
-  move:[7] add foo.txt
-  atop:[0] add r0
-  working directory is now at 545776b4e79f
-  $ hg update --hidden --rev 'predecessors(.)'
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  updated to hidden changeset 459c64f7eaad
-  (hidden revision '459c64f7eaad' was rewritten as: 545776b4e79f)
-  working directory parent is obsolete! (459c64f7eaad)
-  (use 'hg evolve' to update to its successor: 545776b4e79f)
-  $ hg amend --date "0 3"
-  1 new orphan changesets
-  2 new content-divergent changesets
-  $ hg rebase -r . -d 0
-  rebasing 9:c117f15338e6 "add foo.txt" (tip)
-  $ hg log -G
-  @  changeset:   10:7a09c7a39546
-  |  tag:         tip
-  |  parent:      0:a24ed8ad918c
-  |  user:        test
-  |  date:        Wed Dec 31 23:59:57 1969 -0000
-  |  instability: content-divergent
-  |  summary:     add foo.txt
-  |
-  | *  changeset:   8:545776b4e79f
-  |/   parent:      0:a24ed8ad918c
-  |    user:        test
-  |    date:        Wed Dec 31 23:59:58 1969 -0000
-  |    instability: content-divergent
-  |    summary:     add foo.txt
-  |
-  o  changeset:   0:a24ed8ad918c
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add r0
-  
-  $ hg evolve --list -r .
-  7a09c7a39546: add foo.txt
-    content-divergent: 545776b4e79f (draft) (precursor 459c64f7eaad)
-  
-  $ hg log -r 459c64f7eaad+7a09c7a39546+545776b4e79f --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  7 459c64f7eaad 1969-12-31 23:59 -0000: date-changed using amend as 9:c117f15338e6; rebased using evolve as 8:545776b4e79f
-  10 7a09c7a39546 1969-12-31 23:59 -0000: 
-  8 545776b4e79f 1969-12-31 23:59 -0000: 
-  $ hg evolve --content-divergent
-  merge:[8] add foo.txt
-  with: [10] add foo.txt
-  base: [7] add foo.txt
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory is now at 39c4200c0d94
-  $ hg log -r . --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  11 39c4200c0d94 1969-12-31 23:59 -0000: 
-
-date: updated one side to an newer value
-
-  $ hg update --hidden --rev 'predecessors(.)'
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  updated to hidden changeset 7a09c7a39546
-  (hidden revision '7a09c7a39546' was rewritten as: 39c4200c0d94)
-  working directory parent is obsolete! (7a09c7a39546)
-  (use 'hg evolve' to update to its successor: 39c4200c0d94)
-  $ hg amend --date "120 0"
-  2 new content-divergent changesets
-  $ hg log -G
-  @  changeset:   12:da3be3d72fe2
-  |  tag:         tip
-  |  parent:      0:a24ed8ad918c
-  |  user:        test
-  |  date:        Thu Jan 01 00:02:00 1970 +0000
-  |  instability: content-divergent
-  |  summary:     add foo.txt
-  |
-  | *  changeset:   11:39c4200c0d94
-  |/   parent:      0:a24ed8ad918c
-  |    user:        test
-  |    date:        Wed Dec 31 23:59:57 1969 -0000
-  |    instability: content-divergent
-  |    summary:     add foo.txt
-  |
-  o  changeset:   0:a24ed8ad918c
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add r0
-  
-  $ hg evolve --list -r .
-  da3be3d72fe2: add foo.txt
-    content-divergent: 39c4200c0d94 (draft) (precursor 7a09c7a39546)
-  
-  $ hg up 39c4200c0d94
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg log -r 7a09c7a39546+39c4200c0d94+da3be3d72fe2 --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  10 7a09c7a39546 1969-12-31 23:59 -0000: date-changed using amend as 12:da3be3d72fe2; rewritten using evolve as 11:39c4200c0d94
-  11 39c4200c0d94 1969-12-31 23:59 -0000: 
-  12 da3be3d72fe2 1970-01-01 00:02 +0000: 
-  $ hg evolve --content-divergent
-  merge:[11] add foo.txt
-  with: [12] add foo.txt
-  base: [10] add foo.txt
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory is now at 06cde6010a51
-  $ hg log -r . --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  13 06cde6010a51 1970-01-01 00:02 +0000: 
-
-date: updated each side to a different value, newer should win
-
-  $ hg amend --date "235 0"
-  $ hg update --hidden --rev 'predecessors(.)'
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  updated to hidden changeset 06cde6010a51
-  (hidden revision '06cde6010a51' was rewritten as: a7412ff9bfb3)
-  working directory parent is obsolete! (06cde6010a51)
-  (use 'hg evolve' to update to its successor: a7412ff9bfb3)
-  $ hg amend --date "784 0"
-  2 new content-divergent changesets
-  $ hg log -G
-  @  changeset:   15:e3077936ec52
-  |  tag:         tip
-  |  parent:      0:a24ed8ad918c
-  |  user:        test
-  |  date:        Thu Jan 01 00:13:04 1970 +0000
-  |  instability: content-divergent
-  |  summary:     add foo.txt
-  |
-  | *  changeset:   14:a7412ff9bfb3
-  |/   parent:      0:a24ed8ad918c
-  |    user:        test
-  |    date:        Thu Jan 01 00:03:55 1970 +0000
-  |    instability: content-divergent
-  |    summary:     add foo.txt
-  |
-  o  changeset:   0:a24ed8ad918c
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add r0
-  
-  $ hg evolve --list -r .
-  e3077936ec52: add foo.txt
-    content-divergent: a7412ff9bfb3 (draft) (precursor 06cde6010a51)
-  
-  $ hg log -r 39c4200c0d94+a7412ff9bfb3+e3077936ec52 --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  11 39c4200c0d94 1969-12-31 23:59 -0000: date-changed using evolve as 13:06cde6010a51
-  14 a7412ff9bfb3 1970-01-01 00:03 +0000: 
-  15 e3077936ec52 1970-01-01 00:13 +0000: 
-  $ hg evolve --content-divergent
-  merge:[14] add foo.txt
-  with: [15] add foo.txt
-  base: [13] add foo.txt
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory is now at 1a39f3901288
-  $ hg log -r . --hidden -T '{rev} {node|short} {date|isodate}: {join(obsfate, "; ")}\n'
-  16 1a39f3901288 1970-01-01 00:13 +0000: 
--- a/tests/test-evolve-obshistory-amend-then-fold.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory-amend-then-fold.t	Thu Dec 26 21:23:30 2019 +0100
@@ -152,8 +152,7 @@
   [
       {
           "markers": [],
-          "node": "eb5a0daa2192",
-          "rev": 4,
+          "node": "eb5a0daa21923bbf8caeb2c42085b9e463861fd0",
           "shortdescription": "C0"
       },
       {
@@ -163,21 +162,20 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       *, (glob)
                       *, (glob)
                       "content"
                   ],
                   "operation": "fold",
                   "succnodes": [
-                      "eb5a0daa2192"
+                      "eb5a0daa21923bbf8caeb2c42085b9e463861fd0"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "b7ea6d14e664",
-          "rev": 3,
+          "node": "b7ea6d14e664bdc8922221f7992631b50da3fb07",
           "shortdescription": "B1"
       },
       {
@@ -187,19 +185,18 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "b7ea6d14e664"
+                      "b7ea6d14e664bdc8922221f7992631b50da3fb07"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "0dec01379d3b",
-          "rev": 2,
+          "node": "0dec01379d3be6318c470ead31b1fe7ae7cb53d5",
           "shortdescription": "B0"
       },
       {
@@ -209,20 +206,19 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "content"
                   ],
                   "operation": "fold",
                   "succnodes": [
-                      "eb5a0daa2192"
+                      "eb5a0daa21923bbf8caeb2c42085b9e463861fd0"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       }
   ]
--- a/tests/test-evolve-obshistory-amend.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory-amend.t	Thu Dec 26 21:23:30 2019 +0100
@@ -92,6 +92,7 @@
 
   $ hg obslog --no-graph --patch 4ae3a4151de9
   4ae3a4151de9 (2) A1
+  
   471f378eab4c (1) A0
     rewritten(description, content) as 4ae3a4151de9 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       diff -r 471f378eab4c -r 4ae3a4151de9 changeset-description
@@ -109,8 +110,32 @@
       @@ -1,1 +1,2 @@
        A0
       +42
-      
+  
+  
 
+Test that content diff works with templating
+  $ hg obslog --color=debug --patch 4ae3a4151de9 \
+  > -T '{node|short} {desc|firstline}\n{markers % "patch:\n```{patch}```\n"}'
+  @  4ae3a4151de9 A1
+  |
+  x  471f378eab4c A0
+     patch:
+     ```
+     [diff.diffline|diff -r 471f378eab4c -r 4ae3a4151de9 A0]
+     [diff.file_a|--- a/A0	Thu Jan 01 00:00:00 1970 +0000]
+     [diff.file_b|+++ b/A0	Thu Jan 01 00:00:00 1970 +0000]
+     [diff.hunk|@@ -1,1 +1,2 @@]
+      A0
+     [diff.inserted|+42]
+     ```
+
+  $ hg obslog 4ae3a4151de9 --graph -T'{label("log.summary", desc|firstline)} {if(markers, join(markers % "at {date|hgdate} by {user|person} ", " also "))}'
+  @  A1
+  |
+  x  A0 at 0 0 by test
+  
+
+Check that the same thing works with the old {shortdescription} form
   $ hg obslog 4ae3a4151de9 --graph -T'{label("log.summary", shortdescription)} {if(markers, join(markers % "at {date|hgdate} by {user|person} ", " also "))}'
   @  A1
   |
@@ -120,8 +145,7 @@
   [
       {
           "markers": [],
-          "node": "4ae3a4151de9",
-          "rev": 2,
+          "node": "4ae3a4151de9aa872113f0b196e28323308981e8",
           "shortdescription": "A1"
       },
       {
@@ -131,20 +155,19 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "content"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "4ae3a4151de9"
+                      "4ae3a4151de9aa872113f0b196e28323308981e8"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       }
   ]
@@ -177,20 +200,19 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       *, (glob)
                       "content"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "4ae3a4151de9"
+                      "4ae3a4151de9aa872113f0b196e28323308981e8"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       }
   ]
@@ -220,15 +242,19 @@
   
   $ hg obslog -R $TESTTMP/server --no-graph --patch 4ae3a4151de9
   4ae3a4151de9 (1) A1
+  
   471f378eab4c
     rewritten(description, content) as 4ae3a4151de9 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       (No patch available, context is not local)
+  
 
   $ hg obslog -R $TESTTMP/server --no-graph -f --patch 4ae3a4151de9
   4ae3a4151de9 (1) A1
+  
   471f378eab4c
     rewritten(description, content) as 4ae3a4151de9 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       (No patch available, context is not local)
+  
 
 Amend two more times
 ====================
@@ -347,6 +373,50 @@
          +42
   
   
+Test that description diff works with templating
+  $ hg obslog --color=debug --patch 92210308515b \
+  > -T '{node|short} {desc|firstline}\n{markers % "description diff:\n```{descdiff}```\n"}'
+  @  92210308515b A3
+  |
+  x  4f1685185907 A2
+  |  description diff:
+  |  ```
+  |  [diff.diffline|diff -r 4f1685185907 -r 92210308515b changeset-description]
+  |  [diff.file_a|--- a/changeset-description]
+  |  [diff.file_b|+++ b/changeset-description]
+  |  [diff.hunk|@@ -1,3 +1,3 @@]
+  |  [diff.deleted|-A2]
+  |  [diff.inserted|+A3]
+  |
+  |  [diff.deleted|-Better better commit message]
+  |  [diff.inserted|+Better better better commit message]
+  |  ```
+  x  4ae3a4151de9 A1
+  |  description diff:
+  |  ```
+  |  [diff.diffline|diff -r 4ae3a4151de9 -r 4f1685185907 changeset-description]
+  |  [diff.file_a|--- a/changeset-description]
+  |  [diff.file_b|+++ b/changeset-description]
+  |  [diff.hunk|@@ -1,3 +1,3 @@]
+  |  [diff.deleted|-A1]
+  |  [diff.inserted|+A2]
+  |
+  |  [diff.deleted|-Better commit message]
+  |  [diff.inserted|+Better better commit message]
+  |  ```
+  x  471f378eab4c A0
+     description diff:
+     ```
+     [diff.diffline|diff -r 471f378eab4c -r 4ae3a4151de9 changeset-description]
+     [diff.file_a|--- a/changeset-description]
+     [diff.file_b|+++ b/changeset-description]
+     [diff.hunk|@@ -1,1 +1,3 @@]
+     [diff.deleted|-A0]
+     [diff.inserted|+A1]
+     [diff.inserted|+]
+     [diff.inserted|+Better commit message]
+     ```
+
 Check the output on the server
 ------------------------------
 
@@ -383,24 +453,32 @@
   
   $ hg obslog -R $TESTTMP/server --no-graph --patch 92210308515b
   92210308515b (2) A3
+  
   4f1685185907
     rewritten(description) as 92210308515b using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       (No patch available, context is not local)
+  
   4ae3a4151de9 (1) A1
     rewritten(description) as 4f1685185907 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       (No patch available, successor is unknown locally)
+  
   471f378eab4c
     rewritten(description, content) as 4ae3a4151de9 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       (No patch available, context is not local)
+  
 
   $ hg obslog -R $TESTTMP/server --no-graph -f --patch 92210308515b
   92210308515b (2) A3
+  
   4f1685185907
     rewritten(description) as 92210308515b using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       (No patch available, context is not local)
+  
   4ae3a4151de9 (1) A1
     rewritten(description) as 4f1685185907 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       (No patch available, successor is unknown locally)
+  
   471f378eab4c
     rewritten(description, content) as 4ae3a4151de9 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
       (No patch available, context is not local)
+  
--- a/tests/test-evolve-obshistory-content-divergent.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory-content-divergent.t	Thu Dec 26 21:23:30 2019 +0100
@@ -129,12 +129,12 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "65b757b745b9"
+                      "65b757b745b935093c87a2bccd877521cccffcbd"
                   ],
                   "user": "test",
                   "verb": "rewritten"
@@ -144,19 +144,18 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "fdf9bde5129a"
+                      "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       }
   ]
@@ -284,8 +283,7 @@
   [
       {
           "markers": [],
-          "node": "65b757b745b9",
-          "rev": 3,
+          "node": "65b757b745b935093c87a2bccd877521cccffcbd",
           "shortdescription": "A2"
       },
       {
@@ -295,12 +293,12 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "65b757b745b9"
+                      "65b757b745b935093c87a2bccd877521cccffcbd"
                   ],
                   "user": "test",
                   "verb": "rewritten"
@@ -310,25 +308,23 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "fdf9bde5129a"
+                      "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       },
       {
           "markers": [],
-          "node": "fdf9bde5129a",
-          "rev": 2,
+          "node": "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e",
           "shortdescription": "A1"
       }
   ]
--- a/tests/test-evolve-obshistory-fold.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory-fold.t	Thu Dec 26 21:23:30 2019 +0100
@@ -172,8 +172,7 @@
   [
       {
           "markers": [],
-          "node": "eb5a0daa2192",
-          "rev": 3,
+          "node": "eb5a0daa21923bbf8caeb2c42085b9e463861fd0",
           "shortdescription": "C0"
       },
       {
@@ -183,20 +182,19 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "content"
                   ],
                   "operation": "fold",
                   "succnodes": [
-                      "eb5a0daa2192"
+                      "eb5a0daa21923bbf8caeb2c42085b9e463861fd0"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       },
       {
@@ -206,21 +204,20 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
                   "operation": "fold",
                   "succnodes": [
-                      "eb5a0daa2192"
+                      "eb5a0daa21923bbf8caeb2c42085b9e463861fd0"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "0dec01379d3b",
-          "rev": 2,
+          "node": "0dec01379d3be6318c470ead31b1fe7ae7cb53d5",
           "shortdescription": "B0"
       }
   ]
--- a/tests/test-evolve-obshistory-lots-of-splits.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory-lots-of-splits.t	Thu Dec 26 21:23:30 2019 +0100
@@ -201,23 +201,22 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "parent",
                       "content"
                   ],
                   "operation": "split",
                   "succnodes": [
-                      "1ae8bc733a14",
-                      "337fec4d2edc",
-                      "c7f044602e9b",
-                      "f257fde29c7a"
+                      "1ae8bc733a14e374f11767d2ad128d4c891dc43f",
+                      "337fec4d2edcf0e7a467e35f818234bc620068b5",
+                      "c7f044602e9bd5dec6528b33114df3d0221e6359",
+                      "f257fde29c7a847c9b607f6e958656d0df0fb15c"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "de7290d8b885",
-          "rev": 1,
+          "node": "de7290d8b885925115bb9e88887252dfc20ef2a8",
           "shortdescription": "A0"
       }
   ]
@@ -232,8 +231,7 @@
   [
       {
           "markers": [],
-          "node": "c7f044602e9b",
-          "rev": 5,
+          "node": "c7f044602e9bd5dec6528b33114df3d0221e6359",
           "shortdescription": "A0"
       },
       {
@@ -243,23 +241,22 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "parent",
                       "content"
                   ],
                   "operation": "split",
                   "succnodes": [
-                      "1ae8bc733a14",
-                      "337fec4d2edc",
-                      "c7f044602e9b",
-                      "f257fde29c7a"
+                      "1ae8bc733a14e374f11767d2ad128d4c891dc43f",
+                      "337fec4d2edcf0e7a467e35f818234bc620068b5",
+                      "c7f044602e9bd5dec6528b33114df3d0221e6359",
+                      "f257fde29c7a847c9b607f6e958656d0df0fb15c"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "de7290d8b885",
-          "rev": 1,
+          "node": "de7290d8b885925115bb9e88887252dfc20ef2a8",
           "shortdescription": "A0"
       }
   ]
--- a/tests/test-evolve-obshistory-phase-divergent.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory-phase-divergent.t	Thu Dec 26 21:23:30 2019 +0100
@@ -102,19 +102,18 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "description"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "fdf9bde5129a"
+                      "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       }
   ]
@@ -196,30 +195,28 @@
   [
       {
           "markers": [],
-          "node": "fdf9bde5129a",
-          "rev": 2,
+          "node": "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e",
           "shortdescription": "A1"
       },
       {
           "markers": [
               {
                   "date": [
-                      0,
+                      0.0,
                       0
                   ],
-                  "effect": [
+                  "effects": [
                       "description"
                   ],
                   "operation": "amend",
                   "succnodes": [
-                      "fdf9bde5129a"
+                      "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       }
   ]
--- a/tests/test-evolve-obshistory-prune.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory-prune.t	Thu Dec 26 21:23:30 2019 +0100
@@ -82,8 +82,7 @@
                   "verb": "pruned"
               }
           ],
-          "node": "0dec01379d3b",
-          "rev": 2,
+          "node": "0dec01379d3be6318c470ead31b1fe7ae7cb53d5",
           "shortdescription": "B0"
       }
   ]
@@ -94,8 +93,7 @@
   [
       {
           "markers": [],
-          "node": "471f378eab4c",
-          "rev": 1,
+          "node": "471f378eab4c5e25f6c77f785b27c936efb22874",
           "shortdescription": "A0"
       }
   ]
--- a/tests/test-evolve-obshistory-split.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory-split.t	Thu Dec 26 21:23:30 2019 +0100
@@ -119,22 +119,21 @@
                       *, (glob)
                       0 (glob)
                   ],
-                  "effect": [
+                  "effects": [
                       "parent",
                       "content"
                   ],
                   "note": "testing split",
                   "operation": "split",
                   "succnodes": [
-                      "337fec4d2edc",
-                      "f257fde29c7a"
+                      "337fec4d2edcf0e7a467e35f818234bc620068b5",
+                      "f257fde29c7a847c9b607f6e958656d0df0fb15c"
                   ],
                   "user": "test",
                   "verb": "rewritten"
               }
           ],
-          "node": "471597cad322",
-          "rev": 1,
+          "node": "471597cad322d1f659bb169751be9133dad92ef3",
           "shortdescription": "A0"
       }
   ]
@@ -245,14 +244,18 @@
   
   $ hg obslog -R $TESTTMP/server --no-graph -f --all --patch tip
   f257fde29c7a (2) A0
+  
   471597cad322
     rewritten(parent, content) as 337fec4d2edc, f257fde29c7a using split by test (Thu Jan 01 00:00:00 1970 +0000)
       note: testing split
       (No patch available, context is not local)
+  
 
   $ hg obslog -R $TESTTMP/server --no-graph -f --all --patch tip
   f257fde29c7a (2) A0
+  
   471597cad322
     rewritten(parent, content) as 337fec4d2edc, f257fde29c7a using split by test (Thu Jan 01 00:00:00 1970 +0000)
       note: testing split
       (No patch available, context is not local)
+  
--- a/tests/test-evolve-obshistory.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-obshistory.t	Thu Dec 26 21:23:30 2019 +0100
@@ -168,3 +168,12 @@
        [evolve.verb|rewritten](description) as [evolve.node|fdf9bde5129a] using [evolve.operation|amend] by [evolve.user|test] [evolve.date|(Thu Jan 01 00:00:00 1970 +0000)]
          (No patch available, successor is unknown locally)
   
+
+  $ hg obslog 7a230b46bf61 --graph \
+  > -T '{node|short} {rev} {desc|firstline}\n{markers % "rewritten using {operation}"}\n'
+  o  7a230b46bf61 2 A2
+  |
+  x  fdf9bde5129a
+  |  rewritten using amend
+  @  471f378eab4c 1 A0
+     rewritten using amend
--- a/tests/test-evolve-orphan-merge.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-evolve-orphan-merge.t	Thu Dec 26 21:23:30 2019 +0100
@@ -134,11 +134,18 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merging a and b
   
+
+Clean up to prepare for next test case
+  $ hg prune -r 64370c9805e7 -r 3d41537b44ca -r 968d205ba4d8
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  working directory is now at 8fa14d15e168
+  3 changesets pruned
+
 2) When merging both the parents resulted in conflicts
 ------------------------------------------------------
 
   $ hg up 8fa14d15e168
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo foo > c
   $ hg ci -Aqm "foo to c"
   $ hg prev
@@ -152,24 +159,6 @@
   |   () draft
   | o  8:1c165c673853 foo to c
   |/    () draft
-  | o    7:968d205ba4d8 merging a and b
-  | |\    () draft
-  +---o  6:3d41537b44ca added a
-  | |     () draft
-  | o  4:64370c9805e7 added b
-  |/    () draft
-  o  0:8fa14d15e168 added hgignore
-      () draft
-
-Prune old test changesets to have clear graph view
-  $ hg prune -r 64370c9805e7 -r 3d41537b44ca -r 968d205ba4d8
-  3 changesets pruned
-
-  $ hg glog
-  @  9:d0f84b25d4e3 bar to c
-  |   () draft
-  | o  8:1c165c673853 foo to c
-  |/    () draft
   o  0:8fa14d15e168 added hgignore
       () draft
 
@@ -532,21 +521,16 @@
   $ hg evolve -r .
   move:[28] merged l and x
   atop:[0] added hgignore
-  working directory is now at b61ba77b924a
+  evolution of 28:fb8fe870ae7d created no changes to commit
+  working directory is now at 8fa14d15e168
 
   $ hg glog
-  @  29:b61ba77b924a merged l and x
-  |   () draft
-  o  0:8fa14d15e168 added hgignore
+  @  0:8fa14d15e168 added hgignore
       () draft
 
 7) When one parent is pruned without successor and has no parent
 ----------------------------------------------------------------
 
-  $ hg prune -r .
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  working directory is now at 8fa14d15e168
-  1 changesets pruned
   $ hg up null
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
@@ -560,9 +544,9 @@
   (branch merge, don't forget to commit)
   $ hg ci -m "merge commit"
   $ hg glog
-  @    31:32beb84b9dbc merge commit
+  @    30:32beb84b9dbc merge commit
   |\    () draft
-  | o  30:f3ba8b99bb6f added foo
+  | o  29:f3ba8b99bb6f added foo
   |     () draft
   o  0:8fa14d15e168 added hgignore
       () draft
@@ -572,9 +556,9 @@
   1 new orphan changesets
 
   $ hg glog
-  @    31:32beb84b9dbc merge commit
+  @    30:32beb84b9dbc merge commit
   |\    () draft
-  | x  30:f3ba8b99bb6f added foo
+  | x  29:f3ba8b99bb6f added foo
   |     () draft
   o  0:8fa14d15e168 added hgignore
       () draft
@@ -592,12 +576,12 @@
 just remove that chain.
 
   $ hg evolve -r .
-  move:[31] merge commit
+  move:[30] merge commit
   atop:[-1] 
   working directory is now at d2a03dd8c951
 
   $ hg glog
-  @  32:d2a03dd8c951 merge commit
+  @  31:d2a03dd8c951 merge commit
   |   () draft
   o  0:8fa14d15e168 added hgignore
       () draft
--- a/tests/test-topic.t	Thu Dec 26 12:22:49 2019 +0700
+++ b/tests/test-topic.t	Thu Dec 26 21:23:30 2019 +0100
@@ -122,10 +122,15 @@
   
   list of commands:
   
-   stack         list all changesets in a topic and other information
+  Change organization:
+  
    topics        View current topic, set current topic, change topic for a set
                  of revisions, or see all topics.
   
+  Change navigation:
+  
+   stack         list all changesets in a topic and other information
+  
   (use 'hg help -v topic' to show built-in aliases and global options)
   $ hg help topics
   hg topics [OPTION]... [-r REV]... [TOPIC]