changeset 44852:fd3b94f1712d

merge with stable
author Martin von Zweigbergk <martinvonz@google.com>
date Tue, 26 May 2020 08:07:24 -0700
parents 708ad5cf5e5a (diff) 91e509a12dbc (current diff)
children ba5688e3b3bd
files mercurial/dirstate.py mercurial/hook.py mercurial/localrepo.py mercurial/manifest.py rust/hg-core/src/matchers.rs setup.py
diffstat 52 files changed, 1689 insertions(+), 1559 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/extdiff.py	Fri May 15 00:53:37 2020 +0200
+++ b/hgext/extdiff.py	Tue May 26 08:07:24 2020 -0700
@@ -360,14 +360,12 @@
     - just invoke the diff for a single file in the working dir
     '''
 
+    cmdutil.check_at_most_one_arg(opts, b'rev', b'change')
     revs = opts.get(b'rev')
     change = opts.get(b'change')
     do3way = b'$parent2' in cmdline
 
-    if revs and change:
-        msg = _(b'cannot specify --rev and --change at the same time')
-        raise error.Abort(msg)
-    elif change:
+    if change:
         ctx2 = scmutil.revsingle(repo, change, None)
         ctx1a, ctx1b = ctx2.p1(), ctx2.p2()
     else:
--- a/hgext/git/gitlog.py	Fri May 15 00:53:37 2020 +0200
+++ b/hgext/git/gitlog.py	Tue May 26 08:07:24 2020 -0700
@@ -247,6 +247,60 @@
     def descendants(self, revs):
         return dagop.descendantrevs(revs, self.revs, self.parentrevs)
 
+    def incrementalmissingrevs(self, common=None):
+        """Return an object that can be used to incrementally compute the
+        revision numbers of the ancestors of arbitrary sets that are not
+        ancestors of common. This is an ancestor.incrementalmissingancestors
+        object.
+
+        'common' is a list of revision numbers. If common is not supplied, uses
+        nullrev.
+        """
+        if common is None:
+            common = [nodemod.nullrev]
+
+        return ancestor.incrementalmissingancestors(self.parentrevs, common)
+
+    def findmissing(self, common=None, heads=None):
+        """Return the ancestors of heads that are not ancestors of common.
+
+        More specifically, return a list of nodes N such that every N
+        satisfies the following constraints:
+
+          1. N is an ancestor of some node in 'heads'
+          2. N is not an ancestor of any node in 'common'
+
+        The list is sorted by revision number, meaning it is
+        topologically sorted.
+
+        'heads' and 'common' are both lists of node IDs.  If heads is
+        not supplied, uses all of the revlog's heads.  If common is not
+        supplied, uses nullid."""
+        if common is None:
+            common = [nodemod.nullid]
+        if heads is None:
+            heads = self.heads()
+
+        common = [self.rev(n) for n in common]
+        heads = [self.rev(n) for n in heads]
+
+        inc = self.incrementalmissingrevs(common=common)
+        return [self.node(r) for r in inc.missingancestors(heads)]
+
+    def children(self, node):
+        """find the children of a given node"""
+        c = []
+        p = self.rev(node)
+        for r in self.revs(start=p + 1):
+            prevs = [pr for pr in self.parentrevs(r) if pr != nodemod.nullrev]
+            if prevs:
+                for pr in prevs:
+                    if pr == p:
+                        c.append(self.node(r))
+            elif p == nodemod.nullrev:
+                c.append(self.node(r))
+        return c
+
     def reachableroots(self, minroot, heads, roots, includepath=False):
         return dagop._reachablerootspure(
             self.parentrevs, minroot, roots, heads, includepath
@@ -270,7 +324,10 @@
     def parentrevs(self, rev):
         n = self.node(rev)
         hn = gitutil.togitnode(n)
-        c = self.gitrepo[hn]
+        if hn != gitutil.nullgit:
+            c = self.gitrepo[hn]
+        else:
+            return nodemod.nullrev, nodemod.nullrev
         p1 = p2 = nodemod.nullrev
         if c.parents:
             p1 = self.rev(c.parents[0].id.raw)
--- a/hgext/git/manifest.py	Fri May 15 00:53:37 2020 +0200
+++ b/hgext/git/manifest.py	Tue May 26 08:07:24 2020 -0700
@@ -205,7 +205,7 @@
         return memgittreemanifestctx(self._repo, self._tree)
 
     def find(self, path):
-        self.read()[path]
+        return self.read()[path]
 
 
 @interfaceutil.implementer(repository.imanifestrevisionwritable)
--- a/hgext/mq.py	Fri May 15 00:53:37 2020 +0200
+++ b/hgext/mq.py	Tue May 26 08:07:24 2020 -0700
@@ -836,7 +836,15 @@
         stat = opts.get(b'stat')
         m = scmutil.match(repo[node1], files, opts)
         logcmdutil.diffordiffstat(
-            self.ui, repo, diffopts, node1, node2, m, changes, stat, fp
+            self.ui,
+            repo,
+            diffopts,
+            repo[node1],
+            repo[node2],
+            m,
+            changes,
+            stat,
+            fp,
         )
 
     def mergeone(self, repo, mergeq, head, patch, rev, diffopts):
--- a/hgext/phabricator.py	Fri May 15 00:53:37 2020 +0200
+++ b/hgext/phabricator.py	Tue May 26 08:07:24 2020 -0700
@@ -238,8 +238,8 @@
 
     def decorate(fn):
         def inner(*args, **kwargs):
-            cassette = pycompat.fsdecode(kwargs.pop('test_vcr', None))
-            if cassette:
+            if kwargs.get('test_vcr'):
+                cassette = pycompat.fsdecode(kwargs.pop('test_vcr'))
                 import hgdemandimport
 
                 with hgdemandimport.deactivated():
--- a/mercurial/changelog.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/changelog.py	Tue May 26 08:07:24 2020 -0700
@@ -385,9 +385,7 @@
             datafile=datafile,
             checkambig=True,
             mmaplargeindex=True,
-            persistentnodemap=opener.options.get(
-                b'exp-persistent-nodemap', False
-            ),
+            persistentnodemap=opener.options.get(b'persistent-nodemap', False),
         )
 
         if self._initempty and (self.version & 0xFFFF == revlog.REVLOGV1):
--- a/mercurial/commands.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/commands.py	Tue May 26 08:07:24 2020 -0700
@@ -2350,7 +2350,7 @@
     Returns 0 on success, 1 if errors are encountered.
     """
     opts = pycompat.byteskwargs(opts)
-    with repo.wlock(False):
+    with repo.wlock():
         return cmdutil.copy(ui, repo, pats, opts)
 
 
@@ -2475,26 +2475,27 @@
     Returns 0 on success.
     """
 
+    cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
     opts = pycompat.byteskwargs(opts)
     revs = opts.get(b'rev')
     change = opts.get(b'change')
     stat = opts.get(b'stat')
     reverse = opts.get(b'reverse')
 
-    if revs and change:
-        msg = _(b'cannot specify --rev and --change at the same time')
-        raise error.Abort(msg)
-    elif change:
+    if change:
         repo = scmutil.unhidehashlikerevs(repo, [change], b'nowarn')
         ctx2 = scmutil.revsingle(repo, change, None)
         ctx1 = ctx2.p1()
     else:
         repo = scmutil.unhidehashlikerevs(repo, revs, b'nowarn')
         ctx1, ctx2 = scmutil.revpair(repo, revs)
-    node1, node2 = ctx1.node(), ctx2.node()
 
     if reverse:
-        node1, node2 = node2, node1
+        ctxleft = ctx2
+        ctxright = ctx1
+    else:
+        ctxleft = ctx1
+        ctxright = ctx2
 
     diffopts = patch.diffallopts(ui, opts)
     m = scmutil.match(ctx2, pats, opts)
@@ -2504,8 +2505,8 @@
         ui,
         repo,
         diffopts,
-        node1,
-        node2,
+        ctxleft,
+        ctxright,
         m,
         stat=stat,
         listsubrepos=opts.get(b'subrepos'),
@@ -3427,8 +3428,11 @@
                 m = regexp.search(self.line, p)
                 if not m:
                     break
-                yield m.span()
-                p = m.end()
+                if m.end() == p:
+                    p += 1
+                else:
+                    yield m.span()
+                    p = m.end()
 
     matches = {}
     copies = {}
@@ -3574,31 +3578,41 @@
 
     getrenamed = scmutil.getrenamedfn(repo)
 
-    def get_file_content(filename, filelog, filenode, context, revision):
-        try:
-            content = filelog.read(filenode)
-        except error.WdirUnsupported:
-            content = context[filename].data()
-        except error.CensoredNodeError:
-            content = None
-            ui.warn(
-                _(b'cannot search in censored file: %(filename)s:%(revnum)s\n')
-                % {b'filename': filename, b'revnum': pycompat.bytestr(revision)}
-            )
-        return content
+    def readfile(ctx, fn):
+        rev = ctx.rev()
+        if rev is None:
+            fctx = ctx[fn]
+            try:
+                return fctx.data()
+            except IOError as e:
+                if e.errno != errno.ENOENT:
+                    raise
+        else:
+            flog = getfile(fn)
+            fnode = ctx.filenode(fn)
+            try:
+                return flog.read(fnode)
+            except error.CensoredNodeError:
+                ui.warn(
+                    _(
+                        b'cannot search in censored file: %(filename)s:%(revnum)s\n'
+                    )
+                    % {b'filename': fn, b'revnum': pycompat.bytestr(rev),}
+                )
 
     def prep(ctx, fns):
         rev = ctx.rev()
         pctx = ctx.p1()
-        parent = pctx.rev()
         matches.setdefault(rev, {})
-        matches.setdefault(parent, {})
+        if diff:
+            parent = pctx.rev()
+            matches.setdefault(parent, {})
         files = revfiles.setdefault(rev, [])
         for fn in fns:
-            flog = getfile(fn)
-            try:
-                fnode = ctx.filenode(fn)
-            except error.LookupError:
+            # fn might not exist in the revision (could be a file removed by the
+            # revision). We could check `fn not in ctx` even when rev is None,
+            # but it's less racy to protect againt that in readfile.
+            if rev is not None and fn not in ctx:
                 continue
 
             copy = None
@@ -3613,17 +3627,12 @@
             files.append(fn)
 
             if fn not in matches[rev]:
-                content = get_file_content(fn, flog, fnode, ctx, rev)
-                grepbody(fn, rev, content)
-
-            pfn = copy or fn
-            if pfn not in matches[parent]:
-                try:
-                    pfnode = pctx.filenode(pfn)
-                    pcontent = get_file_content(pfn, flog, pfnode, pctx, parent)
-                    grepbody(pfn, parent, pcontent)
-                except error.LookupError:
-                    pass
+                grepbody(fn, rev, readfile(ctx, fn))
+
+            if diff:
+                pfn = copy or fn
+                if pfn not in matches[parent] and pfn in pctx:
+                    grepbody(pfn, parent, readfile(pctx, pfn))
 
     ui.pager(b'grep')
     fm = ui.formatter(b'grep', opts)
@@ -5807,7 +5816,7 @@
     Returns 0 on success, 1 if errors are encountered.
     """
     opts = pycompat.byteskwargs(opts)
-    with repo.wlock(False):
+    with repo.wlock():
         return cmdutil.copy(ui, repo, pats, opts, rename=True)
 
 
@@ -6786,6 +6795,7 @@
 
     """
 
+    cmdutil.check_at_most_one_arg(opts, 'rev', 'change')
     opts = pycompat.byteskwargs(opts)
     revs = opts.get(b'rev')
     change = opts.get(b'change')
@@ -6796,10 +6806,7 @@
         else:
             terse = ui.config(b'commands', b'status.terse')
 
-    if revs and change:
-        msg = _(b'cannot specify --rev and --change at the same time')
-        raise error.Abort(msg)
-    elif revs and terse:
+    if revs and terse:
         msg = _(b'cannot use --terse with --rev')
         raise error.Abort(msg)
     elif change:
--- a/mercurial/configitems.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/configitems.py	Tue May 26 08:07:24 2020 -0700
@@ -405,18 +405,6 @@
 coreconfigitem(
     b'devel', b'legacy.exchange', default=list,
 )
-# TODO before getting `persistent-nodemap` out of experimental
-#
-# * decide for a "status" of the persistent nodemap and associated location
-#   - part of the store next the revlog itself (new requirements)
-#   - part of the cache directory
-#   - part of an `index` directory
-#     (https://www.mercurial-scm.org/wiki/ComputedIndexPlan)
-# * do we want to use this for more than just changelog? if so we need:
-#   - simpler "pending" logic for them
-#   - double check the memory story (we dont want to keep all revlog in memory)
-#   - think about the naming scheme if we are in "cache"
-# * increment the version format to "1" and freeze it.
 coreconfigitem(
     b'devel', b'persistent-nodemap', default=False,
 )
@@ -675,12 +663,6 @@
     b'experimental', b'rust.index', default=False,
 )
 coreconfigitem(
-    b'experimental', b'exp-persistent-nodemap', default=False,
-)
-coreconfigitem(
-    b'experimental', b'exp-persistent-nodemap.mmap', default=True,
-)
-coreconfigitem(
     b'experimental', b'server.filesdata.recommended-batch-size', default=50000,
 )
 coreconfigitem(
@@ -783,6 +765,12 @@
 coreconfigitem(
     b'format', b'usestore', default=True,
 )
+# Right now, the only efficient implement of the nodemap logic is in Rust, so
+# the persistent nodemap feature needs to stay experimental as long as the Rust
+# extensions are an experimental feature.
+coreconfigitem(
+    b'format', b'use-persistent-nodemap', default=False, experimental=True
+)
 coreconfigitem(
     b'format',
     b'exp-use-copies-side-data-changeset',
@@ -1088,6 +1076,14 @@
     default=True,
     alias=[(b'format', b'aggressivemergedeltas')],
 )
+# experimental as long as rust is experimental (or a C version is implemented)
+coreconfigitem(
+    b'storage', b'revlog.nodemap.mmap', default=True, experimental=True
+)
+# experimental as long as format.use-persistent-nodemap is.
+coreconfigitem(
+    b'storage', b'revlog.nodemap.mode', default=b'compat', experimental=True
+)
 coreconfigitem(
     b'storage', b'revlog.reuse-external-delta', default=True,
 )
--- a/mercurial/copies.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/copies.py	Tue May 26 08:07:24 2020 -0700
@@ -183,10 +183,27 @@
     * p1copies: mapping of copies from p1
     * p2copies: mapping of copies from p2
     * removed: a list of removed files
+    * ismerged: a callback to know if file was merged in that revision
     """
     cl = repo.changelog
     parents = cl.parentrevs
 
+    def get_ismerged(rev):
+        ctx = repo[rev]
+
+        def ismerged(path):
+            if path not in ctx.files():
+                return False
+            fctx = ctx[path]
+            parents = fctx._filelog.parents(fctx._filenode)
+            nb_parents = 0
+            for n in parents:
+                if n != node.nullid:
+                    nb_parents += 1
+            return nb_parents >= 2
+
+        return ismerged
+
     if repo.filecopiesmode == b'changeset-sidedata':
         changelogrevision = cl.changelogrevision
         flags = cl.flags
@@ -218,6 +235,7 @@
 
         def revinfo(rev):
             p1, p2 = parents(rev)
+            value = None
             if flags(rev) & REVIDX_SIDEDATA:
                 e = merge_caches.pop(rev, None)
                 if e is not None:
@@ -228,12 +246,22 @@
                 removed = c.filesremoved
                 if p1 != node.nullrev and p2 != node.nullrev:
                     # XXX some case we over cache, IGNORE
-                    merge_caches[rev] = (p1, p2, p1copies, p2copies, removed)
+                    value = merge_caches[rev] = (
+                        p1,
+                        p2,
+                        p1copies,
+                        p2copies,
+                        removed,
+                        get_ismerged(rev),
+                    )
             else:
                 p1copies = {}
                 p2copies = {}
                 removed = []
-            return p1, p2, p1copies, p2copies, removed
+
+            if value is None:
+                value = (p1, p2, p1copies, p2copies, removed, get_ismerged(rev))
+            return value
 
     else:
 
@@ -242,7 +270,7 @@
             ctx = repo[rev]
             p1copies, p2copies = ctx._copies
             removed = ctx.filesremoved()
-            return p1, p2, p1copies, p2copies, removed
+            return p1, p2, p1copies, p2copies, removed, get_ismerged(rev)
 
     return revinfo
 
@@ -256,6 +284,7 @@
     revinfo = _revinfogetter(repo)
 
     cl = repo.changelog
+    isancestor = cl.isancestorrev  # XXX we should had chaching to this.
     missingrevs = cl.findmissingrevs(common=[a.rev()], heads=[b.rev()])
     mrset = set(missingrevs)
     roots = set()
@@ -283,10 +312,14 @@
     iterrevs.update(roots)
     iterrevs.remove(b.rev())
     revs = sorted(iterrevs)
-    return _combinechangesetcopies(revs, children, b.rev(), revinfo, match)
+    return _combinechangesetcopies(
+        revs, children, b.rev(), revinfo, match, isancestor
+    )
 
 
-def _combinechangesetcopies(revs, children, targetrev, revinfo, match):
+def _combinechangesetcopies(
+    revs, children, targetrev, revinfo, match, isancestor
+):
     """combine the copies information for each item of iterrevs
 
     revs: sorted iterable of revision to visit
@@ -305,7 +338,7 @@
             # this is a root
             copies = {}
         for i, c in enumerate(children[r]):
-            p1, p2, p1copies, p2copies, removed = revinfo(c)
+            p1, p2, p1copies, p2copies, removed, ismerged = revinfo(c)
             if r == p1:
                 parent = 1
                 childcopies = p1copies
@@ -319,9 +352,12 @@
                 }
             newcopies = copies
             if childcopies:
-                newcopies = _chain(newcopies, childcopies)
-                # _chain makes a copies, we can avoid doing so in some
-                # simple/linear cases.
+                newcopies = copies.copy()
+                for dest, source in pycompat.iteritems(childcopies):
+                    prev = copies.get(source)
+                    if prev is not None and prev[1] is not None:
+                        source = prev[1]
+                    newcopies[dest] = (c, source)
                 assert newcopies is not copies
             for f in removed:
                 if f in newcopies:
@@ -330,7 +366,7 @@
                         # branches.  when there are no other branches, this
                         # could be avoided.
                         newcopies = copies.copy()
-                    del newcopies[f]
+                    newcopies[f] = (c, None)
             othercopies = all_copies.get(c)
             if othercopies is None:
                 all_copies[c] = newcopies
@@ -338,21 +374,55 @@
                 # we are the second parent to work on c, we need to merge our
                 # work with the other.
                 #
-                # Unlike when copies are stored in the filelog, we consider
-                # it a copy even if the destination already existed on the
-                # other branch. It's simply too expensive to check if the
-                # file existed in the manifest.
-                #
                 # In case of conflict, parent 1 take precedence over parent 2.
                 # This is an arbitrary choice made anew when implementing
                 # changeset based copies. It was made without regards with
                 # potential filelog related behavior.
                 if parent == 1:
-                    othercopies.update(newcopies)
+                    _merge_copies_dict(
+                        othercopies, newcopies, isancestor, ismerged
+                    )
                 else:
-                    newcopies.update(othercopies)
+                    _merge_copies_dict(
+                        newcopies, othercopies, isancestor, ismerged
+                    )
                     all_copies[c] = newcopies
-    return all_copies[targetrev]
+
+    final_copies = {}
+    for dest, (tt, source) in all_copies[targetrev].items():
+        if source is not None:
+            final_copies[dest] = source
+    return final_copies
+
+
+def _merge_copies_dict(minor, major, isancestor, ismerged):
+    """merge two copies-mapping together, minor and major
+
+    In case of conflict, value from "major" will be picked.
+
+    - `isancestors(low_rev, high_rev)`: callable return True if `low_rev` is an
+                                        ancestors of `high_rev`,
+
+    - `ismerged(path)`: callable return True if `path` have been merged in the
+                        current revision,
+    """
+    for dest, value in major.items():
+        other = minor.get(dest)
+        if other is None:
+            minor[dest] = value
+        else:
+            new_tt = value[0]
+            other_tt = other[0]
+            if value[1] == other[1]:
+                continue
+            # content from "major" wins, unless it is older
+            # than the branch point or there is a merge
+            if (
+                new_tt == other_tt
+                or not isancestor(new_tt, other_tt)
+                or ismerged(dest)
+            ):
+                minor[dest] = value
 
 
 def _forwardcopies(a, b, base=None, match=None):
--- a/mercurial/dirstate.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/dirstate.py	Tue May 26 08:07:24 2020 -0700
@@ -187,7 +187,7 @@
 
     @propertycache
     def _checkexec(self):
-        return util.checkexec(self._root)
+        return bool(util.checkexec(self._root))
 
     @propertycache
     def _checkcase(self):
@@ -1114,6 +1114,7 @@
             unknown,
             warnings,
             bad,
+            traversed,
         ) = rustmod.status(
             self._map._rustmap,
             matcher,
@@ -1124,7 +1125,13 @@
             bool(list_clean),
             bool(list_ignored),
             bool(list_unknown),
+            bool(matcher.traversedir),
         )
+
+        if matcher.traversedir:
+            for dir in traversed:
+                matcher.traversedir(dir)
+
         if self._ui.warn:
             for item in warnings:
                 if isinstance(item, tuple):
@@ -1200,10 +1207,8 @@
             use_rust = False
         elif sparse.enabled:
             use_rust = False
-        elif match.traversedir is not None:
-            use_rust = False
         elif not isinstance(match, allowed_matchers):
-            # Matchers have yet to be implemented
+            # Some matchers have yet to be implemented
             use_rust = False
 
         if use_rust:
--- a/mercurial/helptext/internals/requirements.txt	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/helptext/internals/requirements.txt	Tue May 26 08:07:24 2020 -0700
@@ -142,3 +142,16 @@
 August 2019). The requirement will only be present on repositories
 that have opted in to this format (by having
 ``format.bookmarks-in-store=true`` set when they were created).
+
+persistent-nodemap
+==================
+
+The `nodemap` index (mapping nodeid to local revision number) is persisted on
+disk. This provides speed benefit (if the associated native code is used). The
+persistent nodemap is only used for two revlogs: the changelog and the
+manifestlog.
+
+Support for this requirement was added in Mercurial 5.5 (released August 2020).
+Note that as of 5.5, only installations compiled with the Rust extension will
+benefit from a speedup. The other installations will do the necessary work to
+keep the index up to date, but will suffer a slowdown.
--- a/mercurial/hook.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/hook.py	Tue May 26 08:07:24 2020 -0700
@@ -158,6 +158,10 @@
     env[b'HG_HOOKNAME'] = name
 
     for k, v in pycompat.iteritems(args):
+        # transaction changes can accumulate MBs of data, so skip it
+        # for external hooks
+        if k == b'changes':
+            continue
         if callable(v):
             v = v()
         if isinstance(v, (dict, list)):
--- a/mercurial/interfaces/repository.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/interfaces/repository.py	Tue May 26 08:07:24 2020 -0700
@@ -1395,6 +1395,9 @@
         Raises ``error.LookupError`` if the node is not known.
         """
 
+    def update_caches(transaction):
+        """update whatever cache are relevant for the used storage."""
+
 
 class ilocalrepositoryfilestorage(interfaceutil.Interface):
     """Local repository sub-interface providing access to tracked file storage.
--- a/mercurial/localrepo.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/localrepo.py	Tue May 26 08:07:24 2020 -0700
@@ -445,6 +445,9 @@
 # copies related information in changeset's sidedata.
 COPIESSDC_REQUIREMENT = b'exp-copies-sidedata-changeset'
 
+# The repository use persistent nodemap for the changelog and the manifest.
+NODEMAP_REQUIREMENT = b'persistent-nodemap'
+
 # Functions receiving (ui, features) that extensions can register to impact
 # the ability to load repositories with custom requirements. Only
 # functions defined in loaded extensions are called.
@@ -933,10 +936,12 @@
 
     if ui.configbool(b'experimental', b'rust.index'):
         options[b'rust.index'] = True
-    if ui.configbool(b'experimental', b'exp-persistent-nodemap'):
-        options[b'exp-persistent-nodemap'] = True
-    if ui.configbool(b'experimental', b'exp-persistent-nodemap.mmap'):
-        options[b'exp-persistent-nodemap.mmap'] = True
+    if NODEMAP_REQUIREMENT in requirements:
+        options[b'persistent-nodemap'] = True
+    if ui.configbool(b'storage', b'revlog.nodemap.mmap'):
+        options[b'persistent-nodemap.mmap'] = True
+    epnm = ui.config(b'storage', b'revlog.nodemap.mode')
+    options[b'persistent-nodemap.mode'] = epnm
     if ui.configbool(b'devel', b'persistent-nodemap'):
         options[b'devel-force-nodemap'] = True
 
@@ -1021,6 +1026,7 @@
         REVLOGV2_REQUIREMENT,
         SIDEDATA_REQUIREMENT,
         SPARSEREVLOG_REQUIREMENT,
+        NODEMAP_REQUIREMENT,
         bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT,
     }
     _basesupported = supportedformats | {
@@ -2239,6 +2245,7 @@
 
         tr.hookargs[b'txnid'] = txnid
         tr.hookargs[b'txnname'] = desc
+        tr.hookargs[b'changes'] = tr.changes
         # note: writing the fncache only during finalize mean that the file is
         # outdated when running hooks. As fncache is used for streaming clone,
         # this is not expected to break anything that happen during the hooks.
@@ -2511,6 +2518,7 @@
             unfi = self.unfiltered()
 
             self.changelog.update_caches(transaction=tr)
+            self.manifestlog.update_caches(transaction=tr)
 
             rbc = unfi.revbranchcache()
             for r in unfi.changelog:
@@ -3018,6 +3026,12 @@
                     self.ui.write(
                         _(b'note: commit message saved in %s\n') % msgfn
                     )
+                    self.ui.write(
+                        _(
+                            b"note: use 'hg commit --logfile "
+                            b".hg/last-message.txt --edit' to reuse it\n"
+                        )
+                    )
                 raise
 
         def commithook(unused_success):
@@ -3653,6 +3667,9 @@
     if ui.configbool(b'format', b'bookmarks-in-store'):
         requirements.add(bookmarks.BOOKMARKS_IN_STORE_REQUIREMENT)
 
+    if ui.configbool(b'format', b'use-persistent-nodemap'):
+        requirements.add(NODEMAP_REQUIREMENT)
+
     return requirements
 
 
--- a/mercurial/logcmdutil.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/logcmdutil.py	Tue May 26 08:07:24 2020 -0700
@@ -72,8 +72,8 @@
     ui,
     repo,
     diffopts,
-    node1,
-    node2,
+    ctx1,
+    ctx2,
     match,
     changes=None,
     stat=False,
@@ -85,8 +85,6 @@
     hunksfilterfn=None,
 ):
     '''show diff or diffstat.'''
-    ctx1 = repo[node1]
-    ctx2 = repo[node2]
     if root:
         relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
     else:
@@ -173,6 +171,7 @@
             for chunk, label in chunks:
                 ui.write(chunk, label=label)
 
+    node2 = ctx2.node()
     for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
         tempnode2 = node2
         try:
@@ -208,15 +207,12 @@
         return None
 
     def showdiff(self, ui, ctx, diffopts, graphwidth=0, stat=False):
-        repo = ctx.repo()
-        node = ctx.node()
-        prev = ctx.p1().node()
         diffordiffstat(
             ui,
-            repo,
+            ctx.repo(),
             diffopts,
-            prev,
-            node,
+            ctx.p1(),
+            ctx,
             match=self._makefilematcher(ctx),
             stat=stat,
             graphwidth=graphwidth,
--- a/mercurial/manifest.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/manifest.py	Tue May 26 08:07:24 2020 -0700
@@ -1599,6 +1599,7 @@
             checkambig=not bool(tree),
             mmaplargeindex=True,
             upperboundcomp=MAXCOMPRESSION,
+            persistentnodemap=opener.options.get(b'persistent-nodemap', False),
         )
 
         self.index = self._revlog.index
@@ -1959,6 +1960,9 @@
     def rev(self, node):
         return self._rootstore.rev(node)
 
+    def update_caches(self, transaction):
+        return self._rootstore._revlog.update_caches(transaction=transaction)
+
 
 @interfaceutil.implementer(repository.imanifestrevisionwritable)
 class memmanifestctx(object):
--- a/mercurial/revlogutils/nodemap.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/revlogutils/nodemap.py	Tue May 26 08:07:24 2020 -0700
@@ -13,6 +13,8 @@
 import re
 import struct
 
+from ..i18n import _
+
 from .. import (
     error,
     node as nodemod,
@@ -48,7 +50,7 @@
     docket.data_unused = data_unused
 
     filename = _rawdata_filepath(revlog, docket)
-    use_mmap = revlog.opener.options.get(b"exp-persistent-nodemap.mmap")
+    use_mmap = revlog.opener.options.get(b"persistent-nodemap.mmap")
     try:
         with revlog.opener(filename) as fd:
             if use_mmap:
@@ -105,6 +107,9 @@
     def addabort(self, *args, **kwargs):
         pass
 
+    def _report(self, *args):
+        pass
+
 
 def update_persistent_nodemap(revlog):
     """update the persistent nodemap right now
@@ -137,7 +142,14 @@
     can_incremental = util.safehasattr(revlog.index, "nodemap_data_incremental")
     ondisk_docket = revlog._nodemap_docket
     feed_data = util.safehasattr(revlog.index, "update_nodemap_data")
-    use_mmap = revlog.opener.options.get(b"exp-persistent-nodemap.mmap")
+    use_mmap = revlog.opener.options.get(b"persistent-nodemap.mmap")
+    mode = revlog.opener.options.get(b"persistent-nodemap.mode")
+    if not can_incremental:
+        msg = _(b"persistent nodemap in strict mode without efficient method")
+        if mode == b'warn':
+            tr._report(b"%s\n" % msg)
+        elif mode == b'strict':
+            raise error.Abort(msg)
 
     data = None
     # first attemp an incremental update of the data
@@ -255,8 +267,7 @@
 # data. Its content is currently very light, but it will expand as the on disk
 # nodemap gains the necessary features to be used in production.
 
-# version 0 is experimental, no BC garantee, do no use outside of tests.
-ONDISK_VERSION = 0
+ONDISK_VERSION = 1
 S_VERSION = struct.Struct(">B")
 S_HEADER = struct.Struct(">BQQQQ")
 
--- a/mercurial/scmutil.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/scmutil.py	Tue May 26 08:07:24 2020 -0700
@@ -456,9 +456,7 @@
 
 
 def resolvehexnodeidprefix(repo, prefix):
-    if prefix.startswith(b'x') and repo.ui.configbool(
-        b'experimental', b'revisions.prefixhexnode'
-    ):
+    if prefix.startswith(b'x'):
         prefix = prefix[1:]
     try:
         # Uses unfiltered repo because it's faster when prefix is ambiguous/
--- a/mercurial/subrepo.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/subrepo.py	Tue May 26 08:07:24 2020 -0700
@@ -617,8 +617,8 @@
                 ui,
                 self._repo,
                 diffopts,
-                node1,
-                node2,
+                self._repo[node1],
+                self._repo[node2],
                 match,
                 prefix=prefix,
                 listsubrepos=True,
--- a/mercurial/upgrade.py	Fri May 15 00:53:37 2020 +0200
+++ b/mercurial/upgrade.py	Tue May 26 08:07:24 2020 -0700
@@ -78,6 +78,7 @@
         localrepo.SPARSEREVLOG_REQUIREMENT,
         localrepo.SIDEDATA_REQUIREMENT,
         localrepo.COPIESSDC_REQUIREMENT,
+        localrepo.NODEMAP_REQUIREMENT,
     }
     for name in compression.compengines:
         engine = compression.compengines[name]
@@ -105,6 +106,7 @@
         localrepo.SPARSEREVLOG_REQUIREMENT,
         localrepo.SIDEDATA_REQUIREMENT,
         localrepo.COPIESSDC_REQUIREMENT,
+        localrepo.NODEMAP_REQUIREMENT,
     }
     for name in compression.compengines:
         engine = compression.compengines[name]
@@ -132,6 +134,7 @@
         localrepo.SPARSEREVLOG_REQUIREMENT,
         localrepo.SIDEDATA_REQUIREMENT,
         localrepo.COPIESSDC_REQUIREMENT,
+        localrepo.NODEMAP_REQUIREMENT,
     }
     for name in compression.compengines:
         engine = compression.compengines[name]
@@ -374,6 +377,21 @@
 
 
 @registerformatvariant
+class persistentnodemap(requirementformatvariant):
+    name = b'persistent-nodemap'
+
+    _requirement = localrepo.NODEMAP_REQUIREMENT
+
+    default = False
+
+    description = _(
+        b'persist the node -> rev mapping on disk to speedup lookup'
+    )
+
+    upgrademessage = _(b'Speedup revision lookup by node id.')
+
+
+@registerformatvariant
 class copiessdc(requirementformatvariant):
     name = b'copies-sdc'
 
@@ -807,14 +825,14 @@
     if not revcount:
         return
 
-    ui.write(
+    ui.status(
         _(
             b'migrating %d total revisions (%d in filelogs, %d in manifests, '
             b'%d in changelog)\n'
         )
         % (revcount, frevcount, mrevcount, crevcount)
     )
-    ui.write(
+    ui.status(
         _(b'migrating %s in store; %s tracked data\n')
         % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
     )
@@ -837,7 +855,7 @@
         oldrl = _revlogfrompath(srcrepo, unencoded)
 
         if isinstance(oldrl, changelog.changelog) and b'c' not in seen:
-            ui.write(
+            ui.status(
                 _(
                     b'finished migrating %d manifest revisions across %d '
                     b'manifests; change in size: %s\n'
@@ -845,7 +863,7 @@
                 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
             )
 
-            ui.write(
+            ui.status(
                 _(
                     b'migrating changelog containing %d revisions '
                     b'(%s in store; %s tracked data)\n'
@@ -861,7 +879,7 @@
                 _(b'changelog revisions'), total=crevcount
             )
         elif isinstance(oldrl, manifest.manifestrevlog) and b'm' not in seen:
-            ui.write(
+            ui.status(
                 _(
                     b'finished migrating %d filelog revisions across %d '
                     b'filelogs; change in size: %s\n'
@@ -869,7 +887,7 @@
                 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
             )
 
-            ui.write(
+            ui.status(
                 _(
                     b'migrating %d manifests containing %d revisions '
                     b'(%s in store; %s tracked data)\n'
@@ -888,7 +906,7 @@
                 _(b'manifest revisions'), total=mrevcount
             )
         elif b'f' not in seen:
-            ui.write(
+            ui.status(
                 _(
                     b'migrating %d filelogs containing %d revisions '
                     b'(%s in store; %s tracked data)\n'
@@ -941,7 +959,7 @@
 
     progress.complete()
 
-    ui.write(
+    ui.status(
         _(
             b'finished migrating %d changelog revisions; change in size: '
             b'%s\n'
@@ -949,7 +967,7 @@
         % (crevcount, util.bytecount(cdstsize - csrcsize))
     )
 
-    ui.write(
+    ui.status(
         _(
             b'finished migrating %d total revisions; total change in store '
             b'size: %s\n'
@@ -975,7 +993,7 @@
     Function should return ``True`` if the file is to be copied.
     """
     # Skip revlogs.
-    if path.endswith((b'.i', b'.d')):
+    if path.endswith((b'.i', b'.d', b'.n', b'.nd')):
         return False
     # Skip transaction related files.
     if path.startswith(b'undo'):
@@ -1013,7 +1031,7 @@
     assert srcrepo.currentwlock()
     assert dstrepo.currentwlock()
 
-    ui.write(
+    ui.status(
         _(
             b'(it is safe to interrupt this process any time before '
             b'data migration completes)\n'
@@ -1048,14 +1066,14 @@
         if not _filterstorefile(srcrepo, dstrepo, requirements, p, kind, st):
             continue
 
-        srcrepo.ui.write(_(b'copying %s\n') % p)
+        srcrepo.ui.status(_(b'copying %s\n') % p)
         src = srcrepo.store.rawvfs.join(p)
         dst = dstrepo.store.rawvfs.join(p)
         util.copyfile(src, dst, copystat=True)
 
     _finishdatamigration(ui, srcrepo, dstrepo, requirements)
 
-    ui.write(_(b'data fully migrated to temporary repository\n'))
+    ui.status(_(b'data fully migrated to temporary repository\n'))
 
     backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
     backupvfs = vfsmod.vfs(backuppath)
@@ -1067,7 +1085,7 @@
     # as a mechanism to lock out new clients during the data swap. This is
     # better than allowing a client to continue while the repository is in
     # an inconsistent state.
-    ui.write(
+    ui.status(
         _(
             b'marking source repository as being upgraded; clients will be '
             b'unable to read from repository\n'
@@ -1077,18 +1095,18 @@
         srcrepo.vfs, srcrepo.requirements | {b'upgradeinprogress'}
     )
 
-    ui.write(_(b'starting in-place swap of repository data\n'))
-    ui.write(_(b'replaced files will be backed up at %s\n') % backuppath)
+    ui.status(_(b'starting in-place swap of repository data\n'))
+    ui.status(_(b'replaced files will be backed up at %s\n') % backuppath)
 
     # Now swap in the new store directory. Doing it as a rename should make
     # the operation nearly instantaneous and atomic (at least in well-behaved
     # environments).
-    ui.write(_(b'replacing store...\n'))
+    ui.status(_(b'replacing store...\n'))
     tstart = util.timer()
     util.rename(srcrepo.spath, backupvfs.join(b'store'))
     util.rename(dstrepo.spath, srcrepo.spath)
     elapsed = util.timer() - tstart
-    ui.write(
+    ui.status(
         _(
             b'store replacement complete; repository was inconsistent for '
             b'%0.1fs\n'
@@ -1098,7 +1116,7 @@
 
     # We first write the requirements file. Any new requirements will lock
     # out legacy clients.
-    ui.write(
+    ui.status(
         _(
             b'finalizing requirements file and making repository readable '
             b'again\n'
@@ -1274,9 +1292,20 @@
             ui.write((b'\n'))
         ui.write(b'\n')
 
+    def printoptimisations():
+        optimisations = [a for a in actions if a.type == optimisation]
+        optimisations.sort(key=lambda a: a.name)
+        if optimisations:
+            ui.write(_(b'optimisations: '))
+            write_labeled(
+                [a.name for a in optimisations],
+                "upgrade-repo.optimisation.performed",
+            )
+            ui.write(b'\n\n')
+
     def printupgradeactions():
         for a in actions:
-            ui.write(b'%s\n   %s\n\n' % (a.name, a.upgrademessage))
+            ui.status(b'%s\n   %s\n\n' % (a.name, a.upgrademessage))
 
     if not run:
         fromconfig = []
@@ -1291,35 +1320,35 @@
         if fromconfig or onlydefault:
 
             if fromconfig:
-                ui.write(
+                ui.status(
                     _(
                         b'repository lacks features recommended by '
                         b'current config options:\n\n'
                     )
                 )
                 for i in fromconfig:
-                    ui.write(b'%s\n   %s\n\n' % (i.name, i.description))
+                    ui.status(b'%s\n   %s\n\n' % (i.name, i.description))
 
             if onlydefault:
-                ui.write(
+                ui.status(
                     _(
                         b'repository lacks features used by the default '
                         b'config options:\n\n'
                     )
                 )
                 for i in onlydefault:
-                    ui.write(b'%s\n   %s\n\n' % (i.name, i.description))
+                    ui.status(b'%s\n   %s\n\n' % (i.name, i.description))
 
-            ui.write(b'\n')
+            ui.status(b'\n')
         else:
-            ui.write(
+            ui.status(
                 _(
                     b'(no feature deficiencies found in existing '
                     b'repository)\n'
                 )
             )
 
-        ui.write(
+        ui.status(
             _(
                 b'performing an upgrade with "--run" will make the following '
                 b'changes:\n\n'
@@ -1327,31 +1356,33 @@
         )
 
         printrequirements()
+        printoptimisations()
         printupgradeactions()
 
         unusedoptimize = [i for i in alloptimizations if i not in actions]
 
         if unusedoptimize:
-            ui.write(
+            ui.status(
                 _(
                     b'additional optimizations are available by specifying '
                     b'"--optimize <name>":\n\n'
                 )
             )
             for i in unusedoptimize:
-                ui.write(_(b'%s\n   %s\n\n') % (i.name, i.description))
+                ui.status(_(b'%s\n   %s\n\n') % (i.name, i.description))
         return
 
     # Else we're in the run=true case.
     ui.write(_(b'upgrade will perform the following actions:\n\n'))
     printrequirements()
+    printoptimisations()
     printupgradeactions()
 
     upgradeactions = [a.name for a in actions]
 
-    ui.write(_(b'beginning upgrade...\n'))
+    ui.status(_(b'beginning upgrade...\n'))
     with repo.wlock(), repo.lock():
-        ui.write(_(b'repository locked and read-only\n'))
+        ui.status(_(b'repository locked and read-only\n'))
         # Our strategy for upgrading the repository is to create a new,
         # temporary repository, write data to it, then do a swap of the
         # data. There are less heavyweight ways to do this, but it is easier
@@ -1360,7 +1391,7 @@
         tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path)
         backuppath = None
         try:
-            ui.write(
+            ui.status(
                 _(
                     b'creating temporary repository to stage migrated '
                     b'data: %s\n'
@@ -1377,15 +1408,17 @@
                     ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs
                 )
             if not (backup or backuppath is None):
-                ui.write(_(b'removing old repository content%s\n') % backuppath)
+                ui.status(
+                    _(b'removing old repository content%s\n') % backuppath
+                )
                 repo.vfs.rmtree(backuppath, forcibly=True)
                 backuppath = None
 
         finally:
-            ui.write(_(b'removing temporary repository %s\n') % tmppath)
+            ui.status(_(b'removing temporary repository %s\n') % tmppath)
             repo.vfs.rmtree(tmppath, forcibly=True)
 
-            if backuppath:
+            if backuppath and not ui.quiet:
                 ui.warn(
                     _(b'copy of old repository backed up at %s\n') % backuppath
                 )
--- a/rust/chg/Cargo.lock	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/Cargo.lock	Tue May 26 08:07:24 2020 -0700
@@ -6,9 +6,14 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "autocfg"
-version = "1.0.0"
+name = "async-trait"
+version = "0.1.30"
 source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
 
 [[package]]
 name = "bitflags"
@@ -16,20 +21,11 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "byteorder"
-version = "1.3.4"
+name = "bytes"
+version = "0.5.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "bytes"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "cc"
 version = "1.0.50"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -43,91 +39,17 @@
 name = "chg"
 version = "0.1.0"
 dependencies = [
- "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "async-trait 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-hglib 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-process 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-timer 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "cloudabi"
-version = "0.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "crossbeam-deque"
-version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "crossbeam-epoch"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-hglib 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "crossbeam-queue"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "crossbeam-queue"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.6.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "crossbeam-utils"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "fnv"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
 name = "fuchsia-zircon"
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -143,15 +65,84 @@
 
 [[package]]
 name = "futures"
-version = "0.1.29"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "hermit-abi"
-version = "0.1.10"
+name = "futures-executor"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures-task"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures-util"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -159,7 +150,7 @@
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -178,18 +169,10 @@
 
 [[package]]
 name = "libc"
-version = "0.2.68"
+version = "0.2.69"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "lock_api"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "log"
 version = "0.4.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -198,19 +181,11 @@
 ]
 
 [[package]]
-name = "maybe-uninit"
-version = "2.0.0"
+name = "memchr"
+version = "2.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "memoffset"
-version = "0.5.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "mio"
 version = "0.6.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -220,7 +195,7 @@
  "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -245,7 +220,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
  "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -265,7 +240,7 @@
 version = "0.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -275,41 +250,44 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "num_cpus"
-version = "1.12.0"
+name = "pin-project-lite"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0-alpha.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "parking_lot"
-version = "0.9.0"
+name = "quote"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "parking_lot_core"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -318,38 +296,12 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "rustc_version"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "scopeguard"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
-name = "semver"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "semver-parser"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
-[[package]]
 name = "signal-hook-registry"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "arc-swap 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -358,240 +310,85 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
-name = "smallvec"
-version = "0.6.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
 name = "socket2"
-version = "0.3.11"
+version = "0.3.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
  "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "tokio"
-version = "0.1.22"
+name = "syn"
+version = "1.0.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-current-thread 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-executor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-fs 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-sync 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-tcp 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-threadpool 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-timer 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-udp 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-uds 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-codec"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-current-thread"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-executor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "tokio-executor"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-fs"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-threadpool 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-hglib"
-version = "0.2.0"
+name = "tokio"
+version = "0.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-process 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-uds 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-io"
-version = "0.1.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-process"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-signal 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "tokio-reactor"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-executor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-sync 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-signal"
-version = "0.2.9"
+name = "tokio-hglib"
+version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-executor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-sync"
-version = "0.1.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-tcp"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "async-trait 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "tokio-threadpool"
-version = "0.1.18"
+name = "tokio-macros"
+version = "0.2.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-executor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "tokio-timer"
-version = "0.2.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-executor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "tokio-udp"
-version = "0.1.6"
+name = "tokio-util"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
-name = "tokio-uds"
-version = "0.2.6"
+name = "unicode-xid"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
-]
 
 [[package]]
 name = "winapi"
@@ -633,66 +430,50 @@
 
 [metadata]
 "checksum arc-swap 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
-"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
+"checksum async-trait 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "da71fef07bc806586090247e971229289f64c210a278ee5ae419314eb386b31d"
 "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
-"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c"
+"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
 "checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
 "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
-"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
-"checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b"
-"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
-"checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6"
-"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
-"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
 "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
 "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef"
-"checksum hermit-abi 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e"
+"checksum futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
+"checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
+"checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
+"checksum futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
+"checksum futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
+"checksum futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
+"checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
+"checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
+"checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
 "checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
 "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
 "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
-"checksum lock_api 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "79b2de95ecb4691949fea4716ca53cdbcfccb2c612e19644a8bad05edcf9f47b"
+"checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
 "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
-"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
-"checksum memoffset 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8"
+"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
 "checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
 "checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
 "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
 "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
 "checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
 "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
-"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
-"checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252"
-"checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b"
+"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
+"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
+"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63"
+"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
+"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
+"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
 "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
-"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
 "checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
 "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
-"checksum smallvec 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "f7b0758c52e15a8b5e3691eae6cc559f08eee9406e548a4477ba4e67770a82b6"
-"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
-"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
-"checksum tokio-codec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b"
-"checksum tokio-current-thread 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e"
-"checksum tokio-executor 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671"
-"checksum tokio-fs 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4"
-"checksum tokio-hglib 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a138c3cb866c8a95ceddae44634bb159eefeebcdba45aec2158f8ad6c201e6d"
-"checksum tokio-io 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674"
-"checksum tokio-process 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "382d90f43fa31caebe5d3bc6cfd854963394fff3b8cb59d5146607aaae7e7e43"
-"checksum tokio-reactor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351"
-"checksum tokio-signal 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c34c6e548f101053321cba3da7cbb87a610b85555884c41b07da2eb91aff12"
-"checksum tokio-sync 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee"
-"checksum tokio-tcp 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72"
-"checksum tokio-threadpool 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89"
-"checksum tokio-timer 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296"
-"checksum tokio-udp 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82"
-"checksum tokio-uds 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "5076db410d6fdc6523df7595447629099a1fdc47b3d9f896220780fa48faf798"
+"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
+"checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
+"checksum tokio 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "34ef16d072d2b6dc8b4a56c70f5c5ced1a37752116f8e7c1e80c659aa7cb6713"
+"checksum tokio-hglib 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8d7e2b5d44911ebf67a1044423604f5f69206c5cbbd7e911b4966e6831514bca"
+"checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
+"checksum tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
+"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
--- a/rust/chg/Cargo.toml	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/Cargo.toml	Tue May 26 08:07:24 2020 -0700
@@ -7,14 +7,16 @@
 edition = "2018"
 
 [dependencies]
-bytes = "0.4"
-futures = "0.1"
+async-trait = "0.1"
+bytes = "0.5"
+futures = "0.3"
 libc = "0.2"
 log = { version = "0.4", features = ["std"] }
-tokio = "0.1"
-tokio-hglib = "0.2"
-tokio-process = "0.2.3"
-tokio-timer = "0.2"
+tokio-hglib = "0.3"
+
+[dependencies.tokio]
+version = "0.2"
+features = ["rt-core", "io-util", "time", "process", "macros"]
 
 [build-dependencies]
 cc = "1.0"
--- a/rust/chg/src/attachio.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/src/attachio.rs	Tue May 26 08:07:24 2020 -0700
@@ -5,17 +5,15 @@
 
 //! Functions to send client-side fds over the command server channel.
 
-use futures::{try_ready, Async, Future, Poll};
 use std::io;
 use std::os::unix::io::AsRawFd;
 use tokio_hglib::codec::ChannelMessage;
-use tokio_hglib::protocol::MessageLoop;
-use tokio_hglib::{Client, Connection};
+use tokio_hglib::{Connection, Protocol};
 
 use crate::message;
 use crate::procutil;
 
-/// Future to send client-side fds over the command server channel.
+/// Sends client-side fds over the command server channel.
 ///
 /// This works as follows:
 /// 1. Client sends "attachio" request.
@@ -23,92 +21,48 @@
 /// 3. Client sends fds with 1-byte dummy payload in response.
 /// 4. Server returns the number of the fds received.
 ///
-/// If the stderr is omitted, it will be redirected to the stdout. This
-/// allows us to attach the pager stdin to both stdout and stderr, and
-/// dispose of the client-side handle once attached.
-#[must_use = "futures do nothing unless polled"]
-pub struct AttachIo<C, I, O, E>
-where
-    C: Connection,
-{
-    msg_loop: MessageLoop<C>,
-    stdin: I,
-    stdout: O,
-    stderr: Option<E>,
-}
-
-impl<C, I, O, E> AttachIo<C, I, O, E>
-where
-    C: Connection + AsRawFd,
-    I: AsRawFd,
-    O: AsRawFd,
-    E: AsRawFd,
-{
-    pub fn with_client(
-        client: Client<C>,
-        stdin: I,
-        stdout: O,
-        stderr: Option<E>,
-    ) -> AttachIo<C, I, O, E> {
-        let msg_loop = MessageLoop::start(client, b"attachio");
-        AttachIo {
-            msg_loop,
-            stdin,
-            stdout,
-            stderr,
-        }
-    }
-}
-
-impl<C, I, O, E> Future for AttachIo<C, I, O, E>
-where
-    C: Connection + AsRawFd,
-    I: AsRawFd,
-    O: AsRawFd,
-    E: AsRawFd,
-{
-    type Item = Client<C>;
-    type Error = io::Error;
-
-    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
-        loop {
-            let (client, msg) = try_ready!(self.msg_loop.poll());
-            match msg {
-                ChannelMessage::Data(b'r', data) => {
-                    let fd_cnt = message::parse_result_code(data)?;
-                    if fd_cnt == 3 {
-                        return Ok(Async::Ready(client));
-                    } else {
-                        return Err(io::Error::new(
-                            io::ErrorKind::InvalidData,
-                            "unexpected attachio result",
-                        ));
-                    }
-                }
-                ChannelMessage::Data(..) => {
-                    // just ignore data sent to uninteresting (optional) channel
-                    self.msg_loop = MessageLoop::resume(client);
-                }
-                ChannelMessage::InputRequest(1) => {
-                    // this may fail with EWOULDBLOCK in theory, but the
-                    // payload is quite small, and the send buffer should
-                    // be empty so the operation will complete immediately
-                    let sock_fd = client.as_raw_fd();
-                    let ifd = self.stdin.as_raw_fd();
-                    let ofd = self.stdout.as_raw_fd();
-                    let efd = self.stderr.as_ref().map_or(ofd, |f| f.as_raw_fd());
-                    procutil::send_raw_fds(sock_fd, &[ifd, ofd, efd])?;
-                    self.msg_loop = MessageLoop::resume(client);
-                }
-                ChannelMessage::InputRequest(..)
-                | ChannelMessage::LineRequest(..)
-                | ChannelMessage::SystemRequest(..) => {
+/// The client-side fds may be dropped once duplicated to the server.
+pub async fn attach_io(
+    proto: &mut Protocol<impl Connection + AsRawFd>,
+    stdin: &impl AsRawFd,
+    stdout: &impl AsRawFd,
+    stderr: &impl AsRawFd,
+) -> io::Result<()> {
+    proto.send_command("attachio").await?;
+    loop {
+        match proto.fetch_response().await? {
+            ChannelMessage::Data(b'r', data) => {
+                let fd_cnt = message::parse_result_code(data)?;
+                if fd_cnt == 3 {
+                    return Ok(());
+                } else {
                     return Err(io::Error::new(
                         io::ErrorKind::InvalidData,
-                        "unsupported request while attaching io",
+                        "unexpected attachio result",
                     ));
                 }
             }
+            ChannelMessage::Data(..) => {
+                // just ignore data sent to uninteresting (optional) channel
+            }
+            ChannelMessage::InputRequest(1) => {
+                // this may fail with EWOULDBLOCK in theory, but the
+                // payload is quite small, and the send buffer should
+                // be empty so the operation will complete immediately
+                let sock_fd = proto.as_raw_fd();
+                let ifd = stdin.as_raw_fd();
+                let ofd = stdout.as_raw_fd();
+                let efd = stderr.as_raw_fd();
+                procutil::send_raw_fds(sock_fd, &[ifd, ofd, efd])?;
+            }
+            ChannelMessage::InputRequest(..)
+            | ChannelMessage::LineRequest(..)
+            | ChannelMessage::SystemRequest(..) => {
+                return Err(io::Error::new(
+                    io::ErrorKind::InvalidData,
+                    "unsupported request while attaching io",
+                ));
+            }
         }
     }
 }
--- a/rust/chg/src/clientext.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/src/clientext.rs	Tue May 26 08:07:24 2020 -0700
@@ -5,55 +5,99 @@
 
 //! cHg extensions to command server client.
 
-use bytes::{BufMut, Bytes, BytesMut};
+use bytes::{BufMut, BytesMut};
 use std::ffi::OsStr;
 use std::io;
 use std::mem;
 use std::os::unix::ffi::OsStrExt;
 use std::os::unix::io::AsRawFd;
 use std::path::Path;
-use tokio_hglib::protocol::{OneShotQuery, OneShotRequest};
-use tokio_hglib::{Client, Connection};
+use tokio_hglib::UnixClient;
 
-use crate::attachio::AttachIo;
-use crate::message::{self, Instruction};
-use crate::runcommand::ChgRunCommand;
+use crate::attachio;
+use crate::message::{self, Instruction, ServerSpec};
+use crate::runcommand;
 use crate::uihandler::SystemHandler;
 
-pub trait ChgClientExt<C>
-where
-    C: Connection + AsRawFd,
-{
+/// Command-server client that also supports cHg extensions.
+pub struct ChgClient {
+    client: UnixClient,
+}
+
+impl ChgClient {
+    /// Connects to a command server listening at the specified socket path.
+    pub async fn connect(path: impl AsRef<Path>) -> io::Result<Self> {
+        let client = UnixClient::connect(path).await?;
+        Ok(ChgClient { client })
+    }
+
+    /// Server capabilities, encoding, etc.
+    pub fn server_spec(&self) -> &ServerSpec {
+        self.client.server_spec()
+    }
+
     /// Attaches the client file descriptors to the server.
-    fn attach_io<I, O, E>(self, stdin: I, stdout: O, stderr: E) -> AttachIo<C, I, O, E>
-    where
-        I: AsRawFd,
-        O: AsRawFd,
-        E: AsRawFd;
+    pub async fn attach_io(
+        &mut self,
+        stdin: &impl AsRawFd,
+        stdout: &impl AsRawFd,
+        stderr: &impl AsRawFd,
+    ) -> io::Result<()> {
+        attachio::attach_io(self.client.borrow_protocol_mut(), stdin, stdout, stderr).await
+    }
 
     /// Changes the working directory of the server.
-    fn set_current_dir(self, dir: impl AsRef<Path>) -> OneShotRequest<C>;
+    pub async fn set_current_dir(&mut self, dir: impl AsRef<Path>) -> io::Result<()> {
+        let dir_bytes = dir.as_ref().as_os_str().as_bytes().to_owned();
+        self.client
+            .borrow_protocol_mut()
+            .send_command_with_args("chdir", dir_bytes)
+            .await
+    }
 
     /// Updates the environment variables of the server.
-    fn set_env_vars_os(
-        self,
+    pub async fn set_env_vars_os(
+        &mut self,
         vars: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
-    ) -> OneShotRequest<C>;
+    ) -> io::Result<()> {
+        self.client
+            .borrow_protocol_mut()
+            .send_command_with_args("setenv", message::pack_env_vars_os(vars))
+            .await
+    }
 
     /// Changes the process title of the server.
-    fn set_process_name(self, name: impl AsRef<OsStr>) -> OneShotRequest<C>;
+    pub async fn set_process_name(&mut self, name: impl AsRef<OsStr>) -> io::Result<()> {
+        let name_bytes = name.as_ref().as_bytes().to_owned();
+        self.client
+            .borrow_protocol_mut()
+            .send_command_with_args("setprocname", name_bytes)
+            .await
+    }
 
     /// Changes the umask of the server process.
-    fn set_umask(self, mask: u32) -> OneShotRequest<C>;
+    pub async fn set_umask(&mut self, mask: u32) -> io::Result<()> {
+        let mut mask_bytes = BytesMut::with_capacity(mem::size_of_val(&mask));
+        mask_bytes.put_u32(mask);
+        self.client
+            .borrow_protocol_mut()
+            .send_command_with_args("setumask2", mask_bytes)
+            .await
+    }
 
     /// Runs the specified Mercurial command with cHg extension.
-    fn run_command_chg<H>(
-        self,
-        handler: H,
+    pub async fn run_command_chg(
+        &mut self,
+        handler: &mut impl SystemHandler,
         args: impl IntoIterator<Item = impl AsRef<OsStr>>,
-    ) -> ChgRunCommand<C, H>
-    where
-        H: SystemHandler;
+    ) -> io::Result<i32> {
+        runcommand::run_command(
+            self.client.borrow_protocol_mut(),
+            handler,
+            message::pack_args_os(args),
+        )
+        .await
+    }
 
     /// Validates if the server can run Mercurial commands with the expected
     /// configuration.
@@ -63,66 +107,15 @@
     ///
     /// Client-side environment must be sent prior to this request, by
     /// `set_current_dir()` and `set_env_vars_os()`.
-    fn validate(
-        self,
+    pub async fn validate(
+        &mut self,
         args: impl IntoIterator<Item = impl AsRef<OsStr>>,
-    ) -> OneShotQuery<C, fn(Bytes) -> io::Result<Vec<Instruction>>>;
-}
-
-impl<C> ChgClientExt<C> for Client<C>
-where
-    C: Connection + AsRawFd,
-{
-    fn attach_io<I, O, E>(self, stdin: I, stdout: O, stderr: E) -> AttachIo<C, I, O, E>
-    where
-        I: AsRawFd,
-        O: AsRawFd,
-        E: AsRawFd,
-    {
-        AttachIo::with_client(self, stdin, stdout, Some(stderr))
-    }
-
-    fn set_current_dir(self, dir: impl AsRef<Path>) -> OneShotRequest<C> {
-        OneShotRequest::start_with_args(self, b"chdir", dir.as_ref().as_os_str().as_bytes())
-    }
-
-    fn set_env_vars_os(
-        self,
-        vars: impl IntoIterator<Item = (impl AsRef<OsStr>, impl AsRef<OsStr>)>,
-    ) -> OneShotRequest<C> {
-        OneShotRequest::start_with_args(self, b"setenv", message::pack_env_vars_os(vars))
-    }
-
-    fn set_process_name(self, name: impl AsRef<OsStr>) -> OneShotRequest<C> {
-        OneShotRequest::start_with_args(self, b"setprocname", name.as_ref().as_bytes())
-    }
-
-    fn set_umask(self, mask: u32) -> OneShotRequest<C> {
-        let mut args = BytesMut::with_capacity(mem::size_of_val(&mask));
-        args.put_u32_be(mask);
-        OneShotRequest::start_with_args(self, b"setumask2", args)
-    }
-
-    fn run_command_chg<H>(
-        self,
-        handler: H,
-        args: impl IntoIterator<Item = impl AsRef<OsStr>>,
-    ) -> ChgRunCommand<C, H>
-    where
-        H: SystemHandler,
-    {
-        ChgRunCommand::with_client(self, handler, message::pack_args_os(args))
-    }
-
-    fn validate(
-        self,
-        args: impl IntoIterator<Item = impl AsRef<OsStr>>,
-    ) -> OneShotQuery<C, fn(Bytes) -> io::Result<Vec<Instruction>>> {
-        OneShotQuery::start_with_args(
-            self,
-            b"validate",
-            message::pack_args_os(args),
-            message::parse_instructions,
-        )
+    ) -> io::Result<Vec<Instruction>> {
+        let data = self
+            .client
+            .borrow_protocol_mut()
+            .query_with_args("validate", message::pack_args_os(args))
+            .await?;
+        message::parse_instructions(data)
     }
 }
--- a/rust/chg/src/lib.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/src/lib.rs	Tue May 26 08:07:24 2020 -0700
@@ -11,5 +11,5 @@
 mod runcommand;
 mod uihandler;
 
-pub use clientext::ChgClientExt;
+pub use clientext::ChgClient;
 pub use uihandler::{ChgUiHandler, SystemHandler};
--- a/rust/chg/src/locator.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/src/locator.rs	Tue May 26 08:07:24 2020 -0700
@@ -5,7 +5,6 @@
 
 //! Utility for locating command-server process.
 
-use futures::future::{self, Either, Loop};
 use log::debug;
 use std::env;
 use std::ffi::{OsStr, OsString};
@@ -14,14 +13,11 @@
 use std::os::unix::ffi::{OsStrExt, OsStringExt};
 use std::os::unix::fs::{DirBuilderExt, MetadataExt};
 use std::path::{Path, PathBuf};
-use std::process::{self, Command};
-use std::time::Duration;
-use tokio::prelude::*;
-use tokio_hglib::UnixClient;
-use tokio_process::{Child, CommandExt};
-use tokio_timer;
+use std::process::{self, Child, Command};
+use std::time::{Duration, Instant};
+use tokio::time;
 
-use crate::clientext::ChgClientExt;
+use crate::clientext::ChgClient;
 use crate::message::{Instruction, ServerSpec};
 use crate::procutil;
 
@@ -82,43 +78,33 @@
     /// Connects to the server.
     ///
     /// The server process will be spawned if not running.
-    pub fn connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
-        future::loop_fn((self, 0), |(loc, cnt)| {
-            if cnt < 10 {
-                let fut = loc
-                    .try_connect()
-                    .and_then(|(loc, client)| {
-                        client
-                            .validate(&loc.hg_early_args)
-                            .map(|(client, instructions)| (loc, client, instructions))
-                    })
-                    .and_then(move |(loc, client, instructions)| {
-                        loc.run_instructions(client, instructions, cnt)
-                    });
-                Either::A(fut)
-            } else {
-                let msg = format!(
-                    concat!(
-                        "too many redirections.\n",
-                        "Please make sure {:?} is not a wrapper which ",
-                        "changes sensitive environment variables ",
-                        "before executing hg. If you have to use a ",
-                        "wrapper, wrap chg instead of hg.",
-                    ),
-                    loc.hg_command
-                );
-                Either::B(future::err(io::Error::new(io::ErrorKind::Other, msg)))
+    pub async fn connect(&mut self) -> io::Result<ChgClient> {
+        for _cnt in 0..10 {
+            let mut client = self.try_connect().await?;
+            let instructions = client.validate(&self.hg_early_args).await?;
+            let reconnect = self.run_instructions(&instructions)?;
+            if !reconnect {
+                return Ok(client);
             }
-        })
+        }
+
+        let msg = format!(
+            concat!(
+                "too many redirections.\n",
+                "Please make sure {:?} is not a wrapper which ",
+                "changes sensitive environment variables ",
+                "before executing hg. If you have to use a ",
+                "wrapper, wrap chg instead of hg.",
+            ),
+            self.hg_command
+        );
+        Err(io::Error::new(io::ErrorKind::Other, msg))
     }
 
     /// Runs instructions received from the server.
-    fn run_instructions(
-        mut self,
-        client: UnixClient,
-        instructions: Vec<Instruction>,
-        cnt: usize,
-    ) -> io::Result<Loop<(Self, UnixClient), (Self, usize)>> {
+    ///
+    /// Returns true if the client should try connecting to the other server.
+    fn run_instructions(&mut self, instructions: &[Instruction]) -> io::Result<bool> {
         let mut reconnect = false;
         for inst in instructions {
             debug!("instruction: {:?}", inst);
@@ -126,7 +112,7 @@
                 Instruction::Exit(_) => {
                     // Just returns the current connection to run the
                     // unparsable command and report the error
-                    return Ok(Loop::Break((self, client)));
+                    return Ok(false);
                 }
                 Instruction::Reconnect => {
                     reconnect = true;
@@ -139,7 +125,7 @@
                         );
                         return Err(io::Error::new(io::ErrorKind::InvalidData, msg));
                     }
-                    self.redirect_sock_path = Some(path);
+                    self.redirect_sock_path = Some(path.to_owned());
                     reconnect = true;
                 }
                 Instruction::Unlink(path) => {
@@ -155,64 +141,44 @@
             }
         }
 
-        if reconnect {
-            Ok(Loop::Continue((self, cnt + 1)))
-        } else {
-            Ok(Loop::Break((self, client)))
-        }
+        Ok(reconnect)
     }
 
     /// Tries to connect to the existing server, or spawns new if not running.
-    fn try_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
+    async fn try_connect(&mut self) -> io::Result<ChgClient> {
         let sock_path = self
             .redirect_sock_path
             .as_ref()
             .unwrap_or(&self.base_sock_path)
             .clone();
         debug!("try connect to {}", sock_path.display());
-        UnixClient::connect(sock_path)
-            .then(|res| {
-                match res {
-                    Ok(client) => Either::A(future::ok((self, client))),
-                    Err(_) => {
-                        // Prevent us from being re-connected to the outdated
-                        // master server: We were told by the server to redirect
-                        // to redirect_sock_path, which didn't work. We do not
-                        // want to connect to the same master server again
-                        // because it would probably tell us the same thing.
-                        if self.redirect_sock_path.is_some() {
-                            fs::remove_file(&self.base_sock_path).unwrap_or(());
-                            // may race
-                        }
-                        Either::B(self.spawn_connect())
-                    }
+        let mut client = match ChgClient::connect(sock_path).await {
+            Ok(client) => client,
+            Err(_) => {
+                // Prevent us from being re-connected to the outdated
+                // master server: We were told by the server to redirect
+                // to redirect_sock_path, which didn't work. We do not
+                // want to connect to the same master server again
+                // because it would probably tell us the same thing.
+                if self.redirect_sock_path.is_some() {
+                    fs::remove_file(&self.base_sock_path).unwrap_or(());
+                    // may race
                 }
-            })
-            .and_then(|(loc, client)| {
-                check_server_capabilities(client.server_spec())?;
-                Ok((loc, client))
-            })
-            .and_then(|(loc, client)| {
-                // It's purely optional, and the server might not support this command.
-                if client.server_spec().capabilities.contains("setprocname") {
-                    let fut = client
-                        .set_process_name(format!("chg[worker/{}]", loc.process_id))
-                        .map(|client| (loc, client));
-                    Either::A(fut)
-                } else {
-                    Either::B(future::ok((loc, client)))
-                }
-            })
-            .and_then(|(loc, client)| {
-                client
-                    .set_current_dir(&loc.current_dir)
-                    .map(|client| (loc, client))
-            })
-            .and_then(|(loc, client)| {
-                client
-                    .set_env_vars_os(loc.env_vars.iter().cloned())
-                    .map(|client| (loc, client))
-            })
+                self.spawn_connect().await?
+            }
+        };
+        check_server_capabilities(client.server_spec())?;
+        // It's purely optional, and the server might not support this command.
+        if client.server_spec().capabilities.contains("setprocname") {
+            client
+                .set_process_name(format!("chg[worker/{}]", self.process_id))
+                .await?;
+        }
+        client.set_current_dir(&self.current_dir).await?;
+        client
+            .set_env_vars_os(self.env_vars.iter().cloned())
+            .await?;
+        Ok(client)
     }
 
     /// Spawns new server process and connects to it.
@@ -220,10 +186,10 @@
     /// The server will be spawned at the current working directory, then
     /// chdir to "/", so that the server will load configs from the target
     /// repository.
-    fn spawn_connect(self) -> impl Future<Item = (Self, UnixClient), Error = io::Error> {
+    async fn spawn_connect(&mut self) -> io::Result<ChgClient> {
         let sock_path = self.temp_sock_path();
         debug!("start cmdserver at {}", sock_path.display());
-        Command::new(&self.hg_command)
+        let server = Command::new(&self.hg_command)
             .arg("serve")
             .arg("--cmdserver")
             .arg("chgunix")
@@ -236,68 +202,49 @@
             .env_clear()
             .envs(self.env_vars.iter().cloned())
             .env("CHGINTERNALMARK", "")
-            .spawn_async()
-            .into_future()
-            .and_then(|server| self.connect_spawned(server, sock_path))
-            .and_then(|(loc, client, sock_path)| {
-                debug!(
-                    "rename {} to {}",
-                    sock_path.display(),
-                    loc.base_sock_path.display()
-                );
-                fs::rename(&sock_path, &loc.base_sock_path)?;
-                Ok((loc, client))
-            })
+            .spawn()?;
+        let client = self.connect_spawned(server, &sock_path).await?;
+        debug!(
+            "rename {} to {}",
+            sock_path.display(),
+            self.base_sock_path.display()
+        );
+        fs::rename(&sock_path, &self.base_sock_path)?;
+        Ok(client)
     }
 
     /// Tries to connect to the just spawned server repeatedly until timeout
     /// exceeded.
-    fn connect_spawned(
-        self,
-        server: Child,
-        sock_path: PathBuf,
-    ) -> impl Future<Item = (Self, UnixClient, PathBuf), Error = io::Error> {
+    async fn connect_spawned(
+        &mut self,
+        mut server: Child,
+        sock_path: &Path,
+    ) -> io::Result<ChgClient> {
         debug!("try connect to {} repeatedly", sock_path.display());
-        let connect = future::loop_fn(sock_path, |sock_path| {
-            UnixClient::connect(sock_path.clone()).then(|res| {
-                match res {
-                    Ok(client) => Either::A(future::ok(Loop::Break((client, sock_path)))),
-                    Err(_) => {
-                        // try again with slight delay
-                        let fut = tokio_timer::sleep(Duration::from_millis(10))
-                            .map(|()| Loop::Continue(sock_path))
-                            .map_err(|err| io::Error::new(io::ErrorKind::Other, err));
-                        Either::B(fut)
-                    }
-                }
-            })
-        });
-
         // waits for either connection established or server failed to start
-        connect
-            .select2(server)
-            .map_err(|res| res.split().0)
-            .timeout(self.timeout)
-            .map_err(|err| {
-                err.into_inner().unwrap_or_else(|| {
-                    io::Error::new(
-                        io::ErrorKind::TimedOut,
-                        "timed out while connecting to server",
-                    )
-                })
-            })
-            .and_then(|res| {
-                match res {
-                    Either::A(((client, sock_path), server)) => {
-                        server.forget(); // continue to run in background
-                        Ok((self, client, sock_path))
-                    }
-                    Either::B((st, _)) => Err(io::Error::new(
-                        io::ErrorKind::Other,
-                        format!("server exited too early: {}", st),
-                    )),
-                }
-            })
+        let start_time = Instant::now();
+        while start_time.elapsed() < self.timeout {
+            if let Ok(client) = ChgClient::connect(&sock_path).await {
+                // server handle is dropped here, but the detached process
+                // will continue running in background
+                return Ok(client);
+            }
+
+            if let Some(st) = server.try_wait()? {
+                return Err(io::Error::new(
+                    io::ErrorKind::Other,
+                    format!("server exited too early: {}", st),
+                ));
+            }
+
+            // try again with slight delay
+            time::delay_for(Duration::from_millis(10)).await;
+        }
+
+        Err(io::Error::new(
+            io::ErrorKind::TimedOut,
+            "timed out while connecting to server",
+        ))
     }
 }
 
--- a/rust/chg/src/main.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/src/main.rs	Tue May 26 08:07:24 2020 -0700
@@ -5,13 +5,12 @@
 
 use chg::locator::{self, Locator};
 use chg::procutil;
-use chg::{ChgClientExt, ChgUiHandler};
-use futures::sync::oneshot;
+use chg::ChgUiHandler;
 use std::env;
 use std::io;
+use std::io::Write;
 use std::process;
 use std::time::Instant;
-use tokio::prelude::*;
 
 struct DebugLogger {
     start: Instant,
@@ -67,31 +66,23 @@
     process::exit(code);
 }
 
-fn run(umask: u32) -> io::Result<i32> {
+#[tokio::main]
+async fn run(umask: u32) -> io::Result<i32> {
     let mut loc = Locator::prepare_from_env()?;
     loc.set_early_args(locator::collect_early_args(env::args_os().skip(1)));
-    let handler = ChgUiHandler::new();
-    let (result_tx, result_rx) = oneshot::channel();
-    let fut = loc
-        .connect()
-        .and_then(|(_, client)| client.attach_io(io::stdin(), io::stdout(), io::stderr()))
-        .and_then(move |client| client.set_umask(umask))
-        .and_then(|client| {
-            let pid = client.server_spec().process_id.unwrap();
-            let pgid = client.server_spec().process_group_id;
-            procutil::setup_signal_handler_once(pid, pgid)?;
-            Ok(client)
-        })
-        .and_then(|client| client.run_command_chg(handler, env::args_os().skip(1)))
-        .map(|(_client, _handler, code)| {
-            procutil::restore_signal_handler_once()?;
-            Ok(code)
-        })
-        .or_else(|err| Ok(Err(err))) // pass back error to caller
-        .map(|res| result_tx.send(res).unwrap());
-    tokio::run(fut);
-    result_rx.wait().unwrap_or(Err(io::Error::new(
-        io::ErrorKind::Other,
-        "no exit code set",
-    )))
+    let mut handler = ChgUiHandler::new();
+    let mut client = loc.connect().await?;
+    client
+        .attach_io(&io::stdin(), &io::stdout(), &io::stderr())
+        .await?;
+    client.set_umask(umask).await?;
+    let pid = client.server_spec().process_id.unwrap();
+    let pgid = client.server_spec().process_group_id;
+    procutil::setup_signal_handler_once(pid, pgid)?;
+    let code = client
+        .run_command_chg(&mut handler, env::args_os().skip(1))
+        .await?;
+    procutil::restore_signal_handler_once()?;
+    handler.wait_pager().await?;
+    Ok(code)
 }
--- a/rust/chg/src/runcommand.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/src/runcommand.rs	Tue May 26 08:07:24 2020 -0700
@@ -6,164 +6,56 @@
 //! Functions to run Mercurial command in cHg-aware command server.
 
 use bytes::Bytes;
-use futures::future::IntoFuture;
-use futures::{Async, Future, Poll};
 use std::io;
-use std::mem;
 use std::os::unix::io::AsRawFd;
 use tokio_hglib::codec::ChannelMessage;
-use tokio_hglib::protocol::MessageLoop;
-use tokio_hglib::{Client, Connection};
+use tokio_hglib::{Connection, Protocol};
 
-use crate::attachio::AttachIo;
+use crate::attachio;
 use crate::message::{self, CommandType};
 use crate::uihandler::SystemHandler;
 
-enum AsyncS<R, S> {
-    Ready(R),
-    NotReady(S),
-    PollAgain(S),
-}
-
-enum CommandState<C, H>
-where
-    C: Connection,
-    H: SystemHandler,
-{
-    Running(MessageLoop<C>, H),
-    SpawningPager(Client<C>, <H::SpawnPagerResult as IntoFuture>::Future),
-    AttachingPager(AttachIo<C, io::Stdin, H::PagerStdin, H::PagerStdin>, H),
-    WaitingSystem(Client<C>, <H::RunSystemResult as IntoFuture>::Future),
-    Finished,
-}
-
-type CommandPoll<C, H> = io::Result<AsyncS<(Client<C>, H, i32), CommandState<C, H>>>;
-
-/// Future resolves to `(exit_code, client)`.
-#[must_use = "futures do nothing unless polled"]
-pub struct ChgRunCommand<C, H>
-where
-    C: Connection,
-    H: SystemHandler,
-{
-    state: CommandState<C, H>,
-}
-
-impl<C, H> ChgRunCommand<C, H>
-where
-    C: Connection + AsRawFd,
-    H: SystemHandler,
-{
-    pub fn with_client(client: Client<C>, handler: H, packed_args: Bytes) -> ChgRunCommand<C, H> {
-        let msg_loop = MessageLoop::start_with_args(client, b"runcommand", packed_args);
-        ChgRunCommand {
-            state: CommandState::Running(msg_loop, handler),
-        }
-    }
-}
-
-impl<C, H> Future for ChgRunCommand<C, H>
-where
-    C: Connection + AsRawFd,
-    H: SystemHandler,
-{
-    type Item = (Client<C>, H, i32);
-    type Error = io::Error;
-
-    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
-        loop {
-            let state = mem::replace(&mut self.state, CommandState::Finished);
-            match state.poll()? {
-                AsyncS::Ready((client, handler, code)) => {
-                    return Ok(Async::Ready((client, handler, code)));
-                }
-                AsyncS::NotReady(newstate) => {
-                    self.state = newstate;
-                    return Ok(Async::NotReady);
-                }
-                AsyncS::PollAgain(newstate) => {
-                    self.state = newstate;
-                }
-            }
-        }
-    }
-}
-
-impl<C, H> CommandState<C, H>
-where
-    C: Connection + AsRawFd,
-    H: SystemHandler,
-{
-    fn poll(self) -> CommandPoll<C, H> {
-        match self {
-            CommandState::Running(mut msg_loop, handler) => {
-                if let Async::Ready((client, msg)) = msg_loop.poll()? {
-                    process_message(client, handler, msg)
-                } else {
-                    Ok(AsyncS::NotReady(CommandState::Running(msg_loop, handler)))
-                }
-            }
-            CommandState::SpawningPager(client, mut fut) => {
-                if let Async::Ready((handler, pin)) = fut.poll()? {
-                    let fut = AttachIo::with_client(client, io::stdin(), pin, None);
-                    Ok(AsyncS::PollAgain(CommandState::AttachingPager(
-                        fut, handler,
-                    )))
-                } else {
-                    Ok(AsyncS::NotReady(CommandState::SpawningPager(client, fut)))
-                }
-            }
-            CommandState::AttachingPager(mut fut, handler) => {
-                if let Async::Ready(client) = fut.poll()? {
-                    let msg_loop = MessageLoop::start(client, b""); // terminator
-                    Ok(AsyncS::PollAgain(CommandState::Running(msg_loop, handler)))
-                } else {
-                    Ok(AsyncS::NotReady(CommandState::AttachingPager(fut, handler)))
-                }
-            }
-            CommandState::WaitingSystem(client, mut fut) => {
-                if let Async::Ready((handler, code)) = fut.poll()? {
-                    let data = message::pack_result_code(code);
-                    let msg_loop = MessageLoop::resume_with_data(client, data);
-                    Ok(AsyncS::PollAgain(CommandState::Running(msg_loop, handler)))
-                } else {
-                    Ok(AsyncS::NotReady(CommandState::WaitingSystem(client, fut)))
-                }
-            }
-            CommandState::Finished => panic!("poll ChgRunCommand after it's done"),
-        }
-    }
-}
-
-fn process_message<C, H>(client: Client<C>, handler: H, msg: ChannelMessage) -> CommandPoll<C, H>
-where
-    C: Connection,
-    H: SystemHandler,
-{
-    {
-        match msg {
+/// Runs the given Mercurial command in cHg-aware command server, and
+/// fetches the result code.
+///
+/// This is a subset of tokio-hglib's `run_command()` with the additional
+/// SystemRequest support.
+pub async fn run_command(
+    proto: &mut Protocol<impl Connection + AsRawFd>,
+    handler: &mut impl SystemHandler,
+    packed_args: impl Into<Bytes>,
+) -> io::Result<i32> {
+    proto
+        .send_command_with_args("runcommand", packed_args)
+        .await?;
+    loop {
+        match proto.fetch_response().await? {
             ChannelMessage::Data(b'r', data) => {
-                let code = message::parse_result_code(data)?;
-                Ok(AsyncS::Ready((client, handler, code)))
+                return message::parse_result_code(data);
             }
             ChannelMessage::Data(..) => {
                 // just ignores data sent to optional channel
-                let msg_loop = MessageLoop::resume(client);
-                Ok(AsyncS::PollAgain(CommandState::Running(msg_loop, handler)))
             }
-            ChannelMessage::InputRequest(..) | ChannelMessage::LineRequest(..) => Err(
-                io::Error::new(io::ErrorKind::InvalidData, "unsupported request"),
-            ),
+            ChannelMessage::InputRequest(..) | ChannelMessage::LineRequest(..) => {
+                return Err(io::Error::new(
+                    io::ErrorKind::InvalidData,
+                    "unsupported request",
+                ));
+            }
             ChannelMessage::SystemRequest(data) => {
                 let (cmd_type, cmd_spec) = message::parse_command_spec(data)?;
                 match cmd_type {
                     CommandType::Pager => {
-                        let fut = handler.spawn_pager(cmd_spec).into_future();
-                        Ok(AsyncS::PollAgain(CommandState::SpawningPager(client, fut)))
+                        // server spins new command loop while pager request is
+                        // in progress, which can be terminated by "" command.
+                        let pin = handler.spawn_pager(&cmd_spec).await?;
+                        attachio::attach_io(proto, &io::stdin(), &pin, &pin).await?;
+                        proto.send_command("").await?; // terminator
                     }
                     CommandType::System => {
-                        let fut = handler.run_system(cmd_spec).into_future();
-                        Ok(AsyncS::PollAgain(CommandState::WaitingSystem(client, fut)))
+                        let code = handler.run_system(&cmd_spec).await?;
+                        let data = message::pack_result_code(code);
+                        proto.send_data(data).await?;
                     }
                 }
             }
--- a/rust/chg/src/uihandler.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/chg/src/uihandler.rs	Tue May 26 08:07:24 2020 -0700
@@ -3,76 +3,75 @@
 // This software may be used and distributed according to the terms of the
 // GNU General Public License version 2 or any later version.
 
-use futures::future::IntoFuture;
-use futures::Future;
+use async_trait::async_trait;
 use std::io;
 use std::os::unix::io::AsRawFd;
 use std::os::unix::process::ExitStatusExt;
-use std::process::{Command, Stdio};
+use std::process::Stdio;
 use tokio;
-use tokio_process::{ChildStdin, CommandExt};
+use tokio::process::{Child, ChildStdin, Command};
 
 use crate::message::CommandSpec;
 use crate::procutil;
 
 /// Callback to process shell command requests received from server.
-pub trait SystemHandler: Sized {
+#[async_trait]
+pub trait SystemHandler {
     type PagerStdin: AsRawFd;
-    type SpawnPagerResult: IntoFuture<Item = (Self, Self::PagerStdin), Error = io::Error>;
-    type RunSystemResult: IntoFuture<Item = (Self, i32), Error = io::Error>;
 
     /// Handles pager command request.
     ///
     /// Returns the pipe to be attached to the server if the pager is spawned.
-    fn spawn_pager(self, spec: CommandSpec) -> Self::SpawnPagerResult;
+    async fn spawn_pager(&mut self, spec: &CommandSpec) -> io::Result<Self::PagerStdin>;
 
     /// Handles system command request.
     ///
     /// Returns command exit code (positive) or signal number (negative).
-    fn run_system(self, spec: CommandSpec) -> Self::RunSystemResult;
+    async fn run_system(&mut self, spec: &CommandSpec) -> io::Result<i32>;
 }
 
 /// Default cHg implementation to process requests received from server.
-pub struct ChgUiHandler {}
+pub struct ChgUiHandler {
+    pager: Option<Child>,
+}
 
 impl ChgUiHandler {
     pub fn new() -> ChgUiHandler {
-        ChgUiHandler {}
+        ChgUiHandler { pager: None }
+    }
+
+    /// Waits until the pager process exits.
+    pub async fn wait_pager(&mut self) -> io::Result<()> {
+        if let Some(p) = self.pager.take() {
+            p.await?;
+        }
+        Ok(())
     }
 }
 
+#[async_trait]
 impl SystemHandler for ChgUiHandler {
     type PagerStdin = ChildStdin;
-    type SpawnPagerResult = io::Result<(Self, Self::PagerStdin)>;
-    type RunSystemResult = Box<dyn Future<Item = (Self, i32), Error = io::Error> + Send>;
 
-    fn spawn_pager(self, spec: CommandSpec) -> Self::SpawnPagerResult {
-        let mut pager = new_shell_command(&spec)
-            .stdin(Stdio::piped())
-            .spawn_async()?;
-        let pin = pager.stdin().take().unwrap();
+    async fn spawn_pager(&mut self, spec: &CommandSpec) -> io::Result<Self::PagerStdin> {
+        let mut pager = new_shell_command(&spec).stdin(Stdio::piped()).spawn()?;
+        let pin = pager.stdin.take().unwrap();
         procutil::set_blocking_fd(pin.as_raw_fd())?;
         // TODO: if pager exits, notify the server with SIGPIPE immediately.
         // otherwise the server won't get SIGPIPE if it does not write
         // anything. (issue5278)
         // kill(peerpid, SIGPIPE);
-        tokio::spawn(pager.map(|_| ()).map_err(|_| ())); // just ignore errors
-        Ok((self, pin))
+        self.pager = Some(pager);
+        Ok(pin)
     }
 
-    fn run_system(self, spec: CommandSpec) -> Self::RunSystemResult {
-        let fut = new_shell_command(&spec)
-            .spawn_async()
-            .into_future()
-            .flatten()
-            .map(|status| {
-                let code = status
-                    .code()
-                    .or_else(|| status.signal().map(|n| -n))
-                    .expect("either exit code or signal should be set");
-                (self, code)
-            });
-        Box::new(fut)
+    async fn run_system(&mut self, spec: &CommandSpec) -> io::Result<i32> {
+        let status = new_shell_command(&spec).spawn()?.await?;
+        let code = status
+            .code()
+            .or_else(|| status.signal().map(|n| -n))
+            .expect("either exit code or signal should be set");
+        Ok(code)
     }
 }
 
--- a/rust/hg-core/src/dirstate/status.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/hg-core/src/dirstate/status.rs	Tue May 26 08:07:24 2020 -0700
@@ -221,6 +221,7 @@
     dmap: &'a DirstateMap,
     root_dir: impl AsRef<Path> + Sync + Send + 'a,
     options: StatusOptions,
+    traversed_sender: crossbeam::Sender<HgPathBuf>,
 ) -> impl ParallelIterator<Item = IoResult<(&'a HgPath, Dispatch)>> {
     files
         .unwrap_or(&DEFAULT_WORK)
@@ -255,6 +256,13 @@
                         Some(Ok((normalized, Dispatch::Unknown)))
                     } else {
                         if file_type.is_dir() {
+                            if options.collect_traversed_dirs {
+                                // The receiver always outlives the sender,
+                                // so unwrap.
+                                traversed_sender
+                                    .send(normalized.to_owned())
+                                    .unwrap()
+                            }
                             Some(Ok((
                                 normalized,
                                 Dispatch::Directory {
@@ -302,6 +310,9 @@
     pub list_clean: bool,
     pub list_unknown: bool,
     pub list_ignored: bool,
+    /// Whether to collect traversed dirs for applying a callback later.
+    /// Used by `hg purge` for example.
+    pub collect_traversed_dirs: bool,
 }
 
 /// Dispatch a single entry (file, folder, symlink...) found during `traverse`.
@@ -319,6 +330,7 @@
     options: StatusOptions,
     filename: HgPathBuf,
     dir_entry: DirEntry,
+    traversed_sender: crossbeam::Sender<HgPathBuf>,
 ) -> IoResult<()> {
     let file_type = dir_entry.file_type()?;
     let entry_option = dmap.get(&filename);
@@ -341,6 +353,7 @@
             options,
             entry_option,
             filename,
+            traversed_sender,
         );
     } else if file_type.is_file() || file_type.is_symlink() {
         if let Some(entry) = entry_option {
@@ -371,9 +384,11 @@
                         .unwrap();
                 }
             } else {
-                files_sender
-                    .send(Ok((filename.to_owned(), Dispatch::Unknown)))
-                    .unwrap();
+                if options.list_unknown {
+                    files_sender
+                        .send(Ok((filename.to_owned(), Dispatch::Unknown)))
+                        .unwrap();
+                }
             }
         } else if ignore_fn(&filename) && options.list_ignored {
             files_sender
@@ -405,6 +420,7 @@
     options: StatusOptions,
     entry_option: Option<&'a DirstateEntry>,
     directory: HgPathBuf,
+    traversed_sender: crossbeam::Sender<HgPathBuf>,
 ) {
     scope.spawn(move |_| {
         // Nested `if` until `rust-lang/rust#53668` is stable
@@ -431,6 +447,7 @@
                 ignore_fn,
                 dir_ignore_fn,
                 options,
+                traversed_sender,
             )
             .unwrap_or_else(|e| files_sender.send(Err(e)).unwrap())
         }
@@ -449,9 +466,15 @@
     ignore_fn: &IgnoreFnType,
     dir_ignore_fn: &IgnoreFnType,
     options: StatusOptions,
+    traversed_sender: crossbeam::Sender<HgPathBuf>,
 ) -> IoResult<()> {
     let directory = directory.as_ref();
 
+    if options.collect_traversed_dirs {
+        // The receiver always outlives the sender, so unwrap.
+        traversed_sender.send(directory.to_owned()).unwrap()
+    }
+
     let visit_entries = match matcher.visit_children_set(directory) {
         VisitChildrenSet::Empty => return Ok(()),
         VisitChildrenSet::This | VisitChildrenSet::Recursive => None,
@@ -508,6 +531,7 @@
                     options,
                     filename,
                     dir_entry,
+                    traversed_sender.clone(),
                 )?;
             }
         }
@@ -531,6 +555,7 @@
     dir_ignore_fn: &IgnoreFnType,
     options: StatusOptions,
     results: &mut Vec<(Cow<'a, HgPath>, Dispatch)>,
+    traversed_sender: crossbeam::Sender<HgPathBuf>,
 ) -> IoResult<()> {
     let root_dir = root_dir.as_ref();
 
@@ -548,6 +573,7 @@
         &ignore_fn,
         &dir_ignore_fn,
         options,
+        traversed_sender,
     )?;
 
     // Disconnect the channel so the receiver stops waiting
@@ -638,11 +664,14 @@
     pub ignored: Vec<Cow<'a, HgPath>>,
     pub unknown: Vec<Cow<'a, HgPath>>,
     pub bad: Vec<(Cow<'a, HgPath>, BadMatch)>,
+    /// Only filled if `collect_traversed_dirs` is `true`
+    pub traversed: Vec<HgPathBuf>,
 }
 
 #[timed]
 fn build_response<'a>(
     results: impl IntoIterator<Item = (Cow<'a, HgPath>, Dispatch)>,
+    traversed: Vec<HgPathBuf>,
 ) -> (Vec<Cow<'a, HgPath>>, DirstateStatus<'a>) {
     let mut lookup = vec![];
     let mut modified = vec![];
@@ -681,6 +710,7 @@
             ignored,
             unknown,
             bad,
+            traversed,
         },
     )
 }
@@ -817,7 +847,7 @@
     Vec<PatternFileWarning>,
 )> {
     // Needs to outlive `dir_ignore_fn` since it's captured.
-    let mut ignore_fn: IgnoreFnType;
+    let ignore_fn: IgnoreFnType;
 
     // Only involve real ignore mechanism if we're listing unknowns or ignored.
     let (dir_ignore_fn, warnings): (IgnoreFnType, _) = if options.list_ignored
@@ -847,8 +877,17 @@
 
     let files = matcher.file_set();
 
+    // `crossbeam::Sender` is `Send`, while `mpsc::Sender` is not.
+    let (traversed_sender, traversed_recv) = crossbeam::channel::unbounded();
+
     // Step 1: check the files explicitly mentioned by the user
-    let explicit = walk_explicit(files, &dmap, root_dir, options);
+    let explicit = walk_explicit(
+        files,
+        &dmap,
+        root_dir,
+        options,
+        traversed_sender.clone(),
+    );
 
     // Collect results into a `Vec` because we do very few lookups in most
     // cases.
@@ -886,6 +925,7 @@
                             &dir_ignore_fn,
                             options,
                             &mut results,
+                            traversed_sender.clone(),
                         )?;
                     }
                 }
@@ -909,5 +949,9 @@
         }
     }
 
-    Ok((build_response(results), warnings))
+    // Close the channel
+    drop(traversed_sender);
+    let traversed_dirs = traversed_recv.into_iter().collect();
+
+    Ok((build_response(results, traversed_dirs), warnings))
 }
--- a/rust/hg-core/src/filepatterns.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/hg-core/src/filepatterns.rs	Tue May 26 08:07:24 2020 -0700
@@ -176,14 +176,15 @@
         return vec![];
     }
     match syntax {
-        // The `regex` crate adds `.*` to the start and end of expressions
-        // if there are no anchors, so add them.
-        PatternSyntax::Regexp => [b"^", &pattern[..], b"$"].concat(),
+        PatternSyntax::Regexp => pattern.to_owned(),
         PatternSyntax::RelRegexp => {
             // The `regex` crate accepts `**` while `re2` and Python's `re`
             // do not. Checking for `*` correctly triggers the same error all
             // engines.
-            if pattern[0] == b'^' || pattern[0] == b'*' {
+            if pattern[0] == b'^'
+                || pattern[0] == b'*'
+                || pattern.starts_with(b".*")
+            {
                 return pattern.to_owned();
             }
             [&b".*"[..], pattern].concat()
@@ -196,15 +197,14 @@
         }
         PatternSyntax::RootFiles => {
             let mut res = if pattern == b"." {
-                vec![b'^']
+                vec![]
             } else {
                 // Pattern is a directory name.
-                [b"^", escape_pattern(pattern).as_slice(), b"/"].concat()
+                [escape_pattern(pattern).as_slice(), b"/"].concat()
             };
 
             // Anything after the pattern must be a non-directory.
             res.extend(b"[^/]+$");
-            res.push(b'$');
             res
         }
         PatternSyntax::RelGlob => {
@@ -216,7 +216,7 @@
             }
         }
         PatternSyntax::Glob | PatternSyntax::RootGlob => {
-            [b"^", glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
+            [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
         }
         PatternSyntax::Include | PatternSyntax::SubInclude => unreachable!(),
     }
@@ -271,7 +271,7 @@
 /// that don't need to be transformed into a regex.
 pub fn build_single_regex(
     entry: &IgnorePattern,
-) -> Result<Vec<u8>, PatternError> {
+) -> Result<Option<Vec<u8>>, PatternError> {
     let IgnorePattern {
         pattern, syntax, ..
     } = entry;
@@ -288,16 +288,11 @@
     if *syntax == PatternSyntax::RootGlob
         && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b))
     {
-        // The `regex` crate adds `.*` to the start and end of expressions
-        // if there are no anchors, so add the start anchor.
-        let mut escaped = vec![b'^'];
-        escaped.extend(escape_pattern(&pattern));
-        escaped.extend(GLOB_SUFFIX);
-        Ok(escaped)
+        Ok(None)
     } else {
         let mut entry = entry.clone();
         entry.pattern = pattern;
-        Ok(_build_single_regex(&entry))
+        Ok(Some(_build_single_regex(&entry)))
     }
 }
 
@@ -628,7 +623,16 @@
                 Path::new("")
             ))
             .unwrap(),
-            br"(?:.*/)?rust/target(?:/|$)".to_vec(),
+            Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
+        );
+        assert_eq!(
+            build_single_regex(&IgnorePattern::new(
+                PatternSyntax::Regexp,
+                br"rust/target/\d+",
+                Path::new("")
+            ))
+            .unwrap(),
+            Some(br"rust/target/\d+".to_vec()),
         );
     }
 
@@ -641,7 +645,7 @@
                 Path::new("")
             ))
             .unwrap(),
-            br"^\.(?:/|$)".to_vec(),
+            None,
         );
         assert_eq!(
             build_single_regex(&IgnorePattern::new(
@@ -650,7 +654,7 @@
                 Path::new("")
             ))
             .unwrap(),
-            br"^whatever(?:/|$)".to_vec(),
+            None,
         );
         assert_eq!(
             build_single_regex(&IgnorePattern::new(
@@ -659,7 +663,7 @@
                 Path::new("")
             ))
             .unwrap(),
-            br"^[^/]*\.o(?:/|$)".to_vec(),
+            Some(br"[^/]*\.o(?:/|$)".to_vec()),
         );
     }
 }
--- a/rust/hg-core/src/matchers.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/hg-core/src/matchers.rs	Tue May 26 08:07:24 2020 -0700
@@ -24,6 +24,7 @@
     PatternSyntax,
 };
 
+use crate::filepatterns::normalize_path_bytes;
 use std::borrow::ToOwned;
 use std::collections::HashSet;
 use std::fmt::{Display, Error, Formatter};
@@ -31,6 +32,8 @@
 use std::ops::Deref;
 use std::path::{Path, PathBuf};
 
+use micro_timer::timed;
+
 #[derive(Debug, PartialEq)]
 pub enum VisitChildrenSet<'a> {
     /// Don't visit anything
@@ -322,6 +325,7 @@
 ///
 /// This can fail when the pattern is invalid or not supported by the
 /// underlying engine `Re2`, for instance anything with back-references.
+#[timed]
 fn re_matcher(
     pattern: &[u8],
 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
@@ -337,12 +341,15 @@
 /// This can fail when the pattern is invalid or not supported by the
 /// underlying engine (the `regex` crate), for instance anything with
 /// back-references.
+#[timed]
 fn re_matcher(
     pattern: &[u8],
 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
     use std::io::Write;
 
-    let mut escaped_bytes = vec![];
+    // The `regex` crate adds `.*` to the start and end of expressions if there
+    // are no anchors, so add the start anchor.
+    let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
     for byte in pattern {
         if *byte > 127 {
             write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
@@ -350,6 +357,7 @@
             escaped_bytes.push(*byte);
         }
     }
+    escaped_bytes.push(b')');
 
     // Avoid the cost of UTF8 checking
     //
@@ -373,15 +381,32 @@
 fn build_regex_match<'a>(
     ignore_patterns: &'a [&'a IgnorePattern],
 ) -> PatternResult<(Vec<u8>, Box<dyn Fn(&HgPath) -> bool + Sync>)> {
-    let regexps: Result<Vec<_>, PatternError> = ignore_patterns
-        .into_iter()
-        .map(|k| build_single_regex(*k))
-        .collect();
-    let regexps = regexps?;
+    let mut regexps = vec![];
+    let mut exact_set = HashSet::new();
+
+    for pattern in ignore_patterns {
+        if let Some(re) = build_single_regex(pattern)? {
+            regexps.push(re);
+        } else {
+            let exact = normalize_path_bytes(&pattern.pattern);
+            exact_set.insert(HgPathBuf::from_bytes(&exact));
+        }
+    }
+
     let full_regex = regexps.join(&b'|');
 
-    let matcher = re_matcher(&full_regex)?;
-    let func = Box::new(move |filename: &HgPath| matcher(filename));
+    // An empty pattern would cause the regex engine to incorrectly match the
+    // (empty) root directory
+    let func = if !(regexps.is_empty()) {
+        let matcher = re_matcher(&full_regex)?;
+        let func = move |filename: &HgPath| {
+            exact_set.contains(filename) || matcher(filename)
+        };
+        Box::new(func) as Box<dyn Fn(&HgPath) -> bool + Sync>
+    } else {
+        let func = move |filename: &HgPath| exact_set.contains(filename);
+        Box::new(func) as Box<dyn Fn(&HgPath) -> bool + Sync>
+    };
 
     Ok((full_regex, func))
 }
@@ -652,6 +677,12 @@
 
 impl<'a> Display for IncludeMatcher<'a> {
     fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
+        // XXX What about exact matches?
+        // I'm not sure it's worth it to clone the HashSet and keep it
+        // around just in case someone wants to display the matcher, plus
+        // it's going to be unreadable after a few entries, but we need to
+        // inform in this display that exact matches are being used and are
+        // (on purpose) missing from the `includes`.
         write!(
             f,
             "IncludeMatcher(includes='{}')",
--- a/rust/hg-cpython/src/dirstate.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/hg-cpython/src/dirstate.rs	Tue May 26 08:07:24 2020 -0700
@@ -133,7 +133,8 @@
                 last_normal_time: i64,
                 list_clean: bool,
                 list_ignored: bool,
-                list_unknown: bool
+                list_unknown: bool,
+                collect_traversed_dirs: bool
             )
         ),
     )?;
--- a/rust/hg-cpython/src/dirstate/status.rs	Fri May 15 00:53:37 2020 +0200
+++ b/rust/hg-cpython/src/dirstate/status.rs	Tue May 26 08:07:24 2020 -0700
@@ -104,6 +104,7 @@
     list_clean: bool,
     list_ignored: bool,
     list_unknown: bool,
+    collect_traversed_dirs: bool,
 ) -> PyResult<PyTuple> {
     let bytes = root_dir.extract::<PyBytes>(py)?;
     let root_dir = get_path_from_bytes(bytes.data(py));
@@ -134,6 +135,7 @@
                     list_clean,
                     list_ignored,
                     list_unknown,
+                    collect_traversed_dirs,
                 },
             )
             .map_err(|e| handle_fallback(py, e))?;
@@ -170,6 +172,7 @@
                     list_clean,
                     list_ignored,
                     list_unknown,
+                    collect_traversed_dirs,
                 },
             )
             .map_err(|e| handle_fallback(py, e))?;
@@ -224,6 +227,7 @@
                     list_clean,
                     list_ignored,
                     list_unknown,
+                    collect_traversed_dirs,
                 },
             )
             .map_err(|e| handle_fallback(py, e))?;
@@ -256,6 +260,7 @@
     let unknown = collect_pybytes_list(py, status_res.unknown.as_ref());
     let lookup = collect_pybytes_list(py, lookup.as_ref());
     let bad = collect_bad_matches(py, status_res.bad.as_ref())?;
+    let traversed = collect_pybytes_list(py, status_res.traversed.as_ref());
     let py_warnings = PyList::new(py, &[]);
     for warning in warnings.iter() {
         // We use duck-typing on the Python side for dispatch, good enough for
@@ -292,6 +297,7 @@
             unknown.into_object(),
             py_warnings.into_object(),
             bad.into_object(),
+            traversed.into_object(),
         ][..],
     ))
 }
--- a/rust/hgcli/pyoxidizer.bzl	Fri May 15 00:53:37 2020 +0200
+++ b/rust/hgcli/pyoxidizer.bzl	Tue May 26 08:07:24 2020 -0700
@@ -3,19 +3,16 @@
 # Code to run in Python interpreter.
 RUN_CODE = "import hgdemandimport; hgdemandimport.enable(); from mercurial import dispatch; dispatch.run()"
 
-
 set_build_path(ROOT + "/build/pyoxidizer")
 
-
 def make_distribution():
     return default_python_distribution()
 
-
 def make_distribution_windows():
-    return default_python_distribution(flavor="standalone_dynamic")
-
+    return default_python_distribution(flavor = "standalone_dynamic")
 
 def make_exe(dist):
+    """Builds a Rust-wrapped Mercurial binary."""
     config = PythonInterpreterConfig(
         raw_allocator = "system",
         run_eval = RUN_CODE,
@@ -58,23 +55,20 @@
     # On Windows, we install extra packages for convenience.
     if "windows" in BUILD_TARGET_TRIPLE:
         exe.add_python_resources(
-            dist.pip_install(["-r", ROOT + "/contrib/packaging/requirements_win32.txt"])
+            dist.pip_install(["-r", ROOT + "/contrib/packaging/requirements_win32.txt"]),
         )
 
     return exe
 
-
 def make_manifest(dist, exe):
     m = FileManifest()
     m.add_python_resource(".", exe)
 
     return m
 
-
 def make_embedded_resources(exe):
     return exe.to_embedded_resources()
 
-
 register_target("distribution_posix", make_distribution)
 register_target("distribution_windows", make_distribution_windows)
 
--- a/setup.py	Fri May 15 00:53:37 2020 +0200
+++ b/setup.py	Tue May 26 08:07:24 2020 -0700
@@ -1396,7 +1396,7 @@
 
             env['HOME'] = pwd.getpwuid(os.getuid()).pw_dir
 
-        cargocmd = ['cargo', 'rustc', '-vv', '--release']
+        cargocmd = ['cargo', 'rustc', '--release']
 
         feature_flags = []
 
--- a/tests/test-check-rust-format.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-check-rust-format.t	Tue May 26 08:07:24 2020 -0700
@@ -5,5 +5,5 @@
   $ cd "$TESTDIR"/..
   $ RUSTFMT=$(rustup which --toolchain nightly rustfmt)
   $ for f in `testrepohg files 'glob:**/*.rs'` ; do
-  >   $RUSTFMT --check --unstable-features --color=never $f
+  >   $RUSTFMT --check --edition=2018 --unstable-features --color=never $f
   > done
--- a/tests/test-copies-chain-merge.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-copies-chain-merge.t	Tue May 26 08:07:24 2020 -0700
@@ -1,3 +1,5 @@
+#testcases filelog compatibility sidedata
+
 =====================================================
 Test Copy tracing for chain of copies involving merge
 =====================================================
@@ -6,6 +8,7 @@
 are involved. It cheks we do not have unwanted update of behavior and that the
 different options to retrieve copies behave correctly.
 
+
 Setup
 =====
 
@@ -18,6 +21,22 @@
   > logtemplate={rev} {desc}\n
   > EOF
 
+#if compatibility
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > copies.read-from = compatibility
+  > EOF
+#endif
+
+#if sidedata
+  $ cat >> $HGRCPATH << EOF
+  > [format]
+  > exp-use-side-data = yes
+  > exp-use-copies-side-data-changeset = yes
+  > EOF
+#endif
+
+
   $ hg init repo-chain
   $ cd repo-chain
 
@@ -453,17 +472,26 @@
        0       4 0dd616bc7ab1 000000000000 000000000000
        1      10 6da5a2eecb9c 000000000000 000000000000
        2      19 eb806e34ef6b 0dd616bc7ab1 6da5a2eecb9c
+
+# Here the filelog based implementation is not looking at the rename
+# information (because the file exist on both side). However the changelog
+# based on works fine. We have different output.
+
   $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mAEm-0")'
   M f
+    b (no-filelog !)
   R b
   $ hg status --copies --rev 'desc("a-2")' --rev 'desc("mEAm-0")'
   M f
+    b (no-filelog !)
   R b
   $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mAEm-0")'
   M f
+    d (no-filelog !)
   R d
   $ hg status --copies --rev 'desc("e-2")' --rev 'desc("mEAm-0")'
   M f
+    d (no-filelog !)
   R d
   $ hg status --copies --rev 'desc("i-2")' --rev 'desc("a-2")'
   A f
@@ -473,6 +501,18 @@
   A f
     b
   R b
+
+# From here, we run status against revision where both source file exists.
+#
+# The filelog based implementation picks an arbitrary side based on revision
+# numbers. So the same side "wins" whatever the parents order is. This is
+# sub-optimal because depending on revision numbers means the result can be
+# different from one repository to the next.
+#
+# The changeset based algorithm use the parent order to break tie on conflicting
+# information and will have a different order depending on who is p1 and p2.
+# That order is stable accross repositories. (data from p1 prevails)
+
   $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mAEm-0")'
   A f
     d
@@ -480,7 +520,8 @@
   R d
   $ hg status --copies --rev 'desc("i-2")' --rev 'desc("mEAm-0")'
   A f
-    d
+    d (filelog !)
+    b (no-filelog !)
   R b
   R d
   $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mAEm-0")'
@@ -490,7 +531,8 @@
   R b
   $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mEAm-0")'
   A f
-    a
+    a (filelog !)
+    b (no-filelog !)
   R a
   R b
 
@@ -563,21 +605,25 @@
   R h
   $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mBFm-0")'
   M d
+    h (no-filelog !)
   R h
   $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mBFm-0")'
   M b
   $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mBFm-0")'
   M b
   M d
+    i (no-filelog !)
   R i
   $ hg status --copies --rev 'desc("b-1")' --rev 'desc("mFBm-0")'
   M d
+    h (no-filelog !)
   R h
   $ hg status --copies --rev 'desc("f-2")' --rev 'desc("mFBm-0")'
   M b
   $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFBm-0")'
   M b
   M d
+    i (no-filelog !)
   R i
 
 The following graphlog is wrong, the "a -> c -> d" chain was overwritten and should not appear.
@@ -645,9 +691,15 @@
   |
   o  0 i-0 initial commit: a b h
   
+One side of the merge have a long history with rename. The other side of the
+merge point to a new file with a smaller history. Each side is "valid".
+
+(and again the filelog based algorithm only explore one, with a pick based on
+revision numbers)
+
   $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mDGm-0")'
   A d
-    a
+    a (filelog !)
   R a
   $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGDm-0")'
   A d
@@ -740,7 +792,8 @@
   
   $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mFGm-0")'
   A d
-    a
+    h (no-filelog !)
+    a (filelog !)
   R a
   R h
   $ hg status --copies --rev 'desc("i-0")' --rev 'desc("mGFm-0")'
@@ -754,15 +807,19 @@
   M d
   $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mFGm-0")'
   M d
+    i (no-filelog !)
   R i
   $ hg status --copies --rev 'desc("f-1")' --rev 'desc("mGFm-0")'
   M d
+    i (no-filelog !)
   R i
   $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mFGm-0")'
   M d
+    h (no-filelog !)
   R h
   $ hg status --copies --rev 'desc("g-1")' --rev 'desc("mGFm-0")'
   M d
+    h (no-filelog !)
   R h
 
   $ hg log -Gfr 'desc("mFGm-0")' d
--- a/tests/test-copies-in-changeset.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-copies-in-changeset.t	Tue May 26 08:07:24 2020 -0700
@@ -33,28 +33,30 @@
   $ cd repo
 #if sidedata
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:          yes    yes      no
-  copies-sdc:        yes    yes      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:           yes    yes      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:         yes    yes      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
 #else
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
 #endif
   $ echo a > a
   $ hg add a
@@ -424,16 +426,17 @@
 downgrading (keeping some sidedata)
 
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:          yes    yes      no
-  copies-sdc:        yes    yes      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:           yes    yes      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:         yes    yes      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugsidedata -c -- 0
   1 sidedata entries
    entry-0012 size 1
@@ -448,16 +451,17 @@
   > EOF
   $ hg debugupgraderepo --run --quiet --no-backup > /dev/null
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:          yes    yes      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:           yes    yes      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugsidedata -c -- 0
   $ hg debugsidedata -c -- 1
   $ hg debugsidedata -m -- 0
@@ -470,16 +474,17 @@
   > EOF
   $ hg debugupgraderepo --run --quiet --no-backup > /dev/null
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:          yes    yes      no
-  copies-sdc:        yes    yes      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:           yes    yes      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:         yes    yes      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugsidedata -c -- 0
   1 sidedata entries
    entry-0012 size 1
--- a/tests/test-grep.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-grep.t	Tue May 26 08:07:24 2020 -0700
@@ -645,18 +645,24 @@
   $ hg init sng
   $ cd sng
   $ echo "unmod" >> um
-  $ hg ci -A -m "adds unmod to um"
-  adding um
+  $ echo old > old
+  $ hg ci -q -A -m "adds unmod to um"
   $ echo "something else" >> new
   $ hg ci -A -m "second commit"
   adding new
   $ hg grep -r "." "unmod"
   um:1:unmod
 
-Working directory is searched by default
+Existing tracked files in the working directory are searched by default
 
   $ echo modified >> new
-  $ hg grep mod
+  $ echo 'added' > added; hg add added
+  $ echo 'added, missing' > added-missing; hg add added-missing; rm added-missing
+  $ echo 'untracked' > untracked
+  $ hg rm old
+  $ hg grep ''
+  added:added
+  new:something else
   new:modified
   um:unmod
 
@@ -670,17 +676,6 @@
 
   $ cd ..
 
-Fix_Wdir(): test that passing wdir() t -r flag does greps on the
-files modified in the working directory
-
-  $ cd a
-  $ echo "abracadara" >> a
-  $ hg add a
-  $ hg grep -r "wdir()" "abra"
-  a:2147483647:abracadara
-
-  $ cd ..
-
 Change Default of grep by ui.tweakdefaults, that is, the files not in current
 working directory should not be grepp-ed on
 
--- a/tests/test-histedit-edit.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-histedit-edit.t	Tue May 26 08:07:24 2020 -0700
@@ -373,6 +373,7 @@
   transaction abort!
   rollback completed
   note: commit message saved in .hg/last-message.txt
+  note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
   abort: pretxncommit.unexpectedabort hook exited with status 1
   [255]
   $ cat .hg/last-message.txt
@@ -397,6 +398,7 @@
   transaction abort!
   rollback completed
   note: commit message saved in .hg/last-message.txt
+  note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
   abort: pretxncommit.unexpectedabort hook exited with status 1
   [255]
 
--- a/tests/test-hook.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-hook.t	Tue May 26 08:07:24 2020 -0700
@@ -443,7 +443,7 @@
   HG_PENDING=$TESTTMP/a
   
   transaction abort!
-  txnabort Python hook: txnid,txnname
+  txnabort Python hook: changes,txnid,txnname
   txnabort hook: HG_HOOKNAME=txnabort.1
   HG_HOOKTYPE=txnabort
   HG_TXNID=TXN:$ID$
--- a/tests/test-lfs-serve.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-lfs-serve.t	Tue May 26 08:07:24 2020 -0700
@@ -133,30 +133,6 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
-  beginning upgrade...
-  repository locked and read-only
-  creating temporary repository to stage migrated data: * (glob)
-  (it is safe to interrupt this process any time before data migration completes)
-  migrating 3 total revisions (1 in filelogs, 1 in manifests, 1 in changelog)
-  migrating 324 bytes in store; 129 bytes tracked data
-  migrating 1 filelogs containing 1 revisions (73 bytes in store; 8 bytes tracked data)
-  finished migrating 1 filelog revisions across 1 filelogs; change in size: 0 bytes
-  migrating 1 manifests containing 1 revisions (117 bytes in store; 52 bytes tracked data)
-  finished migrating 1 manifest revisions across 1 manifests; change in size: 0 bytes
-  migrating changelog containing 1 revisions (134 bytes in store; 69 bytes tracked data)
-  finished migrating 1 changelog revisions; change in size: 0 bytes
-  finished migrating 3 total revisions; total change in store size: 0 bytes
-  copying phaseroots
-  data fully migrated to temporary repository
-  marking source repository as being upgraded; clients will be unable to read from repository
-  starting in-place swap of repository data
-  replaced files will be backed up at * (glob)
-  replacing store...
-  store replacement complete; repository was inconsistent for *s (glob)
-  finalizing requirements file and making repository readable again
-  removing temporary repository * (glob)
-  copy of old repository backed up at * (glob)
-  the old repository will not be deleted; remove it to free up disk space once the upgraded repository is verified
 
   $ grep 'lfs' .hg/requires $SERVER_REQUIRES
   [1]
--- a/tests/test-mq-qfold.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-mq-qfold.t	Tue May 26 08:07:24 2020 -0700
@@ -230,6 +230,7 @@
   HG: changed a
   ====
   note: commit message saved in .hg/last-message.txt
+  note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
   transaction abort!
   rollback completed
   qrefresh interrupted while patch was popped! (revert --all, qpush to recover)
--- a/tests/test-mq-qnew.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-mq-qnew.t	Tue May 26 08:07:24 2020 -0700
@@ -308,6 +308,7 @@
   transaction abort!
   rollback completed
   note: commit message saved in .hg/last-message.txt
+  note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
   abort: pretxncommit.unexpectedabort hook exited with status 1
   [255]
   $ cat .hg/last-message.txt
--- a/tests/test-mq-qrefresh-replace-log-message.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-mq-qrefresh-replace-log-message.t	Tue May 26 08:07:24 2020 -0700
@@ -186,6 +186,7 @@
   HG: added file2
   ====
   note: commit message saved in .hg/last-message.txt
+  note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
   transaction abort!
   rollback completed
   qrefresh interrupted while patch was popped! (revert --all, qpush to recover)
@@ -229,6 +230,7 @@
   A file2
   ====
   note: commit message saved in .hg/last-message.txt
+  note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
   transaction abort!
   rollback completed
   qrefresh interrupted while patch was popped! (revert --all, qpush to recover)
--- a/tests/test-persistent-nodemap.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-persistent-nodemap.t	Tue May 26 08:07:24 2020 -0700
@@ -2,20 +2,34 @@
 Test the persistent on-disk nodemap
 ===================================
 
-  $ hg init test-repo
-  $ cd test-repo
-  $ cat << EOF >> .hg/hgrc
-  > [experimental]
-  > exp-persistent-nodemap=yes
+  $ cat << EOF >> $HGRCPATH
+  > [format]
+  > use-persistent-nodemap=yes
   > [devel]
   > persistent-nodemap=yes
   > EOF
-  $ hg debugbuilddag .+5000
+  $ hg init test-repo
+  $ cd test-repo
+  $ hg debugformat
+  format-variant     repo
+  fncache:            yes
+  dotencode:          yes
+  generaldelta:       yes
+  sparserevlog:       yes
+  sidedata:            no
+  persistent-nodemap: yes
+  copies-sdc:          no
+  plain-cl-delta:     yes
+  compression:        zlib
+  compression-level:  default
+  $ hg debugbuilddag .+5000 --new-file --config "storage.revlog.nodemap.mode=warn"
+  persistent nodemap in strict mode without efficient method (no-rust no-pure !)
+  persistent nodemap in strict mode without efficient method (no-rust no-pure !)
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5000
-  tip-node: 06ddac466af534d365326c13c3879f97caca3cb1
-  data-length: 122880
+  tip-node: 6b02b8c7b96654c25e86ba69eda198d7e6ad8b3c
+  data-length: 121088
   data-unused: 0
   data-unused: 0.000%
   $ f --size .hg/store/00changelog.n
@@ -31,53 +45,56 @@
 #if rust
 
   $ f --sha256 .hg/store/00changelog-*.nd
-  .hg/store/00changelog-????????????????.nd: sha256=1e38e9ffaa45cad13f15c1a9880ad606f4241e8beea2f61b4d5365abadfb55f6 (glob)
+  .hg/store/00changelog-????????????????.nd: sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd (glob)
+
+  $ f --sha256 .hg/store/00manifest-*.nd
+  .hg/store/00manifest-????????????????.nd: sha256=97117b1c064ea2f86664a124589e47db0e254e8d34739b5c5cc5bf31c9da2b51 (glob)
   $ hg debugnodemap --dump-new | f --sha256 --size
-  size=122880, sha256=1e38e9ffaa45cad13f15c1a9880ad606f4241e8beea2f61b4d5365abadfb55f6
+  size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
   $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
-  size=122880, sha256=1e38e9ffaa45cad13f15c1a9880ad606f4241e8beea2f61b4d5365abadfb55f6
-  0000: 00 00 00 76 00 00 01 65 00 00 00 95 00 00 01 34 |...v...e.......4|
-  0010: 00 00 00 19 00 00 01 69 00 00 00 ab 00 00 00 4b |.......i.......K|
-  0020: 00 00 00 07 00 00 01 4c 00 00 00 f8 00 00 00 8f |.......L........|
-  0030: 00 00 00 c0 00 00 00 a7 00 00 00 89 00 00 01 46 |...............F|
-  0040: 00 00 00 92 00 00 01 bc 00 00 00 71 00 00 00 ac |...........q....|
-  0050: 00 00 00 af 00 00 00 b4 00 00 00 34 00 00 01 ca |...........4....|
-  0060: 00 00 00 23 00 00 01 45 00 00 00 2d 00 00 00 b2 |...#...E...-....|
-  0070: 00 00 00 56 00 00 01 0f 00 00 00 4e 00 00 02 4c |...V.......N...L|
-  0080: 00 00 00 e7 00 00 00 cd 00 00 01 5b 00 00 00 78 |...........[...x|
-  0090: 00 00 00 e3 00 00 01 8e 00 00 00 4f 00 00 00 b1 |...........O....|
-  00a0: 00 00 00 30 00 00 00 11 00 00 00 25 00 00 00 d2 |...0.......%....|
-  00b0: 00 00 00 ec 00 00 00 69 00 00 01 2b 00 00 01 2e |.......i...+....|
-  00c0: 00 00 00 aa 00 00 00 15 00 00 00 3a 00 00 01 4e |...........:...N|
-  00d0: 00 00 00 4d 00 00 00 9d 00 00 00 8e 00 00 00 a4 |...M............|
-  00e0: 00 00 00 c3 00 00 00 eb 00 00 00 29 00 00 00 ad |...........)....|
-  00f0: 00 00 01 3a 00 00 01 32 00 00 00 04 00 00 00 53 |...:...2.......S|
+  size=121088, sha256=2e029d3200bd1a986b32784fc2ef1a3bd60dc331f025718bcf5ff44d93f026fd
+  0000: 00 00 00 91 00 00 00 20 00 00 00 bb 00 00 00 e7 |....... ........|
+  0010: 00 00 00 66 00 00 00 a1 00 00 01 13 00 00 01 22 |...f..........."|
+  0020: 00 00 00 23 00 00 00 fc 00 00 00 ba 00 00 00 5e |...#...........^|
+  0030: 00 00 00 df 00 00 01 4e 00 00 01 65 00 00 00 ab |.......N...e....|
+  0040: 00 00 00 a9 00 00 00 95 00 00 00 73 00 00 00 38 |...........s...8|
+  0050: 00 00 00 cc 00 00 00 92 00 00 00 90 00 00 00 69 |...............i|
+  0060: 00 00 00 ec 00 00 00 8d 00 00 01 4f 00 00 00 12 |...........O....|
+  0070: 00 00 02 0c 00 00 00 77 00 00 00 9c 00 00 00 8f |.......w........|
+  0080: 00 00 00 d5 00 00 00 6b 00 00 00 48 00 00 00 b3 |.......k...H....|
+  0090: 00 00 00 e5 00 00 00 b5 00 00 00 8e 00 00 00 ad |................|
+  00a0: 00 00 00 7b 00 00 00 7c 00 00 00 0b 00 00 00 2b |...{...|.......+|
+  00b0: 00 00 00 c6 00 00 00 1e 00 00 01 08 00 00 00 11 |................|
+  00c0: 00 00 01 30 00 00 00 26 00 00 01 9c 00 00 00 35 |...0...&.......5|
+  00d0: 00 00 00 b8 00 00 01 31 00 00 00 2c 00 00 00 55 |.......1...,...U|
+  00e0: 00 00 00 8a 00 00 00 9a 00 00 00 0c 00 00 01 1e |................|
+  00f0: 00 00 00 a4 00 00 00 83 00 00 00 c9 00 00 00 8c |................|
 
 
 #else
 
   $ f --sha256 .hg/store/00changelog-*.nd
-  .hg/store/00changelog-????????????????.nd: sha256=b961925120e1c9bc345c199b2cc442abc477029fdece37ef9d99cbe59c0558b7 (glob)
+  .hg/store/00changelog-????????????????.nd: sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79 (glob)
   $ hg debugnodemap --dump-new | f --sha256 --size
-  size=122880, sha256=b961925120e1c9bc345c199b2cc442abc477029fdece37ef9d99cbe59c0558b7
+  size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
   $ hg debugnodemap --dump-disk | f --sha256 --bytes=256 --hexdump --size
-  size=122880, sha256=b961925120e1c9bc345c199b2cc442abc477029fdece37ef9d99cbe59c0558b7
+  size=121088, sha256=f544f5462ff46097432caf6d764091f6d8c46d6121be315ead8576d548c9dd79
   0000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
-  0010: ff ff ff ff ff ff ff ff ff ff fa c2 ff ff ff ff |................|
-  0020: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
-  0030: ff ff ff ff ff ff ed b3 ff ff ff ff ff ff ff ff |................|
-  0040: ff ff ff ff ff ff ee 34 00 00 00 00 ff ff ff ff |.......4........|
-  0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
-  0060: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0020: ff ff ff ff ff ff f5 06 ff ff ff ff ff ff f3 e7 |................|
+  0030: ff ff ef ca ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0040: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0050: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ed 08 |................|
+  0060: ff ff ed 66 ff ff ff ff ff ff ff ff ff ff ff ff |...f............|
   0070: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
-  0080: ff ff ff ff ff ff f8 50 ff ff ff ff ff ff ff ff |.......P........|
-  0090: ff ff ff ff ff ff ff ff ff ff ec c7 ff ff ff ff |................|
-  00a0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
-  00b0: ff ff ff ff ff ff fa be ff ff f2 fc ff ff ff ff |................|
-  00c0: ff ff ff ff ff ff ef ea ff ff ff ff ff ff f9 17 |................|
+  0080: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  0090: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f6 ed |................|
+  00a0: ff ff ff ff ff ff fe 61 ff ff ff ff ff ff ff ff |.......a........|
+  00b0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  00c0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
   00d0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
-  00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
-  00f0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................|
+  00e0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff f1 02 |................|
+  00f0: ff ff ff ff ff ff ed 1b ff ff ff ff ff ff ff ff |................|
 
 #endif
 
@@ -88,27 +105,38 @@
 add a new commit
 
   $ hg up
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  5001 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo foo > foo
   $ hg add foo
+
+#if no-pure no-rust
+
+  $ hg ci -m 'foo' --config "storage.revlog.nodemap.mode=strict"
+  transaction abort!
+  rollback completed
+  abort: persistent nodemap in strict mode without efficient method
+  [255]
+
+#endif
+
   $ hg ci -m 'foo'
 
 #if no-pure no-rust
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5001
-  tip-node: 2dd9b5258caa46469ff07d4a3da1eb3529a51f49
-  data-length: 122880
+  tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
+  data-length: 121088
   data-unused: 0
   data-unused: 0.000%
 #else
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5001
-  tip-node: 2dd9b5258caa46469ff07d4a3da1eb3529a51f49
-  data-length: 123072
-  data-unused: 192
-  data-unused: 0.156%
+  tip-node: 16395c3cf7e231394735e6b1717823ada303fb0c
+  data-length: 121344
+  data-unused: 256
+  data-unused: 0.211%
 #endif
 
   $ f --size .hg/store/00changelog.n
@@ -118,17 +146,17 @@
 
 #if pure
   $ f --sha256 .hg/store/00changelog-*.nd --size
-  .hg/store/00changelog-????????????????.nd: size=123072, sha256=136472751566c8198ff09e306a7d2f9bd18bd32298d614752b73da4d6df23340 (glob)
+  .hg/store/00changelog-????????????????.nd: size=121344, sha256=cce54c5da5bde3ad72a4938673ed4064c86231b9c64376b082b163fdb20f8f66 (glob)
 #endif
 
 #if rust
   $ f --sha256 .hg/store/00changelog-*.nd --size
-  .hg/store/00changelog-????????????????.nd: size=123072, sha256=ccc8a43310ace13812fcc648683e259346754ef934c12dd238cf9b7fadfe9a4b (glob)
+  .hg/store/00changelog-????????????????.nd: size=121344, sha256=952b042fcf614ceb37b542b1b723e04f18f83efe99bee4e0f5ccd232ef470e58 (glob)
 #endif
 
 #if no-pure no-rust
   $ f --sha256 .hg/store/00changelog-*.nd --size
-  .hg/store/00changelog-????????????????.nd: size=122880, sha256=bfafebd751c4f6d116a76a37a1dee2a251747affe7efbcc4f4842ccc746d4db9 (glob)
+  .hg/store/00changelog-????????????????.nd: size=121088, sha256=df7c06a035b96cb28c7287d349d603baef43240be7736fe34eea419a49702e17 (glob)
 #endif
 
   $ hg debugnodemap --check
@@ -140,12 +168,12 @@
 
   $ echo bar > bar
   $ hg add bar
-  $ hg ci -m 'bar' --config experimental.exp-persistent-nodemap.mmap=no
+  $ hg ci -m 'bar' --config storage.revlog.nodemap.mmap=no
 
-  $ hg debugnodemap --check --config experimental.exp-persistent-nodemap.mmap=yes
+  $ hg debugnodemap --check --config storage.revlog.nodemap.mmap=yes
   revision in index:   5003
   revision in nodemap: 5003
-  $ hg debugnodemap --check --config experimental.exp-persistent-nodemap.mmap=no
+  $ hg debugnodemap --check --config storage.revlog.nodemap.mmap=no
   revision in index:   5003
   revision in nodemap: 5003
 
@@ -154,34 +182,34 @@
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5002
-  tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
-  data-length: 123328
-  data-unused: 384
-  data-unused: 0.311%
+  tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
+  data-length: 121600
+  data-unused: 512
+  data-unused: 0.421%
   $ f --sha256 .hg/store/00changelog-*.nd --size
-  .hg/store/00changelog-????????????????.nd: size=123328, sha256=10d26e9776b6596af0f89143a54eba8cc581e929c38242a02a7b0760698c6c70 (glob)
+  .hg/store/00changelog-????????????????.nd: size=121600, sha256=def52503d049ccb823974af313a98a935319ba61f40f3aa06a8be4d35c215054 (glob)
 #endif
 #if rust
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5002
-  tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
-  data-length: 123328
-  data-unused: 384
-  data-unused: 0.311%
+  tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
+  data-length: 121600
+  data-unused: 512
+  data-unused: 0.421%
   $ f --sha256 .hg/store/00changelog-*.nd --size
-  .hg/store/00changelog-????????????????.nd: size=123328, sha256=081eec9eb6708f2bf085d939b4c97bc0b6762bc8336bc4b93838f7fffa1516bf (glob)
+  .hg/store/00changelog-????????????????.nd: size=121600, sha256=dacf5b5f1d4585fee7527d0e67cad5b1ba0930e6a0928f650f779aefb04ce3fb (glob)
 #endif
 #if no-pure no-rust
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5002
-  tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
-  data-length: 122944
+  tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
+  data-length: 121088
   data-unused: 0
   data-unused: 0.000%
   $ f --sha256 .hg/store/00changelog-*.nd --size
-  .hg/store/00changelog-????????????????.nd: size=122944, sha256=755976b22b64ab680401b45395953504e64e7fa8c31ac570f58dee21e15f9bc0 (glob)
+  .hg/store/00changelog-????????????????.nd: size=121088, sha256=59fcede3e3cc587755916ceed29e3c33748cd1aa7d2f91828ac83e7979d935e8 (glob)
 #endif
 
 Test force warming the cache
@@ -193,16 +221,16 @@
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5002
-  tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
-  data-length: 122944
+  tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
+  data-length: 121088
   data-unused: 0
   data-unused: 0.000%
 #else
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5002
-  tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
-  data-length: 122944
+  tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
+  data-length: 121088
   data-unused: 0
   data-unused: 0.000%
 #endif
@@ -231,22 +259,22 @@
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5003
-  tip-node: 5c049e9c4a4af159bdcd65dce1b6bf303a0da6cf
-  data-length: 123200 (pure !)
-  data-length: 123200 (rust !)
-  data-length: 122944 (no-rust no-pure !)
-  data-unused: 256 (pure !)
-  data-unused: 256 (rust !)
+  tip-node: c9329770f979ade2d16912267c38ba5f82fd37b3
+  data-length: 121344 (pure !)
+  data-length: 121344 (rust !)
+  data-length: 121152 (no-rust no-pure !)
+  data-unused: 192 (pure !)
+  data-unused: 192 (rust !)
   data-unused: 0 (no-rust no-pure !)
-  data-unused: 0.208% (pure !)
-  data-unused: 0.208% (rust !)
+  data-unused: 0.158% (pure !)
+  data-unused: 0.158% (rust !)
   data-unused: 0.000% (no-rust no-pure !)
   $ cp -f ../tmp-copies/* .hg/store/
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5002
-  tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
-  data-length: 122944
+  tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
+  data-length: 121088
   data-unused: 0
   data-unused: 0.000%
   $ hg log -r "$NODE" -T '{rev}\n'
@@ -260,7 +288,7 @@
 compatible with the persistent nodemap. We need to detect that.
 
   $ hg up "$NODE~5"
-  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
   $ echo bar > babar
   $ hg add babar
   $ hg ci -m 'babar'
@@ -276,23 +304,23 @@
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5002
-  tip-node: 42bf3068c7ddfdfded53c4eb11d02266faeebfee
-  data-length: 123456 (pure !)
-  data-length: 123008 (rust !)
-  data-length: 123008 (no-pure no-rust !)
+  tip-node: b355ef8adce0949b8bdf6afc72ca853740d65944
+  data-length: 121536 (pure !)
+  data-length: 121088 (rust !)
+  data-length: 121088 (no-pure no-rust !)
   data-unused: 448 (pure !)
   data-unused: 0 (rust !)
   data-unused: 0 (no-pure no-rust !)
   data-unused: 0.000% (rust !)
-  data-unused: 0.363% (pure !)
+  data-unused: 0.369% (pure !)
   data-unused: 0.000% (no-pure no-rust !)
 
   $ cp -f ../tmp-copies/* .hg/store/
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5002
-  tip-node: 6ce944fafcee85af91f29ea5b51654cc6101ad7e
-  data-length: 122944
+  tip-node: 880b18d239dfa9f632413a2071bfdbcc4806a4fd
+  data-length: 121088
   data-unused: 0
   data-unused: 0.000%
   $ hg log -r "$OTHERNODE" -T '{rev}\n'
@@ -309,36 +337,36 @@
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5003
-  tip-node: c91af76d172f1053cca41b83f7c2e4e514fe2bcf
-  data-length: 123008
+  tip-node: a52c5079765b5865d97b993b303a18740113bbb2
+  data-length: 121088
   data-unused: 0
   data-unused: 0.000%
   $ echo babar2 > babar
   $ hg ci -m 'babar2' --config "hooks.pretxnclose.nodemap-test=hg debugnodemap --metadata"
   uid: ???????????????? (glob)
   tip-rev: 5004
-  tip-node: ba87cd9559559e4b91b28cb140d003985315e031
-  data-length: 123328 (pure !)
-  data-length: 123328 (rust !)
-  data-length: 123136 (no-pure no-rust !)
+  tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
+  data-length: 121280 (pure !)
+  data-length: 121280 (rust !)
+  data-length: 121088 (no-pure no-rust !)
   data-unused: 192 (pure !)
   data-unused: 192 (rust !)
   data-unused: 0 (no-pure no-rust !)
-  data-unused: 0.156% (pure !)
-  data-unused: 0.156% (rust !)
+  data-unused: 0.158% (pure !)
+  data-unused: 0.158% (rust !)
   data-unused: 0.000% (no-pure no-rust !)
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5004
-  tip-node: ba87cd9559559e4b91b28cb140d003985315e031
-  data-length: 123328 (pure !)
-  data-length: 123328 (rust !)
-  data-length: 123136 (no-pure no-rust !)
+  tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
+  data-length: 121280 (pure !)
+  data-length: 121280 (rust !)
+  data-length: 121088 (no-pure no-rust !)
   data-unused: 192 (pure !)
   data-unused: 192 (rust !)
   data-unused: 0 (no-pure no-rust !)
-  data-unused: 0.156% (pure !)
-  data-unused: 0.156% (rust !)
+  data-unused: 0.158% (pure !)
+  data-unused: 0.158% (rust !)
   data-unused: 0.000% (no-pure no-rust !)
 
 Another process does not see the pending nodemap content during run.
@@ -356,28 +384,28 @@
   > wait-on-file 20 sync-txn-close sync-repo-read
   uid: ???????????????? (glob)
   tip-rev: 5004
-  tip-node: ba87cd9559559e4b91b28cb140d003985315e031
-  data-length: 123328 (pure !)
-  data-length: 123328 (rust !)
-  data-length: 123136 (no-pure no-rust !)
+  tip-node: 2f5fb1c06a16834c5679d672e90da7c5f3b1a984
+  data-length: 121280 (pure !)
+  data-length: 121280 (rust !)
+  data-length: 121088 (no-pure no-rust !)
   data-unused: 192 (pure !)
   data-unused: 192 (rust !)
   data-unused: 0 (no-pure no-rust !)
-  data-unused: 0.156% (pure !)
-  data-unused: 0.156% (rust !)
+  data-unused: 0.158% (pure !)
+  data-unused: 0.158% (rust !)
   data-unused: 0.000% (no-pure no-rust !)
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5005
-  tip-node: bae4d45c759e30f1cb1a40e1382cf0e0414154db
-  data-length: 123584 (pure !)
-  data-length: 123584 (rust !)
-  data-length: 123136 (no-pure no-rust !)
+  tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
+  data-length: 121536 (pure !)
+  data-length: 121536 (rust !)
+  data-length: 121088 (no-pure no-rust !)
   data-unused: 448 (pure !)
   data-unused: 448 (rust !)
   data-unused: 0 (no-pure no-rust !)
-  data-unused: 0.363% (pure !)
-  data-unused: 0.363% (rust !)
+  data-unused: 0.369% (pure !)
+  data-unused: 0.369% (rust !)
   data-unused: 0.000% (no-pure no-rust !)
 
   $ cat output.txt
@@ -386,9 +414,9 @@
 
   $ echo plakfe > a
   $ f --size --sha256 .hg/store/00changelog-*.nd
-  .hg/store/00changelog-????????????????.nd: size=123584, sha256=8c6cef6fd3d3fac291968793ee19a4be6d0b8375e9508bd5c7d4a8879e8df180 (glob) (pure !)
-  .hg/store/00changelog-????????????????.nd: size=123584, sha256=eb9e9a4bcafdb5e1344bc8a0cbb3288b2106413b8efae6265fb8a7973d7e97f9 (glob) (rust !)
-  .hg/store/00changelog-????????????????.nd: size=123136, sha256=4f504f5a834db3811ced50ab3e9e80bcae3581bb0f9b13a7a9f94b7fc34bcebe (glob) (no-pure no-rust !)
+  .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
+  .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
+  .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
   $ hg ci -m a3 --config "extensions.abort=$RUNTESTDIR/testlib/crash_transaction_late.py"
   transaction abort!
   rollback completed
@@ -397,17 +425,113 @@
   $ hg debugnodemap --metadata
   uid: ???????????????? (glob)
   tip-rev: 5005
-  tip-node: bae4d45c759e30f1cb1a40e1382cf0e0414154db
-  data-length: 123584 (pure !)
-  data-length: 123584 (rust !)
-  data-length: 123136 (no-pure no-rust !)
+  tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
+  data-length: 121536 (pure !)
+  data-length: 121536 (rust !)
+  data-length: 121088 (no-pure no-rust !)
   data-unused: 448 (pure !)
   data-unused: 448 (rust !)
   data-unused: 0 (no-pure no-rust !)
-  data-unused: 0.363% (pure !)
-  data-unused: 0.363% (rust !)
+  data-unused: 0.369% (pure !)
+  data-unused: 0.369% (rust !)
   data-unused: 0.000% (no-pure no-rust !)
   $ f --size --sha256 .hg/store/00changelog-*.nd
-  .hg/store/00changelog-????????????????.nd: size=123584, sha256=8c6cef6fd3d3fac291968793ee19a4be6d0b8375e9508bd5c7d4a8879e8df180 (glob) (pure !)
-  .hg/store/00changelog-????????????????.nd: size=123584, sha256=eb9e9a4bcafdb5e1344bc8a0cbb3288b2106413b8efae6265fb8a7973d7e97f9 (glob) (rust !)
-  .hg/store/00changelog-????????????????.nd: size=123136, sha256=4f504f5a834db3811ced50ab3e9e80bcae3581bb0f9b13a7a9f94b7fc34bcebe (glob) (no-pure no-rust !)
+  .hg/store/00changelog-????????????????.nd: size=121536, sha256=bb414468d225cf52d69132e1237afba34d4346ee2eb81b505027e6197b107f03 (glob) (pure !)
+  .hg/store/00changelog-????????????????.nd: size=121536, sha256=909ac727bc4d1c0fda5f7bff3c620c98bd4a2967c143405a1503439e33b377da (glob) (rust !)
+  .hg/store/00changelog-????????????????.nd: size=121088, sha256=342d36d30d86dde67d3cb6c002606c4a75bcad665595d941493845066d9c8ee0 (glob) (no-pure no-rust !)
+
+Test upgrade / downgrade
+========================
+
+downgrading
+
+  $ cat << EOF >> .hg/hgrc
+  > [format]
+  > use-persistent-nodemap=no
+  > EOF
+  $ hg debugformat -v
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap: yes     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
+  $ hg debugupgraderepo --run --no-backup --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
+     removed: persistent-nodemap
+  
+  $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  [1]
+  $ hg debugnodemap --metadata
+
+
+upgrading
+
+  $ cat << EOF >> .hg/hgrc
+  > [format]
+  > use-persistent-nodemap=yes
+  > EOF
+  $ hg debugformat -v
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no    yes      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
+  $ hg debugupgraderepo --run --no-backup --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
+     added: persistent-nodemap
+  
+  $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  00changelog-*.nd (glob)
+  00changelog.n
+  00manifest-*.nd (glob)
+  00manifest.n
+
+  $ hg debugnodemap --metadata
+  uid: * (glob)
+  tip-rev: 5005
+  tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
+  data-length: 121088
+  data-unused: 0
+  data-unused: 0.000%
+
+Running unrelated upgrade
+
+  $ hg debugupgraderepo --run --no-backup --quiet --optimize re-delta-all
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, persistent-nodemap, revlogv1, sparserevlog, store
+  
+  optimisations: re-delta-all
+  
+  $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  00changelog-*.nd (glob)
+  00changelog.n
+  00manifest-*.nd (glob)
+  00manifest.n
+
+  $ hg debugnodemap --metadata
+  uid: * (glob)
+  tip-rev: 5005
+  tip-node: 90d5d3ba2fc47db50f712570487cb261a68c8ffe
+  data-length: 121088
+  data-unused: 0
+  data-unused: 0.000%
--- a/tests/test-revset.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-revset.t	Tue May 26 08:07:24 2020 -0700
@@ -1864,12 +1864,12 @@
   $ log 'id(2)'
   $ log 'id(8)'
   3
-  $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'id(x8)'
+  $ hg log --template '{rev}\n' -r 'id(x8)'
   3
-  $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'x8'
+  $ hg log --template '{rev}\n' -r 'x8'
   3
-  $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'id(x)'
-  $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'x'
+  $ hg log --template '{rev}\n' -r 'id(x)'
+  $ hg log --template '{rev}\n' -r 'x'
   abort: 00changelog.i@: ambiguous identifier!
   [255]
   $ log 'id(23268)'
--- a/tests/test-rollback.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-rollback.t	Tue May 26 08:07:24 2020 -0700
@@ -116,6 +116,7 @@
   transaction abort!
   rollback completed
   note: commit message saved in .hg/last-message.txt
+  note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
   abort: pretxncommit hook exited with status * (glob)
   [255]
   $ cat .hg/last-message.txt
--- a/tests/test-sidedata.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-sidedata.t	Tue May 26 08:07:24 2020 -0700
@@ -50,27 +50,29 @@
 
   $ hg init up-no-side-data --config format.exp-use-side-data=no
   $ hg debugformat -v -R up-no-side-data
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugformat -v -R up-no-side-data --config format.exp-use-side-data=yes
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no    yes      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no    yes      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugupgraderepo -R up-no-side-data --config format.exp-use-side-data=yes > /dev/null
 
 Check that we can downgrade from sidedata
@@ -78,25 +80,27 @@
 
   $ hg init up-side-data --config format.exp-use-side-data=yes
   $ hg debugformat -v -R up-side-data
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:          yes     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:           yes     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugformat -v -R up-side-data --config format.exp-use-side-data=no
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:          yes     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:           yes     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugupgraderepo -R up-side-data --config format.exp-use-side-data=no > /dev/null
--- a/tests/test-tag.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-tag.t	Tue May 26 08:07:24 2020 -0700
@@ -323,6 +323,7 @@
   transaction abort!
   rollback completed
   note: commit message saved in .hg/last-message.txt
+  note: use 'hg commit --logfile .hg/last-message.txt --edit' to reuse it
   abort: pretxncommit.unexpectedabort hook exited with status 1
   [255]
   $ cat .hg/last-message.txt
--- a/tests/test-upgrade-repo.t	Fri May 15 00:53:37 2020 +0200
+++ b/tests/test-upgrade-repo.t	Tue May 26 08:07:24 2020 -0700
@@ -52,49 +52,53 @@
   $ hg init empty
   $ cd empty
   $ hg debugformat
-  format-variant    repo
-  fncache:           yes
-  dotencode:         yes
-  generaldelta:      yes
-  sparserevlog:      yes
-  sidedata:           no
-  copies-sdc:         no
-  plain-cl-delta:    yes
-  compression:       zlib
-  compression-level: default
+  format-variant     repo
+  fncache:            yes
+  dotencode:          yes
+  generaldelta:       yes
+  sparserevlog:       yes
+  sidedata:            no
+  persistent-nodemap:  no
+  copies-sdc:          no
+  plain-cl-delta:     yes
+  compression:        zlib
+  compression-level:  default
   $ hg debugformat --verbose
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugformat --verbose --config format.usefncache=no
-  format-variant    repo config default
-  fncache:           yes     no     yes
-  dotencode:         yes     no     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes     no     yes
+  dotencode:          yes     no     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugformat --verbose --config format.usefncache=no --color=debug
-  format-variant    repo config default
-  [formatvariant.name.mismatchconfig|fncache:          ][formatvariant.repo.mismatchconfig| yes][formatvariant.config.special|     no][formatvariant.default|     yes]
-  [formatvariant.name.mismatchconfig|dotencode:        ][formatvariant.repo.mismatchconfig| yes][formatvariant.config.special|     no][formatvariant.default|     yes]
-  [formatvariant.name.uptodate|generaldelta:     ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
-  [formatvariant.name.uptodate|sparserevlog:     ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
-  [formatvariant.name.uptodate|sidedata:         ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
-  [formatvariant.name.uptodate|copies-sdc:       ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
-  [formatvariant.name.uptodate|plain-cl-delta:   ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
-  [formatvariant.name.uptodate|compression:      ][formatvariant.repo.uptodate| zlib][formatvariant.config.default|   zlib][formatvariant.default|    zlib]
-  [formatvariant.name.uptodate|compression-level:][formatvariant.repo.uptodate| default][formatvariant.config.default| default][formatvariant.default| default]
+  format-variant     repo config default
+  [formatvariant.name.mismatchconfig|fncache:           ][formatvariant.repo.mismatchconfig| yes][formatvariant.config.special|     no][formatvariant.default|     yes]
+  [formatvariant.name.mismatchconfig|dotencode:         ][formatvariant.repo.mismatchconfig| yes][formatvariant.config.special|     no][formatvariant.default|     yes]
+  [formatvariant.name.uptodate|generaldelta:      ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
+  [formatvariant.name.uptodate|sparserevlog:      ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
+  [formatvariant.name.uptodate|sidedata:          ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
+  [formatvariant.name.uptodate|persistent-nodemap:][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
+  [formatvariant.name.uptodate|copies-sdc:        ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
+  [formatvariant.name.uptodate|plain-cl-delta:    ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
+  [formatvariant.name.uptodate|compression:       ][formatvariant.repo.uptodate| zlib][formatvariant.config.default|   zlib][formatvariant.default|    zlib]
+  [formatvariant.name.uptodate|compression-level: ][formatvariant.repo.uptodate| default][formatvariant.config.default| default][formatvariant.default| default]
   $ hg debugformat -Tjson
   [
    {
@@ -130,6 +134,12 @@
    {
     "config": false,
     "default": false,
+    "name": "persistent-nodemap",
+    "repo": false
+   },
+   {
+    "config": false,
+    "default": false,
     "name": "copies-sdc",
     "repo": false
    },
@@ -174,6 +184,11 @@
      every revision will be re-added as if it was new content. It will go through the full storage mechanism giving extensions a chance to process it (eg. lfs). This is similar to "re-delta-all" but even slower since more logic is involved.
   
 
+  $ hg debugupgraderepo --quiet
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
+  
+
 --optimize can be used to add optimizations
 
   $ hg debugupgrade --optimize redeltaparent
@@ -183,6 +198,8 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
+  optimisations: re-delta-parent
+  
   re-delta-parent
      deltas within internal storage will choose a new base revision if needed
   
@@ -207,6 +224,8 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
+  optimisations: re-delta-parent
+  
   re-delta-parent
      deltas within internal storage will choose a new base revision if needed
   
@@ -221,6 +240,12 @@
   re-delta-fulladd
      every revision will be re-added as if it was new content. It will go through the full storage mechanism giving extensions a chance to process it (eg. lfs). This is similar to "re-delta-all" but even slower since more logic is involved.
   
+  $ hg debugupgrade --optimize re-delta-parent --quiet
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
+  
+  optimisations: re-delta-parent
+  
 
 unknown optimization:
 
@@ -237,49 +262,53 @@
   > EOF
 
   $ hg debugformat
-  format-variant    repo
-  fncache:            no
-  dotencode:          no
-  generaldelta:       no
-  sparserevlog:       no
-  sidedata:           no
-  copies-sdc:         no
-  plain-cl-delta:    yes
-  compression:       zlib
-  compression-level: default
+  format-variant     repo
+  fncache:             no
+  dotencode:           no
+  generaldelta:        no
+  sparserevlog:        no
+  sidedata:            no
+  persistent-nodemap:  no
+  copies-sdc:          no
+  plain-cl-delta:     yes
+  compression:        zlib
+  compression-level:  default
   $ hg debugformat --verbose
-  format-variant    repo config default
-  fncache:            no    yes     yes
-  dotencode:          no    yes     yes
-  generaldelta:       no    yes     yes
-  sparserevlog:       no    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:             no    yes     yes
+  dotencode:           no    yes     yes
+  generaldelta:        no    yes     yes
+  sparserevlog:        no    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugformat --verbose --config format.usegeneraldelta=no
-  format-variant    repo config default
-  fncache:            no    yes     yes
-  dotencode:          no    yes     yes
-  generaldelta:       no     no     yes
-  sparserevlog:       no     no     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:             no    yes     yes
+  dotencode:           no    yes     yes
+  generaldelta:        no     no     yes
+  sparserevlog:        no     no     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ hg debugformat --verbose --config format.usegeneraldelta=no --color=debug
-  format-variant    repo config default
-  [formatvariant.name.mismatchconfig|fncache:          ][formatvariant.repo.mismatchconfig|  no][formatvariant.config.default|    yes][formatvariant.default|     yes]
-  [formatvariant.name.mismatchconfig|dotencode:        ][formatvariant.repo.mismatchconfig|  no][formatvariant.config.default|    yes][formatvariant.default|     yes]
-  [formatvariant.name.mismatchdefault|generaldelta:     ][formatvariant.repo.mismatchdefault|  no][formatvariant.config.special|     no][formatvariant.default|     yes]
-  [formatvariant.name.mismatchdefault|sparserevlog:     ][formatvariant.repo.mismatchdefault|  no][formatvariant.config.special|     no][formatvariant.default|     yes]
-  [formatvariant.name.uptodate|sidedata:         ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
-  [formatvariant.name.uptodate|copies-sdc:       ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
-  [formatvariant.name.uptodate|plain-cl-delta:   ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
-  [formatvariant.name.uptodate|compression:      ][formatvariant.repo.uptodate| zlib][formatvariant.config.default|   zlib][formatvariant.default|    zlib]
-  [formatvariant.name.uptodate|compression-level:][formatvariant.repo.uptodate| default][formatvariant.config.default| default][formatvariant.default| default]
+  format-variant     repo config default
+  [formatvariant.name.mismatchconfig|fncache:           ][formatvariant.repo.mismatchconfig|  no][formatvariant.config.default|    yes][formatvariant.default|     yes]
+  [formatvariant.name.mismatchconfig|dotencode:         ][formatvariant.repo.mismatchconfig|  no][formatvariant.config.default|    yes][formatvariant.default|     yes]
+  [formatvariant.name.mismatchdefault|generaldelta:      ][formatvariant.repo.mismatchdefault|  no][formatvariant.config.special|     no][formatvariant.default|     yes]
+  [formatvariant.name.mismatchdefault|sparserevlog:      ][formatvariant.repo.mismatchdefault|  no][formatvariant.config.special|     no][formatvariant.default|     yes]
+  [formatvariant.name.uptodate|sidedata:          ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
+  [formatvariant.name.uptodate|persistent-nodemap:][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
+  [formatvariant.name.uptodate|copies-sdc:        ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
+  [formatvariant.name.uptodate|plain-cl-delta:    ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
+  [formatvariant.name.uptodate|compression:       ][formatvariant.repo.uptodate| zlib][formatvariant.config.default|   zlib][formatvariant.default|    zlib]
+  [formatvariant.name.uptodate|compression-level: ][formatvariant.repo.uptodate| default][formatvariant.config.default| default][formatvariant.default| default]
   $ hg debugupgraderepo
   repository lacks features recommended by current config options:
   
@@ -328,6 +357,11 @@
   re-delta-fulladd
      every revision will be re-added as if it was new content. It will go through the full storage mechanism giving extensions a chance to process it (eg. lfs). This is similar to "re-delta-all" but even slower since more logic is involved.
   
+  $ hg debugupgraderepo --quiet
+  requirements
+     preserved: revlogv1, store
+     added: dotencode, fncache, generaldelta, sparserevlog
+  
 
   $ hg --config format.dotencode=false debugupgraderepo
   repository lacks features recommended by current config options:
@@ -569,6 +603,8 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
+  optimisations: re-delta-parent
+  
   re-delta-parent
      deltas within internal storage will choose a new base revision if needed
   
@@ -643,6 +679,8 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
+  optimisations: re-delta-parent
+  
   re-delta-parent
      deltas within internal storage will choose a new base revision if needed
   
@@ -689,6 +727,8 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
+  optimisations: re-delta-parent
+  
   re-delta-parent
      deltas within internal storage will choose a new base revision if needed
   
@@ -735,6 +775,8 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
+  optimisations: re-delta-parent
+  
   re-delta-parent
      deltas within internal storage will choose a new base revision if needed
   
@@ -786,6 +828,8 @@
      preserved: dotencode, fncache, generaldelta, revlogv1, store
      removed: sparserevlog
   
+  optimisations: re-delta-parent
+  
   re-delta-parent
      deltas within internal storage will choose a new base revision if needed
   
@@ -835,6 +879,8 @@
      preserved: dotencode, fncache, generaldelta, revlogv1, store
      added: sparserevlog
   
+  optimisations: re-delta-parent
+  
   sparserevlog
      Revlog supports delta chain with more unused data between payload. These gaps will be skipped at read time. This allows for better delta chains, making a better compression and faster exchange with server.
   
@@ -923,6 +969,8 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
+  optimisations: re-delta-fulladd
+  
   re-delta-fulladd
      each revision will be added as new content to the internal storage; this will likely drastically slow down execution time, but some extensions might need it
   
@@ -1135,6 +1183,8 @@
   requirements
      preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
   
+  optimisations: re-delta-all
+  
   re-delta-all
      deltas within internal storage will be fully recomputed; this will likely drastically slow down execution time
   
@@ -1190,9 +1240,13 @@
   store
 
 Check that we can add the sparse-revlog format requirement
-  $ hg --config format.sparse-revlog=yes debugupgraderepo --run >/dev/null
-  copy of old repository backed up at $TESTTMP/sparserevlogrepo/.hg/upgradebackup.* (glob)
-  the old repository will not be deleted; remove it to free up disk space once the upgraded repository is verified
+  $ hg --config format.sparse-revlog=yes debugupgraderepo --run --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, store
+     added: sparserevlog
+  
   $ cat .hg/requires
   dotencode
   fncache
@@ -1202,9 +1256,13 @@
   store
 
 Check that we can remove the sparse-revlog format requirement
-  $ hg --config format.sparse-revlog=no debugupgraderepo --run >/dev/null
-  copy of old repository backed up at $TESTTMP/sparserevlogrepo/.hg/upgradebackup.* (glob)
-  the old repository will not be deleted; remove it to free up disk space once the upgraded repository is verified
+  $ hg --config format.sparse-revlog=no debugupgraderepo --run --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, store
+     removed: sparserevlog
+  
   $ cat .hg/requires
   dotencode
   fncache
@@ -1219,18 +1277,25 @@
 
 upgrade
 
-  $ hg --config format.revlog-compression=zstd debugupgraderepo --run  --no-backup >/dev/null
+  $ hg --config format.revlog-compression=zstd debugupgraderepo --run  --no-backup --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, store
+     added: revlog-compression-zstd, sparserevlog
+  
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zstd   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zstd   zlib    zlib
+  compression-level:  default default default
   $ cat .hg/requires
   dotencode
   fncache
@@ -1242,18 +1307,25 @@
 
 downgrade
 
-  $ hg debugupgraderepo --run --no-backup > /dev/null
+  $ hg debugupgraderepo --run --no-backup --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
+     removed: revlog-compression-zstd
+  
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zlib   zlib    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib
+  compression-level:  default default default
   $ cat .hg/requires
   dotencode
   fncache
@@ -1268,18 +1340,25 @@
   > [format]
   > revlog-compression=zstd
   > EOF
-  $ hg debugupgraderepo --run --no-backup > /dev/null
+  $ hg debugupgraderepo --run --no-backup --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store
+     added: revlog-compression-zstd
+  
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zstd   zstd    zlib
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zstd   zstd    zlib
+  compression-level:  default default default
   $ cat .hg/requires
   dotencode
   fncache
@@ -1296,19 +1375,28 @@
 
 upgrade
 
-  $ hg --config format.exp-use-side-data=yes debugupgraderepo --run  --no-backup --config "extensions.sidedata=$TESTDIR/testlib/ext-sidedata.py" >/dev/null
+  $ hg --config format.exp-use-side-data=yes debugupgraderepo --run  --no-backup --config "extensions.sidedata=$TESTDIR/testlib/ext-sidedata.py" --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, store (no-zstd !)
+     preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !)
+     added: exp-sidedata-flag (zstd !)
+     added: exp-sidedata-flag, sparserevlog (no-zstd !)
+  
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:          yes     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zstd   zstd    zlib (zstd !)
-  compression:       zlib   zlib    zlib (no-zstd !)
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:           yes     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib (no-zstd !)
+  compression:        zstd   zstd    zlib (zstd !)
+  compression-level:  default default default
   $ cat .hg/requires
   dotencode
   exp-sidedata-flag
@@ -1325,19 +1413,27 @@
 
 downgrade
 
-  $ hg debugupgraderepo --config format.exp-use-side-data=no --run --no-backup > /dev/null
+  $ hg debugupgraderepo --config format.exp-use-side-data=no --run --no-backup --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !)
+     preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !)
+     removed: exp-sidedata-flag
+  
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:           no     no      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zstd   zstd    zlib (zstd !)
-  compression:       zlib   zlib    zlib (no-zstd !)
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:            no     no      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib (no-zstd !)
+  compression:        zstd   zstd    zlib (zstd !)
+  compression-level:  default default default
   $ cat .hg/requires
   dotencode
   fncache
@@ -1354,19 +1450,27 @@
   > [format]
   > exp-use-side-data=yes
   > EOF
-  $ hg debugupgraderepo --run --no-backup > /dev/null
+  $ hg debugupgraderepo --run --no-backup --quiet
+  upgrade will perform the following actions:
+  
+  requirements
+     preserved: dotencode, fncache, generaldelta, revlogv1, sparserevlog, store (no-zstd !)
+     preserved: dotencode, fncache, generaldelta, revlog-compression-zstd, revlogv1, sparserevlog, store (zstd !)
+     added: exp-sidedata-flag
+  
   $ hg debugformat -v
-  format-variant    repo config default
-  fncache:           yes    yes     yes
-  dotencode:         yes    yes     yes
-  generaldelta:      yes    yes     yes
-  sparserevlog:      yes    yes     yes
-  sidedata:          yes    yes      no
-  copies-sdc:         no     no      no
-  plain-cl-delta:    yes    yes     yes
-  compression:       zstd   zstd    zlib (zstd !)
-  compression:       zlib   zlib    zlib (no-zstd !)
-  compression-level: default default default
+  format-variant     repo config default
+  fncache:            yes    yes     yes
+  dotencode:          yes    yes     yes
+  generaldelta:       yes    yes     yes
+  sparserevlog:       yes    yes     yes
+  sidedata:           yes    yes      no
+  persistent-nodemap:  no     no      no
+  copies-sdc:          no     no      no
+  plain-cl-delta:     yes    yes     yes
+  compression:        zlib   zlib    zlib (no-zstd !)
+  compression:        zstd   zstd    zlib (zstd !)
+  compression-level:  default default default
   $ cat .hg/requires
   dotencode
   exp-sidedata-flag