branching: merge with stable
authorPierre-Yves David <pierre-yves.david@octobus.net>
Thu, 23 Apr 2020 03:06:30 +0200
changeset 5299 426f2800b793
parent 5293 4da1d21231ae (diff)
parent 5298 a828c7a7ace1 (current diff)
child 5300 372dc6c5692a
branching: merge with stable
hgext3rd/topic/__init__.py
hgext3rd/topic/revset.py
--- a/CHANGELOG	Mon Apr 20 00:05:05 2020 +0800
+++ b/CHANGELOG	Thu Apr 23 03:06:30 2020 +0200
@@ -1,6 +1,11 @@
 Changelog
 =========
 
+9.4.0 - in progress
+-------------------
+
+  * compat: clean up old compatibility code
+
 9.3.1 -- 2020-04-08
 -------------------
 
--- a/README	Mon Apr 20 00:05:05 2020 +0800
+++ b/README	Thu Apr 23 03:06:30 2020 +0200
@@ -139,20 +139,32 @@
     $ cd tests
     $ python $HGSRC/tests/run-tests.py
 
+When certain blocks of code need to cope with API changes in core Mercurial,
+they should have comments in the ``hg <= x.y (commit hash)`` format. For
+example, if a function needs another code path because of changes introduced in
+02802fa87b74 that was first included in Mercurial 5.3, then the comment should
+be::
+
+    # hg <= 5.2 (02802fa87b74)
+
+See also tests/test-check-compat-strings.t.
+
 Branch policy
 -------------
 
-The evolve test are highly impacted by changes in core. To deal with this, we use named branches.
+The evolve tests are highly impacted by changes in core. To deal with this, we
+use named branches.
 
-There are two main branches: "stable" and "default". Tests on these branch are
-supposed to pass with the corresponding "default" and "stable" branch from core
-Mercurial. The documentation is built from the tip of stable.
+There are two main branches: "stable" and "default". Tests on these branches
+are supposed to pass with the corresponding "default" and "stable" branch from
+core Mercurial. The documentation is built from the tip of stable.
 
-In addition, we have compatibility branches to check tests on older version of
+In addition, we have compatibility branches to check tests on older versions of
 Mercurial. They are the "mercurial-x.y" branches. They are used to apply
-expected test change only, no code change should happen there.
+expected test changes only, no code changes should happen there.
 
-test output change from a changeset in core should adds the following line to their description:
+Test output changes from a changeset in core should add the following line to
+their patch description:
 
 CORE-TEST-OUTPUT-UPDATE: <CORE-NODE-ID>
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/check-compat-strings.sh	Thu Apr 23 03:06:30 2020 +0200
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+unset GREP_OPTIONS
+
+# This script finds compatibility-related comments with a node hash specified
+# in all files in a given directory (. by default) and looks up the hash in a
+# repo (~/hg by default) to determine if each of the comments is correct and,
+# if not, it suggests the correct release. This can prevent accidentally
+# removing a piece of code that was misattributed to a different (earlier)
+# release of core hg.
+
+# Usage: $0 WDIR HGREPO where WDIR is usually evolve/hgext3rd/ and HGREPO is
+# the place with core Mercurial repo (not just checkout). Said repo has to be
+# sufficiently up-to-date, otherwise this script may not work correctly.
+
+workdir=${1:-'.'}
+hgdir=${2:-~/hg}
+grep -Ern 'hg <= [0-9.]+ \([0-9a-f+]+\)' "$workdir" | while read -r line; do
+    bashre='hg <= ([0-9.]+) \(([0-9a-f+]+)\)'
+    if [[ $line =~ $bashre ]]; then
+        expected=${BASH_REMATCH[1]}
+        revset=${BASH_REMATCH[2]}
+        tagrevset="max(tag('re:^[0-9]\\.[0-9]$') - ($revset)::)"
+        lastrel=$(HGPLAIN=1 hg --cwd "$hgdir" log -r "$tagrevset" -T '{tags}')
+        if [[ "$lastrel" != "$expected" ]]; then
+            echo "$line"
+            echo "actual last major release without $revset is $lastrel"
+            echo
+        fi
+    fi
+done
--- a/hgext3rd/evolve/__init__.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/__init__.py	Thu Apr 23 03:06:30 2020 +0200
@@ -33,7 +33,7 @@
 backported to older version of Mercurial by this extension. Some older
 experimental protocol are also supported for a longer time in the extensions to
 help people transitioning. (The extensions is currently compatible down to
-Mercurial version 4.4).
+Mercurial version 4.6).
 
 New Config::
 
@@ -269,14 +269,12 @@
     bookmarks as bookmarksmod,
     cmdutil,
     commands,
-    context,
-    dirstate,
     error,
     help,
     hg,
     lock as lockmod,
+    logcmdutil,
     node,
-    patch,
     pycompat,
     revset,
     scmutil,
@@ -361,29 +359,6 @@
 eh.configitem(b'experimental', b'evolution.allnewcommands', None)
 eh.configitem(b'experimental', b'prunestrip', False)
 
-# pre hg 4.0 compat
-
-if not util.safehasattr(dirstate.dirstate, 'parentchange'):
-    import contextlib
-
-    @contextlib.contextmanager
-    def parentchange(self):
-        '''Context manager for handling dirstate parents.
-
-        If an exception occurs in the scope of the context manager,
-        the incoherent dirstate won't be written when wlock is
-        released.
-        '''
-        self._parentwriters += 1
-        yield
-        # Typically we want the "undo" step of a context manager in a
-        # finally block so it happens even when an exception
-        # occurs. In this case, however, we only want to decrement
-        # parentwriters if the code in the with statement exits
-        # normally, so we don't have a try/finally here on purpose.
-        self._parentwriters -= 1
-    dirstate.dirstate.parentchange = parentchange
-
 #####################################################################
 ### Option configuration                                          ###
 #####################################################################
@@ -723,38 +698,6 @@
 
     ui.warn(b"(%s)\n" % solvemsg)
 
-if util.safehasattr(context, '_filterederror'): # <= hg-4.5
-    @eh.wrapfunction(context, '_filterederror')
-    def evolve_filtererror(original, repo, changeid):
-        """build an exception to be raised about a filtered changeid
-
-        This is extracted in a function to help extensions (eg: evolve) to
-        experiment with various message variants."""
-        if repo.filtername.startswith(b'visible'):
-
-            unfilteredrepo = repo.unfiltered()
-            rev = repo[scmutil.revsingle(unfilteredrepo, changeid)]
-            reason, successors = obshistory._getobsfateandsuccs(unfilteredrepo, rev.node())
-
-            # Be more precise in case the revision is superseded
-            if reason == b'superseded':
-                reason = _(b"successor: %s") % successors[0]
-            elif reason == b'superseded_split':
-                if len(successors) <= 2:
-                    reason = _(b"successors: %s") % b", ".join(successors)
-                else:
-                    firstsuccessors = b", ".join(successors[:2])
-                    remainingnumber = len(successors) - 2
-                    successorsmsg = _(b"%s and %d more") % (firstsuccessors, remainingnumber)
-                    reason = _(b"successors: %s") % successorsmsg
-
-            msg = _(b"hidden revision '%s'") % changeid
-            hint = _(b'use --hidden to access hidden revisions; %s') % reason
-            return error.FilteredRepoLookupError(msg, hint=hint)
-        msg = _(b"filtered revision '%s' (not in '%s' subset)")
-        msg %= (changeid, repo.filtername)
-        return error.FilteredRepoLookupError(msg)
-
 @eh.wrapcommand(b"update")
 @eh.wrapcommand(b"pull")
 def wrapmayobsoletewc(origfn, ui, repo, *args, **opts):
@@ -827,21 +770,8 @@
 @eh.wrapfunction(mercurial.cmdutil, 'tryimportone')
 def tryimportone(orig, ui, repo, hunk, parents, opts, *args, **kwargs):
     expected = {b'node': None}
-    if not util.safehasattr(hunk, 'get'): # hg < 4.6
-        oldextract = patch.extract
-
-        def extract(*args, **kwargs):
-            ret = oldextract(*args, **kwargs)
-            _getnodefrompatch(ret, expected)
-            return ret
-        try:
-            patch.extract = extract
-            ret = orig(ui, repo, hunk, parents, opts, *args, **kwargs)
-        finally:
-            patch.extract = oldextract
-    else:
-        _getnodefrompatch(hunk, expected)
-        ret = orig(ui, repo, hunk, parents, opts, *args, **kwargs)
+    _getnodefrompatch(hunk, expected)
+    ret = orig(ui, repo, hunk, parents, opts, *args, **kwargs)
     created = ret[1]
     if (opts[b'obsolete'] and None not in (created, expected[b'node'])
         and created != expected[b'node']):
@@ -1019,7 +949,8 @@
         if topic and hastopic:
             template = utility.stacktemplate
 
-        displayer = compat.changesetdisplayer(ui, repo, {b'template': template})
+        displayer = logcmdutil.changesetdisplayer(ui, repo,
+                                                  {b'template': template})
 
         target, bookmark = _findprevtarget(repo, displayer,
                                            opts.get('move_bookmark'), topic)
@@ -1077,7 +1008,8 @@
             children = [ctx for ctx in children if ctx not in filtered]
             template = utility.stacktemplate
             opts['stacktemplate'] = True
-        displayer = compat.changesetdisplayer(ui, repo, {b'template': template})
+        displayer = logcmdutil.changesetdisplayer(ui, repo,
+                                                  {b'template': template})
 
         # check if we need to evolve while updating to the next child revision
         needevolve = False
@@ -1376,7 +1308,7 @@
         statemod.addunfinished(b'pick', fname=b'pickstate', continueflag=True,
                                abortfunc=cmdrewrite.hgabortpick)
     else:
-        # compat <= hg-5.0 (5f2f6912c9e6)
+        # hg <= 5.0 (5f2f6912c9e6)
         estate = (b'evolvestate', False, False, _(b'evolve in progress'),
                   _(b"use 'hg evolve --continue' or 'hg evolve --abort' to abort"))
         cmdutil.unfinishedstates.append(estate)
--- a/hgext3rd/evolve/cmdrewrite.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/cmdrewrite.py	Thu Apr 23 03:06:30 2020 +0200
@@ -11,7 +11,6 @@
 
 from __future__ import absolute_import
 
-import contextlib
 import random
 
 from mercurial import (
@@ -23,6 +22,7 @@
     error,
     hg,
     lock as lockmod,
+    logcmdutil,
     merge,
     node,
     obsolete,
@@ -33,6 +33,8 @@
     util,
 )
 
+from mercurial.utils import dateutil
+
 from mercurial.i18n import _
 
 from . import (
@@ -74,7 +76,7 @@
     """
     # N.B. this is extremely similar to setupheaderopts() in mq.py
     if not opts.get('date') and opts.get('current_date'):
-        opts['date'] = b'%d %d' % compat.makedate()
+        opts['date'] = b'%d %d' % dateutil.makedate()
     if not opts.get('user') and opts.get('current_user'):
         opts['user'] = ui.username()
 
@@ -216,14 +218,7 @@
 def _writepatch(ui, repo, old, fp):
     """utility function to use filestore and patchrepo to apply a patch to the
     repository with metadata being extracted from the patch"""
-    metadata = patch.extract(ui, fp)
-    if util.safehasattr(metadata, 'get'): # < hg-4.6
-        @contextlib.contextmanager
-        def patchcontext():
-            yield metadata
-        patchcontext = patchcontext()
-    else:
-        patchcontext = metadata
+    patchcontext = patch.extract(ui, fp)
     pold = old.p1()
 
     with patchcontext as metadata:
@@ -267,7 +262,7 @@
     fp.write(b"# HG changeset patch\n")
     fp.write(b"# User %s\n" % ctx.user())
     fp.write(b"# Date %d %d\n" % ctx.date())
-    fp.write(b"#      %s\n" % compat.datestr(ctx.date()))
+    fp.write(b"#      %s\n" % dateutil.datestr(ctx.date()))
     if branch and branch != b'default':
         fp.write(b"# Branch %s\n" % branch)
     fp.write(b"# Node ID %s\n" % node.hex(nodeval))
@@ -525,7 +520,8 @@
             try:
                 uipathfn = scmutil.getuipathfn(repo)
             except AttributeError:
-                uipathfn = match.rel   # <= 4.9
+                # hg <= 4.9 (e6ec0737b706)
+                uipathfn = match.rel
 
             for f in sorted(badfiles):
                 if f in s.clean:
@@ -970,7 +966,7 @@
     date = opts.get('date')
     user = opts.get('user')
     if date:
-        metadata[b'date'] = b'%i %i' % compat.parsedate(date)
+        metadata[b'date'] = b'%i %i' % dateutil.parsedate(date)
     if user:
         metadata[b'user'] = user
     return metadata
@@ -1395,7 +1391,7 @@
         with ui.configoverride(overrides, b'touch'):
             rewriteutil.precheck(repo, revs, b'touch')
     tmpl = utility.shorttemplate
-    displayer = compat.changesetdisplayer(ui, repo, {b'template': tmpl})
+    displayer = logcmdutil.changesetdisplayer(ui, repo, {b'template': tmpl})
     with repo.wlock(), repo.lock(), repo.transaction(b'touch'):
         touchnodes(ui, repo, revs, displayer, **opts)
 
@@ -1495,7 +1491,7 @@
             with ui.configoverride(overrides, b'pick'):
                 stats = merge.graft(repo, origctx, origctx.p1(),
                                     [b'local', b'destination'])
-            if compat.hasconflict(stats):
+            if stats.unresolvedcount:
                 pickstate.addopts({b'orignode': origctx.node(),
                                    b'oldpctx': pctx.node()})
                 pickstate.save()
--- a/hgext3rd/evolve/compat.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/compat.py	Thu Apr 23 03:06:30 2020 +0200
@@ -8,12 +8,10 @@
 
 import array
 import contextlib
-import inspect
 
 from mercurial import (
     context,
     copies,
-    mdiff,
     obsolete,
     pycompat,
     registrar,
@@ -22,7 +20,6 @@
     util,
     ui as uimod,
 )
-from mercurial.hgweb import hgweb_mod
 
 if pycompat.ispy3:
     arraytobytes = array.array.tobytes
@@ -31,21 +28,7 @@
     arraytobytes = array.array.tostring
     arrayfrombytes = array.array.fromstring
 
-# hg < 4.6 compat (c8e2d6ed1f9e)
-try:
-    from mercurial import logcmdutil
-    changesetdisplayer = logcmdutil.changesetdisplayer
-    changesetprinter = logcmdutil.changesetprinter
-    displaygraph = logcmdutil.displaygraph
-    changesetdiffer = logcmdutil.changesetdiffer
-except (AttributeError, ImportError):
-    from mercurial import cmdutil
-    changesetdisplayer = cmdutil.show_changeset  # pytype: disable=module-attr
-    changesetprinter = cmdutil.changeset_printer  # pytype: disable=module-attr
-    displaygraph = cmdutil.displaygraph  # pytype: disable=module-attr
-    changesetdiffer = None
-
-# hg <= 5.3 (c21aca51b392)
+# hg <= 5.2 (c21aca51b392)
 try:
     from mercurial import pathutil
     dirs = pathutil.dirs
@@ -95,96 +78,27 @@
                                   islink=b'l' in flags,
                                   isexec=b'x' in flags,
                                   copysource=copied.get(path))
-    # compat with hg <- 4.9
+    # hg <= 4.9 (550a172a603b)
     elif varnames[2] == r"changectx":
         mctx = context.memfilectx(repo, ctx, fctx.path(), fctx.data(),
                                   islink=b'l' in flags,
                                   isexec=b'x' in flags,
                                   copied=copied.get(path))  # pytype: disable=wrong-keyword-args
-    else:
-        mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
-                                  islink=b'l' in flags,
-                                  isexec=b'x' in flags,
-                                  copied=copied.get(path))  # pytype: disable=wrong-keyword-args
     return mctx
 
-def strdiff(a, b, fn1, fn2):
-    """ A version of mdiff.unidiff for comparing two strings
-    """
-    args = [a, b'', b, b'', fn1, fn2]
-
-    # hg < 4.6 compat 8b6dd3922f70
-    if util.safehasattr(inspect, 'signature'):
-        signature = inspect.signature(mdiff.unidiff)
-        needsbinary = r'binary' in signature.parameters
-    else:
-        argspec = inspect.getargspec(mdiff.unidiff)
-        needsbinary = r'binary' in argspec.args
-
-    if needsbinary:
-        args.append(False)
-
-    return mdiff.unidiff(*args)
-
-# date related
-
-# hg <= 4.5 (c6061cadb400)
 try:
-    import mercurial.utils.dateutil
-    datestr = mercurial.utils.dateutil.datestr
-    makedate = mercurial.utils.dateutil.makedate
-    parsedate = mercurial.utils.dateutil.parsedate
-except ImportError:
-    import mercurial.util
-    datestr = mercurial.util.datestr  # pytype: disable=module-attr
-    makedate = mercurial.util.makedate  # pytype: disable=module-attr
-    parsedate = mercurial.util.parsedate  # pytype: disable=module-attr
-
-def wireprotocommand(exthelper, name, args=b'', permission=b'pull'):
-    try:
-        # Since b4d85bc1
-        from mercurial.wireprotov1server import wireprotocommand
-        return wireprotocommand(name, args, permission=permission)
-    except (ImportError, AttributeError):
-        from mercurial import wireproto
-
-    if 3 <= len(wireproto.wireprotocommand.func_defaults):
-        return wireproto.wireprotocommand(name, args, permission=permission)
-
-    # <= hg-4.5 permission must be registered in dictionnary
-    def decorator(func):
-        @eh.extsetup
-        def install(ui):
-            hgweb_mod.perms[name] = permission
-            wireproto.commands[name] = (func, args)
-    return decorator
-
-# mercurial <= 4.5 do not have the updateresult object
-try:
-    from mercurial.merge import updateresult
-except (ImportError, AttributeError):
-    updateresult = None
-
-# 46c2b19a1263f18a5829a21b7a5053019b0c5a31 in hg moved repair.stripbmrevset to
-# scmutil.bookmarkrevs
-# This change is a part of 4.7 cycle, so drop this when we drop support for 4.6
-try:
+    # hg <= 4.6 (46c2b19a1263)
     bmrevset = repair.stripbmrevset  # pytype: disable=module-attr
 except AttributeError:
     bmrevset = scmutil.bookmarkrevs
 
-def hasconflict(upres):
-    if updateresult is None:
-        return bool(upres[-1])
-    return bool(upres.unresolvedcount)
-
 hg48 = util.safehasattr(copies, 'stringutil')
 # code imported from Mercurial core at ae17555ef93f + patch
 def fixedcopytracing(repo, c1, c2, base):
     """A complete copy-patse of copies._fullcopytrace with a one line fix to
     handle when the base is not parent of both c1 and c2. This should be
     converted in a compat function once https://phab.mercurial-scm.org/D3896
-    gets in and once we drop support for 4.7, this should be removed."""
+    gets in and once we drop support for 4.6, this should be removed."""
 
     from mercurial import pathutil
 
@@ -205,7 +119,7 @@
     if util.safehasattr(base, 'isancestorof'):
         dirtyc1 = not base.isancestorof(_c1)
         dirtyc2 = not base.isancestorof(_c2)
-    else: # hg <= 4.6
+    else: # hg <= 4.6 (fbec9c0b32d3)
         dirtyc1 = not base.descendant(_c1)
         dirtyc2 = not base.descendant(_c2)
     graft = dirtyc1 or dirtyc2
@@ -213,7 +127,7 @@
     if graft:
         tca = _c1.ancestor(_c2)
 
-    # hg < 4.8 compat (dc50121126ae)
+    # hg <= 4.9 (dc50121126ae)
     try:
         limit = copies._findlimit(repo, c1, c2)  # pytype: disable=module-attr
     except (AttributeError, TypeError):
@@ -434,7 +348,7 @@
 
     return copy, movewithdir, diverge, renamedelete, dirmove
 
-# hg <= 4.9 compat (7694b685bb10)
+# hg <= 4.9 (7694b685bb10)
 fixupstreamed = util.safehasattr(scmutil, 'movedirstate')
 if not fixupstreamed:
     copies._fullcopytracing = fixedcopytracing
@@ -449,7 +363,7 @@
     return {'helpcategory': category}
 
 # nodemap.get and index.[has_node|rev|get_rev]
-# hg <= 5.3 (02802fa87b74)
+# hg <= 5.2 (02802fa87b74)
 def getgetrev(cl):
     """Returns index.get_rev or nodemap.get (for pre-5.3 Mercurial)."""
     if util.safehasattr(cl.index, 'get_rev'):
--- a/hgext3rd/evolve/depthcache.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/depthcache.py	Thu Apr 23 03:06:30 2020 +0200
@@ -16,11 +16,7 @@
     scmutil,
 )
 
-try:
-    from mercurial.utils.stringutil import forcebytestr
-except ImportError:
-    # hg <= 4.5 (f99d64e8a4e4)
-    from mercurial.util import forcebytestr
+from mercurial.utils.stringutil import forcebytestr
 
 from . import (
     compat,
--- a/hgext3rd/evolve/evolvecmd.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/evolvecmd.py	Thu Apr 23 03:06:30 2020 +0200
@@ -16,9 +16,10 @@
     cmdutil,
     commands,
     context,
+    encoding,
     error,
-    encoding,
     hg,
+    logcmdutil,
     merge,
     mergeutil,
     node as nodemod,
@@ -68,11 +69,11 @@
     assert tr is not None
     displayer = None
     if stacktmplt:
-        displayer = compat.changesetdisplayer(ui, repo,
-                                              {b'template': stacktemplate})
+        displayer = logcmdutil.changesetdisplayer(ui, repo,
+                                                  {b'template': stacktemplate})
     else:
-        displayer = compat.changesetdisplayer(ui, repo,
-                                              {b'template': shorttemplate})
+        displayer = logcmdutil.changesetdisplayer(ui, repo,
+                                                  {b'template': shorttemplate})
     if b'orphan' == category:
         result = _solveunstable(ui, repo, ctx, evolvestate, displayer,
                                 dryrun, confirm, progresscb,
@@ -608,7 +609,7 @@
         hg._showstats(repo, stats)
 
         # conflicts while merging content-divergent changesets
-        if compat.hasconflict(stats):
+        if stats.unresolvedcount:
             hint = _(b"see 'hg help evolve.interrupted'")
             raise error.InterventionRequired(_(b"unresolved merge conflicts"),
                                              hint=hint)
@@ -780,7 +781,7 @@
             try:
                 effectflag = obsutil.geteffectflag(prec, (succ,))
             except TypeError:
-                # hg <= 4.7
+                # hg <= 4.7 (bae6f1418a95)
                 effectflag = obsutil.geteffectflag((prec, (succ,)))
             metadata[obsutil.EFFECTFLAGFIELD] = b"%d" % effectflag
 
@@ -989,10 +990,7 @@
     sha1s = re.findall(sha1re, commitmsg)
     unfi = repo.unfiltered()
     for sha1 in sha1s:
-        if util.safehasattr(scmutil, 'resolvehexnodeidprefix'): # > hg-4.6
-            fullnode = scmutil.resolvehexnodeidprefix(unfi, sha1)
-        else:
-            fullnode = unfi.changelog.index.partialmatch(sha1)
+        fullnode = scmutil.resolvehexnodeidprefix(unfi, sha1)
         if fullnode is None:
             continue
         ctx = unfi[fullnode]
@@ -1013,7 +1011,7 @@
     tr = repo.currenttransaction()
     assert tr is not None
     r = _evolvemerge(repo, orig, dest, pctx, keepbranch)
-    if compat.hasconflict(r): # some conflict
+    if r.unresolvedcount: # some conflict
         with repo.dirstate.parentchange(), compat.parentchange(repo):
             repo.setparents(dest.node(), orig.node())
             repo.dirstate.write(tr)
@@ -1814,8 +1812,8 @@
     oldid = repo[b'.'].node()
     startctx = repo[b'.']
     dryrunopt = opts.get('dry_run', False)
-    displayer = compat.changesetdisplayer(ui, repo,
-                                          {b'template': shorttemplate})
+    displayer = logcmdutil.changesetdisplayer(ui, repo,
+                                              {b'template': shorttemplate})
     try:
         ctx = repo[utility._singlesuccessor(repo, repo[b'.'])]
     except utility.MultipleSuccessorsError as exc:
--- a/hgext3rd/evolve/firstmergecache.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/firstmergecache.py	Thu Apr 23 03:06:30 2020 +0200
@@ -16,11 +16,7 @@
     node as nodemod,
 )
 
-try:
-    from mercurial.utils.stringutil import forcebytestr
-except ImportError:
-    # hg <= 4.5 (f99d64e8a4e4)
-    from mercurial.util import forcebytestr
+from mercurial.utils.stringutil import forcebytestr
 
 from . import (
     compat,
--- a/hgext3rd/evolve/metadata.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/metadata.py	Thu Apr 23 03:06:30 2020 +0200
@@ -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.3.2.dev'
+__version__ = b'9.4.0.dev'
 testedwith = b'4.6.2 4.7 4.8 4.9 5.0 5.1 5.2 5.3'
 minimumhgversion = b'4.6'
 buglink = b'https://bz.mercurial-scm.org/'
--- a/hgext3rd/evolve/obscache.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/obscache.py	Thu Apr 23 03:06:30 2020 +0200
@@ -19,11 +19,7 @@
     util,
 )
 
-try:
-    from mercurial.utils.stringutil import forcebytestr
-except ImportError:
-    # hg <= 4.5 (f99d64e8a4e4)
-    from mercurial.util import forcebytestr
+from mercurial.utils.stringutil import forcebytestr
 
 from . import (
     compat,
--- a/hgext3rd/evolve/obsdiscovery.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/obsdiscovery.py	Thu Apr 23 03:06:30 2020 +0200
@@ -34,11 +34,7 @@
 )
 from mercurial.i18n import _
 
-try:
-    from mercurial.utils.stringutil import forcebytestr
-except ImportError:
-    # hg <= 4.5 (f99d64e8a4e4)
-    from mercurial.util import forcebytestr
+from mercurial.utils.stringutil import forcebytestr
 
 from . import (
     compat,
@@ -49,14 +45,9 @@
     stablerangecache,
 )
 
-try:
-    from mercurial import wireprototypes, wireprotov1server
-    from mercurial.wireprotov1peer import wirepeer
-    from mercurial.wireprototypes import encodelist, decodelist
-except (ImportError, AttributeError): # <= hg-4.5
-    from mercurial import wireproto as wireprototypes
-    wireprotov1server = wireprototypes
-    from mercurial.wireproto import wirepeer, encodelist, decodelist
+from mercurial import wireprototypes, wireprotov1server
+from mercurial.wireprotov1peer import wirepeer
+from mercurial.wireprototypes import encodelist, decodelist
 
 _pack = struct.pack
 _unpack = struct.unpack
@@ -702,7 +693,7 @@
     except ValueError:
         self._abort(error.ResponseError(_(b"unexpected response:"), d))
 
-@compat.wireprotocommand(eh, b'evoext_obshashrange_v1', b'ranges')
+@wireprotov1server.wireprotocommand(b'evoext_obshashrange_v1', b'ranges', b'pull')
 def srv_obshashrange_v1(repo, proto, ranges):
     ranges = decodelist(ranges)
     ranges = [_decrange(r) for r in ranges]
@@ -726,21 +717,10 @@
     caps = orig(repo, proto)
     enabled = _useobshashrange(repo)
     if obsolete.isenabled(repo, obsolete.exchangeopt) and enabled:
-
-        # Compat hg 4.6+ (2f7290555c96)
-        bytesresponse = False
-        if util.safehasattr(caps, 'data'):
-            bytesresponse = True
-            caps = caps.data
-
-        caps = caps.split()
+        caps = caps.data.split()
         caps.append(b'_evoext_obshashrange_v1')
         caps.sort()
-        caps = b' '.join(caps)
-
-        # Compat hg 4.6+ (2f7290555c96)
-        if bytesresponse:
-            caps = wireprototypes.bytesresponse(caps)
+        caps = wireprototypes.bytesresponse(b' '.join(caps))
     return caps
 
 @eh.extsetup
--- a/hgext3rd/evolve/obsexchange.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/obsexchange.py	Thu Apr 23 03:06:30 2020 +0200
@@ -16,6 +16,8 @@
     obsolete,
     pushkey,
     util,
+    wireprototypes,
+    wireprotov1server
 )
 
 from mercurial.hgweb import common as hgwebcommon
@@ -44,13 +46,7 @@
 
 @eh.uisetup
 def addgetbundleargs(self):
-    try:
-        from mercurial import wireprototypes
-        gboptsmap = wireprototypes.GETBUNDLE_ARGUMENTS
-    except (ImportError, AttributeError):
-        # <= hg 4.5
-        from mercurial import wireproto
-        gboptsmap = wireproto.gboptsmap
+    gboptsmap = wireprototypes.GETBUNDLE_ARGUMENTS
     gboptsmap[b'evo_obscommon'] = b'nodes'
     gboptsmap[b'evo_missing_nodes'] = b'nodes'
 
@@ -126,34 +122,15 @@
     """wrapper to advertise new capability"""
     caps = orig(repo, proto)
     if obsolete.isenabled(repo, obsolete.exchangeopt):
-
-        # Compat hg 4.6+ (2f7290555c96)
-        bytesresponse = False
-        if util.safehasattr(caps, 'data'):
-            bytesresponse = True
-            caps = caps.data
-
-        caps = caps.split()
+        caps = caps.data.split()
         caps.append(b'_evoext_getbundle_obscommon')
         caps.sort()
-        caps = b' '.join(caps)
-
-        # Compat hg 4.6+ (2f7290555c96)
-        if bytesresponse:
-            from mercurial import wireprototypes
-            caps = wireprototypes.bytesresponse(caps)
+        caps = wireprototypes.bytesresponse(b' '.join(caps))
     return caps
 
 @eh.extsetup
 def extsetup_obscommon(ui):
-    try:
-        from mercurial import wireprototypes, wireprotov1server
-        gboptsmap = wireprototypes.GETBUNDLE_ARGUMENTS
-    except (ImportError, AttributeError):
-        # <= hg 4.5
-        from mercurial import wireproto
-        gboptsmap = wireproto.gboptsmap
-        wireprotov1server = wireproto
+    gboptsmap = wireprototypes.GETBUNDLE_ARGUMENTS
     gboptsmap[b'evo_obscommon'] = b'nodes'
 
     # wrap module content
--- a/hgext3rd/evolve/obshistory.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/obshistory.py	Thu Apr 23 03:06:30 2020 +0200
@@ -13,9 +13,11 @@
     commands,
     error,
     graphmod,
-    patch,
+    logcmdutil,
+    mdiff,
+    node as nodemod,
     obsutil,
-    node as nodemod,
+    patch,
     pycompat,
     scmutil,
     util,
@@ -90,11 +92,8 @@
         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']):
+    # Use the default template unless the user provided one.
+    if not opts['template']:
         opts['template'] = DEFAULT_TEMPLATE
 
     if opts['graph']:
@@ -115,24 +114,29 @@
     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_OPERATIONS = b"""{if(operations, "using {label("evolve.operation", join(operations, ", "))}")}"""
+TEMPLATE_USERS = b"""by {label("evolve.user", join(users, ", "))}"""
+TEMPLATE_ONE_DATE = b"""({date(max(dates), "%a %b %d %H:%M:%S %Y %1%2")})"""
+TEMPLATE_MANY_DATES = b"""(between {date(min(dates), "%a %b %d %H:%M:%S %Y %1%2")} and {date(max(dates), "%a %b %d %H:%M:%S %Y %1%2")})"""
+TEMPLATE_DATES = b"""{label("evolve.date", ifeq(min(dates), max(dates), "%(onedate)s", "%(manydates)s"))}""" % {
+    b"onedate": TEMPLATE_ONE_DATE,
+    b"manydates": TEMPLATE_MANY_DATES
+}
+TEMPLATE_NOTES = b"""{if(notes, notes % "\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"}
+{markers %% "  {separate(" ", "%(rewrite)s", "%(operations)s", "%(users)s", "%(dates)s")}%(notes)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"operations": TEMPLATE_OPERATIONS,
+    b"users": TEMPLATE_USERS,
+    b"dates": TEMPLATE_DATES,
+    b"notes": TEMPLATE_NOTES,
     b"patch": TEMPLATE_PATCH,
 }
 
-class obsmarker_printer(compat.changesetprinter):
+class obsmarker_printer(logcmdutil.changesetprinter):
     """show (available) information about a node
 
     We display the node, description (if available) and various information
@@ -141,7 +145,7 @@
     def __init__(self, ui, repo, *args, **kwargs):
 
         if kwargs.pop('obspatch', False):
-            if compat.changesetdiffer is None:
+            if logcmdutil.changesetdiffer is None:
                 kwargs['matchfn'] = scmutil.matchall(repo)
             else:
                 kwargs['differ'] = scmutil.matchall(repo)
@@ -149,7 +153,7 @@
         super(obsmarker_printer, self).__init__(ui, repo, *args, **kwargs)
         diffopts = kwargs.get('diffopts', {})
 
-        # Compat 4.6
+        # hg <= 4.6 (3fe1c9263024)
         if not util.safehasattr(self, "_includediff"):
             self._includediff = diffopts and diffopts.get(b'patch')
 
@@ -175,7 +179,8 @@
                 succs = sorted(succs)
 
                 for successor in succs:
-                    _debugobshistorydisplaymarker(self.ui, markerfm, successor,
+                    _debugobshistorydisplaymarker(self.ui, markerfm,
+                                                  successor[1], [successor],
                                                   ctx.node(), self.repo,
                                                   self._includediff)
 
@@ -189,7 +194,10 @@
                     if not markers:
                         continue
                     successors = succset[b"successors"]
-                    _debugobshistorydisplaysuccsandmarkers(self.ui, markerfm, successors, markers, ctx.node(), self.repo, self._includediff)
+                    _debugobshistorydisplaymarker(self.ui, markerfm,
+                                                  successors, markers,
+                                                  ctx.node(), self.repo,
+                                                  self._includediff)
 
             markerfm.end()
 
@@ -241,8 +249,8 @@
     basename = b"changeset-description"
     succname = b"changeset-description"
 
-    d = compat.strdiff(basedesc, succdesc, basename, succname)
-    uheaders, hunks = d
+    uheaders, hunks = mdiff.unidiff(basedesc, b'', succdesc, b'',
+                                    basename, succname, False)
 
     # Copied from patch.diff
     text = b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
@@ -361,10 +369,8 @@
                 relations = nodeprec.get(cand, ())
             else:
                 relations = obsutil.closestpredecessors(repo, cand)
-            # print("RELATIONS", relations, list(closestpred))
-            childrens = [(graphmod.PARENT, x) for x in relations]
-            # print("YIELD", changectx, childrens)
-            yield (cand, graphmod.CHANGESET, changectx, childrens)
+            parents = [(graphmod.PARENT, x) for x in relations]
+            yield (cand, graphmod.CHANGESET, changectx, parents)
 
 def _obshistorywalker_links(repo, revs, walksuccessors=False):
     """ Iterate the obs history tree starting from revs, traversing
@@ -429,7 +435,7 @@
     edges = graphmod.asciiedges
     walker = _obshistorywalker(repo.unfiltered(), revs, opts.get('all', False),
                                opts.get('filternonlocal', False))
-    compat.displaygraph(ui, repo, walker, displayer, edges)
+    logcmdutil.displaygraph(ui, repo, walker, displayer, edges)
 
 def _debugobshistoryrevs(ui, repo, revs, opts):
     """ Display the obsolescence history for revset
@@ -453,7 +459,7 @@
         markerfm = fm.nested(b"markers")
         for successor in sorted(succs):
             includediff = opts and opts.get("patch")
-            _debugobshistorydisplaymarker(ui, markerfm, successor, ctxnode, unfi, includediff)
+            _debugobshistorydisplaymarker(ui, markerfm, successor[1], [successor], ctxnode, unfi, includediff)
         markerfm.end()
         fm.plain(b'\n')
 
@@ -496,42 +502,44 @@
              label=b"evolve.node evolve.missing_change_ctx")
     fm.plain(b'\n')
 
-def _debugobshistorydisplaymarker(ui, fm, marker, node, repo, includediff=False):
-    succnodes = marker[1]
-    date = marker[4]
-    metadata = dict(marker[3])
-
+def _debugobshistorydisplaymarker(ui, fm, succnodes, markers, node, repo, includediff=False):
     fm.startitem()
 
-    verb = _successorsetverb(succnodes, [marker])[b"verb"]
+    verb = _successorsetverb(succnodes, markers)[b"verb"]
 
     fm.data(verb=verb)
 
-    effects = _markerseffects([marker])
+    effects = _markerseffects(markers)
     if effects:
-        fmteffect = fm.formatlist(effects, b'effect', sep=b', ')
-        fm.write(b'effects', b'(%s)', fmteffect)
+        fmteffects = fm.formatlist(effects, b'effect', sep=b', ')
+        fm.data(effects=fmteffects)
 
     if len(succnodes) > 0:
         hexnodes = (nodemod.hex(succnode) for succnode in sorted(succnodes))
         nodes = fm.formatlist(hexnodes, b'succnode')
-        fm.write(b'succnodes', b'%s', nodes)
+        fm.data(succnodes=nodes)
 
-    operation = metadata.get(b'operation')
-    if operation:
-        fm.data(operation=operation)
+    # Operations
+    operations = obsutil.markersoperations(markers)
+    if operations:
+        fm.data(operations=fm.formatlist(operations, name=b'operation', sep=b', '))
 
-    fm.data(user=metadata[b'user'])
-
-    fm.data(date=date)
+    # Users
+    users = obsutil.markersusers(markers)
+    fm.data(users=fm.formatlist(users, name=b'user', sep=b', '))
 
-    # initial support for showing note
-    if metadata.get(b'note'):
-        fm.data(note=metadata[b'note'])
+    # Dates
+    dates = obsutil.markersdates(markers)
+    fm.data(dates=fm.formatlist(dates, name=b'date'))
+
+    # Notes
+    notes = _markersnotes(markers)
+    if notes:
+        fm.data(notes=fm.formatlist(notes, name=b'note', sep=b'\n'))
 
     # Patch display
     if includediff is True:
-        _patchavailable = patchavailable(node, repo, marker[1])
+        _patchavailable = patchavailable(node, repo, succnodes)
 
         if _patchavailable[0] is True:
             succ = _patchavailable[1]
@@ -580,120 +588,6 @@
         else:
             fm.data(nopatchreason=_patchavailable[1])
 
-def _debugobshistorydisplaysuccsandmarkers(ui, fm, succnodes, markers, node, repo, includediff=False):
-    """
-    This function is a duplication of _debugobshistorydisplaymarker modified
-    to accept multiple markers as input.
-    """
-    fm.startitem()
-    fm.plain(b'  ')
-
-    verb = _successorsetverb(succnodes, markers)[b"verb"]
-
-    fm.write(b'verb', b'%s', verb,
-             label=b"evolve.verb")
-
-    effects = _markerseffects(markers)
-    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'succnode', sep=b', ')
-        fm.write(b'succnodes', b'%s', nodes,
-                 label=b"evolve.node")
-
-    # Operations
-    operations = obsutil.markersoperations(markers)
-    if operations:
-        fm.plain(b' using ')
-        fm.write(b'operation', b'%s', b", ".join(operations), label=b"evolve.operation")
-
-    fm.plain(b' by ')
-
-    # Users
-    users = obsutil.markersusers(markers)
-    fm.write(b'user', b'%s', b", ".join(users),
-             label=b"evolve.user")
-    fm.plain(b' ')
-
-    # Dates
-    dates = obsutil.markersdates(markers)
-    if dates:
-        min_date = min(dates)
-        max_date = max(dates)
-
-        if min_date == max_date:
-            fm.write(b"date", b"(at %s)", fm.formatdate(min_date), label=b"evolve.date")
-        else:
-            fm.write(b"date", b"(between %s and %s)", fm.formatdate(min_date),
-                     fm.formatdate(max_date), label=b"evolve.date")
-
-    # initial support for showing note
-    # if metadata.get('note'):
-    #     fm.plain('\n    note: ')
-    #     fm.write('note', "%s", metadata['note'], label="evolve.note")
-
-    # Patch display
-    if includediff is True:
-        _patchavailable = patchavailable(node, repo, succnodes)
-
-        if _patchavailable[0] is True:
-            succ = _patchavailable[1]
-
-            basectx = repo[node]
-            succctx = repo[succ]
-            # Description patch
-            descriptionpatch = getmarkerdescriptionpatch(repo,
-                                                         basectx.description(),
-                                                         succctx.description())
-
-            if descriptionpatch:
-                # add the diffheader
-                diffheader = b"diff -r %s -r %s changeset-description\n" %\
-                             (basectx, succctx)
-                descriptionpatch = diffheader + descriptionpatch
-
-                def tolist(text):
-                    return [text]
-
-                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':
-                        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
-            linestart = True
-            for chunk, label in patch.diffui(repo, node, succ, matchfn,
-                                             opts=diffopts):
-                if firstline:
-                    ui.write(b'\n')
-                    firstline = False
-                if linestart:
-                    ui.write(b'    ')
-                    linestart = False
-                if chunk == b'\n':
-                    linestart = True
-                ui.write(chunk, label=label)
-            fm.write(b'patch', b'%s', ui.popbuffer())
-        else:
-            fm.write(b'nopatchreason', b"\n    (No patch available, %s)",
-                     _patchavailable[1])
-
-    fm.plain(b"\n")
-
 def _prepare_hunk(hunk):
     """Drop all information but the username and patch"""
     cleanunk = []
@@ -731,6 +625,11 @@
 
     return (fate, successors)
 
+def _markersnotes(markers):
+    markersmeta = [dict(m[3]) for m in markers]
+    notes = [meta.get(b'note') for meta in markersmeta]
+    return sorted(note for note in notes if note)
+
 EFFECTMAPPING = util.sortdict([
     (obsutil.DESCCHANGED, b'description'),
     (obsutil.METACHANGED, b'meta'),
--- a/hgext3rd/evolve/rewind.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/rewind.py	Thu Apr 23 03:06:30 2020 +0200
@@ -12,6 +12,8 @@
     scmutil,
 )
 
+from mercurial.utils import dateutil
+
 from mercurial.i18n import _
 
 from . import (
@@ -194,7 +196,7 @@
         user = unfi.ui.username()
     date = unfi.ui.configdate(b'devel', b'default-date')
     if date is None:
-        date = compat.makedate()
+        date = dateutil.makedate()
     noise = b"%s\0%s\0%d\0%d" % (ctx.node(), user, date[0], date[1])
     extra[b'__rewind-hash__'] = hashlib.sha256(noise).hexdigest().encode('ascii')
 
--- a/hgext3rd/evolve/stablerangecache.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/stablerangecache.py	Thu Apr 23 03:06:30 2020 +0200
@@ -22,11 +22,7 @@
     util,
 )
 
-try:
-    from mercurial.utils.stringutil import forcebytestr
-except ImportError:
-    # hg <= 4.5 (f99d64e8a4e4)
-    from mercurial.util import forcebytestr
+from mercurial.utils.stringutil import forcebytestr
 
 from . import (
     compat,
--- a/hgext3rd/evolve/stablesort.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/stablesort.py	Thu Apr 23 03:06:30 2020 +0200
@@ -252,18 +252,15 @@
 
 from mercurial import (
     commands,
+    error,
     localrepo,
-    error,
+    logcmdutil,
     node as nodemod,
     pycompat,
     scmutil,
 )
 
-try:
-    from mercurial.utils.stringutil import forcebytestr
-except ImportError:
-    # hg <= 4.5 (f99d64e8a4e4)
-    from mercurial.util import forcebytestr
+from mercurial.utils.stringutil import forcebytestr
 
 from mercurial.i18n import _
 
@@ -318,8 +315,9 @@
         raise error.Abort(b'unknown sorting method: "%s"' % method,
                           hint=b'pick one of: %s' % valid_method)
 
-    displayer = compat.changesetdisplayer(ui, repo, pycompat.byteskwargs(opts),
-                                          buffered=True)
+    displayer = logcmdutil.changesetdisplayer(ui, repo,
+                                              pycompat.byteskwargs(opts),
+                                              buffered=True)
     kwargs = {}
     if opts['limit']:
         kwargs['limit'] = int(opts['limit'])
--- a/hgext3rd/evolve/templatekw.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/evolve/templatekw.py	Thu Apr 23 03:06:30 2020 +0200
@@ -22,36 +22,20 @@
 
 ### template keywords
 
-if util.safehasattr(templatekw, 'compatlist'):
-    @eh.templatekeyword(b'instabilities', requires={b'ctx', b'templ'})
-    def showinstabilities(context, mapping):
-        """List of strings. Evolution instabilities affecting the changeset
-        (zero or more of "orphan", "content-divergent" or "phase-divergent")."""
-        ctx = context.resource(mapping, b'ctx')
-        return templatekw.compatlist(context, mapping, b'instability',
-                                     ctx.instabilities(),
-                                     plural=b'instabilities')
+@eh.templatekeyword(b'instabilities', requires={b'ctx', b'templ'})
+def showinstabilities(context, mapping):
+    """List of strings. Evolution instabilities affecting the changeset
+    (zero or more of "orphan", "content-divergent" or "phase-divergent")."""
+    ctx = context.resource(mapping, b'ctx')
+    return templatekw.compatlist(context, mapping, b'instability',
+                                 ctx.instabilities(),
+                                 plural=b'instabilities')
 
-    @eh.templatekeyword(b'troubles', requires={b'ctx', b'templ'})
-    def showtroubles(context, mapping):   # legacy name for instabilities
-        ctx = context.resource(mapping, b'ctx')
-        return templatekw.compatlist(context, mapping, b'trouble',
-                                     ctx.instabilities(), plural=b'troubles')
-else:
-    # older template API in hg < 4.6
-    @eh.templatekeyword(b'instabilities')
-    def showinstabilities(**args):
-        """List of strings. Evolution instabilities affecting the changeset
-        (zero or more of "orphan", "content-divergent" or "phase-divergent")."""
-        ctx = args[b'ctx']
-        return templatekw.showlist(b'instability', ctx.instabilities(), args,
-                                   plural=b'instabilities')
-
-    @eh.templatekeyword(b'troubles')
-    def showtroubles(**args):
-        ctx = args[b'ctx']
-        return templatekw.showlist(b'trouble', ctx.instabilities(), args,
-                                   plural=b'troubles')
+@eh.templatekeyword(b'troubles', requires={b'ctx', b'templ'})
+def showtroubles(context, mapping):   # legacy name for instabilities
+    ctx = context.resource(mapping, b'ctx')
+    return templatekw.compatlist(context, mapping, b'trouble',
+                                 ctx.instabilities(), plural=b'troubles')
 
 _sp = templatekw.showpredecessors
 if util.safehasattr(_sp, '_requires'):
--- a/hgext3rd/pullbundle.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/pullbundle.py	Thu Apr 23 03:06:30 2020 +0200
@@ -640,7 +640,7 @@
         ui.progress(topic, pos, item, unit, total)
 
 # nodemap.get and index.[has_node|rev|get_rev]
-# hg <= 5.3 (02802fa87b74)
+# hg <= 5.2 (02802fa87b74)
 def getgetrev(cl):
     """Returns index.get_rev or nodemap.get (for pre-5.3 Mercurial)."""
     if util.safehasattr(cl.index, 'get_rev'):
--- a/hgext3rd/serverminitopic.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/serverminitopic.py	Thu Apr 23 03:06:30 2020 +0200
@@ -23,12 +23,7 @@
     util,
 )
 
-# hg <= 4.5 (b4d85bc122bd)
-try:
-    from mercurial import wireproto  # pytype: disable=import-error
-    wireproto.branchmap
-except ImportError:
-    from mercurial import wireprotov1server as wireproto
+from mercurial import wireprotov1server
 
 if util.safehasattr(registrar, 'configitem'):
 
@@ -163,7 +158,7 @@
         if util.safehasattr(self, '_entries'):
             _entries = self._entries
         else:
-            # hg <= 4.9 (624d6683c705, b137a6793c51)
+            # hg <= 4.9 (624d6683c705+b137a6793c51)
             _entries = self
         new = self.__class__(_entries, self.tipnode, self.tiprev,
                              self.filteredhash, self._closednodes)
@@ -237,11 +232,11 @@
 def uisetup(ui):
     wrapclass(branchmap, 'branchcache', _topiccache)
     try:
-        # Mercurial 4.8 and older
+        # hg <= 4.9 (3461814417f3)
         extensions.wrapfunction(branchmap, 'read', wrapread)
     except AttributeError:
-        # Mercurial 4.9; branchcache.fromfile now takes care of this
+        # Mercurial 5.0; branchcache.fromfile now takes care of this
         # which is alredy defined on _topiccache
         pass
-    extensions.wrapfunction(wireproto, '_capabilities', wireprotocaps)
+    extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps)
     extensions.wrapfunction(context.changectx, 'branch', topicbranch)
--- a/hgext3rd/topic/__init__.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/topic/__init__.py	Thu Apr 23 03:06:30 2020 +0200
@@ -156,7 +156,6 @@
     registrar,
     scmutil,
     templatefilters,
-    templatekw,
     util,
 )
 
@@ -203,7 +202,7 @@
               b'topic.active': b'green',
               }
 
-__version__ = b'0.18.2.dev'
+__version__ = b'0.19.0.dev'
 
 testedwith = b'4.6.2 4.7 4.8 4.9 5.0 5.1 5.2 5.3'
 minimumhgversion = b'4.6'
@@ -259,10 +258,6 @@
                       default=None,
             )
 
-# we need to do old style declaration for <= 4.5
-templatekeyword = registrar.templatekeyword()
-post45template = r'requires=' in templatekeyword.__doc__
-
 def _contexttopic(self, force=False):
     if not (force or self.mutable()):
         return b''
@@ -374,9 +369,6 @@
 
     cmdutil.summaryhooks.add(b'topic', summaryhook)
 
-    if not post45template:
-        templatekw.keywords[b'topic'] = topickw
-        templatekw.keywords[b'topicidx'] = topicidxkw
     # Wrap workingctx extra to return the topic name
     extensions.wrapfunction(context.workingctx, '__init__', wrapinit)
     # Wrap changelog.add to drop empty topic
@@ -510,38 +502,57 @@
 
             reporef = weakref.ref(self)
             if self.ui.configbool(b'experimental', b'enforce-single-head'):
-                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66)
                     origvalidator = tr.validator
+                elif util.safehasattr(tr, '_validator'):
+                    # hg <= 5.3 (36f08ae87ef6)
+                    origvalidator = tr._validator
                 else:
-                    origvalidator = tr._validator
+                    origvalidator = None
+
+                def _validate(tr2):
+                    repo = reporef()
+                    flow.enforcesinglehead(repo, tr2)
 
                 def validator(tr2):
-                    repo = reporef()
-                    flow.enforcesinglehead(repo, tr2)
+                    _validate(tr2)
                     origvalidator(tr2)
 
-                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66)
                     tr.validator = validator
+                elif util.safehasattr(tr, '_validator'):
+                    # hg <= 5.3 (36f08ae87ef6)
+                    tr._validator = validator
                 else:
-                    tr._validator = validator
+                    tr.addvalidator(b'000-enforce-single-head', _validate)
 
             topicmodeserver = self.ui.config(b'experimental',
                                              b'topic-mode.server', b'ignore')
             ispush = (desc.startswith(b'push') or desc.startswith(b'serve'))
             if (topicmodeserver != b'ignore' and ispush):
-                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66)
                     origvalidator = tr.validator
+                elif util.safehasattr(tr, '_validator'):
+                    # hg <= 5.3 (36f08ae87ef6)
+                    origvalidator = tr._validator
                 else:
-                    origvalidator = tr._validator
+                    origvalidator = None
+
+                def _validate(tr2):
+                    repo = reporef()
+                    flow.rejectuntopicedchangeset(repo, tr2)
 
                 def validator(tr2):
-                    repo = reporef()
-                    flow.rejectuntopicedchangeset(repo, tr2)
+                    _validate(tr2)
                     return origvalidator(tr2)
-                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66)
                     tr.validator = validator
+                elif util.safehasattr(tr, '_validator'):
+                    # hg <= 5.3 (36f08ae87ef6)
+                    tr._validator = validator
                 else:
-                    tr._validator = validator
+                    tr.addvalidator(b'000-reject-untopiced', _validate)
 
             elif (self.ui.configbool(b'experimental', b'topic.publish-bare-branch')
                     and (desc.startswith(b'push')
@@ -560,19 +571,29 @@
                                                b'topic.allow-publish',
                                                True)
             if not allow_publish:
-                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66)
                     origvalidator = tr.validator
+                elif util.safehasattr(tr, '_validator'):
+                    # hg <= 5.3 (36f08ae87ef6)
+                    origvalidator = tr._validator
                 else:
-                    origvalidator = tr._validator
+                    origvalidator = None
+
+                def _validate(tr2):
+                    repo = reporef()
+                    flow.reject_publish(repo, tr2)
 
                 def validator(tr2):
-                    repo = reporef()
-                    flow.reject_publish(repo, tr2)
+                    _validate(tr2)
                     return origvalidator(tr2)
-                if util.safehasattr(tr, 'validator'): # hg <= 4.7
+
+                if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66)
                     tr.validator = validator
+                elif util.safehasattr(tr, '_validator'):
+                    # hg <= 5.3 (36f08ae87ef6)
+                    tr._validator = validator
                 else:
-                    tr._validator = validator
+                    tr.addvalidator(b'000-reject-publish', _validate)
 
             # real transaction start
             ct = self.currenttopic
@@ -613,26 +634,19 @@
             b'topics', b'topic', namemap=_namemap, nodemap=_nodemap,
             listnames=lambda repo: repo.topics))
 
-if post45template:
-    @templatekeyword(b'topic', requires={b'ctx'})
-    def topickw(context, mapping):
-        """:topic: String. The topic of the changeset"""
-        ctx = context.resource(mapping, b'ctx')
-        return ctx.topic()
+templatekeyword = registrar.templatekeyword()
 
-    @templatekeyword(b'topicidx', requires={b'ctx'})
-    def topicidxkw(context, mapping):
-        """:topicidx: Integer. Index of the changeset as a stack alias"""
-        ctx = context.resource(mapping, b'ctx')
-        return ctx.topicidx()
-else:
-    def topickw(**args):
-        """:topic: String. The topic of the changeset"""
-        return args[b'ctx'].topic()
+@templatekeyword(b'topic', requires={b'ctx'})
+def topickw(context, mapping):
+    """:topic: String. The topic of the changeset"""
+    ctx = context.resource(mapping, b'ctx')
+    return ctx.topic()
 
-    def topicidxkw(**args):
-        """:topicidx: Integer. Index of the changeset as a stack alias"""
-        return args[b'ctx'].topicidx()
+@templatekeyword(b'topicidx', requires={b'ctx'})
+def topicidxkw(context, mapping):
+    """:topicidx: Integer. Index of the changeset as a stack alias"""
+    ctx = context.resource(mapping, b'ctx')
+    return ctx.topicidx()
 
 def wrapinit(orig, self, repo, *args, **kwargs):
     orig(self, repo, *args, **kwargs)
--- a/hgext3rd/topic/compat.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/topic/compat.py	Thu Apr 23 03:06:30 2020 +0200
@@ -32,7 +32,7 @@
     return {'helpcategory': category}
 
 # nodemap.get and index.[has_node|rev|get_rev]
-# hg <= 5.3 (02802fa87b74)
+# hg <= 5.2 (02802fa87b74)
 def getgetrev(cl):
     """Returns index.get_rev or nodemap.get (for pre-5.3 Mercurial)."""
     if util.safehasattr(cl.index, 'get_rev'):
--- a/hgext3rd/topic/discovery.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/topic/discovery.py	Thu Apr 23 03:06:30 2020 +0200
@@ -17,15 +17,9 @@
     compat,
 )
 
-# hg <= 4.5 (b4d85bc122bd)
-try:
-    from mercurial import wireproto  # pytype: disable=import-error
-    wireproto.branchmap
-except (AttributeError, ImportError):
-    from mercurial import wireprotov1server as wireproto
+from mercurial import wireprotov1server
 
 def _headssummary(orig, pushop, *args, **kwargs):
-    # In mercurial > 4.3, we receive the pushop as arguments
     repo = pushop.repo.unfiltered()
     remote = pushop.remote
 
@@ -205,12 +199,13 @@
         return
     tr._prepushheads = _nbheads(op.repo)
     reporef = weakref.ref(op.repo)
-    if util.safehasattr(tr, 'validator'): # hg <= 4.7
+    if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66)
         oldvalidator = tr.validator
-    else:
+    elif util.safehasattr(tr, '_validator'):
+        # hg <= 5.3 (36f08ae87ef6)
         oldvalidator = tr._validator
 
-    def validator(tr):
+    def _validate(tr):
         repo = reporef()
         if repo is not None:
             repo.invalidatecaches()
@@ -225,11 +220,19 @@
                     msg = _(b'push create more than 1 head on new branch "%s"'
                             % branch)
                     raise error.Abort(msg)
+
+    def validator(tr):
+        _validate(tr)
         return oldvalidator(tr)
-    if util.safehasattr(tr, 'validator'): # hg <= 4.7
+
+    if util.safehasattr(tr, 'validator'): # hg <= 4.7 (ebbba3ba3f66)
         tr.validator = validator
+    elif util.safehasattr(tr, '_validator'):
+        # hg <= 5.3 (36f08ae87ef6)
+        tr._validator = validator
     else:
-        tr._validator = validator
+        tr.addvalidator(b'000-new-head-check', _validate)
+
 handlecheckheads.params = frozenset()
 
 def _pushb2phases(orig, pushop, bundler):
@@ -249,8 +252,8 @@
 def modsetup(ui):
     """run at uisetup time to install all destinations wrapping"""
     extensions.wrapfunction(discovery, '_headssummary', _headssummary)
-    extensions.wrapfunction(wireproto, 'branchmap', wireprotobranchmap)
-    extensions.wrapfunction(wireproto, '_capabilities', wireprotocaps)
+    extensions.wrapfunction(wireprotov1server, 'branchmap', wireprotobranchmap)
+    extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps)
     # we need a proper wrap b2 part stuff
     extensions.wrapfunction(bundle2, 'handlecheckheads', handlecheckheads)
     bundle2.handlecheckheads.params = frozenset()
--- a/hgext3rd/topic/flow.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/topic/flow.py	Thu Apr 23 03:06:30 2020 +0200
@@ -7,6 +7,7 @@
     extensions,
     node,
     phases,
+    util,
 )
 
 from mercurial.i18n import _
@@ -62,10 +63,18 @@
 
 def reject_publish(repo, tr):
     """prevent a transaction to be publish anything"""
-    published = set()
-    for r, (o, n) in tr.changes[b'phases'].items():
-        if n == phases.public:
-            published.add(r)
+    if util.safehasattr(tr.changes[b'phases'], 'items'):
+        # hg <= 5.3 (fdc802f29b2c)
+        published = {
+            r for r, (o, n) in tr.changes[b'phases'].items()
+            if n == phases.public
+        }
+    else:
+        revranges = [
+            r for r, (o, n) in tr.changes[b'phases']
+            if n == phases.public
+        ]
+        published = {r for revrange in revranges for r in revrange}
     if published:
         r = min(published)
         msg = b"rejecting publishing of changeset %s" % repo[r]
@@ -101,7 +110,8 @@
 
 def installpushflag(ui):
     entry = extensions.wrapcommand(commands.table, b'push', wrappush)
-    if not any(opt for opt in entry[1] if opt[1] == b'publish'): # hg <= 4.9
+    if not any(opt for opt in entry[1] if opt[1] == b'publish'):
+        # hg <= 4.8 (9b8d1ad851f8)
         entry[1].append((b'', b'publish', False,
                          _(b'push the changeset as public')))
     extensions.wrapfunction(exchange.pushoperation, '__init__',
--- a/hgext3rd/topic/revset.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/topic/revset.py	Thu Apr 23 03:06:30 2020 +0200
@@ -124,6 +124,7 @@
                 b'relation subscript bounds must be integers',
                 None, None)
         else:
+            # hg <= 4.9 (431cf2c8c839+13f7a6a4f0db)
             a = b = z
 
         s = revset.getset(repo, revset.fullreposet(repo), x)
--- a/hgext3rd/topic/server.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/topic/server.py	Thu Apr 23 03:06:30 2020 +0200
@@ -10,14 +10,13 @@
     wireprotov1server,
 )
 
-
 try:
     from mercurial.utils import (
         repoviewutil,
     )
     repoviewutil.subsettable
 except (AttributeError, ImportError):
-    # hg <= 4.8
+    # hg <= 4.9 (caebe5e7f4bd)
     from mercurial import branchmap as repoviewutil
 
 from . import (
@@ -54,14 +53,6 @@
         filteredrevs = frozenset(filteredrevs | extrafiltered)
     return filteredrevs
 
-def wireprotocommand(name, args=b'', permission=b'pull'):
-    try:
-        from mercurial.wireprotov1server import wireprotocommand
-    except (ImportError, AttributeError):
-        # hg <= 4.6 (b4d85bc122bd)
-        from mercurial.wireproto import wireprotocommand
-    return wireprotocommand(name, args, permission=permission)
-
 def wrapheads(orig, repo, proto):
     """wrap head to hide topic^W draft changeset to old client"""
     hidetopics = repo.ui.configbool(b'experimental', b'topic.server-gate-topic-changesets')
@@ -85,8 +76,8 @@
 def setupserver(ui):
     extensions.wrapfunction(wireprotov1server, 'heads', wrapheads)
     wireprotov1server.commands.pop(b'heads')
-    wireprotocommand(b'heads', permission=b'pull')(wireprotov1server.heads)
-    wireprotocommand(b'_exttopics_heads', permission=b'pull')(topicheads)
+    wireprotov1server.wireprotocommand(b'heads', permission=b'pull')(wireprotov1server.heads)
+    wireprotov1server.wireprotocommand(b'_exttopics_heads', permission=b'pull')(topicheads)
     extensions.wrapfunction(wireprotov1server, '_capabilities', wireprotocaps)
 
     class topicpeerexecutor(wireprotov1peer.peerexecutor):
--- a/hgext3rd/topic/topicmap.py	Mon Apr 20 00:05:05 2020 +0800
+++ b/hgext3rd/topic/topicmap.py	Thu Apr 23 03:06:30 2020 +0200
@@ -125,7 +125,7 @@
     branchmap.branchcache = topiccache
 
     try:
-        # Mercurial 4.9
+        # Mercurial 5.0
         class remotetopiccache(_topiccache, branchmap.remotebranchcache):
             pass
         branchmap.remotebranchcache = remotetopiccache
@@ -135,7 +135,7 @@
             return _wrapupdatebmcache(orig.__get__(self), repo)
         extensions.wrapfunction(branchmap.BranchMapCache, 'updatecache', _wrapupdatebmcachemethod)
     except AttributeError:
-        # Mercurial 4.8 and before
+        # hg <= 4.9 (3461814417f3)
         extensions.wrapfunction(branchmap, 'updatecache', _wrapupdatebmcache)
 
 
@@ -173,7 +173,7 @@
         if util.safehasattr(self, '_entries'):
             _entries = self._entries
         else:
-            # hg <= 4.9 (624d6683c705, b137a6793c51)
+            # hg <= 4.9 (624d6683c705+b137a6793c51)
             _entries = self
         new = self.__class__(_entries, self.tipnode, self.tiprev,
                              self.filteredhash, self._closednodes)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-compat-strings.t	Thu Apr 23 03:06:30 2020 +0200
@@ -0,0 +1,8 @@
+Enable obsolescence to avoid the warning issue when obsmarkers are found
+
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > evolution = all
+  > EOF
+
+  $ $TESTDIR/../contrib/check-compat-strings.sh "$TESTDIR/../hgext3rd/" "$RUNTESTDIR/.."
--- a/tests/test-discovery-obshashrange.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-discovery-obshashrange.t	Thu Apr 23 03:06:30 2020 +0200
@@ -328,7 +328,7 @@
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending hello command (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending between command (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> remote: * (glob)
-  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> remote: capabilities: _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache%0Astream%3Dv2 changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob)
+  * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> remote: capabilities: _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Acheckheads%3Drelated%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache%0Astream%3Dv2 changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> remote: 1 (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> sending protocaps command (glob)
   * @45f8b879de922f6a6e620ba04205730335b6fc7e (*)> query 1; heads (glob)
--- a/tests/test-evolve-content-divergent-first-changeset.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-content-divergent-first-changeset.t	Thu Apr 23 03:06:30 2020 +0200
@@ -6,6 +6,7 @@
   > evolution.allowdivergence = True
   > EOF
 
+
 This test file tests the case of content-divergence resolution of changesets
 that have the null revision as the parent.
 
--- a/tests/test-evolve-cycles.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-cycles.t	Thu Apr 23 03:06:30 2020 +0200
@@ -296,20 +296,26 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
-                  "operation": "prune",
+                  "operations": [
+                      "prune"
+                  ],
                   "succnodes": [
                       "0da815c333f6364b46c86b0a897c00eb617397b6"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "rewritten"
               }
           ],
@@ -319,20 +325,26 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
-                  "operation": "prune",
+                  "operations": [
+                      "prune"
+                  ],
                   "succnodes": [
                       "868d2e0eb19c2b55a2894d37e1c435c221384d48"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "rewritten"
               }
           ],
@@ -342,20 +354,26 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
-                  "operation": "prune",
+                  "operations": [
+                      "prune"
+                  ],
                   "succnodes": [
                       "d9f908fde1a10ad198a462a3ec8b440bb397fc9c"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "rewritten"
               }
           ],
@@ -365,21 +383,27 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
-                  "operation": "prune",
+                  "operations": [
+                      "prune"
+                  ],
                   "succnodes": [
                       "2a34000d35446022104f7a091c06fe21ff2b5912",
                       "868d2e0eb19c2b55a2894d37e1c435c221384d48"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "split"
               }
           ],
@@ -389,20 +413,26 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
-                  "operation": "prune",
+                  "operations": [
+                      "prune"
+                  ],
                   "succnodes": [
                       "a8df460dbbfe9ef0c1e5ab4fff02e9514672e379"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "rewritten"
               }
           ],
@@ -412,20 +442,26 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
-                  "operation": "prune",
+                  "operations": [
+                      "prune"
+                  ],
                   "succnodes": [
                       "c473644ee0e988d7f537e31423831bbc409f12f7"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "rewritten"
               }
           ],
--- a/tests/test-evolve-interrupted.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-interrupted.t	Thu Apr 23 03:06:30 2020 +0200
@@ -52,7 +52,7 @@
   $ hg l
   @  2 apricot and blueberry
   
-  *  1 banana
+  %  1 banana
   |
   x  0 apricot
   
@@ -80,7 +80,7 @@
   $ hg l
   @  2 apricot and blueberry
   
-  *  1 banana
+  %  1 banana
   |
   x  0 apricot
   
--- a/tests/test-evolve-obshistory-amend-then-fold.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory-amend-then-fold.t	Thu Apr 23 03:06:30 2020 +0200
@@ -158,20 +158,26 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       *, (glob)
                       *, (glob)
                       "content"
                   ],
-                  "operation": "fold",
+                  "operations": [
+                      "fold"
+                  ],
                   "succnodes": [
                       "eb5a0daa21923bbf8caeb2c42085b9e463861fd0"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "folded"
               }
           ],
@@ -181,18 +187,24 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "b7ea6d14e664bdc8922221f7992631b50da3fb07"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "reworded"
               }
           ],
@@ -202,19 +214,25 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "content"
                   ],
-                  "operation": "fold",
+                  "operations": [
+                      "fold"
+                  ],
                   "succnodes": [
                       "eb5a0daa21923bbf8caeb2c42085b9e463861fd0"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "folded"
               }
           ],
--- a/tests/test-evolve-obshistory-amend.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory-amend.t	Thu Apr 23 03:06:30 2020 +0200
@@ -151,19 +151,25 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "content"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "4ae3a4151de9aa872113f0b196e28323308981e8"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "rewritten"
               }
           ],
@@ -196,19 +202,25 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       *, (glob)
                       "content"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "4ae3a4151de9aa872113f0b196e28323308981e8"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "rewritten"
               }
           ],
@@ -285,7 +297,7 @@
   $ hg amend -m "A2
   > 
   > Better better commit message"
-  $ hg amend -m "A3
+  $ hg amend --config devel.default-date='1 0' -m "A3
   > 
   > Better better better commit message"
   $ sync
@@ -329,7 +341,7 @@
   @  92210308515b (4) A3
   |
   x  4f1685185907 (3) A2
-  |    reworded(description) as 92210308515b using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+  |    reworded(description) as 92210308515b using amend by test (Thu Jan 01 00:00:01 1970 +0000)
   |      diff -r 4f1685185907 -r 92210308515b changeset-description
   |      --- a/changeset-description
   |      +++ b/changeset-description
@@ -424,7 +436,7 @@
   o  92210308515b (2) A3
   |
   x  4f1685185907
-  |    reworded(description) as 92210308515b using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+  |    reworded(description) as 92210308515b using amend by test (Thu Jan 01 00:00:01 1970 +0000)
   |      (No patch available, context is not local)
   |
   x  4ae3a4151de9 (1) A1
@@ -439,7 +451,7 @@
   o  92210308515b (2) A3
   |
   x  4ae3a4151de9 (1) A1
-       reworded(description) as 92210308515b using amend by test (at Thu Jan 01 00:00:00 1970 +0000)
+       reworded(description) as 92210308515b using amend by test (between Thu Jan 01 00:00:00 1970 +0000 and Thu Jan 01 00:00:01 1970 +0000)
          diff -r 4ae3a4151de9 -r 92210308515b changeset-description
          --- a/changeset-description
          +++ b/changeset-description
@@ -455,7 +467,7 @@
   92210308515b (2) A3
   
   4f1685185907
-    reworded(description) as 92210308515b using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+    reworded(description) as 92210308515b using amend by test (Thu Jan 01 00:00:01 1970 +0000)
       (No patch available, context is not local)
   
   4ae3a4151de9 (1) A1
@@ -471,7 +483,7 @@
   92210308515b (2) A3
   
   4f1685185907
-    reworded(description) as 92210308515b using amend by test (Thu Jan 01 00:00:00 1970 +0000)
+    reworded(description) as 92210308515b using amend by test (Thu Jan 01 00:00:01 1970 +0000)
       (No patch available, context is not local)
   
   4ae3a4151de9 (1) A1
--- a/tests/test-evolve-obshistory-content-divergent.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory-content-divergent.t	Thu Apr 23 03:06:30 2020 +0200
@@ -129,33 +129,45 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "65b757b745b935093c87a2bccd877521cccffcbd"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "reworded"
               },
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "reworded"
               }
           ],
@@ -293,33 +305,45 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "65b757b745b935093c87a2bccd877521cccffcbd"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "reworded"
               },
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "reworded"
               }
           ],
--- a/tests/test-evolve-obshistory-fold.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory-fold.t	Thu Apr 23 03:06:30 2020 +0200
@@ -178,19 +178,25 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "content"
                   ],
-                  "operation": "fold",
+                  "operations": [
+                      "fold"
+                  ],
                   "succnodes": [
                       "eb5a0daa21923bbf8caeb2c42085b9e463861fd0"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "folded"
               }
           ],
@@ -200,20 +206,26 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description",
                       "parent",
                       "content"
                   ],
-                  "operation": "fold",
+                  "operations": [
+                      "fold"
+                  ],
                   "succnodes": [
                       "eb5a0daa21923bbf8caeb2c42085b9e463861fd0"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "folded"
               }
           ],
@@ -272,7 +284,7 @@
   o  eb5a0daa2192 (2) C0
   |
   x  471f378eab4c (1) A0
-       folded(description, content) as eb5a0daa2192 using fold by test (at Thu Jan 01 00:00:00 1970 +0000)
+       folded(description, content) as eb5a0daa2192 using fold by test (Thu Jan 01 00:00:00 1970 +0000)
          diff -r 471f378eab4c -r eb5a0daa2192 changeset-description
          --- a/changeset-description
          +++ b/changeset-description
@@ -315,7 +327,7 @@
   o  eb5a0daa2192 (2) C0
   |
   x  471f378eab4c (1) A0
-       folded(description, content) as eb5a0daa2192 using fold by test (at Thu Jan 01 00:00:00 1970 +0000)
+       folded(description, content) as eb5a0daa2192 using fold by test (Thu Jan 01 00:00:00 1970 +0000)
          diff -r 471f378eab4c -r eb5a0daa2192 changeset-description
          --- a/changeset-description
          +++ b/changeset-description
--- a/tests/test-evolve-obshistory-lots-of-splits.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory-lots-of-splits.t	Thu Apr 23 03:06:30 2020 +0200
@@ -197,22 +197,28 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "parent",
                       "content"
                   ],
-                  "operation": "split",
+                  "operations": [
+                      "split"
+                  ],
                   "succnodes": [
                       "1ae8bc733a14e374f11767d2ad128d4c891dc43f",
                       "337fec4d2edcf0e7a467e35f818234bc620068b5",
                       "c7f044602e9bd5dec6528b33114df3d0221e6359",
                       "f257fde29c7a847c9b607f6e958656d0df0fb15c"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "split"
               }
           ],
@@ -237,22 +243,28 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "parent",
                       "content"
                   ],
-                  "operation": "split",
+                  "operations": [
+                      "split"
+                  ],
                   "succnodes": [
                       "1ae8bc733a14e374f11767d2ad128d4c891dc43f",
                       "337fec4d2edcf0e7a467e35f818234bc620068b5",
                       "c7f044602e9bd5dec6528b33114df3d0221e6359",
                       "f257fde29c7a847c9b607f6e958656d0df0fb15c"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "split"
               }
           ],
--- a/tests/test-evolve-obshistory-phase-divergent.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory-phase-divergent.t	Thu Apr 23 03:06:30 2020 +0200
@@ -98,18 +98,24 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "description"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "reworded"
               }
           ],
@@ -201,18 +207,24 @@
       {
           "markers": [
               {
-                  "date": [
-                      0.0,
-                      0
+                  "dates": [
+                      [
+                          0.0,
+                          0
+                      ]
                   ],
                   "effects": [
                       "description"
                   ],
-                  "operation": "amend",
+                  "operations": [
+                      "amend"
+                  ],
                   "succnodes": [
                       "fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "reworded"
               }
           ],
--- a/tests/test-evolve-obshistory-prune.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory-prune.t	Thu Apr 23 03:06:30 2020 +0200
@@ -73,12 +73,18 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
-                  "operation": "prune",
-                  "user": "test",
+                  "operations": [
+                      "prune"
+                  ],
+                  "users": [
+                      "test"
+                  ],
                   "verb": "pruned"
               }
           ],
@@ -115,7 +121,7 @@
 
   $ hg obslog -f -R $TESTTMP/server --patch 0dec01379d3b --hidden
   x  0dec01379d3b (2) B0
-       pruned using prune by test (at Thu Jan 01 00:00:00 1970 +0000)
+       pruned using prune by test (Thu Jan 01 00:00:00 1970 +0000)
          (No patch available, no successors)
   
 # TODO ADD amend + prune
--- a/tests/test-evolve-obshistory-split.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory-split.t	Thu Apr 23 03:06:30 2020 +0200
@@ -115,21 +115,29 @@
       {
           "markers": [
               {
-                  "date": [
-                      *, (glob)
-                      0
+                  "dates": [
+                      [
+                          *, (glob)
+                          0
+                      ]
                   ],
                   "effects": [
                       "parent",
                       "content"
                   ],
-                  "note": "testing split",
-                  "operation": "split",
+                  "notes": [
+                      "testing split"
+                  ],
+                  "operations": [
+                      "split"
+                  ],
                   "succnodes": [
                       "337fec4d2edcf0e7a467e35f818234bc620068b5",
                       "f257fde29c7a847c9b607f6e958656d0df0fb15c"
                   ],
-                  "user": "test",
+                  "users": [
+                      "test"
+                  ],
                   "verb": "split"
               }
           ],
--- a/tests/test-evolve-obshistory.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-obshistory.t	Thu Apr 23 03:06:30 2020 +0200
@@ -148,7 +148,7 @@
   o  7a230b46bf61 (2) A2
   |
   @  471f378eab4c (1) A0
-       reworded(description) as 7a230b46bf61 using amend by test (at Thu Jan 01 00:00:00 1970 +0000)
+       reworded(description) as 7a230b46bf61 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
          diff -r 471f378eab4c -r 7a230b46bf61 changeset-description
          --- a/changeset-description
          +++ b/changeset-description
@@ -170,10 +170,54 @@
   
 
   $ hg obslog 7a230b46bf61 --graph \
-  > -T '{node|short} {rev} {desc|firstline}\n{markers % "rewritten using {operation}"}\n'
+  > -T '{node|short} {rev} {desc|firstline}\n{markers % "rewritten using {operations}"}\n'
   o  7a230b46bf61 2 A2
   |
   x  fdf9bde5129a
   |  rewritten using amend
   @  471f378eab4c 1 A0
      rewritten using amend
+
+Test output with non-local changesets and various templates
+===========================================================
+
+  $ hg init $TESTTMP/obslog-non-local-templates
+  $ cd $TESTTMP/obslog-non-local-templates
+  $ cat << EOF >> .hg/hgrc
+  > [templates]
+  > fancy = '{desc|firstline}\n{markers % "{verb} using {operations} by {users} ({join(notes, "; ")})"}'
+  > fancier = '{desc|firstline}\n{markers % "{join(users, ", ")} had {verb} this commit using {join(operations, "/")} and said: {join(notes % "\"{note}\"", ", ")})"}'
+  > EOF
+
+  $ mkcommit A0
+  $ hg metaedit -m 'A1' -n 'note1' -d '42 0' --config devel.default-date='1 0' -q
+  $ HGUSER=alice hg amend -m 'A2' -n 'note2'
+  $ HGUSER=bob hg amend -m 'A3' -n 'note3'
+
+  $ hg debugobsolete
+  6ffd04c870fe2b73e7c885e508c4f1213a75c4d4 e481355d236800802337be3e637bd820870b04d2 0 (Thu Jan 01 00:00:01 1970 +0000) {'ef1': '33', 'note': 'note1', 'operation': 'metaedit', 'user': 'test'}
+  e481355d236800802337be3e637bd820870b04d2 65835bf83412a950e6a47f023690d5e6ae09718d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'note': 'note2', 'operation': 'amend', 'user': 'alice'}
+  65835bf83412a950e6a47f023690d5e6ae09718d 41985e02b792daac8907f6b30b513bbf6e8e034d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'note': 'note3', 'operation': 'amend', 'user': 'bob'}
+
+  $ hg strip 'desc(A1) + desc(A2)' --hidden --quiet --config extensions.strip=
+
+  $ hg obslog -f
+  @  41985e02b792 (1) A3
+  |
+  x  6ffd04c870fe (0) A0
+       rewritten(description, date) as 41985e02b792 using amend, metaedit by alice, bob, test (between Thu Jan 01 00:00:00 1970 +0000 and Thu Jan 01 00:00:01 1970 +0000)
+         note: note1
+         note: note2
+         note: note3
+  
+  $ hg obslog -f -T fancy
+  @  A3
+  |
+  x  A0
+     rewritten using amend, metaedit by alice, bob, test (note1; note2; note3)
+
+  $ hg obslog -f -T fancier
+  @  A3
+  |
+  x  A0
+     alice, bob, test had rewritten this commit using amend/metaedit and said: "note1", "note2", "note3")
--- a/tests/test-evolve-phase-divergence.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-phase-divergence.t	Thu Apr 23 03:06:30 2020 +0200
@@ -345,7 +345,7 @@
   $ hg glog
   @  5:3d62500c673d phase-divergent update to aa071e5554e3:
   |   (bm) draft
-  o  3:aa071e5554e3 added foo to foo
+  %  3:aa071e5554e3 added foo to foo
   |   () public
   o  1:4d1169d82e47 modify a
   |   () public
@@ -355,10 +355,10 @@
   $ hg obslog -r . --all
   @  3d62500c673d (5) phase-divergent update to aa071e5554e3:
   |
-  x  d47f2b37ed82 (4) added bar to foo
+  %  d47f2b37ed82 (4) added bar to foo
   |    rewritten(description, parent, content) as 3d62500c673d using evolve by test (Thu Jan 01 00:00:00 1970 +0000)
   |
-  o  aa071e5554e3 (3) added foo to foo
+  %  aa071e5554e3 (3) added foo to foo
        rewritten(description, content) as d47f2b37ed82 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
   
 
@@ -495,7 +495,7 @@
   $ hg glog
   @  8:502e73736632 phase-divergent update to b756eb10ea73:
   |   (bm) draft
-  o  6:b756eb10ea73 added bar to bar
+  %  6:b756eb10ea73 added bar to bar
   |   () public
   o  5:3d62500c673d phase-divergent update to aa071e5554e3:
   |   () public
@@ -753,7 +753,7 @@
   $ hg glog
   @  16:8c2bb6fb44e9 phase-divergent update to dc88f5aa9bc9:
   |   () draft
-  o  12:dc88f5aa9bc9 y to y and foobar to foo
+  %  12:dc88f5aa9bc9 y to y and foobar to foo
   |   () public
   o  9:2352021b3785 added x to x
   |   (bm) public
@@ -841,7 +841,7 @@
   $ hg glog
   @  21:e3090241a10c phase-divergent update to f3794e5a91dc:
   |   () draft
-  o  17:f3794e5a91dc added l to l
+  %  17:f3794e5a91dc added l to l
   |   () public
   o  16:8c2bb6fb44e9 phase-divergent update to dc88f5aa9bc9:
   |   () public
@@ -1267,7 +1267,7 @@
   $ hg glog --hidden
   @  7:88b0dae5369a phase-divergent update to a51bce62c219:
   |   () draft
-  | x  6:98dad8812511 added n
+  | %  6:98dad8812511 added n
   | |   () draft
   +---o  5:86419909e017 phase-divergent update to a51bce62c219:
   | |     () draft
@@ -1277,7 +1277,7 @@
   | | |/    () draft
   | | x  2:4f25cd9cd2bf added m
   | |/    () draft
-  o |  1:a51bce62c219 added m and n
+  % |  1:a51bce62c219 added m and n
   |/    () public
   o  0:d3873e73d99e init
       () public
@@ -1290,7 +1290,7 @@
   x |  4f25cd9cd2bf (2) added m
   | |    rewritten(description, parent, content) as 86419909e017 using evolve by test (Thu Jan 01 00:00:00 1970 +0000)
   | |
-  | x  98dad8812511 (6) added n
+  | %  98dad8812511 (6) added n
   | |    rewritten(description, parent, content) as 88b0dae5369a using evolve by test (Thu Jan 01 00:00:00 1970 +0000)
   | |
   | x  52ca78bb98c7 (4) added n
@@ -1299,7 +1299,7 @@
   | x  e1154ec0206a (3) added n
   |/     amended(content) as 52ca78bb98c7 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
   |
-  o  a51bce62c219 (1) added m and n
+  %  a51bce62c219 (1) added m and n
        split(description, parent, content) as 4f25cd9cd2bf, e1154ec0206a using prune by test (Thu Jan 01 00:00:00 1970 +0000)
   
 
@@ -1730,7 +1730,7 @@
   |   IV
   |   V
   |
-  o  changeset:   1:3074c7249d20
+  %  changeset:   1:3074c7249d20
   |  user:        test
   ~  date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     E1
@@ -1766,11 +1766,11 @@
   $ hg obslog --patch
   @  9eebcb77a7e2 (3) phase-divergent update to 3074c7249d20:
   |
-  x  599454370881 (2) E2
+  %  599454370881 (2) E2
   |    rewritten(description, parent, content) as 9eebcb77a7e2 using evolve by test (Thu Jan 01 00:00:00 1970 +0000)
   |      (No patch available, changesets rebased)
   |
-  o  3074c7249d20 (1) E1
+  %  3074c7249d20 (1) E1
        rewritten(description, content) as 599454370881 using amend by test (Thu Jan 01 00:00:00 1970 +0000)
          diff -r 3074c7249d20 -r 599454370881 changeset-description
          --- a/changeset-description
--- a/tests/test-evolve-progress.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-progress.t	Thu Apr 23 03:06:30 2020 +0200
@@ -33,7 +33,7 @@
    ancestor: a87874c6ec31, local: fd0a2402f834+, remote: 4f60c78b6d58
    a: remote is newer -> g
   getting a
-  updating: a 1/1 files (100.00%)
+  updating: a 1/2 files (50.00%)
   committing files:
   a
   committing manifest
@@ -42,8 +42,6 @@
   move:[2] third
   hg rebase -r 769574b07a96 -d 5f16d91ecde0
   evolve: 2/3 changesets (66.67%)
-    unmatched files in other:
-     b
   resolving manifests
    branchmerge: True, force: True, partial: False
    ancestor: 4f60c78b6d58, local: 5f16d91ecde0+, remote: 769574b07a96
@@ -63,7 +61,7 @@
    ancestor: 769574b07a96, local: 53c0008d98a0+, remote: 22782fddc0ab
    b: remote is newer -> g
   getting b
-  updating: b 1/1 files (100.00%)
+  updating: b 1/2 files (50.00%)
   committing files:
   b
   committing manifest
@@ -96,7 +94,7 @@
    ancestor: fd0a2402f834, local: 152c368c622b+, remote: 5f16d91ecde0
    a: remote is newer -> g
   getting a
-  updating: a 1/1 files (100.00%)
+  updating: a 1/2 files (50.00%)
   committing files:
   a
   committing manifest
@@ -154,8 +152,6 @@
   atop:[11] second
   hg rebase -r 53c0008d98a0 -d 60a86497fbfe
   evolve: 2/3 changesets (66.67%)
-    unmatched files in other:
-     b
   resolving manifests
    branchmerge: True, force: True, partial: False
    ancestor: 5f16d91ecde0, local: 60a86497fbfe+, remote: 53c0008d98a0
@@ -174,7 +170,7 @@
    ancestor: 53c0008d98a0, local: b2de95304e32+, remote: 385376d04062
    b: remote is newer -> g
   getting b
-  updating: b 1/1 files (100.00%)
+  updating: b 1/2 files (50.00%)
   committing files:
   b
   committing manifest
--- a/tests/test-evolve-public-content-divergent-corner-cases.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-public-content-divergent-corner-cases.t	Thu Apr 23 03:06:30 2020 +0200
@@ -151,7 +151,7 @@
   |   c
   |  +cc
   |
-  o  4:c0d7ee6604ea added c
+  %  4:c0d7ee6604ea added c
   |   public
   |
   |  diff -r c9241b0f2d5b -r c0d7ee6604ea c
--- a/tests/test-evolve-public-content-divergent-main.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve-public-content-divergent-main.t	Thu Apr 23 03:06:30 2020 +0200
@@ -94,7 +94,7 @@
   |   b
   |  +I am second
   |
-  o  2:44f360db368f added b
+  %  2:44f360db368f added b
   |   public
   |
   |  diff -r 9092f1db7931 -r 44f360db368f b
@@ -188,7 +188,7 @@
   @  5:1a739394e9d4 phase-divergent update to 580f2d01e52c:
   |   draft
   |
-  o  2:580f2d01e52c added b
+  %  2:580f2d01e52c added b
   |   public
   |
   o  0:9092f1db7931 added a
--- a/tests/test-evolve.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-evolve.t	Thu Apr 23 03:06:30 2020 +0200
@@ -412,7 +412,7 @@
   $ glog
   @  7:aca219761afb@default(draft) phase-divergent update to 99833d22b0c6:
   |
-  o  5:99833d22b0c6@default(public) another feature (child of ba0ec09b1bab)
+  %  5:99833d22b0c6@default(public) another feature (child of ba0ec09b1bab)
   |
   o  4:ba0ec09b1bab@default(public) a nifty feature
   |
--- a/tests/test-unstability-resolution-result.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-unstability-resolution-result.t	Thu Apr 23 03:06:30 2020 +0200
@@ -189,7 +189,7 @@
   |
   | o  7:7bc2f5967f5e@default(draft) bk:[] add c
   | |
-  o |  6:1cf0aacfd363@default(public) bk:[] newer a
+  % |  6:1cf0aacfd363@default(public) bk:[] newer a
   |/
   o  5:66719795a494@default(public) bk:[changea] changea
   |
--- a/tests/test-wireproto.t	Mon Apr 20 00:05:05 2020 +0800
+++ b/tests/test-wireproto.t	Thu Apr 23 03:06:30 2020 +0200
@@ -195,7 +195,7 @@
   $ cat hg.pid >> $DAEMON_PIDS
 
   $ curl -s http://localhost:$HGPORT/?cmd=capabilities
-  _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache%0Astream%3Dv2 changegroupsubset compression=*zlib getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-eol) (glob)
+  _evoext_getbundle_obscommon _evoext_obshashrange_v1 batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Acheckheads%3Drelated%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aobsmarkers%3DV0%2CV1%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache%0Astream%3Dv2 changegroupsubset compression=*zlib getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1,sparserevlog unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (no-eol) (glob)
 
 Check we cannot use pushkey for marker exchange anymore