changeset 39832:85a474adaf26

merge with stable
author Yuya Nishihara <yuya@tcha.org>
date Wed, 26 Sep 2018 20:33:09 +0900
parents 7a9e2d85f475 (diff) 823f34acfd46 (current diff)
children 28626957395a
files mercurial/revset.py tests/test-revset.t
diffstat 602 files changed, 38372 insertions(+), 11241 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue Sep 25 16:32:38 2018 -0400
+++ b/.hgignore	Wed Sep 26 20:33:09 2018 +0900
@@ -19,6 +19,7 @@
 *.zip
 \#*\#
 .\#*
+tests/artifacts/cache/big-file-churn.hg
 tests/.coverage*
 tests/.testtimes*
 tests/.hypothesis
--- a/Makefile	Tue Sep 25 16:32:38 2018 -0400
+++ b/Makefile	Wed Sep 26 20:33:09 2018 +0900
@@ -9,7 +9,8 @@
 $(eval HGROOT := $(shell pwd))
 HGPYTHONS ?= $(HGROOT)/build/pythons
 PURE=
-PYFILES:=$(shell find mercurial hgext doc -name '*.py')
+PYFILESCMD=find mercurial hgext doc -name '*.py'
+PYFILES:=$(shell $(PYFILESCMD))
 DOCFILES=mercurial/help/*.txt
 export LANGUAGE=C
 export LC_ALL=C
@@ -145,7 +146,7 @@
         # parse them even though they are not marked for translation.
         # Extracting with an explicit encoding of ISO-8859-1 will make
         # xgettext "parse" and ignore them.
-	echo $(PYFILES) | xargs \
+	$(PYFILESCMD) | xargs \
 	  xgettext --package-name "Mercurial" \
 	  --msgid-bugs-address "<mercurial-devel@mercurial-scm.org>" \
 	  --copyright-holder "Matt Mackall <mpm@selenic.com> and others" \
--- a/contrib/bash_completion	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/bash_completion	Wed Sep 26 20:33:09 2018 +0900
@@ -152,7 +152,7 @@
 {
     local cur prev cmd cmd_index opts i aliashg
     # global options that receive an argument
-    local global_args='--cwd|-R|--repository'
+    local global_args='--cwd|-R|--repository|--color|--config|--encoding|--encodingmode|--pager'
     local hg="$1"
     local canonical=0
 
@@ -206,6 +206,18 @@
             _hg_fix_wordlist
             return
         ;;
+        --color)
+            local choices='true false yes no always auto never debug'
+            COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$choices' -- "$cur"))
+            _hg_fix_wordlist
+            return
+        ;;
+        --pager)
+            local choices='true false yes no always auto never'
+            COMPREPLY=(${COMPREPLY[@]:-} $(compgen -W '$choices' -- "$cur"))
+            _hg_fix_wordlist
+            return
+        ;;
     esac
 
     if [ -z "$cmd" ] || [ $COMP_CWORD -eq $i ]; then
--- a/contrib/byteify-strings.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/byteify-strings.py	Wed Sep 26 20:33:09 2018 +0900
@@ -169,6 +169,11 @@
                 yield adjusttokenpos(t._replace(string=fn[4:]), coloffset)
                 continue
 
+        # Looks like "if __name__ == '__main__'".
+        if (t.type == token.NAME and t.string == '__name__'
+            and _isop(i + 1, '==')):
+            _ensuresysstr(i + 2)
+
         # Emit unmodified token.
         yield adjusttokenpos(t, coloffset)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/catapipe.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,90 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018 Google LLC.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""Tool read primitive events from a pipe to produce a catapult trace.
+
+For now the event stream supports
+
+  START $SESSIONID ...
+
+and
+
+  END $SESSIONID ...
+
+events. Everything after the SESSIONID (which must not contain spaces)
+is used as a label for the event. Events are timestamped as of when
+they arrive in this process and are then used to produce catapult
+traces that can be loaded in Chrome's about:tracing utility. It's
+important that the event stream *into* this process stay simple,
+because we have to emit it from the shell scripts produced by
+run-tests.py.
+
+Typically you'll want to place the path to the named pipe in the
+HGCATAPULTSERVERPIPE environment variable, which both run-tests and hg
+understand.
+"""
+from __future__ import absolute_import, print_function
+
+import argparse
+import json
+import os
+import timeit
+
+_TYPEMAP = {
+    'START': 'B',
+    'END': 'E',
+}
+
+_threadmap = {}
+
+# Timeit already contains the whole logic about which timer to use based on
+# Python version and OS
+timer = timeit.default_timer
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('pipe', type=str, nargs=1,
+                        help='Path of named pipe to create and listen on.')
+    parser.add_argument('output', default='trace.json', type=str, nargs='?',
+                        help='Path of json file to create where the traces '
+                             'will be stored.')
+    parser.add_argument('--debug', default=False, action='store_true',
+                        help='Print useful debug messages')
+    args = parser.parse_args()
+    fn = args.pipe[0]
+    os.mkfifo(fn)
+    try:
+        with open(fn) as f, open(args.output, 'w') as out:
+            out.write('[\n')
+            start = timer()
+            while True:
+                ev = f.readline().strip()
+                if not ev:
+                    continue
+                now = timer()
+                if args.debug:
+                    print(ev)
+                verb, session, label = ev.split(' ', 2)
+                if session not in _threadmap:
+                    _threadmap[session] = len(_threadmap)
+                pid = _threadmap[session]
+                ts_micros = (now - start).total_seconds() * 1000000
+                out.write(json.dumps(
+                    {
+                        "name": label,
+                        "cat": "misc",
+                        "ph": _TYPEMAP[verb],
+                        "ts": ts_micros,
+                        "pid": pid,
+                        "tid": 1,
+                        "args": {}
+                    }))
+                out.write(',\n')
+    finally:
+        os.unlink(fn)
+
+if __name__ == '__main__':
+    main()
--- a/contrib/check-code.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/check-code.py	Wed Sep 26 20:33:09 2018 +0900
@@ -30,7 +30,7 @@
     opentext = open
 else:
     def opentext(f):
-        return open(f, encoding='ascii')
+        return open(f, encoding='latin1')
 try:
     xrange
 except NameError:
@@ -503,7 +503,7 @@
   [
     (r'os\.environ', "use encoding.environ instead (py3)", r'#.*re-exports'),
     (r'os\.name', "use pycompat.osname instead (py3)"),
-    (r'os\.getcwd', "use pycompat.getcwd instead (py3)"),
+    (r'os\.getcwd', "use encoding.getcwd instead (py3)", r'#.*re-exports'),
     (r'os\.sep', "use pycompat.ossep instead (py3)"),
     (r'os\.pathsep', "use pycompat.ospathsep instead (py3)"),
     (r'os\.altsep', "use pycompat.osaltsep instead (py3)"),
@@ -511,6 +511,7 @@
     (r'getopt\.getopt', "use pycompat.getoptb instead (py3)"),
     (r'os\.getenv', "use encoding.environ.get instead"),
     (r'os\.setenv', "modifying the environ dict is not preferred"),
+    (r'(?<!pycompat\.)xrange', "use pycompat.xrange instead (py3)"),
   ],
   # warnings
   [],
--- a/contrib/check-config.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/check-config.py	Wed Sep 26 20:33:09 2018 +0900
@@ -104,12 +104,12 @@
                 ctype = m.group('ctype')
                 if not ctype:
                     ctype = 'str'
-                name = m.group('section') + "." + m.group('option')
+                name = m.group('section') + b"." + m.group('option')
                 default = m.group('default')
                 if default in (None, 'False', 'None', '0', '[]', '""', "''"):
-                    default = ''
+                    default = b''
                 if re.match(b'[a-z.]+$', default):
-                    default = '<variable>'
+                    default = b'<variable>'
                 if (name in foundopts and (ctype, default) != foundopts[name]
                     and name not in allowinconsistent):
                     print(l.rstrip())
@@ -117,19 +117,19 @@
                                                         foundopts[name]))
                     print("at %s:%d:" % (f, linenum))
                 foundopts[name] = (ctype, default)
-                carryover = ''
+                carryover = b''
             else:
                 m = re.search(configpartialre, line)
                 if m:
                     carryover = line
                 else:
-                    carryover = ''
+                    carryover = b''
 
     for name in sorted(foundopts):
         if name not in documented:
-            if not (name.startswith("devel.") or
-                    name.startswith("experimental.") or
-                    name.startswith("debug.")):
+            if not (name.startswith(b"devel.") or
+                    name.startswith(b"experimental.") or
+                    name.startswith(b"debug.")):
                 ctype, default = foundopts[name]
                 if default:
                     default = ' [%s]' % default
--- a/contrib/import-checker.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/import-checker.py	Wed Sep 26 20:33:09 2018 +0900
@@ -28,6 +28,8 @@
     'mercurial.hgweb.request',
     'mercurial.i18n',
     'mercurial.node',
+    # for revlog to re-export constant to extensions
+    'mercurial.revlogutils.constants',
     # for cffi modules to re-export pure functions
     'mercurial.pure.base85',
     'mercurial.pure.bdiff',
@@ -36,6 +38,7 @@
     'mercurial.pure.parsers',
     # third-party imports should be directly imported
     'mercurial.thirdparty',
+    'mercurial.thirdparty.attr',
     'mercurial.thirdparty.cbor',
     'mercurial.thirdparty.cbor.cbor2',
     'mercurial.thirdparty.zope',
--- a/contrib/perf.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/perf.py	Wed Sep 26 20:33:09 2018 +0900
@@ -64,12 +64,27 @@
     from mercurial import scmutil # since 1.9 (or 8b252e826c68)
 except ImportError:
     pass
+
+def identity(a):
+    return a
+
 try:
     from mercurial import pycompat
     getargspec = pycompat.getargspec  # added to module after 4.5
+    _byteskwargs = pycompat.byteskwargs  # since 4.1 (or fbc3f73dc802)
+    _sysstr = pycompat.sysstr         # since 4.0 (or 2219f4f82ede)
+    _xrange = pycompat.xrange         # since 4.8 (or 7eba8f83129b)
+    if pycompat.ispy3:
+        _maxint = sys.maxsize  # per py3 docs for replacing maxint
+    else:
+        _maxint = sys.maxint
 except (ImportError, AttributeError):
     import inspect
     getargspec = inspect.getargspec
+    _byteskwargs = identity
+    _maxint = sys.maxint              # no py3 support
+    _sysstr = lambda x: x             # no py3 support
+    _xrange = xrange
 
 try:
     # 4.7+
@@ -95,7 +110,7 @@
 # available since 1.9.3 (or 94b200a11cf7)
 _undefined = object()
 def safehasattr(thing, attr):
-    return getattr(thing, attr, _undefined) is not _undefined
+    return getattr(thing, _sysstr(attr), _undefined) is not _undefined
 setattr(util, 'safehasattr', safehasattr)
 
 # for "historical portability":
@@ -103,7 +118,7 @@
 # since ae5d60bb70c9
 if safehasattr(time, 'perf_counter'):
     util.timer = time.perf_counter
-elif os.name == 'nt':
+elif os.name == b'nt':
     util.timer = time.clock
 else:
     util.timer = time.time
@@ -123,9 +138,9 @@
 # since 1.9 (or a79fea6b3e77).
 revlogopts = getattr(cmdutil, "debugrevlogopts",
                      getattr(commands, "debugrevlogopts", [
-        ('c', 'changelog', False, ('open changelog')),
-        ('m', 'manifest', False, ('open manifest')),
-        ('', 'dir', False, ('open directory manifest')),
+        (b'c', b'changelog', False, (b'open changelog')),
+        (b'm', b'manifest', False, (b'open manifest')),
+        (b'', b'dir', False, (b'open directory manifest')),
         ]))
 
 cmdtable = {}
@@ -134,20 +149,20 @@
 # define parsealiases locally, because cmdutil.parsealiases has been
 # available since 1.5 (or 6252852b4332)
 def parsealiases(cmd):
-    return cmd.lstrip("^").split("|")
+    return cmd.lstrip(b"^").split(b"|")
 
 if safehasattr(registrar, 'command'):
     command = registrar.command(cmdtable)
 elif safehasattr(cmdutil, 'command'):
     command = cmdutil.command(cmdtable)
-    if 'norepo' not in getargspec(command).args:
+    if b'norepo' not in getargspec(command).args:
         # for "historical portability":
         # wrap original cmdutil.command, because "norepo" option has
         # been available since 3.1 (or 75a96326cecb)
         _command = command
         def command(name, options=(), synopsis=None, norepo=False):
             if norepo:
-                commands.norepo += ' %s' % ' '.join(parsealiases(name))
+                commands.norepo += b' %s' % b' '.join(parsealiases(name))
             return _command(name, list(options), synopsis)
 else:
     # for "historical portability":
@@ -160,7 +175,7 @@
             else:
                 cmdtable[name] = func, list(options)
             if norepo:
-                commands.norepo += ' %s' % ' '.join(parsealiases(name))
+                commands.norepo += b' %s' % b' '.join(parsealiases(name))
             return func
         return decorator
 
@@ -169,23 +184,23 @@
     import mercurial.configitems
     configtable = {}
     configitem = mercurial.registrar.configitem(configtable)
-    configitem('perf', 'presleep',
+    configitem(b'perf', b'presleep',
         default=mercurial.configitems.dynamicdefault,
     )
-    configitem('perf', 'stub',
+    configitem(b'perf', b'stub',
         default=mercurial.configitems.dynamicdefault,
     )
-    configitem('perf', 'parentscount',
+    configitem(b'perf', b'parentscount',
         default=mercurial.configitems.dynamicdefault,
     )
-    configitem('perf', 'all-timing',
+    configitem(b'perf', b'all-timing',
         default=mercurial.configitems.dynamicdefault,
     )
 except (ImportError, AttributeError):
     pass
 
 def getlen(ui):
-    if ui.configbool("perf", "stub", False):
+    if ui.configbool(b"perf", b"stub", False):
         return lambda x: 1
     return len
 
@@ -197,14 +212,14 @@
 
     # enforce an idle period before execution to counteract power management
     # experimental config: perf.presleep
-    time.sleep(getint(ui, "perf", "presleep", 1))
+    time.sleep(getint(ui, b"perf", b"presleep", 1))
 
     if opts is None:
         opts = {}
     # redirect all to stderr unless buffer api is in use
     if not ui._buffers:
         ui = ui.copy()
-        uifout = safeattrsetter(ui, 'fout', ignoremissing=True)
+        uifout = safeattrsetter(ui, b'fout', ignoremissing=True)
         if uifout:
             # for "historical portability":
             # ui.fout/ferr have been available since 1.9 (or 4e1ccd4c2b6d)
@@ -213,7 +228,7 @@
     # get a formatter
     uiformatter = getattr(ui, 'formatter', None)
     if uiformatter:
-        fm = uiformatter('perf', opts)
+        fm = uiformatter(b'perf', opts)
     else:
         # for "historical portability":
         # define formatter locally, because ui.formatter has been
@@ -244,15 +259,15 @@
                 self._ui.write(text, **opts)
             def end(self):
                 pass
-        fm = defaultformatter(ui, 'perf', opts)
+        fm = defaultformatter(ui, b'perf', opts)
 
     # stub function, runs code only once instead of in a loop
     # experimental config: perf.stub
-    if ui.configbool("perf", "stub", False):
+    if ui.configbool(b"perf", b"stub", False):
         return functools.partial(stub_timer, fm), fm
 
     # experimental config: perf.all-timing
-    displayall = ui.configbool("perf", "all-timing", False)
+    displayall = ui.configbool(b"perf", b"all-timing", False)
     return functools.partial(_timer, fm, displayall=displayall), fm
 
 def stub_timer(fm, func, title=None):
@@ -280,30 +295,30 @@
     fm.startitem()
 
     if title:
-        fm.write('title', '! %s\n', title)
+        fm.write(b'title', b'! %s\n', title)
     if r:
-        fm.write('result', '! result: %s\n', r)
+        fm.write(b'result', b'! result: %s\n', r)
     def display(role, entry):
-        prefix = ''
-        if role != 'best':
-            prefix = '%s.' % role
-        fm.plain('!')
-        fm.write(prefix + 'wall', ' wall %f', entry[0])
-        fm.write(prefix + 'comb', ' comb %f', entry[1] + entry[2])
-        fm.write(prefix + 'user', ' user %f', entry[1])
-        fm.write(prefix + 'sys',  ' sys %f', entry[2])
-        fm.write(prefix + 'count',  ' (%s of %d)', role, count)
-        fm.plain('\n')
+        prefix = b''
+        if role != b'best':
+            prefix = b'%s.' % role
+        fm.plain(b'!')
+        fm.write(prefix + b'wall', b' wall %f', entry[0])
+        fm.write(prefix + b'comb', b' comb %f', entry[1] + entry[2])
+        fm.write(prefix + b'user', b' user %f', entry[1])
+        fm.write(prefix + b'sys',  b' sys %f', entry[2])
+        fm.write(prefix + b'count',  b' (%s of %d)', role, count)
+        fm.plain(b'\n')
     results.sort()
     min_val = results[0]
-    display('best', min_val)
+    display(b'best', min_val)
     if displayall:
         max_val = results[-1]
-        display('max', max_val)
+        display(b'max', max_val)
         avg = tuple([sum(x) / count for x in zip(*results)])
-        display('avg', avg)
+        display(b'avg', avg)
         median = results[len(results) // 2]
-        display('median', median)
+        display(b'median', median)
 
 # utilities for historical portability
 
@@ -316,7 +331,7 @@
     try:
         return int(v)
     except ValueError:
-        raise error.ConfigError(("%s.%s is not an integer ('%s')")
+        raise error.ConfigError((b"%s.%s is not an integer ('%s')")
                                 % (section, name, v))
 
 def safeattrsetter(obj, name, ignoremissing=False):
@@ -337,15 +352,15 @@
     if not util.safehasattr(obj, name):
         if ignoremissing:
             return None
-        raise error.Abort(("missing attribute %s of %s might break assumption"
-                           " of performance measurement") % (name, obj))
+        raise error.Abort((b"missing attribute %s of %s might break assumption"
+                           b" of performance measurement") % (name, obj))
 
-    origvalue = getattr(obj, name)
+    origvalue = getattr(obj, _sysstr(name))
     class attrutil(object):
         def set(self, newvalue):
-            setattr(obj, name, newvalue)
+            setattr(obj, _sysstr(name), newvalue)
         def restore(self):
-            setattr(obj, name, origvalue)
+            setattr(obj, _sysstr(name), origvalue)
 
     return attrutil()
 
@@ -364,8 +379,8 @@
     # bisecting in bcee63733aad::59a9f18d4587 can reach here (both
     # branchmap and repoview modules exist, but subsettable attribute
     # doesn't)
-    raise error.Abort(("perfbranchmap not available with this Mercurial"),
-                      hint="use 2.5 or later")
+    raise error.Abort((b"perfbranchmap not available with this Mercurial"),
+                      hint=b"use 2.5 or later")
 
 def getsvfs(repo):
     """Return appropriate object to access files under .hg/store
@@ -392,22 +407,22 @@
 def repocleartagscachefunc(repo):
     """Return the function to clear tags cache according to repo internal API
     """
-    if util.safehasattr(repo, '_tagscache'): # since 2.0 (or 9dca7653b525)
+    if util.safehasattr(repo, b'_tagscache'): # since 2.0 (or 9dca7653b525)
         # in this case, setattr(repo, '_tagscache', None) or so isn't
         # correct way to clear tags cache, because existing code paths
         # expect _tagscache to be a structured object.
         def clearcache():
             # _tagscache has been filteredpropertycache since 2.5 (or
             # 98c867ac1330), and delattr() can't work in such case
-            if '_tagscache' in vars(repo):
-                del repo.__dict__['_tagscache']
+            if b'_tagscache' in vars(repo):
+                del repo.__dict__[b'_tagscache']
         return clearcache
 
-    repotags = safeattrsetter(repo, '_tags', ignoremissing=True)
+    repotags = safeattrsetter(repo, b'_tags', ignoremissing=True)
     if repotags: # since 1.4 (or 5614a628d173)
         return lambda : repotags.set(None)
 
-    repotagscache = safeattrsetter(repo, 'tagscache', ignoremissing=True)
+    repotagscache = safeattrsetter(repo, b'tagscache', ignoremissing=True)
     if repotagscache: # since 0.6 (or d7df759d0e97)
         return lambda : repotagscache.set(None)
 
@@ -416,7 +431,7 @@
     # - repo.tags of such Mercurial isn't "callable", and repo.tags()
     #   in perftags() causes failure soon
     # - perf.py itself has been available since 1.1 (or eb240755386d)
-    raise error.Abort(("tags API of this hg command is unknown"))
+    raise error.Abort((b"tags API of this hg command is unknown"))
 
 # utilities to clear cache
 
@@ -428,56 +443,61 @@
 
 # perf commands
 
-@command('perfwalk', formatteropts)
+@command(b'perfwalk', formatteropts)
 def perfwalk(ui, repo, *pats, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     m = scmutil.match(repo[None], pats, {})
     timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
                                               ignored=False))))
     fm.end()
 
-@command('perfannotate', formatteropts)
+@command(b'perfannotate', formatteropts)
 def perfannotate(ui, repo, f, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
-    fc = repo['.'][f]
+    fc = repo[b'.'][f]
     timer(lambda: len(fc.annotate(True)))
     fm.end()
 
-@command('perfstatus',
-         [('u', 'unknown', False,
-           'ask status to look for unknown files')] + formatteropts)
+@command(b'perfstatus',
+         [(b'u', b'unknown', False,
+           b'ask status to look for unknown files')] + formatteropts)
 def perfstatus(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     #m = match.always(repo.root, repo.getcwd())
     #timer(lambda: sum(map(len, repo.dirstate.status(m, [], False, False,
     #                                                False))))
     timer, fm = gettimer(ui, opts)
-    timer(lambda: sum(map(len, repo.status(unknown=opts['unknown']))))
+    timer(lambda: sum(map(len, repo.status(unknown=opts[b'unknown']))))
     fm.end()
 
-@command('perfaddremove', formatteropts)
+@command(b'perfaddremove', formatteropts)
 def perfaddremove(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     try:
         oldquiet = repo.ui.quiet
         repo.ui.quiet = True
         matcher = scmutil.match(repo[None])
-        opts['dry_run'] = True
-        timer(lambda: scmutil.addremove(repo, matcher, "", opts))
+        opts[b'dry_run'] = True
+        timer(lambda: scmutil.addremove(repo, matcher, b"", opts))
     finally:
         repo.ui.quiet = oldquiet
         fm.end()
 
 def clearcaches(cl):
     # behave somewhat consistently across internal API changes
-    if util.safehasattr(cl, 'clearcaches'):
+    if util.safehasattr(cl, b'clearcaches'):
         cl.clearcaches()
-    elif util.safehasattr(cl, '_nodecache'):
+    elif util.safehasattr(cl, b'_nodecache'):
         from mercurial.node import nullid, nullrev
         cl._nodecache = {nullid: nullrev}
         cl._nodepos = None
 
-@command('perfheads', formatteropts)
+@command(b'perfheads', formatteropts)
 def perfheads(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     cl = repo.changelog
     def d():
@@ -486,23 +506,28 @@
     timer(d)
     fm.end()
 
-@command('perftags', formatteropts)
+@command(b'perftags', formatteropts)
 def perftags(ui, repo, **opts):
     import mercurial.changelog
     import mercurial.manifest
+
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     svfs = getsvfs(repo)
     repocleartagscache = repocleartagscachefunc(repo)
     def t():
         repo.changelog = mercurial.changelog.changelog(svfs)
-        repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo)
+        rootmanifest = mercurial.manifest.manifestrevlog(svfs)
+        repo.manifestlog = mercurial.manifest.manifestlog(svfs, repo,
+                                                          rootmanifest)
         repocleartagscache()
         return len(repo.tags())
     timer(t)
     fm.end()
 
-@command('perfancestors', formatteropts)
+@command(b'perfancestors', formatteropts)
 def perfancestors(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     heads = repo.changelog.headrevs()
     def d():
@@ -511,8 +536,9 @@
     timer(d)
     fm.end()
 
-@command('perfancestorset', formatteropts)
+@command(b'perfancestorset', formatteropts)
 def perfancestorset(ui, repo, revset, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     revs = repo.revs(revset)
     heads = repo.changelog.headrevs()
@@ -523,17 +549,18 @@
     timer(d)
     fm.end()
 
-@command('perfbookmarks', formatteropts)
+@command(b'perfbookmarks', formatteropts)
 def perfbookmarks(ui, repo, **opts):
     """benchmark parsing bookmarks from disk to memory"""
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     def d():
-        clearfilecache(repo, '_bookmarks')
+        clearfilecache(repo, b'_bookmarks')
         repo._bookmarks
     timer(d)
     fm.end()
 
-@command('perfbundleread', formatteropts, 'BUNDLE')
+@command(b'perfbundleread', formatteropts, b'BUNDLE')
 def perfbundleread(ui, repo, bundlepath, **opts):
     """Benchmark reading of bundle files.
 
@@ -546,9 +573,11 @@
         streamclone,
     )
 
+    opts = _byteskwargs(opts)
+
     def makebench(fn):
         def run():
-            with open(bundlepath, 'rb') as fh:
+            with open(bundlepath, b'rb') as fh:
                 bundle = exchange.readbundle(ui, fh, bundlepath)
                 fn(bundle)
 
@@ -556,7 +585,7 @@
 
     def makereadnbytes(size):
         def run():
-            with open(bundlepath, 'rb') as fh:
+            with open(bundlepath, b'rb') as fh:
                 bundle = exchange.readbundle(ui, fh, bundlepath)
                 while bundle.read(size):
                     pass
@@ -565,7 +594,7 @@
 
     def makestdioread(size):
         def run():
-            with open(bundlepath, 'rb') as fh:
+            with open(bundlepath, b'rb') as fh:
                 while fh.read(size):
                     pass
 
@@ -601,7 +630,7 @@
 
     def makepartreadnbytes(size):
         def run():
-            with open(bundlepath, 'rb') as fh:
+            with open(bundlepath, b'rb') as fh:
                 bundle = exchange.readbundle(ui, fh, bundlepath)
                 for part in bundle.iterparts():
                     while part.read(size):
@@ -610,49 +639,49 @@
         return run
 
     benches = [
-        (makestdioread(8192), 'read(8k)'),
-        (makestdioread(16384), 'read(16k)'),
-        (makestdioread(32768), 'read(32k)'),
-        (makestdioread(131072), 'read(128k)'),
+        (makestdioread(8192), b'read(8k)'),
+        (makestdioread(16384), b'read(16k)'),
+        (makestdioread(32768), b'read(32k)'),
+        (makestdioread(131072), b'read(128k)'),
     ]
 
-    with open(bundlepath, 'rb') as fh:
+    with open(bundlepath, b'rb') as fh:
         bundle = exchange.readbundle(ui, fh, bundlepath)
 
         if isinstance(bundle, changegroup.cg1unpacker):
             benches.extend([
-                (makebench(deltaiter), 'cg1 deltaiter()'),
-                (makebench(iterchunks), 'cg1 getchunks()'),
-                (makereadnbytes(8192), 'cg1 read(8k)'),
-                (makereadnbytes(16384), 'cg1 read(16k)'),
-                (makereadnbytes(32768), 'cg1 read(32k)'),
-                (makereadnbytes(131072), 'cg1 read(128k)'),
+                (makebench(deltaiter), b'cg1 deltaiter()'),
+                (makebench(iterchunks), b'cg1 getchunks()'),
+                (makereadnbytes(8192), b'cg1 read(8k)'),
+                (makereadnbytes(16384), b'cg1 read(16k)'),
+                (makereadnbytes(32768), b'cg1 read(32k)'),
+                (makereadnbytes(131072), b'cg1 read(128k)'),
             ])
         elif isinstance(bundle, bundle2.unbundle20):
             benches.extend([
-                (makebench(forwardchunks), 'bundle2 forwardchunks()'),
-                (makebench(iterparts), 'bundle2 iterparts()'),
-                (makebench(iterpartsseekable), 'bundle2 iterparts() seekable'),
-                (makebench(seek), 'bundle2 part seek()'),
-                (makepartreadnbytes(8192), 'bundle2 part read(8k)'),
-                (makepartreadnbytes(16384), 'bundle2 part read(16k)'),
-                (makepartreadnbytes(32768), 'bundle2 part read(32k)'),
-                (makepartreadnbytes(131072), 'bundle2 part read(128k)'),
+                (makebench(forwardchunks), b'bundle2 forwardchunks()'),
+                (makebench(iterparts), b'bundle2 iterparts()'),
+                (makebench(iterpartsseekable), b'bundle2 iterparts() seekable'),
+                (makebench(seek), b'bundle2 part seek()'),
+                (makepartreadnbytes(8192), b'bundle2 part read(8k)'),
+                (makepartreadnbytes(16384), b'bundle2 part read(16k)'),
+                (makepartreadnbytes(32768), b'bundle2 part read(32k)'),
+                (makepartreadnbytes(131072), b'bundle2 part read(128k)'),
             ])
         elif isinstance(bundle, streamclone.streamcloneapplier):
-            raise error.Abort('stream clone bundles not supported')
+            raise error.Abort(b'stream clone bundles not supported')
         else:
-            raise error.Abort('unhandled bundle type: %s' % type(bundle))
+            raise error.Abort(b'unhandled bundle type: %s' % type(bundle))
 
     for fn, title in benches:
         timer, fm = gettimer(ui, opts)
         timer(fn, title=title)
         fm.end()
 
-@command('perfchangegroupchangelog', formatteropts +
-         [('', 'version', '02', 'changegroup version'),
-          ('r', 'rev', '', 'revisions to add to changegroup')])
-def perfchangegroupchangelog(ui, repo, version='02', rev=None, **opts):
+@command(b'perfchangegroupchangelog', formatteropts +
+         [(b'', b'version', b'02', b'changegroup version'),
+          (b'r', b'rev', b'', b'revisions to add to changegroup')])
+def perfchangegroupchangelog(ui, repo, version=b'02', rev=None, **opts):
     """Benchmark producing a changelog group for a changegroup.
 
     This measures the time spent processing the changelog during a
@@ -662,92 +691,99 @@
 
     By default, all revisions are added to the changegroup.
     """
+    opts = _byteskwargs(opts)
     cl = repo.changelog
-    revs = [cl.lookup(r) for r in repo.revs(rev or 'all()')]
+    nodes = [cl.lookup(r) for r in repo.revs(rev or b'all()')]
     bundler = changegroup.getbundler(version, repo)
 
-    def lookup(node):
-        # The real bundler reads the revision in order to access the
-        # manifest node and files list. Do that here.
-        cl.read(node)
-        return node
-
     def d():
-        for chunk in bundler.group(revs, cl, lookup):
+        state, chunks = bundler._generatechangelog(cl, nodes)
+        for chunk in chunks:
             pass
 
     timer, fm = gettimer(ui, opts)
-    timer(d)
+
+    # Terminal printing can interfere with timing. So disable it.
+    with ui.configoverride({(b'progress', b'disable'): True}):
+        timer(d)
+
     fm.end()
 
-@command('perfdirs', formatteropts)
+@command(b'perfdirs', formatteropts)
 def perfdirs(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     dirstate = repo.dirstate
-    'a' in dirstate
+    b'a' in dirstate
     def d():
-        dirstate.hasdir('a')
+        dirstate.hasdir(b'a')
         del dirstate._map._dirs
     timer(d)
     fm.end()
 
-@command('perfdirstate', formatteropts)
+@command(b'perfdirstate', formatteropts)
 def perfdirstate(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
-    "a" in repo.dirstate
+    b"a" in repo.dirstate
     def d():
         repo.dirstate.invalidate()
-        "a" in repo.dirstate
+        b"a" in repo.dirstate
     timer(d)
     fm.end()
 
-@command('perfdirstatedirs', formatteropts)
+@command(b'perfdirstatedirs', formatteropts)
 def perfdirstatedirs(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
-    "a" in repo.dirstate
+    b"a" in repo.dirstate
     def d():
-        repo.dirstate.hasdir("a")
+        repo.dirstate.hasdir(b"a")
         del repo.dirstate._map._dirs
     timer(d)
     fm.end()
 
-@command('perfdirstatefoldmap', formatteropts)
+@command(b'perfdirstatefoldmap', formatteropts)
 def perfdirstatefoldmap(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     dirstate = repo.dirstate
-    'a' in dirstate
+    b'a' in dirstate
     def d():
-        dirstate._map.filefoldmap.get('a')
+        dirstate._map.filefoldmap.get(b'a')
         del dirstate._map.filefoldmap
     timer(d)
     fm.end()
 
-@command('perfdirfoldmap', formatteropts)
+@command(b'perfdirfoldmap', formatteropts)
 def perfdirfoldmap(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     dirstate = repo.dirstate
-    'a' in dirstate
+    b'a' in dirstate
     def d():
-        dirstate._map.dirfoldmap.get('a')
+        dirstate._map.dirfoldmap.get(b'a')
         del dirstate._map.dirfoldmap
         del dirstate._map._dirs
     timer(d)
     fm.end()
 
-@command('perfdirstatewrite', formatteropts)
+@command(b'perfdirstatewrite', formatteropts)
 def perfdirstatewrite(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     ds = repo.dirstate
-    "a" in ds
+    b"a" in ds
     def d():
         ds._dirty = True
         ds.write(repo.currenttransaction())
     timer(d)
     fm.end()
 
-@command('perfmergecalculate',
-         [('r', 'rev', '.', 'rev to merge against')] + formatteropts)
+@command(b'perfmergecalculate',
+         [(b'r', b'rev', b'.', b'rev to merge against')] + formatteropts)
 def perfmergecalculate(ui, repo, rev, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     wctx = repo[None]
     rctx = scmutil.revsingle(repo, rev, rev)
@@ -763,8 +799,9 @@
     timer(d)
     fm.end()
 
-@command('perfpathcopies', [], "REV REV")
+@command(b'perfpathcopies', [], b"REV REV")
 def perfpathcopies(ui, repo, rev1, rev2, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     ctx1 = scmutil.revsingle(repo, rev1, rev1)
     ctx2 = scmutil.revsingle(repo, rev2, rev2)
@@ -773,26 +810,27 @@
     timer(d)
     fm.end()
 
-@command('perfphases',
-         [('', 'full', False, 'include file reading time too'),
-         ], "")
+@command(b'perfphases',
+         [(b'', b'full', False, b'include file reading time too'),
+          ], b"")
 def perfphases(ui, repo, **opts):
     """benchmark phasesets computation"""
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     _phases = repo._phasecache
-    full = opts.get('full')
+    full = opts.get(b'full')
     def d():
         phases = _phases
         if full:
-            clearfilecache(repo, '_phasecache')
+            clearfilecache(repo, b'_phasecache')
             phases = repo._phasecache
         phases.invalidate()
         phases.loadphaserevs(repo)
     timer(d)
     fm.end()
 
-@command('perfphasesremote',
-         [], "[DEST]")
+@command(b'perfphasesremote',
+         [], b"[DEST]")
 def perfphasesremote(ui, repo, dest=None, **opts):
     """benchmark time needed to analyse phases of the remote server"""
     from mercurial.node import (
@@ -803,16 +841,17 @@
         hg,
         phases,
     )
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
 
-    path = ui.paths.getpath(dest, default=('default-push', 'default'))
+    path = ui.paths.getpath(dest, default=(b'default-push', b'default'))
     if not path:
-        raise error.Abort(('default repository not configured!'),
-                         hint=("see 'hg help config.paths'"))
+        raise error.Abort((b'default repository not configured!'),
+                          hint=(b"see 'hg help config.paths'"))
     dest = path.pushloc or path.loc
-    branches = (path.branch, opts.get('branch') or [])
-    ui.status(('analysing phase of %s\n') % util.hidepassword(dest))
-    revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get('rev'))
+    branches = (path.branch, opts.get(b'branch') or [])
+    ui.status((b'analysing phase of %s\n') % util.hidepassword(dest))
+    revs, checkout = hg.addbranchrevs(repo, repo, branches, opts.get(b'rev'))
     other = hg.peer(repo, opts, dest)
 
     # easier to perform discovery through the operation
@@ -822,25 +861,25 @@
     remotesubset = op.fallbackheads
 
     with other.commandexecutor() as e:
-        remotephases = e.callcommand('listkeys',
-                       {'namespace': 'phases'}).result()
+        remotephases = e.callcommand(b'listkeys',
+                       {b'namespace': b'phases'}).result()
     del other
-    publishing = remotephases.get('publishing', False)
+    publishing = remotephases.get(b'publishing', False)
     if publishing:
-        ui.status(('publishing: yes\n'))
+        ui.status((b'publishing: yes\n'))
     else:
-        ui.status(('publishing: no\n'))
+        ui.status((b'publishing: no\n'))
 
     nodemap = repo.changelog.nodemap
     nonpublishroots = 0
     for nhex, phase in remotephases.iteritems():
-        if nhex == 'publishing': # ignore data related to publish option
+        if nhex == b'publishing': # ignore data related to publish option
             continue
         node = bin(nhex)
         if node in nodemap and int(phase):
             nonpublishroots += 1
-    ui.status(('number of roots: %d\n') % len(remotephases))
-    ui.status(('number of known non public roots: %d\n') % nonpublishroots)
+    ui.status((b'number of roots: %d\n') % len(remotephases))
+    ui.status((b'number of known non public roots: %d\n') % nonpublishroots)
     def d():
         phases.remotephasessummary(repo,
                                    remotesubset,
@@ -848,23 +887,45 @@
     timer(d)
     fm.end()
 
-@command('perfmanifest', [], 'REV')
-def perfmanifest(ui, repo, rev, **opts):
+@command(b'perfmanifest',[
+            (b'm', b'manifest-rev', False, b'Look up a manifest node revision'),
+            (b'', b'clear-disk', False, b'clear on-disk caches too'),
+         ], b'REV|NODE')
+def perfmanifest(ui, repo, rev, manifest_rev=False, clear_disk=False, **opts):
     """benchmark the time to read a manifest from disk and return a usable
     dict-like object
 
     Manifest caches are cleared before retrieval."""
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
-    ctx = scmutil.revsingle(repo, rev, rev)
-    t = ctx.manifestnode()
+    if not manifest_rev:
+        ctx = scmutil.revsingle(repo, rev, rev)
+        t = ctx.manifestnode()
+    else:
+        from mercurial.node import bin
+
+        if len(rev) == 40:
+            t = bin(rev)
+        else:
+            try:
+                rev = int(rev)
+
+                if util.safehasattr(repo.manifestlog, b'getstorage'):
+                    t = repo.manifestlog.getstorage(b'').node(rev)
+                else:
+                    t = repo.manifestlog._revlog.lookup(rev)
+            except ValueError:
+                raise error.Abort(b'manifest revision must be integer or full '
+                                  b'node')
     def d():
-        repo.manifestlog.clearcaches()
+        repo.manifestlog.clearcaches(clear_persisted_data=clear_disk)
         repo.manifestlog[t].read()
     timer(d)
     fm.end()
 
-@command('perfchangeset', formatteropts)
+@command(b'perfchangeset', formatteropts)
 def perfchangeset(ui, repo, rev, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     n = scmutil.revsingle(repo, rev).node()
     def d():
@@ -873,50 +934,54 @@
     timer(d)
     fm.end()
 
-@command('perfindex', formatteropts)
+@command(b'perfindex', formatteropts)
 def perfindex(ui, repo, **opts):
     import mercurial.revlog
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
-    n = repo["tip"].node()
+    n = repo[b"tip"].node()
     svfs = getsvfs(repo)
     def d():
-        cl = mercurial.revlog.revlog(svfs, "00changelog.i")
+        cl = mercurial.revlog.revlog(svfs, b"00changelog.i")
         cl.rev(n)
     timer(d)
     fm.end()
 
-@command('perfstartup', formatteropts)
+@command(b'perfstartup', formatteropts)
 def perfstartup(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     cmd = sys.argv[0]
     def d():
-        if os.name != 'nt':
-            os.system("HGRCPATH= %s version -q > /dev/null" % cmd)
+        if os.name != r'nt':
+            os.system(b"HGRCPATH= %s version -q > /dev/null" % cmd)
         else:
-            os.environ['HGRCPATH'] = ' '
-            os.system("%s version -q > NUL" % cmd)
+            os.environ[r'HGRCPATH'] = r' '
+            os.system(r"%s version -q > NUL" % cmd)
     timer(d)
     fm.end()
 
-@command('perfparents', formatteropts)
+@command(b'perfparents', formatteropts)
 def perfparents(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     # control the number of commits perfparents iterates over
     # experimental config: perf.parentscount
-    count = getint(ui, "perf", "parentscount", 1000)
+    count = getint(ui, b"perf", b"parentscount", 1000)
     if len(repo.changelog) < count:
-        raise error.Abort("repo needs %d commits for this test" % count)
+        raise error.Abort(b"repo needs %d commits for this test" % count)
     repo = repo.unfiltered()
-    nl = [repo.changelog.node(i) for i in xrange(count)]
+    nl = [repo.changelog.node(i) for i in _xrange(count)]
     def d():
         for n in nl:
             repo.changelog.parents(n)
     timer(d)
     fm.end()
 
-@command('perfctxfiles', formatteropts)
+@command(b'perfctxfiles', formatteropts)
 def perfctxfiles(ui, repo, x, **opts):
+    opts = _byteskwargs(opts)
     x = int(x)
     timer, fm = gettimer(ui, opts)
     def d():
@@ -924,8 +989,9 @@
     timer(d)
     fm.end()
 
-@command('perfrawfiles', formatteropts)
+@command(b'perfrawfiles', formatteropts)
 def perfrawfiles(ui, repo, x, **opts):
+    opts = _byteskwargs(opts)
     x = int(x)
     timer, fm = gettimer(ui, opts)
     cl = repo.changelog
@@ -934,77 +1000,119 @@
     timer(d)
     fm.end()
 
-@command('perflookup', formatteropts)
+@command(b'perflookup', formatteropts)
 def perflookup(ui, repo, rev, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     timer(lambda: len(repo.lookup(rev)))
     fm.end()
 
-@command('perfrevrange', formatteropts)
+@command(b'perflinelogedits',
+         [(b'n', b'edits', 10000, b'number of edits'),
+          (b'', b'max-hunk-lines', 10, b'max lines in a hunk'),
+          ], norepo=True)
+def perflinelogedits(ui, **opts):
+    from mercurial import linelog
+
+    opts = _byteskwargs(opts)
+
+    edits = opts[b'edits']
+    maxhunklines = opts[b'max_hunk_lines']
+
+    maxb1 = 100000
+    random.seed(0)
+    randint = random.randint
+    currentlines = 0
+    arglist = []
+    for rev in _xrange(edits):
+        a1 = randint(0, currentlines)
+        a2 = randint(a1, min(currentlines, a1 + maxhunklines))
+        b1 = randint(0, maxb1)
+        b2 = randint(b1, b1 + maxhunklines)
+        currentlines += (b2 - b1) - (a2 - a1)
+        arglist.append((rev, a1, a2, b1, b2))
+
+    def d():
+        ll = linelog.linelog()
+        for args in arglist:
+            ll.replacelines(*args)
+
+    timer, fm = gettimer(ui, opts)
+    timer(d)
+    fm.end()
+
+@command(b'perfrevrange', formatteropts)
 def perfrevrange(ui, repo, *specs, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     revrange = scmutil.revrange
     timer(lambda: len(revrange(repo, specs)))
     fm.end()
 
-@command('perfnodelookup', formatteropts)
+@command(b'perfnodelookup', formatteropts)
 def perfnodelookup(ui, repo, rev, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     import mercurial.revlog
     mercurial.revlog._prereadsize = 2**24 # disable lazy parser in old hg
     n = scmutil.revsingle(repo, rev).node()
-    cl = mercurial.revlog.revlog(getsvfs(repo), "00changelog.i")
+    cl = mercurial.revlog.revlog(getsvfs(repo), b"00changelog.i")
     def d():
         cl.rev(n)
         clearcaches(cl)
     timer(d)
     fm.end()
 
-@command('perflog',
-         [('', 'rename', False, 'ask log to follow renames')] + formatteropts)
+@command(b'perflog',
+         [(b'', b'rename', False, b'ask log to follow renames')
+         ] + formatteropts)
 def perflog(ui, repo, rev=None, **opts):
+    opts = _byteskwargs(opts)
     if rev is None:
         rev=[]
     timer, fm = gettimer(ui, opts)
     ui.pushbuffer()
-    timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
-                               copies=opts.get('rename')))
+    timer(lambda: commands.log(ui, repo, rev=rev, date=b'', user=b'',
+                               copies=opts.get(b'rename')))
     ui.popbuffer()
     fm.end()
 
-@command('perfmoonwalk', formatteropts)
+@command(b'perfmoonwalk', formatteropts)
 def perfmoonwalk(ui, repo, **opts):
     """benchmark walking the changelog backwards
 
     This also loads the changelog data for each revision in the changelog.
     """
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     def moonwalk():
-        for i in xrange(len(repo), -1, -1):
+        for i in repo.changelog.revs(start=(len(repo) - 1), stop=-1):
             ctx = repo[i]
             ctx.branch() # read changelog data (in addition to the index)
     timer(moonwalk)
     fm.end()
 
-@command('perftemplating',
-         [('r', 'rev', [], 'revisions to run the template on'),
-         ] + formatteropts)
+@command(b'perftemplating',
+         [(b'r', b'rev', [], b'revisions to run the template on'),
+          ] + formatteropts)
 def perftemplating(ui, repo, testedtemplate=None, **opts):
     """test the rendering time of a given template"""
     if makelogtemplater is None:
-        raise error.Abort(("perftemplating not available with this Mercurial"),
-                          hint="use 4.3 or later")
+        raise error.Abort((b"perftemplating not available with this Mercurial"),
+                          hint=b"use 4.3 or later")
+
+    opts = _byteskwargs(opts)
 
     nullui = ui.copy()
-    nullui.fout = open(os.devnull, 'wb')
+    nullui.fout = open(os.devnull, r'wb')
     nullui.disablepager()
-    revs = opts.get('rev')
+    revs = opts.get(b'rev')
     if not revs:
-        revs = ['all()']
+        revs = [b'all()']
     revs = list(scmutil.revrange(repo, revs))
 
-    defaulttemplate = ('{date|shortdate} [{rev}:{node|short}]'
-                       ' {author|person}: {desc|firstline}\n')
+    defaulttemplate = (b'{date|shortdate} [{rev}:{node|short}]'
+                       b' {author|person}: {desc|firstline}\n')
     if testedtemplate is None:
         testedtemplate = defaulttemplate
     displayer = makelogtemplater(nullui, repo, testedtemplate)
@@ -1018,14 +1126,16 @@
     timer(format)
     fm.end()
 
-@command('perfcca', formatteropts)
+@command(b'perfcca', formatteropts)
 def perfcca(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     timer(lambda: scmutil.casecollisionauditor(ui, False, repo.dirstate))
     fm.end()
 
-@command('perffncacheload', formatteropts)
+@command(b'perffncacheload', formatteropts)
 def perffncacheload(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     s = repo.store
     def d():
@@ -1033,14 +1143,15 @@
     timer(d)
     fm.end()
 
-@command('perffncachewrite', formatteropts)
+@command(b'perffncachewrite', formatteropts)
 def perffncachewrite(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     s = repo.store
     lock = repo.lock()
     s.fncache._load()
-    tr = repo.transaction('perffncachewrite')
-    tr.addbackup('fncache')
+    tr = repo.transaction(b'perffncachewrite')
+    tr.addbackup(b'fncache')
     def d():
         s.fncache._dirty = True
         s.fncache.write(tr)
@@ -1049,8 +1160,9 @@
     lock.release()
     fm.end()
 
-@command('perffncacheencode', formatteropts)
+@command(b'perffncacheencode', formatteropts)
 def perffncacheencode(ui, repo, **opts):
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     s = repo.store
     s.fncache._load()
@@ -1076,15 +1188,25 @@
         with ready:
             ready.wait()
 
-@command('perfbdiff', revlogopts + formatteropts + [
-    ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
-    ('', 'alldata', False, 'test bdiffs for all associated revisions'),
-    ('', 'threads', 0, 'number of thread to use (disable with 0)'),
-    ('', 'blocks', False, 'test computing diffs into blocks'),
-    ('', 'xdiff', False, 'use xdiff algorithm'),
+def _manifestrevision(repo, mnode):
+    ml = repo.manifestlog
+
+    if util.safehasattr(ml, b'getstorage'):
+        store = ml.getstorage(b'')
+    else:
+        store = ml._revlog
+
+    return store.revision(mnode)
+
+@command(b'perfbdiff', revlogopts + formatteropts + [
+    (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
+    (b'', b'alldata', False, b'test bdiffs for all associated revisions'),
+    (b'', b'threads', 0, b'number of thread to use (disable with 0)'),
+    (b'', b'blocks', False, b'test computing diffs into blocks'),
+    (b'', b'xdiff', False, b'use xdiff algorithm'),
     ],
 
-    '-c|-m|FILE REV')
+    b'-c|-m|FILE REV')
 def perfbdiff(ui, repo, file_, rev=None, count=None, threads=0, **opts):
     """benchmark a bdiff between revisions
 
@@ -1097,33 +1219,33 @@
     measure bdiffs for all changes related to that changeset (manifest
     and filelogs).
     """
-    opts = pycompat.byteskwargs(opts)
+    opts = _byteskwargs(opts)
 
-    if opts['xdiff'] and not opts['blocks']:
-        raise error.CommandError('perfbdiff', '--xdiff requires --blocks')
+    if opts[b'xdiff'] and not opts[b'blocks']:
+        raise error.CommandError(b'perfbdiff', b'--xdiff requires --blocks')
 
-    if opts['alldata']:
-        opts['changelog'] = True
+    if opts[b'alldata']:
+        opts[b'changelog'] = True
 
-    if opts.get('changelog') or opts.get('manifest'):
+    if opts.get(b'changelog') or opts.get(b'manifest'):
         file_, rev = None, file_
     elif rev is None:
-        raise error.CommandError('perfbdiff', 'invalid arguments')
+        raise error.CommandError(b'perfbdiff', b'invalid arguments')
 
-    blocks = opts['blocks']
-    xdiff = opts['xdiff']
+    blocks = opts[b'blocks']
+    xdiff = opts[b'xdiff']
     textpairs = []
 
-    r = cmdutil.openrevlog(repo, 'perfbdiff', file_, opts)
+    r = cmdutil.openrevlog(repo, b'perfbdiff', file_, opts)
 
     startrev = r.rev(r.lookup(rev))
     for rev in range(startrev, min(startrev + count, len(r) - 1)):
-        if opts['alldata']:
+        if opts[b'alldata']:
             # Load revisions associated with changeset.
             ctx = repo[rev]
-            mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
+            mtext = _manifestrevision(repo, ctx.manifestnode())
             for pctx in ctx.parents():
-                pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
+                pman = _manifestrevision(repo, pctx.manifestnode())
                 textpairs.append((pman, mtext))
 
             # Load filelog revisions by iterating manifest delta.
@@ -1150,18 +1272,18 @@
                     mdiff.textdiff(*pair)
     else:
         q = queue()
-        for i in xrange(threads):
+        for i in _xrange(threads):
             q.put(None)
         ready = threading.Condition()
         done = threading.Event()
-        for i in xrange(threads):
+        for i in _xrange(threads):
             threading.Thread(target=_bdiffworker,
                              args=(q, blocks, xdiff, ready, done)).start()
         q.join()
         def d():
             for pair in textpairs:
                 q.put(pair)
-            for i in xrange(threads):
+            for i in _xrange(threads):
                 q.put(None)
             with ready:
                 ready.notify_all()
@@ -1172,15 +1294,15 @@
 
     if withthreads:
         done.set()
-        for i in xrange(threads):
+        for i in _xrange(threads):
             q.put(None)
         with ready:
             ready.notify_all()
 
-@command('perfunidiff', revlogopts + formatteropts + [
-    ('', 'count', 1, 'number of revisions to test (when using --startrev)'),
-    ('', 'alldata', False, 'test unidiffs for all associated revisions'),
-    ], '-c|-m|FILE REV')
+@command(b'perfunidiff', revlogopts + formatteropts + [
+    (b'', b'count', 1, b'number of revisions to test (when using --startrev)'),
+    (b'', b'alldata', False, b'test unidiffs for all associated revisions'),
+    ], b'-c|-m|FILE REV')
 def perfunidiff(ui, repo, file_, rev=None, count=None, **opts):
     """benchmark a unified diff between revisions
 
@@ -1196,26 +1318,27 @@
     measure diffs for all changes related to that changeset (manifest
     and filelogs).
     """
-    if opts['alldata']:
-        opts['changelog'] = True
+    opts = _byteskwargs(opts)
+    if opts[b'alldata']:
+        opts[b'changelog'] = True
 
-    if opts.get('changelog') or opts.get('manifest'):
+    if opts.get(b'changelog') or opts.get(b'manifest'):
         file_, rev = None, file_
     elif rev is None:
-        raise error.CommandError('perfunidiff', 'invalid arguments')
+        raise error.CommandError(b'perfunidiff', b'invalid arguments')
 
     textpairs = []
 
-    r = cmdutil.openrevlog(repo, 'perfunidiff', file_, opts)
+    r = cmdutil.openrevlog(repo, b'perfunidiff', file_, opts)
 
     startrev = r.rev(r.lookup(rev))
     for rev in range(startrev, min(startrev + count, len(r) - 1)):
-        if opts['alldata']:
+        if opts[b'alldata']:
             # Load revisions associated with changeset.
             ctx = repo[rev]
-            mtext = repo.manifestlog._revlog.revision(ctx.manifestnode())
+            mtext = _manifestrevision(repo, ctx.manifestnode())
             for pctx in ctx.parents():
-                pman = repo.manifestlog._revlog.revision(pctx.manifestnode())
+                pman = _manifestrevision(repo, pctx.manifestnode())
                 textpairs.append((pman, mtext))
 
             # Load filelog revisions by iterating manifest delta.
@@ -1234,7 +1357,7 @@
         for left, right in textpairs:
             # The date strings don't matter, so we pass empty strings.
             headerlines, hunks = mdiff.unidiff(
-                left, '', right, '', 'left', 'right', binary=False)
+                left, b'', right, b'', b'left', b'right', binary=False)
             # consume iterators in roughly the way patch.py does
             b'\n'.join(headerlines)
             b''.join(sum((list(hlines) for hrange, hlines in hunks), []))
@@ -1242,28 +1365,29 @@
     timer(d)
     fm.end()
 
-@command('perfdiffwd', formatteropts)
+@command(b'perfdiffwd', formatteropts)
 def perfdiffwd(ui, repo, **opts):
     """Profile diff of working directory changes"""
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     options = {
-        'w': 'ignore_all_space',
-        'b': 'ignore_space_change',
-        'B': 'ignore_blank_lines',
+        b'w': b'ignore_all_space',
+        b'b': b'ignore_space_change',
+        b'B': b'ignore_blank_lines',
         }
 
-    for diffopt in ('', 'w', 'b', 'B', 'wB'):
-        opts = dict((options[c], '1') for c in diffopt)
+    for diffopt in (b'', b'w', b'b', b'B', b'wB'):
+        opts = dict((options[c], b'1') for c in diffopt)
         def d():
             ui.pushbuffer()
             commands.diff(ui, repo, **opts)
             ui.popbuffer()
-        title = 'diffopts: %s' % (diffopt and ('-' + diffopt) or 'none')
+        title = b'diffopts: %s' % (diffopt and (b'-' + diffopt) or b'none')
         timer(d, title)
     fm.end()
 
-@command('perfrevlogindex', revlogopts + formatteropts,
-         '-c|-m|FILE')
+@command(b'perfrevlogindex', revlogopts + formatteropts,
+         b'-c|-m|FILE')
 def perfrevlogindex(ui, repo, file_=None, **opts):
     """Benchmark operations against a revlog index.
 
@@ -1272,19 +1396,21 @@
     index data.
     """
 
-    rl = cmdutil.openrevlog(repo, 'perfrevlogindex', file_, opts)
+    opts = _byteskwargs(opts)
+
+    rl = cmdutil.openrevlog(repo, b'perfrevlogindex', file_, opts)
 
     opener = getattr(rl, 'opener')  # trick linter
     indexfile = rl.indexfile
     data = opener.read(indexfile)
 
-    header = struct.unpack('>I', data[0:4])[0]
+    header = struct.unpack(b'>I', data[0:4])[0]
     version = header & 0xFFFF
     if version == 1:
         revlogio = revlog.revlogio()
         inline = header & (1 << 16)
     else:
-        raise error.Abort(('unsupported revlog version: %d') % version)
+        raise error.Abort((b'unsupported revlog version: %d') % version)
 
     rllen = len(rl)
 
@@ -1344,33 +1470,33 @@
                     pass
 
     benches = [
-        (constructor, 'revlog constructor'),
-        (read, 'read'),
-        (parseindex, 'create index object'),
-        (lambda: getentry(0), 'retrieve index entry for rev 0'),
-        (lambda: resolvenode('a' * 20), 'look up missing node'),
-        (lambda: resolvenode(node0), 'look up node at rev 0'),
-        (lambda: resolvenode(node25), 'look up node at 1/4 len'),
-        (lambda: resolvenode(node50), 'look up node at 1/2 len'),
-        (lambda: resolvenode(node75), 'look up node at 3/4 len'),
-        (lambda: resolvenode(node100), 'look up node at tip'),
+        (constructor, b'revlog constructor'),
+        (read, b'read'),
+        (parseindex, b'create index object'),
+        (lambda: getentry(0), b'retrieve index entry for rev 0'),
+        (lambda: resolvenode(b'a' * 20), b'look up missing node'),
+        (lambda: resolvenode(node0), b'look up node at rev 0'),
+        (lambda: resolvenode(node25), b'look up node at 1/4 len'),
+        (lambda: resolvenode(node50), b'look up node at 1/2 len'),
+        (lambda: resolvenode(node75), b'look up node at 3/4 len'),
+        (lambda: resolvenode(node100), b'look up node at tip'),
         # 2x variation is to measure caching impact.
         (lambda: resolvenodes(allnodes),
-         'look up all nodes (forward)'),
+         b'look up all nodes (forward)'),
         (lambda: resolvenodes(allnodes, 2),
-         'look up all nodes 2x (forward)'),
+         b'look up all nodes 2x (forward)'),
         (lambda: resolvenodes(allnodesrev),
-         'look up all nodes (reverse)'),
+         b'look up all nodes (reverse)'),
         (lambda: resolvenodes(allnodesrev, 2),
-         'look up all nodes 2x (reverse)'),
+         b'look up all nodes 2x (reverse)'),
         (lambda: getentries(allrevs),
-         'retrieve all index entries (forward)'),
+         b'retrieve all index entries (forward)'),
         (lambda: getentries(allrevs, 2),
-         'retrieve all index entries 2x (forward)'),
+         b'retrieve all index entries 2x (forward)'),
         (lambda: getentries(allrevsrev),
-         'retrieve all index entries (reverse)'),
+         b'retrieve all index entries (reverse)'),
         (lambda: getentries(allrevsrev, 2),
-         'retrieve all index entries 2x (reverse)'),
+         b'retrieve all index entries 2x (reverse)'),
     ]
 
     for fn, title in benches:
@@ -1378,11 +1504,11 @@
         timer(fn, title=title)
         fm.end()
 
-@command('perfrevlogrevisions', revlogopts + formatteropts +
-         [('d', 'dist', 100, 'distance between the revisions'),
-          ('s', 'startrev', 0, 'revision to start reading at'),
-          ('', 'reverse', False, 'read in reverse')],
-         '-c|-m|FILE')
+@command(b'perfrevlogrevisions', revlogopts + formatteropts +
+         [(b'd', b'dist', 100, b'distance between the revisions'),
+          (b's', b'startrev', 0, b'revision to start reading at'),
+          (b'', b'reverse', False, b'read in reverse')],
+         b'-c|-m|FILE')
 def perfrevlogrevisions(ui, repo, file_=None, startrev=0, reverse=False,
                         **opts):
     """Benchmark reading a series of revisions from a revlog.
@@ -1392,7 +1518,9 @@
 
     The start revision can be defined via ``-s/--startrev``.
     """
-    rl = cmdutil.openrevlog(repo, 'perfrevlogrevisions', file_, opts)
+    opts = _byteskwargs(opts)
+
+    rl = cmdutil.openrevlog(repo, b'perfrevlogrevisions', file_, opts)
     rllen = getlen(ui)(rl)
 
     def d():
@@ -1400,13 +1528,13 @@
 
         beginrev = startrev
         endrev = rllen
-        dist = opts['dist']
+        dist = opts[b'dist']
 
         if reverse:
             beginrev, endrev = endrev, beginrev
             dist = -1 * dist
 
-        for x in xrange(beginrev, endrev, dist):
+        for x in _xrange(beginrev, endrev, dist):
             # Old revisions don't support passing int.
             n = rl.node(x)
             rl.revision(n)
@@ -1415,10 +1543,10 @@
     timer(d)
     fm.end()
 
-@command('perfrevlogchunks', revlogopts + formatteropts +
-         [('e', 'engines', '', 'compression engines to use'),
-          ('s', 'startrev', 0, 'revision to start at')],
-         '-c|-m|FILE')
+@command(b'perfrevlogchunks', revlogopts + formatteropts +
+         [(b'e', b'engines', b'', b'compression engines to use'),
+          (b's', b'startrev', 0, b'revision to start at')],
+         b'-c|-m|FILE')
 def perfrevlogchunks(ui, repo, file_=None, engines=None, startrev=0, **opts):
     """Benchmark operations on revlog chunks.
 
@@ -1431,7 +1559,9 @@
     For measurements of higher-level operations like resolving revisions,
     see ``perfrevlogrevisions`` and ``perfrevlogrevision``.
     """
-    rl = cmdutil.openrevlog(repo, 'perfrevlogchunks', file_, opts)
+    opts = _byteskwargs(opts)
+
+    rl = cmdutil.openrevlog(repo, b'perfrevlogchunks', file_, opts)
 
     # _chunkraw was renamed to _getsegmentforrevs.
     try:
@@ -1441,19 +1571,19 @@
 
     # Verify engines argument.
     if engines:
-        engines = set(e.strip() for e in engines.split(','))
+        engines = set(e.strip() for e in engines.split(b','))
         for engine in engines:
             try:
                 util.compressionengines[engine]
             except KeyError:
-                raise error.Abort('unknown compression engine: %s' % engine)
+                raise error.Abort(b'unknown compression engine: %s' % engine)
     else:
         engines = []
         for e in util.compengines:
             engine = util.compengines[e]
             try:
                 if engine.available():
-                    engine.revlogcompressor().compress('dummy')
+                    engine.revlogcompressor().compress(b'dummy')
                     engines.append(e)
             except NotImplementedError:
                 pass
@@ -1513,27 +1643,27 @@
             rl._compressor = oldcompressor
 
     benches = [
-        (lambda: doread(), 'read'),
-        (lambda: doreadcachedfh(), 'read w/ reused fd'),
-        (lambda: doreadbatch(), 'read batch'),
-        (lambda: doreadbatchcachedfh(), 'read batch w/ reused fd'),
-        (lambda: dochunk(), 'chunk'),
-        (lambda: dochunkbatch(), 'chunk batch'),
+        (lambda: doread(), b'read'),
+        (lambda: doreadcachedfh(), b'read w/ reused fd'),
+        (lambda: doreadbatch(), b'read batch'),
+        (lambda: doreadbatchcachedfh(), b'read batch w/ reused fd'),
+        (lambda: dochunk(), b'chunk'),
+        (lambda: dochunkbatch(), b'chunk batch'),
     ]
 
     for engine in sorted(engines):
         compressor = util.compengines[engine].revlogcompressor()
         benches.append((functools.partial(docompress, compressor),
-                        'compress w/ %s' % engine))
+                        b'compress w/ %s' % engine))
 
     for fn, title in benches:
         timer, fm = gettimer(ui, opts)
         timer(fn, title=title)
         fm.end()
 
-@command('perfrevlogrevision', revlogopts + formatteropts +
-         [('', 'cache', False, 'use caches instead of clearing')],
-         '-c|-m|FILE REV')
+@command(b'perfrevlogrevision', revlogopts + formatteropts +
+         [(b'', b'cache', False, b'use caches instead of clearing')],
+         b'-c|-m|FILE REV')
 def perfrevlogrevision(ui, repo, file_, rev=None, cache=None, **opts):
     """Benchmark obtaining a revlog revision.
 
@@ -1547,12 +1677,14 @@
 
     This command measures the time spent in each of these phases.
     """
-    if opts.get('changelog') or opts.get('manifest'):
+    opts = _byteskwargs(opts)
+
+    if opts.get(b'changelog') or opts.get(b'manifest'):
         file_, rev = None, file_
     elif rev is None:
-        raise error.CommandError('perfrevlogrevision', 'invalid arguments')
+        raise error.CommandError(b'perfrevlogrevision', b'invalid arguments')
 
-    r = cmdutil.openrevlog(repo, 'perfrevlogrevision', file_, opts)
+    r = cmdutil.openrevlog(repo, b'perfrevlogrevision', file_, opts)
 
     # _chunkraw was renamed to _getsegmentforrevs.
     try:
@@ -1627,13 +1759,13 @@
     text = mdiff.patches(text, bins)
 
     benches = [
-        (lambda: dorevision(), 'full'),
-        (lambda: dodeltachain(rev), 'deltachain'),
-        (lambda: doread(chain), 'read'),
-        (lambda: dorawchunks(data, chain), 'rawchunks'),
-        (lambda: dodecompress(rawchunks), 'decompress'),
-        (lambda: dopatch(text, bins), 'patch'),
-        (lambda: dohash(text), 'hash'),
+        (lambda: dorevision(), b'full'),
+        (lambda: dodeltachain(rev), b'deltachain'),
+        (lambda: doread(chain), b'read'),
+        (lambda: dorawchunks(data, chain), b'rawchunks'),
+        (lambda: dodecompress(rawchunks), b'decompress'),
+        (lambda: dopatch(text, bins), b'patch'),
+        (lambda: dohash(text), b'hash'),
     ]
 
     for fn, title in benches:
@@ -1641,16 +1773,18 @@
         timer(fn, title=title)
         fm.end()
 
-@command('perfrevset',
-         [('C', 'clear', False, 'clear volatile cache between each call.'),
-          ('', 'contexts', False, 'obtain changectx for each revision')]
-         + formatteropts, "REVSET")
+@command(b'perfrevset',
+         [(b'C', b'clear', False, b'clear volatile cache between each call.'),
+          (b'', b'contexts', False, b'obtain changectx for each revision')]
+         + formatteropts, b"REVSET")
 def perfrevset(ui, repo, expr, clear=False, contexts=False, **opts):
     """benchmark the execution time of a revset
 
     Use the --clean option if need to evaluate the impact of build volatile
     revisions set cache on the revset execution. Volatile cache hold filtered
     and obsolete related cache."""
+    opts = _byteskwargs(opts)
+
     timer, fm = gettimer(ui, opts)
     def d():
         if clear:
@@ -1662,21 +1796,22 @@
     timer(d)
     fm.end()
 
-@command('perfvolatilesets',
-         [('', 'clear-obsstore', False, 'drop obsstore between each call.'),
-         ] + formatteropts)
+@command(b'perfvolatilesets',
+         [(b'', b'clear-obsstore', False, b'drop obsstore between each call.'),
+          ] + formatteropts)
 def perfvolatilesets(ui, repo, *names, **opts):
     """benchmark the computation of various volatile set
 
     Volatile set computes element related to filtering and obsolescence."""
+    opts = _byteskwargs(opts)
     timer, fm = gettimer(ui, opts)
     repo = repo.unfiltered()
 
     def getobs(name):
         def d():
             repo.invalidatevolatilesets()
-            if opts['clear_obsstore']:
-                clearfilecache(repo, 'obsstore')
+            if opts[b'clear_obsstore']:
+                clearfilecache(repo, b'obsstore')
             obsolete.getrevs(repo, name)
         return d
 
@@ -1690,8 +1825,8 @@
     def getfiltered(name):
         def d():
             repo.invalidatevolatilesets()
-            if opts['clear_obsstore']:
-                clearfilecache(repo, 'obsstore')
+            if opts[b'clear_obsstore']:
+                clearfilecache(repo, b'obsstore')
             repoview.filterrevs(repo, name)
         return d
 
@@ -1703,19 +1838,20 @@
         timer(getfiltered(name), title=name)
     fm.end()
 
-@command('perfbranchmap',
-         [('f', 'full', False,
-           'Includes build time of subset'),
-          ('', 'clear-revbranch', False,
-           'purge the revbranch cache between computation'),
-         ] + formatteropts)
+@command(b'perfbranchmap',
+         [(b'f', b'full', False,
+           b'Includes build time of subset'),
+          (b'', b'clear-revbranch', False,
+           b'purge the revbranch cache between computation'),
+          ] + formatteropts)
 def perfbranchmap(ui, repo, *filternames, **opts):
     """benchmark the update of a branchmap
 
     This benchmarks the full repo.branchmap() call with read and write disabled
     """
-    full = opts.get("full", False)
-    clear_revbranch = opts.get("clear_revbranch", False)
+    opts = _byteskwargs(opts)
+    full = opts.get(b"full", False)
+    clear_revbranch = opts.get(b"clear_revbranch", False)
     timer, fm = gettimer(ui, opts)
     def getbranchmap(filtername):
         """generate a benchmark function for the filtername"""
@@ -1744,7 +1880,7 @@
             if subset not in possiblefilters:
                 break
         else:
-            assert False, 'subset cycle %s!' % possiblefilters
+            assert False, b'subset cycle %s!' % possiblefilters
         allfilters.append(name)
         possiblefilters.remove(name)
 
@@ -1752,26 +1888,53 @@
     if not full:
         for name in allfilters:
             repo.filtered(name).branchmap()
-    if not filternames or 'unfiltered' in filternames:
+    if not filternames or b'unfiltered' in filternames:
         # add unfiltered
         allfilters.append(None)
 
-    branchcacheread = safeattrsetter(branchmap, 'read')
-    branchcachewrite = safeattrsetter(branchmap.branchcache, 'write')
+    branchcacheread = safeattrsetter(branchmap, b'read')
+    branchcachewrite = safeattrsetter(branchmap.branchcache, b'write')
     branchcacheread.set(lambda repo: None)
     branchcachewrite.set(lambda bc, repo: None)
     try:
         for name in allfilters:
             printname = name
             if name is None:
-                printname = 'unfiltered'
+                printname = b'unfiltered'
             timer(getbranchmap(name), title=str(printname))
     finally:
         branchcacheread.restore()
         branchcachewrite.restore()
     fm.end()
 
-@command('perfloadmarkers')
+@command(b'perfbranchmapload', [
+     (b'f', b'filter', b'', b'Specify repoview filter'),
+     (b'', b'list', False, b'List brachmap filter caches'),
+    ] + formatteropts)
+def perfbranchmapread(ui, repo, filter=b'', list=False, **opts):
+    """benchmark reading the branchmap"""
+    opts = _byteskwargs(opts)
+
+    if list:
+        for name, kind, st in repo.cachevfs.readdir(stat=True):
+            if name.startswith(b'branch2'):
+                filtername = name.partition(b'-')[2] or b'unfiltered'
+                ui.status(b'%s - %s\n'
+                          % (filtername, util.bytecount(st.st_size)))
+        return
+    if filter:
+        repo = repoview.repoview(repo, filter)
+    else:
+        repo = repo.unfiltered()
+    # try once without timer, the filter may not be cached
+    if branchmap.read(repo) is None:
+        raise error.Abort(b'No brachmap cached for %s repo'
+                          % (filter or b'unfiltered'))
+    timer, fm = gettimer(ui, opts)
+    timer(lambda: branchmap.read(repo) and None)
+    fm.end()
+
+@command(b'perfloadmarkers')
 def perfloadmarkers(ui, repo):
     """benchmark the time to parse the on-disk markers for a repo
 
@@ -1781,27 +1944,34 @@
     timer(lambda: len(obsolete.obsstore(svfs)))
     fm.end()
 
-@command('perflrucachedict', formatteropts +
-    [('', 'size', 4, 'size of cache'),
-     ('', 'gets', 10000, 'number of key lookups'),
-     ('', 'sets', 10000, 'number of key sets'),
-     ('', 'mixed', 10000, 'number of mixed mode operations'),
-     ('', 'mixedgetfreq', 50, 'frequency of get vs set ops in mixed mode')],
+@command(b'perflrucachedict', formatteropts +
+    [(b'', b'costlimit', 0, b'maximum total cost of items in cache'),
+     (b'', b'mincost', 0, b'smallest cost of items in cache'),
+     (b'', b'maxcost', 100, b'maximum cost of items in cache'),
+     (b'', b'size', 4, b'size of cache'),
+     (b'', b'gets', 10000, b'number of key lookups'),
+     (b'', b'sets', 10000, b'number of key sets'),
+     (b'', b'mixed', 10000, b'number of mixed mode operations'),
+     (b'', b'mixedgetfreq', 50, b'frequency of get vs set ops in mixed mode')],
     norepo=True)
-def perflrucache(ui, size=4, gets=10000, sets=10000, mixed=10000,
-                 mixedgetfreq=50, **opts):
+def perflrucache(ui, mincost=0, maxcost=100, costlimit=0, size=4,
+                 gets=10000, sets=10000, mixed=10000, mixedgetfreq=50, **opts):
+    opts = _byteskwargs(opts)
+
     def doinit():
-        for i in xrange(10000):
+        for i in _xrange(10000):
             util.lrucachedict(size)
 
+    costrange = list(range(mincost, maxcost + 1))
+
     values = []
-    for i in xrange(size):
-        values.append(random.randint(0, sys.maxint))
+    for i in _xrange(size):
+        values.append(random.randint(0, _maxint))
 
     # Get mode fills the cache and tests raw lookup performance with no
     # eviction.
     getseq = []
-    for i in xrange(gets):
+    for i in _xrange(gets):
         getseq.append(random.choice(values))
 
     def dogets():
@@ -1812,10 +1982,33 @@
             value = d[key]
             value # silence pyflakes warning
 
+    def dogetscost():
+        d = util.lrucachedict(size, maxcost=costlimit)
+        for i, v in enumerate(values):
+            d.insert(v, v, cost=costs[i])
+        for key in getseq:
+            try:
+                value = d[key]
+                value # silence pyflakes warning
+            except KeyError:
+                pass
+
     # Set mode tests insertion speed with cache eviction.
     setseq = []
-    for i in xrange(sets):
-        setseq.append(random.randint(0, sys.maxint))
+    costs = []
+    for i in _xrange(sets):
+        setseq.append(random.randint(0, _maxint))
+        costs.append(random.choice(costrange))
+
+    def doinserts():
+        d = util.lrucachedict(size)
+        for v in setseq:
+            d.insert(v, v)
+
+    def doinsertscost():
+        d = util.lrucachedict(size, maxcost=costlimit)
+        for i, v in enumerate(setseq):
+            d.insert(v, v, cost=costs[i])
 
     def dosets():
         d = util.lrucachedict(size)
@@ -1824,19 +2017,21 @@
 
     # Mixed mode randomly performs gets and sets with eviction.
     mixedops = []
-    for i in xrange(mixed):
+    for i in _xrange(mixed):
         r = random.randint(0, 100)
         if r < mixedgetfreq:
             op = 0
         else:
             op = 1
 
-        mixedops.append((op, random.randint(0, size * 2)))
+        mixedops.append((op,
+                         random.randint(0, size * 2),
+                         random.choice(costrange)))
 
     def domixed():
         d = util.lrucachedict(size)
 
-        for op, v in mixedops:
+        for op, v, cost in mixedops:
             if op == 0:
                 try:
                     d[v]
@@ -1845,40 +2040,65 @@
             else:
                 d[v] = v
 
+    def domixedcost():
+        d = util.lrucachedict(size, maxcost=costlimit)
+
+        for op, v, cost in mixedops:
+            if op == 0:
+                try:
+                    d[v]
+                except KeyError:
+                    pass
+            else:
+                d.insert(v, v, cost=cost)
+
     benches = [
-        (doinit, 'init'),
-        (dogets, 'gets'),
-        (dosets, 'sets'),
-        (domixed, 'mixed')
+        (doinit, b'init'),
     ]
 
+    if costlimit:
+        benches.extend([
+            (dogetscost, b'gets w/ cost limit'),
+            (doinsertscost, b'inserts w/ cost limit'),
+            (domixedcost, b'mixed w/ cost limit'),
+        ])
+    else:
+        benches.extend([
+            (dogets, b'gets'),
+            (doinserts, b'inserts'),
+            (dosets, b'sets'),
+            (domixed, b'mixed')
+        ])
+
     for fn, title in benches:
         timer, fm = gettimer(ui, opts)
         timer(fn, title=title)
         fm.end()
 
-@command('perfwrite', formatteropts)
+@command(b'perfwrite', formatteropts)
 def perfwrite(ui, repo, **opts):
     """microbenchmark ui.write
     """
+    opts = _byteskwargs(opts)
+
     timer, fm = gettimer(ui, opts)
     def write():
         for i in range(100000):
-            ui.write(('Testing write performance\n'))
+            ui.write((b'Testing write performance\n'))
     timer(write)
     fm.end()
 
 def uisetup(ui):
-    if (util.safehasattr(cmdutil, 'openrevlog') and
-        not util.safehasattr(commands, 'debugrevlogopts')):
+    if (util.safehasattr(cmdutil, b'openrevlog') and
+        not util.safehasattr(commands, b'debugrevlogopts')):
         # for "historical portability":
         # In this case, Mercurial should be 1.9 (or a79fea6b3e77) -
         # 3.7 (or 5606f7d0d063). Therefore, '--dir' option for
         # openrevlog() should cause failure, because it has been
         # available since 3.5 (or 49c583ca48c4).
         def openrevlog(orig, repo, cmd, file_, opts):
-            if opts.get('dir') and not util.safehasattr(repo, 'dirlog'):
-                raise error.Abort("This version doesn't support --dir option",
-                                  hint="use 3.5 or later")
+            if opts.get(b'dir') and not util.safehasattr(repo, b'dirlog'):
+                raise error.Abort(b"This version doesn't support --dir option",
+                                  hint=b"use 3.5 or later")
             return orig(repo, cmd, file_, opts)
-        extensions.wrapfunction(cmdutil, 'openrevlog', openrevlog)
+        extensions.wrapfunction(cmdutil, b'openrevlog', openrevlog)
--- a/contrib/phabricator.py	Tue Sep 25 16:32:38 2018 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,980 +0,0 @@
-# phabricator.py - simple Phabricator integration
-#
-# Copyright 2017 Facebook, Inc.
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-"""simple Phabricator integration
-
-This extension provides a ``phabsend`` command which sends a stack of
-changesets to Phabricator, and a ``phabread`` command which prints a stack of
-revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
-to update statuses in batch.
-
-By default, Phabricator requires ``Test Plan`` which might prevent some
-changeset from being sent. The requirement could be disabled by changing
-``differential.require-test-plan-field`` config server side.
-
-Config::
-
-    [phabricator]
-    # Phabricator URL
-    url = https://phab.example.com/
-
-    # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
-    # callsign is "FOO".
-    callsign = FOO
-
-    # curl command to use. If not set (default), use builtin HTTP library to
-    # communicate. If set, use the specified curl command. This could be useful
-    # if you need to specify advanced options that is not easily supported by
-    # the internal library.
-    curlcmd = curl --connect-timeout 2 --retry 3 --silent
-
-    [auth]
-    example.schemes = https
-    example.prefix = phab.example.com
-
-    # API token. Get it from https://$HOST/conduit/login/
-    example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
-"""
-
-from __future__ import absolute_import
-
-import itertools
-import json
-import operator
-import re
-
-from mercurial.node import bin, nullid
-from mercurial.i18n import _
-from mercurial import (
-    cmdutil,
-    context,
-    encoding,
-    error,
-    httpconnection as httpconnectionmod,
-    mdiff,
-    obsutil,
-    parser,
-    patch,
-    registrar,
-    scmutil,
-    smartset,
-    tags,
-    url as urlmod,
-    util,
-)
-from mercurial.utils import (
-    procutil,
-    stringutil,
-)
-
-cmdtable = {}
-command = registrar.command(cmdtable)
-
-configtable = {}
-configitem = registrar.configitem(configtable)
-
-# developer config: phabricator.batchsize
-configitem(b'phabricator', b'batchsize',
-    default=12,
-)
-configitem(b'phabricator', b'callsign',
-    default=None,
-)
-configitem(b'phabricator', b'curlcmd',
-    default=None,
-)
-# developer config: phabricator.repophid
-configitem(b'phabricator', b'repophid',
-    default=None,
-)
-configitem(b'phabricator', b'url',
-    default=None,
-)
-configitem(b'phabsend', b'confirm',
-    default=False,
-)
-
-colortable = {
-    b'phabricator.action.created': b'green',
-    b'phabricator.action.skipped': b'magenta',
-    b'phabricator.action.updated': b'magenta',
-    b'phabricator.desc': b'',
-    b'phabricator.drev': b'bold',
-    b'phabricator.node': b'',
-}
-
-def urlencodenested(params):
-    """like urlencode, but works with nested parameters.
-
-    For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
-    flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
-    urlencode. Note: the encoding is consistent with PHP's http_build_query.
-    """
-    flatparams = util.sortdict()
-    def process(prefix, obj):
-        items = {list: enumerate, dict: lambda x: x.items()}.get(type(obj))
-        if items is None:
-            flatparams[prefix] = obj
-        else:
-            for k, v in items(obj):
-                if prefix:
-                    process(b'%s[%s]' % (prefix, k), v)
-                else:
-                    process(k, v)
-    process(b'', params)
-    return util.urlreq.urlencode(flatparams)
-
-printed_token_warning = False
-
-def readlegacytoken(repo, url):
-    """Transitional support for old phabricator tokens.
-
-    Remove before the 4.7 release.
-    """
-    groups = {}
-    for key, val in repo.ui.configitems(b'phabricator.auth'):
-        if b'.' not in key:
-            repo.ui.warn(_(b"ignoring invalid [phabricator.auth] key '%s'\n")
-                         % key)
-            continue
-        group, setting = key.rsplit(b'.', 1)
-        groups.setdefault(group, {})[setting] = val
-
-    token = None
-    for group, auth in groups.iteritems():
-        if url != auth.get(b'url'):
-            continue
-        token = auth.get(b'token')
-        if token:
-            break
-
-    global printed_token_warning
-
-    if token and not printed_token_warning:
-        printed_token_warning = True
-        repo.ui.warn(_(b'phabricator.auth.token is deprecated - please '
-                       b'migrate to auth.phabtoken.\n'))
-    return token
-
-def readurltoken(repo):
-    """return conduit url, token and make sure they exist
-
-    Currently read from [auth] config section. In the future, it might
-    make sense to read from .arcconfig and .arcrc as well.
-    """
-    url = repo.ui.config(b'phabricator', b'url')
-    if not url:
-        raise error.Abort(_(b'config %s.%s is required')
-                          % (b'phabricator', b'url'))
-
-    res = httpconnectionmod.readauthforuri(repo.ui, url, util.url(url).user)
-    token = None
-
-    if res:
-        group, auth = res
-
-        repo.ui.debug(b"using auth.%s.* for authentication\n" % group)
-
-        token = auth.get(b'phabtoken')
-
-    if not token:
-        token = readlegacytoken(repo, url)
-        if not token:
-            raise error.Abort(_(b'Can\'t find conduit token associated to %s')
-                              % (url,))
-
-    return url, token
-
-def callconduit(repo, name, params):
-    """call Conduit API, params is a dict. return json.loads result, or None"""
-    host, token = readurltoken(repo)
-    url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
-    repo.ui.debug(b'Conduit Call: %s %s\n' % (url, params))
-    params = params.copy()
-    params[b'api.token'] = token
-    data = urlencodenested(params)
-    curlcmd = repo.ui.config(b'phabricator', b'curlcmd')
-    if curlcmd:
-        sin, sout = procutil.popen2(b'%s -d @- %s'
-                                    % (curlcmd, procutil.shellquote(url)))
-        sin.write(data)
-        sin.close()
-        body = sout.read()
-    else:
-        urlopener = urlmod.opener(repo.ui, authinfo)
-        request = util.urlreq.request(url, data=data)
-        body = urlopener.open(request).read()
-    repo.ui.debug(b'Conduit Response: %s\n' % body)
-    parsed = json.loads(body)
-    if parsed.get(r'error_code'):
-        msg = (_(b'Conduit Error (%s): %s')
-               % (parsed[r'error_code'], parsed[r'error_info']))
-        raise error.Abort(msg)
-    return parsed[r'result']
-
-@command(b'debugcallconduit', [], _(b'METHOD'))
-def debugcallconduit(ui, repo, name):
-    """call Conduit API
-
-    Call parameters are read from stdin as a JSON blob. Result will be written
-    to stdout as a JSON blob.
-    """
-    params = json.loads(ui.fin.read())
-    result = callconduit(repo, name, params)
-    s = json.dumps(result, sort_keys=True, indent=2, separators=(b',', b': '))
-    ui.write(b'%s\n' % s)
-
-def getrepophid(repo):
-    """given callsign, return repository PHID or None"""
-    # developer config: phabricator.repophid
-    repophid = repo.ui.config(b'phabricator', b'repophid')
-    if repophid:
-        return repophid
-    callsign = repo.ui.config(b'phabricator', b'callsign')
-    if not callsign:
-        return None
-    query = callconduit(repo, b'diffusion.repository.search',
-                        {b'constraints': {b'callsigns': [callsign]}})
-    if len(query[r'data']) == 0:
-        return None
-    repophid = encoding.strtolocal(query[r'data'][0][r'phid'])
-    repo.ui.setconfig(b'phabricator', b'repophid', repophid)
-    return repophid
-
-_differentialrevisiontagre = re.compile(b'\AD([1-9][0-9]*)\Z')
-_differentialrevisiondescre = re.compile(
-    b'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M)
-
-def getoldnodedrevmap(repo, nodelist):
-    """find previous nodes that has been sent to Phabricator
-
-    return {node: (oldnode, Differential diff, Differential Revision ID)}
-    for node in nodelist with known previous sent versions, or associated
-    Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
-    be ``None``.
-
-    Examines commit messages like "Differential Revision:" to get the
-    association information.
-
-    If such commit message line is not found, examines all precursors and their
-    tags. Tags with format like "D1234" are considered a match and the node
-    with that tag, and the number after "D" (ex. 1234) will be returned.
-
-    The ``old node``, if not None, is guaranteed to be the last diff of
-    corresponding Differential Revision, and exist in the repo.
-    """
-    url, token = readurltoken(repo)
-    unfi = repo.unfiltered()
-    nodemap = unfi.changelog.nodemap
-
-    result = {} # {node: (oldnode?, lastdiff?, drev)}
-    toconfirm = {} # {node: (force, {precnode}, drev)}
-    for node in nodelist:
-        ctx = unfi[node]
-        # For tags like "D123", put them into "toconfirm" to verify later
-        precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
-        for n in precnodes:
-            if n in nodemap:
-                for tag in unfi.nodetags(n):
-                    m = _differentialrevisiontagre.match(tag)
-                    if m:
-                        toconfirm[node] = (0, set(precnodes), int(m.group(1)))
-                        continue
-
-        # Check commit message
-        m = _differentialrevisiondescre.search(ctx.description())
-        if m:
-            toconfirm[node] = (1, set(precnodes), int(m.group(b'id')))
-
-    # Double check if tags are genuine by collecting all old nodes from
-    # Phabricator, and expect precursors overlap with it.
-    if toconfirm:
-        drevs = [drev for force, precs, drev in toconfirm.values()]
-        alldiffs = callconduit(unfi, b'differential.querydiffs',
-                               {b'revisionIDs': drevs})
-        getnode = lambda d: bin(encoding.unitolocal(
-            getdiffmeta(d).get(r'node', b''))) or None
-        for newnode, (force, precset, drev) in toconfirm.items():
-            diffs = [d for d in alldiffs.values()
-                     if int(d[r'revisionID']) == drev]
-
-            # "precursors" as known by Phabricator
-            phprecset = set(getnode(d) for d in diffs)
-
-            # Ignore if precursors (Phabricator and local repo) do not overlap,
-            # and force is not set (when commit message says nothing)
-            if not force and not bool(phprecset & precset):
-                tagname = b'D%d' % drev
-                tags.tag(repo, tagname, nullid, message=None, user=None,
-                         date=None, local=True)
-                unfi.ui.warn(_(b'D%s: local tag removed - does not match '
-                               b'Differential history\n') % drev)
-                continue
-
-            # Find the last node using Phabricator metadata, and make sure it
-            # exists in the repo
-            oldnode = lastdiff = None
-            if diffs:
-                lastdiff = max(diffs, key=lambda d: int(d[r'id']))
-                oldnode = getnode(lastdiff)
-                if oldnode and oldnode not in nodemap:
-                    oldnode = None
-
-            result[newnode] = (oldnode, lastdiff, drev)
-
-    return result
-
-def getdiff(ctx, diffopts):
-    """plain-text diff without header (user, commit message, etc)"""
-    output = util.stringio()
-    for chunk, _label in patch.diffui(ctx.repo(), ctx.p1().node(), ctx.node(),
-                                      None, opts=diffopts):
-        output.write(chunk)
-    return output.getvalue()
-
-def creatediff(ctx):
-    """create a Differential Diff"""
-    repo = ctx.repo()
-    repophid = getrepophid(repo)
-    # Create a "Differential Diff" via "differential.createrawdiff" API
-    params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
-    if repophid:
-        params[b'repositoryPHID'] = repophid
-    diff = callconduit(repo, b'differential.createrawdiff', params)
-    if not diff:
-        raise error.Abort(_(b'cannot create diff for %s') % ctx)
-    return diff
-
-def writediffproperties(ctx, diff):
-    """write metadata to diff so patches could be applied losslessly"""
-    params = {
-        b'diff_id': diff[r'id'],
-        b'name': b'hg:meta',
-        b'data': json.dumps({
-            b'user': ctx.user(),
-            b'date': b'%d %d' % ctx.date(),
-            b'node': ctx.hex(),
-            b'parent': ctx.p1().hex(),
-        }),
-    }
-    callconduit(ctx.repo(), b'differential.setdiffproperty', params)
-
-    params = {
-        b'diff_id': diff[r'id'],
-        b'name': b'local:commits',
-        b'data': json.dumps({
-            ctx.hex(): {
-                b'author': stringutil.person(ctx.user()),
-                b'authorEmail': stringutil.email(ctx.user()),
-                b'time': ctx.date()[0],
-            },
-        }),
-    }
-    callconduit(ctx.repo(), b'differential.setdiffproperty', params)
-
-def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
-                               olddiff=None, actions=None):
-    """create or update a Differential Revision
-
-    If revid is None, create a new Differential Revision, otherwise update
-    revid. If parentrevid is not None, set it as a dependency.
-
-    If oldnode is not None, check if the patch content (without commit message
-    and metadata) has changed before creating another diff.
-
-    If actions is not None, they will be appended to the transaction.
-    """
-    repo = ctx.repo()
-    if oldnode:
-        diffopts = mdiff.diffopts(git=True, context=32767)
-        oldctx = repo.unfiltered()[oldnode]
-        neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
-    else:
-        neednewdiff = True
-
-    transactions = []
-    if neednewdiff:
-        diff = creatediff(ctx)
-        transactions.append({b'type': b'update', b'value': diff[r'phid']})
-    else:
-        # Even if we don't need to upload a new diff because the patch content
-        # does not change. We might still need to update its metadata so
-        # pushers could know the correct node metadata.
-        assert olddiff
-        diff = olddiff
-    writediffproperties(ctx, diff)
-
-    # Use a temporary summary to set dependency. There might be better ways but
-    # I cannot find them for now. But do not do that if we are updating an
-    # existing revision (revid is not None) since that introduces visible
-    # churns (someone edited "Summary" twice) on the web page.
-    if parentrevid and revid is None:
-        summary = b'Depends on D%s' % parentrevid
-        transactions += [{b'type': b'summary', b'value': summary},
-                         {b'type': b'summary', b'value': b' '}]
-
-    if actions:
-        transactions += actions
-
-    # Parse commit message and update related fields.
-    desc = ctx.description()
-    info = callconduit(repo, b'differential.parsecommitmessage',
-                       {b'corpus': desc})
-    for k, v in info[r'fields'].items():
-        if k in [b'title', b'summary', b'testPlan']:
-            transactions.append({b'type': k, b'value': v})
-
-    params = {b'transactions': transactions}
-    if revid is not None:
-        # Update an existing Differential Revision
-        params[b'objectIdentifier'] = revid
-
-    revision = callconduit(repo, b'differential.revision.edit', params)
-    if not revision:
-        raise error.Abort(_(b'cannot create revision for %s') % ctx)
-
-    return revision, diff
-
-def userphids(repo, names):
-    """convert user names to PHIDs"""
-    query = {b'constraints': {b'usernames': names}}
-    result = callconduit(repo, b'user.search', query)
-    # username not found is not an error of the API. So check if we have missed
-    # some names here.
-    data = result[r'data']
-    resolved = set(entry[r'fields'][r'username'] for entry in data)
-    unresolved = set(names) - resolved
-    if unresolved:
-        raise error.Abort(_(b'unknown username: %s')
-                          % b' '.join(sorted(unresolved)))
-    return [entry[r'phid'] for entry in data]
-
-@command(b'phabsend',
-         [(b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
-          (b'', b'amend', True, _(b'update commit messages')),
-          (b'', b'reviewer', [], _(b'specify reviewers')),
-          (b'', b'confirm', None, _(b'ask for confirmation before sending'))],
-         _(b'REV [OPTIONS]'))
-def phabsend(ui, repo, *revs, **opts):
-    """upload changesets to Phabricator
-
-    If there are multiple revisions specified, they will be send as a stack
-    with a linear dependencies relationship using the order specified by the
-    revset.
-
-    For the first time uploading changesets, local tags will be created to
-    maintain the association. After the first time, phabsend will check
-    obsstore and tags information so it can figure out whether to update an
-    existing Differential Revision, or create a new one.
-
-    If --amend is set, update commit messages so they have the
-    ``Differential Revision`` URL, remove related tags. This is similar to what
-    arcanist will do, and is more desired in author-push workflows. Otherwise,
-    use local tags to record the ``Differential Revision`` association.
-
-    The --confirm option lets you confirm changesets before sending them. You
-    can also add following to your configuration file to make it default
-    behaviour::
-
-        [phabsend]
-        confirm = true
-
-    phabsend will check obsstore and the above association to decide whether to
-    update an existing Differential Revision, or create a new one.
-    """
-    revs = list(revs) + opts.get(b'rev', [])
-    revs = scmutil.revrange(repo, revs)
-
-    if not revs:
-        raise error.Abort(_(b'phabsend requires at least one changeset'))
-    if opts.get(b'amend'):
-        cmdutil.checkunfinished(repo)
-
-    # {newnode: (oldnode, olddiff, olddrev}
-    oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
-
-    confirm = ui.configbool(b'phabsend', b'confirm')
-    confirm |= bool(opts.get(b'confirm'))
-    if confirm:
-        confirmed = _confirmbeforesend(repo, revs, oldmap)
-        if not confirmed:
-            raise error.Abort(_(b'phabsend cancelled'))
-
-    actions = []
-    reviewers = opts.get(b'reviewer', [])
-    if reviewers:
-        phids = userphids(repo, reviewers)
-        actions.append({b'type': b'reviewers.add', b'value': phids})
-
-    drevids = [] # [int]
-    diffmap = {} # {newnode: diff}
-
-    # Send patches one by one so we know their Differential Revision IDs and
-    # can provide dependency relationship
-    lastrevid = None
-    for rev in revs:
-        ui.debug(b'sending rev %d\n' % rev)
-        ctx = repo[rev]
-
-        # Get Differential Revision ID
-        oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
-        if oldnode != ctx.node() or opts.get(b'amend'):
-            # Create or update Differential Revision
-            revision, diff = createdifferentialrevision(
-                ctx, revid, lastrevid, oldnode, olddiff, actions)
-            diffmap[ctx.node()] = diff
-            newrevid = int(revision[r'object'][r'id'])
-            if revid:
-                action = b'updated'
-            else:
-                action = b'created'
-
-            # Create a local tag to note the association, if commit message
-            # does not have it already
-            m = _differentialrevisiondescre.search(ctx.description())
-            if not m or int(m.group(b'id')) != newrevid:
-                tagname = b'D%d' % newrevid
-                tags.tag(repo, tagname, ctx.node(), message=None, user=None,
-                         date=None, local=True)
-        else:
-            # Nothing changed. But still set "newrevid" so the next revision
-            # could depend on this one.
-            newrevid = revid
-            action = b'skipped'
-
-        actiondesc = ui.label(
-            {b'created': _(b'created'),
-             b'skipped': _(b'skipped'),
-             b'updated': _(b'updated')}[action],
-            b'phabricator.action.%s' % action)
-        drevdesc = ui.label(b'D%s' % newrevid, b'phabricator.drev')
-        nodedesc = ui.label(bytes(ctx), b'phabricator.node')
-        desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
-        ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
-                                             desc))
-        drevids.append(newrevid)
-        lastrevid = newrevid
-
-    # Update commit messages and remove tags
-    if opts.get(b'amend'):
-        unfi = repo.unfiltered()
-        drevs = callconduit(repo, b'differential.query', {b'ids': drevids})
-        with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
-            wnode = unfi[b'.'].node()
-            mapping = {} # {oldnode: [newnode]}
-            for i, rev in enumerate(revs):
-                old = unfi[rev]
-                drevid = drevids[i]
-                drev = [d for d in drevs if int(d[r'id']) == drevid][0]
-                newdesc = getdescfromdrev(drev)
-                # Make sure commit message contain "Differential Revision"
-                if old.description() != newdesc:
-                    parents = [
-                        mapping.get(old.p1().node(), (old.p1(),))[0],
-                        mapping.get(old.p2().node(), (old.p2(),))[0],
-                    ]
-                    new = context.metadataonlyctx(
-                        repo, old, parents=parents, text=newdesc,
-                        user=old.user(), date=old.date(), extra=old.extra())
-
-                    newnode = new.commit()
-
-                    mapping[old.node()] = [newnode]
-                    # Update diff property
-                    writediffproperties(unfi[newnode], diffmap[old.node()])
-                # Remove local tags since it's no longer necessary
-                tagname = b'D%d' % drevid
-                if tagname in repo.tags():
-                    tags.tag(repo, tagname, nullid, message=None, user=None,
-                             date=None, local=True)
-            scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
-            if wnode in mapping:
-                unfi.setparents(mapping[wnode][0])
-
-# Map from "hg:meta" keys to header understood by "hg import". The order is
-# consistent with "hg export" output.
-_metanamemap = util.sortdict([(r'user', b'User'), (r'date', b'Date'),
-                              (r'node', b'Node ID'), (r'parent', b'Parent ')])
-
-def _confirmbeforesend(repo, revs, oldmap):
-    url, token = readurltoken(repo)
-    ui = repo.ui
-    for rev in revs:
-        ctx = repo[rev]
-        desc = ctx.description().splitlines()[0]
-        oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
-        if drevid:
-            drevdesc = ui.label(b'D%s' % drevid, b'phabricator.drev')
-        else:
-            drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
-
-        ui.write(_(b'%s - %s: %s\n')
-                 % (drevdesc,
-                    ui.label(bytes(ctx), b'phabricator.node'),
-                    ui.label(desc, b'phabricator.desc')))
-
-    if ui.promptchoice(_(b'Send the above changes to %s (yn)?'
-                         b'$$ &Yes $$ &No') % url):
-        return False
-
-    return True
-
-_knownstatusnames = {b'accepted', b'needsreview', b'needsrevision', b'closed',
-                     b'abandoned'}
-
-def _getstatusname(drev):
-    """get normalized status name from a Differential Revision"""
-    return drev[r'statusName'].replace(b' ', b'').lower()
-
-# Small language to specify differential revisions. Support symbols: (), :X,
-# +, and -.
-
-_elements = {
-    # token-type: binding-strength, primary, prefix, infix, suffix
-    b'(':      (12, None, (b'group', 1, b')'), None, None),
-    b':':      (8, None, (b'ancestors', 8), None, None),
-    b'&':      (5,  None, None, (b'and_', 5), None),
-    b'+':      (4,  None, None, (b'add', 4), None),
-    b'-':      (4,  None, None, (b'sub', 4), None),
-    b')':      (0,  None, None, None, None),
-    b'symbol': (0, b'symbol', None, None, None),
-    b'end':    (0, None, None, None, None),
-}
-
-def _tokenize(text):
-    view = memoryview(text) # zero-copy slice
-    special = b'():+-& '
-    pos = 0
-    length = len(text)
-    while pos < length:
-        symbol = b''.join(itertools.takewhile(lambda ch: ch not in special,
-                                              view[pos:]))
-        if symbol:
-            yield (b'symbol', symbol, pos)
-            pos += len(symbol)
-        else: # special char, ignore space
-            if text[pos] != b' ':
-                yield (text[pos], None, pos)
-            pos += 1
-    yield (b'end', None, pos)
-
-def _parse(text):
-    tree, pos = parser.parser(_elements).parse(_tokenize(text))
-    if pos != len(text):
-        raise error.ParseError(b'invalid token', pos)
-    return tree
-
-def _parsedrev(symbol):
-    """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
-    if symbol.startswith(b'D') and symbol[1:].isdigit():
-        return int(symbol[1:])
-    if symbol.isdigit():
-        return int(symbol)
-
-def _prefetchdrevs(tree):
-    """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
-    drevs = set()
-    ancestordrevs = set()
-    op = tree[0]
-    if op == b'symbol':
-        r = _parsedrev(tree[1])
-        if r:
-            drevs.add(r)
-    elif op == b'ancestors':
-        r, a = _prefetchdrevs(tree[1])
-        drevs.update(r)
-        ancestordrevs.update(r)
-        ancestordrevs.update(a)
-    else:
-        for t in tree[1:]:
-            r, a = _prefetchdrevs(t)
-            drevs.update(r)
-            ancestordrevs.update(a)
-    return drevs, ancestordrevs
-
-def querydrev(repo, spec):
-    """return a list of "Differential Revision" dicts
-
-    spec is a string using a simple query language, see docstring in phabread
-    for details.
-
-    A "Differential Revision dict" looks like:
-
-        {
-            "id": "2",
-            "phid": "PHID-DREV-672qvysjcczopag46qty",
-            "title": "example",
-            "uri": "https://phab.example.com/D2",
-            "dateCreated": "1499181406",
-            "dateModified": "1499182103",
-            "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
-            "status": "0",
-            "statusName": "Needs Review",
-            "properties": [],
-            "branch": null,
-            "summary": "",
-            "testPlan": "",
-            "lineCount": "2",
-            "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
-            "diffs": [
-              "3",
-              "4",
-            ],
-            "commits": [],
-            "reviewers": [],
-            "ccs": [],
-            "hashes": [],
-            "auxiliary": {
-              "phabricator:projects": [],
-              "phabricator:depends-on": [
-                "PHID-DREV-gbapp366kutjebt7agcd"
-              ]
-            },
-            "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
-            "sourcePath": null
-        }
-    """
-    def fetch(params):
-        """params -> single drev or None"""
-        key = (params.get(r'ids') or params.get(r'phids') or [None])[0]
-        if key in prefetched:
-            return prefetched[key]
-        drevs = callconduit(repo, b'differential.query', params)
-        # Fill prefetched with the result
-        for drev in drevs:
-            prefetched[drev[r'phid']] = drev
-            prefetched[int(drev[r'id'])] = drev
-        if key not in prefetched:
-            raise error.Abort(_(b'cannot get Differential Revision %r')
-                              % params)
-        return prefetched[key]
-
-    def getstack(topdrevids):
-        """given a top, get a stack from the bottom, [id] -> [id]"""
-        visited = set()
-        result = []
-        queue = [{r'ids': [i]} for i in topdrevids]
-        while queue:
-            params = queue.pop()
-            drev = fetch(params)
-            if drev[r'id'] in visited:
-                continue
-            visited.add(drev[r'id'])
-            result.append(int(drev[r'id']))
-            auxiliary = drev.get(r'auxiliary', {})
-            depends = auxiliary.get(r'phabricator:depends-on', [])
-            for phid in depends:
-                queue.append({b'phids': [phid]})
-        result.reverse()
-        return smartset.baseset(result)
-
-    # Initialize prefetch cache
-    prefetched = {} # {id or phid: drev}
-
-    tree = _parse(spec)
-    drevs, ancestordrevs = _prefetchdrevs(tree)
-
-    # developer config: phabricator.batchsize
-    batchsize = repo.ui.configint(b'phabricator', b'batchsize')
-
-    # Prefetch Differential Revisions in batch
-    tofetch = set(drevs)
-    for r in ancestordrevs:
-        tofetch.update(range(max(1, r - batchsize), r + 1))
-    if drevs:
-        fetch({r'ids': list(tofetch)})
-    validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
-
-    # Walk through the tree, return smartsets
-    def walk(tree):
-        op = tree[0]
-        if op == b'symbol':
-            drev = _parsedrev(tree[1])
-            if drev:
-                return smartset.baseset([drev])
-            elif tree[1] in _knownstatusnames:
-                drevs = [r for r in validids
-                         if _getstatusname(prefetched[r]) == tree[1]]
-                return smartset.baseset(drevs)
-            else:
-                raise error.Abort(_(b'unknown symbol: %s') % tree[1])
-        elif op in {b'and_', b'add', b'sub'}:
-            assert len(tree) == 3
-            return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
-        elif op == b'group':
-            return walk(tree[1])
-        elif op == b'ancestors':
-            return getstack(walk(tree[1]))
-        else:
-            raise error.ProgrammingError(b'illegal tree: %r' % tree)
-
-    return [prefetched[r] for r in walk(tree)]
-
-def getdescfromdrev(drev):
-    """get description (commit message) from "Differential Revision"
-
-    This is similar to differential.getcommitmessage API. But we only care
-    about limited fields: title, summary, test plan, and URL.
-    """
-    title = drev[r'title']
-    summary = drev[r'summary'].rstrip()
-    testplan = drev[r'testPlan'].rstrip()
-    if testplan:
-        testplan = b'Test Plan:\n%s' % testplan
-    uri = b'Differential Revision: %s' % drev[r'uri']
-    return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
-
-def getdiffmeta(diff):
-    """get commit metadata (date, node, user, p1) from a diff object
-
-    The metadata could be "hg:meta", sent by phabsend, like:
-
-        "properties": {
-          "hg:meta": {
-            "date": "1499571514 25200",
-            "node": "98c08acae292b2faf60a279b4189beb6cff1414d",
-            "user": "Foo Bar <foo@example.com>",
-            "parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
-          }
-        }
-
-    Or converted from "local:commits", sent by "arc", like:
-
-        "properties": {
-          "local:commits": {
-            "98c08acae292b2faf60a279b4189beb6cff1414d": {
-              "author": "Foo Bar",
-              "time": 1499546314,
-              "branch": "default",
-              "tag": "",
-              "commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
-              "rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
-              "local": "1000",
-              "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
-              "summary": "...",
-              "message": "...",
-              "authorEmail": "foo@example.com"
-            }
-          }
-        }
-
-    Note: metadata extracted from "local:commits" will lose time zone
-    information.
-    """
-    props = diff.get(r'properties') or {}
-    meta = props.get(r'hg:meta')
-    if not meta and props.get(r'local:commits'):
-        commit = sorted(props[r'local:commits'].values())[0]
-        meta = {
-            r'date': r'%d 0' % commit[r'time'],
-            r'node': commit[r'rev'],
-            r'user': r'%s <%s>' % (commit[r'author'], commit[r'authorEmail']),
-        }
-        if len(commit.get(r'parents', ())) >= 1:
-            meta[r'parent'] = commit[r'parents'][0]
-    return meta or {}
-
-def readpatch(repo, drevs, write):
-    """generate plain-text patch readable by 'hg import'
-
-    write is usually ui.write. drevs is what "querydrev" returns, results of
-    "differential.query".
-    """
-    # Prefetch hg:meta property for all diffs
-    diffids = sorted(set(max(int(v) for v in drev[r'diffs']) for drev in drevs))
-    diffs = callconduit(repo, b'differential.querydiffs', {b'ids': diffids})
-
-    # Generate patch for each drev
-    for drev in drevs:
-        repo.ui.note(_(b'reading D%s\n') % drev[r'id'])
-
-        diffid = max(int(v) for v in drev[r'diffs'])
-        body = callconduit(repo, b'differential.getrawdiff',
-                           {b'diffID': diffid})
-        desc = getdescfromdrev(drev)
-        header = b'# HG changeset patch\n'
-
-        # Try to preserve metadata from hg:meta property. Write hg patch
-        # headers that can be read by the "import" command. See patchheadermap
-        # and extract in mercurial/patch.py for supported headers.
-        meta = getdiffmeta(diffs[str(diffid)])
-        for k in _metanamemap.keys():
-            if k in meta:
-                header += b'# %s %s\n' % (_metanamemap[k], meta[k])
-
-        content = b'%s%s\n%s' % (header, desc, body)
-        write(encoding.unitolocal(content))
-
-@command(b'phabread',
-         [(b'', b'stack', False, _(b'read dependencies'))],
-         _(b'DREVSPEC [OPTIONS]'))
-def phabread(ui, repo, spec, **opts):
-    """print patches from Phabricator suitable for importing
-
-    DREVSPEC could be a Differential Revision identity, like ``D123``, or just
-    the number ``123``. It could also have common operators like ``+``, ``-``,
-    ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
-    select a stack.
-
-    ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
-    could be used to filter patches by status. For performance reason, they
-    only represent a subset of non-status selections and cannot be used alone.
-
-    For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
-    D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
-    stack up to D9.
-
-    If --stack is given, follow dependencies information and read all patches.
-    It is equivalent to the ``:`` operator.
-    """
-    if opts.get(b'stack'):
-        spec = b':(%s)' % spec
-    drevs = querydrev(repo, spec)
-    readpatch(repo, drevs, ui.write)
-
-@command(b'phabupdate',
-         [(b'', b'accept', False, _(b'accept revisions')),
-          (b'', b'reject', False, _(b'reject revisions')),
-          (b'', b'abandon', False, _(b'abandon revisions')),
-          (b'', b'reclaim', False, _(b'reclaim revisions')),
-          (b'm', b'comment', b'', _(b'comment on the last revision')),
-          ], _(b'DREVSPEC [OPTIONS]'))
-def phabupdate(ui, repo, spec, **opts):
-    """update Differential Revision in batch
-
-    DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
-    """
-    flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)]
-    if len(flags) > 1:
-        raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
-
-    actions = []
-    for f in flags:
-        actions.append({b'type': f, b'value': b'true'})
-
-    drevs = querydrev(repo, spec)
-    for i, drev in enumerate(drevs):
-        if i + 1 == len(drevs) and opts.get(b'comment'):
-            actions.append({b'type': b'comment', b'value': opts[b'comment']})
-        if actions:
-            params = {b'objectIdentifier': drev[r'phid'],
-                      b'transactions': actions}
-            callconduit(repo, b'differential.revision.edit', params)
-
-templatekeyword = registrar.templatekeyword()
-
-@templatekeyword(b'phabreview', requires={b'ctx'})
-def template_review(context, mapping):
-    """:phabreview: Object describing the review for this changeset.
-    Has attributes `url` and `id`.
-    """
-    ctx = context.resource(mapping, b'ctx')
-    m = _differentialrevisiondescre.search(ctx.description())
-    if m:
-        return {
-            b'url': m.group(b'url'),
-            b'id': b"D{}".format(m.group(b'id')),
-        }
--- a/contrib/python3-whitelist	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/python3-whitelist	Wed Sep 26 20:33:09 2018 +0900
@@ -1,4 +1,9 @@
 test-abort-checkin.t
+test-absorb-filefixupstate.py
+test-absorb-phase.t
+test-absorb-rename.t
+test-absorb-strip.t
+test-absorb.t
 test-add.t
 test-addremove-similar.t
 test-addremove.t
@@ -17,6 +22,7 @@
 test-backwards-remove.t
 test-bad-pull.t
 test-basic.t
+test-bdiff.py
 test-bheads.t
 test-bisect.t
 test-bisect2.t
@@ -48,10 +54,12 @@
 test-cbor.py
 test-censor.t
 test-changelog-exec.t
+test-check-code.t
 test-check-commit.t
 test-check-execute.t
 test-check-interfaces.py
 test-check-module-imports.t
+test-check-py3-compat.t
 test-check-pyflakes.t
 test-check-pylint.t
 test-check-shbang.t
@@ -59,6 +67,7 @@
 test-clone-cgi.t
 test-clone-pull-corruption.t
 test-clone-r.t
+test-clone-uncompressed.t
 test-clone-update-order.t
 test-clonebundles.t
 test-commit-amend.t
@@ -179,9 +188,12 @@
 test-generaldelta.t
 test-getbundle.t
 test-git-export.t
+test-glog-beautifygraph.t
 test-glog-topological.t
+test-glog.t
 test-gpg.t
 test-graft.t
+test-grep.t
 test-hg-parseurl.py
 test-hghave.t
 test-hgignore.t
@@ -211,6 +223,7 @@
 test-http-branchmap.t
 test-http-bundle1.t
 test-http-clone-r.t
+test-http-permissions.t
 test-http.t
 test-hybridencode.py
 test-identify.t
@@ -238,6 +251,7 @@
 test-issue4074.t
 test-issue522.t
 test-issue586.t
+test-issue5979.t
 test-issue612.t
 test-issue619.t
 test-issue660.t
@@ -254,6 +268,7 @@
 test-largefiles.t
 test-lfs-largefiles.t
 test-lfs-pointer.py
+test-linelog.py
 test-linerange.py
 test-locate.t
 test-lock-badness.t
@@ -277,6 +292,7 @@
 test-merge-halt.t
 test-merge-internal-tools-pattern.t
 test-merge-local.t
+test-merge-no-file-change.t
 test-merge-remove.t
 test-merge-revert.t
 test-merge-revert2.t
@@ -296,6 +312,7 @@
 test-minifileset.py
 test-minirst.py
 test-mq-git.t
+test-mq-guards.t
 test-mq-header-date.t
 test-mq-header-from.t
 test-mq-merge.t
@@ -308,6 +325,7 @@
 test-mq-qimport-fail-cleanup.t
 test-mq-qnew.t
 test-mq-qpush-exact.t
+test-mq-qpush-fail.t
 test-mq-qqueue.t
 test-mq-qrefresh-interactive.t
 test-mq-qrefresh-replace-log-message.t
@@ -317,7 +335,9 @@
 test-mq-safety.t
 test-mq-subrepo.t
 test-mq-symlinks.t
+test-mq.t
 test-mv-cp-st-diff.t
+test-narrow-acl.t
 test-narrow-archive.t
 test-narrow-clone-no-ellipsis.t
 test-narrow-clone-non-narrow-server.t
@@ -338,15 +358,19 @@
 test-narrow-shallow-merges.t
 test-narrow-shallow.t
 test-narrow-strip.t
+test-narrow-trackedcmd.t
 test-narrow-update.t
+test-narrow-widen-no-ellipsis.t
 test-narrow-widen.t
 test-narrow.t
 test-nested-repo.t
 test-newbranch.t
+test-newercgi.t
 test-nointerrupt.t
 test-obshistory.t
 test-obsmarker-template.t
 test-obsmarkers-effectflag.t
+test-obsolete-bounds-checking.t
 test-obsolete-bundle-strip.t
 test-obsolete-changeset-exchange.t
 test-obsolete-checkheads.t
@@ -358,6 +382,9 @@
 test-parseindex2.py
 test-patch-offset.t
 test-patch.t
+test-patchbomb-bookmark.t
+test-patchbomb-tls.t
+test-patchbomb.t
 test-pathconflicts-merge.t
 test-pathconflicts-update.t
 test-pathencode.py
@@ -372,6 +399,7 @@
 test-pull-update.t
 test-pull.t
 test-purge.t
+test-push-cgi.t
 test-push-checkheads-partial-C1.t
 test-push-checkheads-partial-C2.t
 test-push-checkheads-partial-C3.t
@@ -405,6 +433,7 @@
 test-pushvars.t
 test-qrecord.t
 test-rebase-abort.t
+test-rebase-backup.t
 test-rebase-base-flag.t
 test-rebase-bookmarks.t
 test-rebase-brute-force.t
@@ -439,6 +468,7 @@
 test-rename-after-merge.t
 test-rename-dir-merge.t
 test-rename-merge1.t
+test-rename-merge2.t
 test-rename.t
 test-repair-strip.t
 test-repo-compengines.t
@@ -446,6 +476,8 @@
 test-revert-flags.t
 test-revert-interactive.t
 test-revert-unknown.t
+test-revert.t
+test-revisions.t
 test-revlog-ancestry.py
 test-revlog-group-emptyiter.t
 test-revlog-mmapindex.t
@@ -489,6 +521,7 @@
 test-status-inprocess.py
 test-status-rev.t
 test-status-terse.t
+test-stream-bundle-v2.t
 test-strict.t
 test-strip-cross.t
 test-strip.t
@@ -529,6 +562,7 @@
 test-url-rev.t
 test-url.py
 test-username-newline.t
+test-util.py
 test-verify.t
 test-walk.t
 test-walkrepo.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/relnotes	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+"""Generate release notes from our commit log.
+
+This uses the relnotes extension directives when they're available,
+and falls back to our old pre-relnotes logic that used to live in the
+release-tools repo.
+"""
+import argparse
+import re
+import subprocess
+
+# Regenerate this list with
+#   hg export 'grep("\.\. [a-z]+::")' | grep '^\.\.' | \
+#     sed 's/.. //;s/::.*//' | sort -u
+rnsections = ["api", "bc", "container", "feature", "fix", "note", "perf"]
+
+rules = {
+    # keep
+    r"\(issue": 100,
+    r"\(BC\)": 100,
+    r"\(API\)": 100,
+    # core commands, bump up
+    r"(commit|files|log|pull|push|patch|status|tag|summary)(|s|es):": 20,
+    r"(annotate|alias|branch|bookmark|clone|graft|import|verify).*:": 20,
+    # extensions, bump up
+    r"(mq|shelve|rebase):": 20,
+    # newsy
+    r": deprecate": 20,
+    r"(option|feature|command|support)": 10,
+    # bug-like?
+    r"(fix|don't break|improve)": 7,
+    # boring stuff, bump down
+    r"^contrib": -5,
+    r"debug": -5,
+    r"help": -5,
+    r"(doc|bundle2|obsolete|obsmarker|rpm|setup|debug\S+:)": -15,
+    r"(check-code|check-commit|import-checker)": -20,
+    # cleanups and refactoring
+    r"(cleanup|whitespace|nesting|indent|spelling|comment)": -20,
+    r"(typo|hint|note|style:|correct doc)": -20,
+    r"_": -10,
+    r"(argument|absolute_import|attribute|assignment|mutable)": -15,
+    r"(unused|useless|unnecessary|duplicate|deprecated|scope|True|False)": -10,
+    r"(redundant|pointless|confusing|uninitialized|meaningless|dead)": -10,
+    r": (drop|remove|inherit|rename|simplify|naming|inline)": -10,
+    r"(docstring|document .* method)": -20,
+    r"(factor|extract|prepare|split|replace| import)": -20,
+    r": add.*(function|method|implementation|test|example)": -10,
+    r": (move|extract) .* (to|into|from)": -20,
+    r": implement ": -5,
+    r": use .* implementation": -20,
+    r"\S\S\S+\.\S\S\S\S+": -5,
+    r": use .* instead of": -20,
+    r"__": -5,
+    # dumb keywords
+    r"\S+/\S+:": -10,
+    r"\S+\.\S+:": -10,
+    # drop
+    r"^i18n-": -50,
+    r"^i18n:.*(hint|comment)": -50,
+    r"perf:": -50,
+    r"check-code:": -50,
+    r"Added.*for changeset": -50,
+    r"tests?:": -50,
+    r"test-": -50,
+    r"add.* tests": -50,
+    r"^_": -50,
+}
+
+cutoff = 10
+commits = []
+
+groupings = [
+    (r"util|parsers|repo|ctx|context|revlog|filelog|alias|cmdutil", "core"),
+    (r"revset|templater|ui|dirstate|hook|i18n|transaction|wire", "core"),
+    (r"color|pager", "core"),
+    (r"hgweb|paper|coal|gitweb", "hgweb"),
+    (r"pull|push|revert|resolve|annotate|bookmark|branch|clone", "commands"),
+    (r"commands|commit|config|files|graft|import|log|merge|patch", "commands"),
+    (r"phases|status|summary|amend|tag|help|verify", "commands"),
+    (r"rebase|mq|convert|eol|histedit|largefiles", "extensions"),
+    (r"shelve|unshelve", "extensions"),
+]
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument(
+        "startrev",
+        metavar="REV",
+        type=str,
+        nargs=1,
+        help=(
+            "Starting revision for the release notes. This revision "
+            "won't be included, but later revisions will."
+        ),
+    )
+    ap.add_argument(
+        "--stoprev",
+        metavar="REV",
+        type=str,
+        default="@",
+        nargs=1,
+        help=(
+            "Stop revision for release notes. This revision will be included,"
+            " but no later revisions will. This revision needs to be "
+            "a descendant of startrev."
+        ),
+    )
+    args = ap.parse_args()
+    fromext = subprocess.check_output(
+        [
+            "hg",
+            "--config",
+            "extensions.releasenotes=",
+            "releasenotes",
+            "-r",
+            "%s::%s" % (args.startrev[0], args.stoprev[0]),
+        ]
+    ).decode("utf-8")
+    # Find all release notes from un-relnotes-flagged commits.
+    for entry in sorted(
+        subprocess.check_output(
+            [
+                "hg",
+                "log",
+                "-r",
+                r'%s::%s - merge() - grep("\n\.\. (%s)::")'
+                % (args.startrev[0], args.stoprev[0], "|".join(rnsections)),
+                "-T",
+                r"{desc|firstline}\n",
+            ]
+        )
+        .decode("utf-8")
+        .splitlines()
+    ):
+        desc = entry.replace("`", "'")
+
+        score = 0
+        for rule, val in rules.items():
+            if re.search(rule, desc):
+                score += val
+
+        desc = desc.replace("(issue", "(Bts:issue")
+
+        if score >= cutoff:
+            commits.append(desc)
+    # Group unflagged notes.
+    groups = {}
+    bcs = []
+    apis = []
+
+    for d in commits:
+        if "(BC)" in d:
+            bcs.append(d)
+        if "(API)" in d:
+            apis.append(d)
+        for rule, g in groupings:
+            if re.match(rule, d):
+                groups.setdefault(g, []).append(d)
+                break
+        else:
+            groups.setdefault("unsorted", []).append(d)
+    print(fromext)
+    # print legacy release notes sections
+    for g in sorted(groups):
+        print("\n=== %s ===" % g)
+        for d in sorted(groups[g]):
+            print(" * %s" % d)
+
+    print("\n=== BC ===\n")
+
+    for d in sorted(bcs):
+        print(" * %s" % d)
+
+    print("\n=== API Changes ===\n")
+
+    for d in sorted(apis):
+        print(" * %s" % d)
+
+if __name__ == "__main__":
+    main()
--- a/contrib/simplemerge	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/simplemerge	Wed Sep 26 20:33:09 2018 +0900
@@ -12,6 +12,7 @@
     context,
     error,
     fancyopts,
+    pycompat,
     simplemerge,
     ui as uimod,
 )
@@ -19,13 +20,13 @@
     procutil,
 )
 
-options = [('L', 'label', [], _('labels to use on conflict markers')),
-           ('a', 'text', None, _('treat all files as text')),
-           ('p', 'print', None,
-            _('print results instead of overwriting LOCAL')),
-           ('', 'no-minimal', None, _('no effect (DEPRECATED)')),
-           ('h', 'help', None, _('display help and exit')),
-           ('q', 'quiet', None, _('suppress output'))]
+options = [(b'L', b'label', [], _(b'labels to use on conflict markers')),
+           (b'a', b'text', None, _(b'treat all files as text')),
+           (b'p', b'print', None,
+            _(b'print results instead of overwriting LOCAL')),
+           (b'', b'no-minimal', None, _(b'no effect (DEPRECATED)')),
+           (b'h', b'help', None, _(b'display help and exit')),
+           (b'q', b'quiet', None, _(b'suppress output'))]
 
 usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
 
@@ -41,16 +42,16 @@
 
 def showhelp():
     sys.stdout.write(usage)
-    sys.stdout.write('\noptions:\n')
+    sys.stdout.write(b'\noptions:\n')
 
     out_opts = []
     for shortopt, longopt, default, desc in options:
-        out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
-                                    longopt and ' --%s' % longopt),
-                         '%s' % desc))
+        out_opts.append((b'%2s%s' % (shortopt and b'-%s' % shortopt,
+                                     longopt and b' --%s' % longopt),
+                         b'%s' % desc))
     opts_len = max([len(opt[0]) for opt in out_opts])
     for first, second in out_opts:
-        sys.stdout.write(' %-*s  %s\n' % (opts_len, first, second))
+        sys.stdout.write(b' %-*s  %s\n' % (opts_len, first, second))
 
 try:
     for fp in (sys.stdin, sys.stdout, sys.stderr):
@@ -61,23 +62,23 @@
         args = fancyopts.fancyopts(sys.argv[1:], options, opts)
     except getopt.GetoptError as e:
         raise ParseError(e)
-    if opts['help']:
+    if opts[b'help']:
         showhelp()
         sys.exit(0)
     if len(args) != 3:
-            raise ParseError(_('wrong number of arguments'))
+            raise ParseError(_(b'wrong number of arguments'))
     local, base, other = args
     sys.exit(simplemerge.simplemerge(uimod.ui.load(),
                                      context.arbitraryfilectx(local),
                                      context.arbitraryfilectx(base),
                                      context.arbitraryfilectx(other),
-                                     **opts))
+                                     **pycompat.strkwargs(opts)))
 except ParseError as e:
-    sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
+    sys.stdout.write(b"%s: %s\n" % (sys.argv[0], e))
     showhelp()
     sys.exit(1)
 except error.Abort as e:
-    sys.stderr.write("abort: %s\n" % e)
+    sys.stderr.write(b"abort: %s\n" % e)
     sys.exit(255)
 except KeyboardInterrupt:
     sys.exit(255)
--- a/contrib/wix/help.wxs	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/wix/help.wxs	Wed Sep 26 20:33:09 2018 +0900
@@ -43,12 +43,16 @@
           <Component Id="help.internals" Guid="$(var.help.internals.guid)" Win64='$(var.IsX64)'>
             <File Id="internals.bundle2.txt"      Name="bundle2.txt" />
             <File Id="internals.bundles.txt"      Name="bundles.txt" KeyPath="yes" />
+            <File Id="internals.cbor.txt"         Name="cbor.txt" />
             <File Id="internals.censor.txt"       Name="censor.txt" />
             <File Id="internals.changegroups.txt" Name="changegroups.txt" />
             <File Id="internals.config.txt"       Name="config.txt" />
+            <File Id="internals.linelog.txt"      Name="linelog.txt" />
             <File Id="internals.requirements.txt" Name="requirements.txt" />
             <File Id="internals.revlogs.txt"      Name="revlogs.txt" />
             <File Id="internals.wireprotocol.txt" Name="wireprotocol.txt" />
+            <File Id="internals.wireprotocolrpc.txt" Name="wireprotocolrpc.txt" />
+            <File Id="internals.wireprotocolv2.txt" Name="wireprotocolv2.txt" />
           </Component>
         </Directory>
 
--- a/contrib/zsh_completion	Tue Sep 25 16:32:38 2018 -0400
+++ b/contrib/zsh_completion	Wed Sep 26 20:33:09 2018 +0900
@@ -82,7 +82,7 @@
 
   if [[ -z "$cmd" ]]
   then
-    _arguments -s -w : $_hg_global_opts \
+    _arguments -s -S : $_hg_global_opts \
     ':mercurial command:_hg_commands'
     return
   fi
@@ -119,7 +119,7 @@
     _hg_cmd_${cmd}
   else
     # complete unknown commands normally
-    _arguments -s -w : $_hg_global_opts \
+    _arguments -s -S : $_hg_global_opts \
       '*:files:_hg_files'
   fi
 }
@@ -139,7 +139,7 @@
   typeset -gA _hg_alias_list
   local hline cmd cmdalias
 
-  _call_program hg hg debugcomplete -v | while read -A hline
+  _call_program hg HGPLAINEXCEPT=alias hg debugcomplete -v | while read -A hline
   do
     cmd=$hline[1]
     _hg_cmd_list+=($cmd)
@@ -193,21 +193,13 @@
 
 # likely merge candidates
 _hg_mergerevs() {
-  typeset -a heads
-  local myrev
+  typeset -a heads branches
+  local revset='sort(head() and not ., -rev)'
 
-  heads=(${(f)"$(_hg_cmd heads --template '{rev}:{branch}\\n')"})
-  # exclude own revision
-  myrev=$(_hg_cmd log -r . --template '{rev}:{branch}\\n')
-  heads=(${heads:#$myrev})
-
+  heads=(${(f)"$(_hg_cmd log -r '$revset' --template '{rev}:{branch}\\n')"})
   (( $#heads )) && _describe -t heads 'heads' heads
 
-  branches=(${(f)"$(_hg_cmd heads --template '{branch}\\n')"})
-  # exclude own revision
-  myrev=$(_hg_cmd log -r . --template '{branch}\\n')
-  branches=(${branches:#$myrev})
-
+  branches=(${(S)heads/#*:/})
   (( $#branches )) && _describe -t branches 'branches' branches
 }
 
@@ -245,10 +237,10 @@
   _wanted files expl 'missing files' _multi_parts / status_files
 }
 
-_hg_modified() {
+_hg_committable() {
   typeset -a status_files
-  _hg_status m
-  _wanted files expl 'modified files' _multi_parts / status_files
+  _hg_status mar
+  _wanted files expl 'modified, added or removed files' _multi_parts / status_files
 }
 
 _hg_resolve() {
@@ -281,6 +273,23 @@
     (( $#items )) && _describe -t config 'config item' items
 }
 
+_hg_internal_merge_tools=(
+  \\:dump \\:fail \\:forcedump \\:local \\:merge \\:merge-local \\:merge-other
+  \\:merge3 \\:other \\:prompt \\:tagmerge \\:union
+)
+
+_hg_merge_tools() {
+  typeset -a external_tools
+  _describe -t internal_tools 'internal merge tools' _hg_internal_merge_tools
+  external_tools=(${(f)"$(_hg_cmd showconfig merge-tools | cut -d . -f 2)"})
+  (( $#external_tools )) && _describe -t external_tools 'external merge tools' external_tools
+}
+
+_hg_shelves() {
+  shelves=("${(f)$(_hg_cmd shelve -ql)}")
+  (( $#shelves )) && _describe -t shelves 'shelves' shelves
+}
+
 _hg_addremove() {
   _alternative 'files:unknown files:_hg_unknown' \
     'files:missing files:_hg_missing'
@@ -371,27 +380,29 @@
 
 # Common options
 _hg_global_opts=(
-    '(--repository -R)'{-R+,--repository=}'[repository root directory]:repository:_files -/'
-    '--cwd[change working directory]:new working directory:_files -/'
-    '(--noninteractive -y)'{-y,--noninteractive}'[do not prompt, assume yes for any required answers]'
+    '(--repository -R)'{-R+,--repository=}'[repository root directory or name of overlay bundle file]:repository:_files -/'
+    '--cwd=[change working directory]:new working directory:_files -/'
+    '(--noninteractive -y)'{-y,--noninteractive}'[do not prompt, automatically pick the first choice for all prompts]'
     '(--verbose -v)'{-v,--verbose}'[enable additional output]'
-    '*--config[set/override config option]:defined config items:_hg_config'
+    '*--config=[set/override config option]:defined config items:_hg_config'
     '(--quiet -q)'{-q,--quiet}'[suppress output]'
     '(--help -h)'{-h,--help}'[display help and exit]'
-    '--debug[debug mode]'
+    '--debug[enable debugging output]'
     '--debugger[start debugger]'
-    '--encoding[set the charset encoding]'
-    '--encodingmode[set the charset encoding mode]'
-    '--lsprof[print improved command execution profile]'
-    '--traceback[print traceback on exception]'
+    '--encoding=[set the charset encoding]:encoding'
+    '--encodingmode=[set the charset encoding mode]:encoding mode'
+    '--traceback[always print a traceback on exception]'
     '--time[time how long the command takes]'
-    '--profile[profile]'
+    '--profile[print command execution profile]'
     '--version[output version information and exit]'
+    '--hidden[consider hidden changesets]'
+    '--color=[when to colorize]:when:(true false yes no always auto never debug)'
+    '--pager=[when to paginate (default: auto)]:when:(true false yes no always auto never)'
 )
 
 _hg_pat_opts=(
-  '*'{-I+,--include=}'[include names matching the given patterns]:dir:_files -W $(_hg_cmd root) -/'
-  '*'{-X+,--exclude=}'[exclude names matching the given patterns]:dir:_files -W $(_hg_cmd root) -/')
+  '*'{-I+,--include=}'[include names matching the given patterns]:pattern:_files -W $(_hg_cmd root) -/'
+  '*'{-X+,--exclude=}'[exclude names matching the given patterns]:pattern:_files -W $(_hg_cmd root) -/')
 
 _hg_clone_opts=(
   $_hg_remote_opts
@@ -402,8 +413,8 @@
 _hg_date_user_opts=(
   '(--currentdate -D)'{-D,--currentdate}'[record the current date as commit date]'
   '(--currentuser -U)'{-U,--currentuser}'[record the current user as committer]'
-  '(--date -d)'{-d+,--date=}'[record the specified date as commit date]:date:'
-  '(--user -u)'{-u+,--user=}'[record the specified user as committer]:user:')
+  '(--date -d)'{-d+,--date=}'[record the specified date as commit date]:date'
+  '(--user -u)'{-u+,--user=}'[record the specified user as committer]:user')
 
 _hg_gitlike_opts=(
   '(--git -g)'{-g,--git}'[use git extended diff format]')
@@ -411,10 +422,13 @@
 _hg_diff_opts=(
   $_hg_gitlike_opts
   '(--text -a)'{-a,--text}'[treat all files as text]'
-  '--nodates[omit dates from diff headers]')
+  '--binary[generate binary diffs in git mode (default)]'
+  '--nodates[omit dates from diff headers]'
+)
 
 _hg_mergetool_opts=(
-  '(--tool -t)'{-t+,--tool=}'[specify merge tool]:tool:')
+  '(--tool -t)'{-t+,--tool=}'[specify merge tool]:merge tool:_hg_merge_tools'
+)
 
 _hg_dryrun_opts=(
   '(--dry-run -n)'{-n,--dry-run}'[do not perform actions, just print output]')
@@ -422,32 +436,32 @@
 _hg_ignore_space_opts=(
   '(--ignore-all-space -w)'{-w,--ignore-all-space}'[ignore white space when comparing lines]'
   '(--ignore-space-change -b)'{-b,--ignore-space-change}'[ignore changes in the amount of white space]'
-  '(--ignore-blank-lines -B)'{-B,--ignore-blank-lines}'[ignore changes whose lines are all blank]')
+  '(--ignore-blank-lines -B)'{-B,--ignore-blank-lines}'[ignore changes whose lines are all blank]'
+  '(--ignore-space-at-eol -Z)'{-Z,--ignore-space-at-eol}'[ignore changes in whitespace at EOL]'
+)
 
-_hg_style_opts=(
-  '--style[display using template map file]:'
-  '--template[display with template]:')
+_hg_template_opts=(
+  '(--template -T)'{-T+,--template=}'[display with template]:template'
+)
 
 _hg_log_opts=(
-  $_hg_global_opts $_hg_style_opts $_hg_gitlike_opts
-  '(--limit -l)'{-l+,--limit=}'[limit number of changes displayed]:'
+  $_hg_global_opts $_hg_template_opts $_hg_gitlike_opts
+  '(--limit -l)'{-l+,--limit=}'[limit number of changes displayed]:limit'
   '(--no-merges -M)'{-M,--no-merges}'[do not show merges]'
   '(--patch -p)'{-p,--patch}'[show patch]'
   '--stat[output diffstat-style summary of changes]'
+  '(--graph -G)'{-G,--graph}'[show the revision DAG]'
 )
 
 _hg_commit_opts=(
   '(-m --message -l --logfile --edit -e)'{-e,--edit}'[edit commit message]'
-  '(-e --edit -l --logfile --message -m)'{-m+,--message=}'[use <text> as commit message]:message:'
+  '(-e --edit -l --logfile --message -m)'{-m+,--message=}'[use <text> as commit message]:message'
   '(-e --edit -m --message --logfile -l)'{-l+,--logfile=}'[read the commit message from <file>]:log file:_files')
 
 _hg_remote_opts=(
-  '(--ssh -e)'{-e+,--ssh=}'[specify ssh command to use]:'
-  '--remotecmd[specify hg command to run on the remote side]:')
-
-_hg_branch_bmark_opts=(
-  '(--bookmark -B)'{-B+,--bookmark=}'[specify bookmark(s)]:bookmark:_hg_bookmarks'
-  '(--branch -b)'{-b+,--branch=}'[specify branch(es)]:branch:_hg_branches'
+  '(--ssh -e)'{-e+,--ssh=}'[specify ssh command to use]:command'
+  '--remotecmd=[specify hg command to run on the remote side]:remote command'
+  '--insecure[do not verify server certificate (ignoring web.cacerts config)]'
 )
 
 _hg_subrepos_opts=(
@@ -458,50 +472,53 @@
 }
 
 _hg_cmd_add() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts $_hg_subrepos_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts $_hg_subrepos_opts \
   '*:unknown files:_hg_unknown'
 }
 
 _hg_cmd_addremove() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
-  '(--similarity -s)'{-s+,--similarity=}'[guess renamed files by similarity (0<=s<=100)]:' \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts $_hg_subrepos_opts \
+  '(--similarity -s)'{-s+,--similarity=}'[guess renamed files by similarity (0<=s<=100)]:similarity' \
   '*:unknown or missing files:_hg_addremove'
 }
 
 _hg_cmd_annotate() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_ignore_space_opts $_hg_pat_opts \
   '(--rev -r)'{-r+,--rev=}'[annotate the specified revision]:revision:_hg_labels' \
-  '(--follow -f)'{-f,--follow}'[follow file copies and renames]' \
+  "--no-follow[don't follow copies and renames]" \
   '(--text -a)'{-a,--text}'[treat all files as text]' \
-  '(--user -u)'{-u,--user}'[list the author]' \
-  '(--date -d)'{-d,--date}'[list the date]' \
+  '(--user -u)'{-u,--user}'[list the author (long with -v)]' \
+  '(--file -f)'{-f,--file}'[list the filename]' \
+  '(--date -d)'{-d,--date}'[list the date (short with -q)]' \
   '(--number -n)'{-n,--number}'[list the revision number (default)]' \
   '(--changeset -c)'{-c,--changeset}'[list the changeset]' \
+  '(--line-number -l)'{-l,--line-number}'[show line number at the first appearance]' \
   '*:files:_hg_files'
 }
 
 _hg_cmd_archive() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_subrepos_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_subrepos_opts \
   '--no-decode[do not pass files through decoders]' \
-  '(--prefix -p)'{-p+,--prefix=}'[directory prefix for files in archive]:' \
+  '(--prefix -p)'{-p+,--prefix=}'[directory prefix for files in archive]:prefix' \
   '(--rev -r)'{-r+,--rev=}'[revision to distribute]:revision:_hg_labels' \
   '(--type -t)'{-t+,--type=}'[type of distribution to create]:archive type:(files tar tbz2 tgz uzip zip)' \
   '*:destination:_files'
 }
 
 _hg_cmd_backout() {
-  _arguments -s -w : $_hg_global_opts $_hg_mergetool_opts $_hg_pat_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_mergetool_opts $_hg_pat_opts \
     '--merge[merge with old dirstate parent after backout]' \
-    '(--date -d)'{-d+,--date=}'[record datecode as commit date]:date code:' \
-    '--parent[parent to choose when backing out merge]' \
-    '(--user -u)'{-u+,--user=}'[record user as commiter]:user:' \
-    '(--rev -r)'{-r+,--rev=}'[revision]:revision:_hg_labels' \
-    '(--message -m)'{-m+,--message=}'[use <text> as commit message]:text:' \
-    '(--logfile -l)'{-l+,--logfile=}'[read commit message from <file>]:log file:_files'
+    '--no-commit[do not commit]' \
+    '(--date -d)'{-d+,--date=}'[record the specified date as commit date]:date' \
+    '(--user -u)'{-u+,--user=}'[record the specified user as committer]:user' \
+    '(--rev -r 1)'{-r+,--rev=}'[revision to backout]:revision:_hg_labels' \
+    '(--message -m)'{-m+,--message=}'[use <text> as commit message]:text' \
+    '(--logfile -l)'{-l+,--logfile=}'[read commit message from <file>]:log file:_files' \
+    ':revision:_hg_labels'
 }
 
 _hg_cmd_bisect() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(-)'{-r,--reset}'[reset bisect state]' \
   '(--extend -e)'{-e,--extend}'[extend the bisect range]' \
   '(--good -g --bad -b --skip -s --reset -r)'{-g,--good}'[mark changeset good]'::revision:_hg_labels \
@@ -512,7 +529,7 @@
 }
 
 _hg_cmd_bookmarks() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--force -f)'{-f,--force}'[force]' \
   '(--inactive -i)'{-i,--inactive}'[mark a bookmark inactive]' \
   '(--rev -r --delete -d --rename -m)'{-r+,--rev=}'[revision]:revision:_hg_labels' \
@@ -522,381 +539,417 @@
 }
 
 _hg_cmd_branch() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--force -f)'{-f,--force}'[set branch name even if it shadows an existing branch]' \
   '(--clean -C)'{-C,--clean}'[reset branch name to parent branch name]'
 }
 
 _hg_cmd_branches() {
-  _arguments -s -w : $_hg_global_opts \
-  '(--active -a)'{-a,--active}'[show only branches that have unmerge heads]' \
+  _arguments -s -S : $_hg_global_opts \
   '(--closed -c)'{-c,--closed}'[show normal and closed branches]'
 }
 
 _hg_cmd_bundle() {
-  _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
-  '(--force -f)'{-f,--force}'[run even when remote repository is unrelated]' \
-  '(2)*--base[a base changeset to specify instead of a destination]:revision:_hg_labels' \
-  '(--branch -b)'{-b+,--branch=}'[a specific branch to bundle]:' \
-  '(--rev -r)'{-r+,--rev=}'[changeset(s) to bundle]:' \
-  '--all[bundle all changesets in the repository]' \
+  _arguments -s -S : $_hg_global_opts $_hg_remote_opts \
+  '(--force -f)'{-f,--force}'[run even when the destination is unrelated]' \
+  '(2)*--base[a base changeset assumed to be available at the destination]:revision:_hg_labels' \
+  '*'{-b+,--branch=}'[a specific branch you would like to bundle]:branch:_hg_branches' \
+  '*'{-r+,--rev=}'[a changeset intended to be added to the destination]:revision:_hg_labels' \
+  '(--all -a)'{-a,--all}'[bundle all changesets in the repository]' \
+  '--type[bundle compression type to use (default: bzip2)]:bundle type' \
   ':output file:_files' \
   ':destination repository:_files -/'
 }
 
 _hg_cmd_cat() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
-  '(--output -o)'{-o+,--output=}'[print output to file with formatted name]:filespec:' \
-  '(--rev -r)'{-r+,--rev=}'[revision]:revision:_hg_labels' \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts \
+  '(--output -o)'{-o+,--output=}'[print output to file with formatted name]:format string' \
+  '(--rev -r)'{-r+,--rev=}'[print the given revision]:revision:_hg_labels' \
   '--decode[apply any matching decode filter]' \
   '*:file:_hg_files'
 }
 
 _hg_cmd_clone() {
-  _arguments -s -w : $_hg_global_opts $_hg_clone_opts \
-  '(--rev -r)'{-r+,--rev=}'[a changeset you would like to have after cloning]:' \
-  '(--updaterev -u)'{-u+,--updaterev=}'[revision, tag or branch to check out]:' \
-  '(--branch -b)'{-b+,--branch=}'[clone only the specified branch]:' \
+  _arguments -s -S : $_hg_global_opts $_hg_clone_opts \
+  '*'{-r+,--rev=}'[do not clone everything, but include this changeset and its ancestors]:revision' \
+  '(--updaterev -u)'{-u+,--updaterev=}'[revision, tag, or branch to check out]:revision' \
+  '*'{-b+,--branch=}"[do not clone everything, but include this branch's changesets and their ancestors]:branch" \
   ':source repository:_hg_remote' \
   ':destination:_hg_clone_dest'
 }
 
 _hg_cmd_commit() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_subrepos_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_subrepos_opts \
   '(--addremove -A)'{-A,--addremove}'[mark new/missing files as added/removed before committing]' \
-  '(--message -m)'{-m+,--message=}'[use <text> as commit message]:text:' \
+  '(--message -m)'{-m+,--message=}'[use <text> as commit message]:text' \
   '(--logfile -l)'{-l+,--logfile=}'[read commit message from <file>]:log file:_files' \
-  '(--date -d)'{-d+,--date=}'[record datecode as commit date]:date code:' \
-  '(--user -u)'{-u+,--user=}'[record user as commiter]:user:' \
-  '--amend[amend the parent of the working dir]' \
-  '--close-branch[mark a branch as closed]' \
-  '*:file:_hg_files'
+  '(--date -d)'{-d+,--date=}'[record the specified date as commit date]:date' \
+  '(--user -u)'{-u+,--user=}'[record the specified user as committer]:user' \
+  '--amend[amend the parent of the working directory]' \
+  '--close-branch[mark a branch head as closed]' \
+  '(--interactive -i)'{-i,--interactive}'[use interactive mode]' \
+  '(--secret -s)'{-s,--secret}'[use the secret phase for committing]' \
+  '*:file:_hg_committable'
 }
 
 _hg_cmd_copy() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
   '(--after -A)'{-A,--after}'[record a copy that has already occurred]' \
   '(--force -f)'{-f,--force}'[forcibly copy over an existing managed file]' \
   '*:file:_hg_files'
 }
 
 _hg_cmd_diff() {
+  local context state state_descr line ret=1
   typeset -A opt_args
-  _arguments -s -w : $_hg_global_opts $_hg_diff_opts $_hg_ignore_space_opts \
+
+  _arguments -s -S : $_hg_global_opts $_hg_diff_opts $_hg_ignore_space_opts \
                      $_hg_pat_opts $_hg_subrepos_opts \
   '*'{-r+,--rev=}'[revision]:revision:_hg_revrange' \
+  '--noprefix[omit a/ and b/ prefixes from filenames]' \
   '(--show-function -p)'{-p,--show-function}'[show which function each change is in]' \
-  '(--change -c)'{-c+,--change=}'[change made by revision]:' \
-  '(--text -a)'{-a,--text}'[treat all files as text]' \
+  '(--change -c)'{-c+,--change=}'[change made by revision]:revision:_hg_labels' \
   '--reverse[produce a diff that undoes the changes]' \
-  '(--unified -U)'{-U+,--unified=}'[number of lines of context to show]:' \
+  '(--unified -U)'{-U+,--unified=}'[number of lines of context to show]:count' \
   '--stat[output diffstat-style summary of changes]' \
-  '*:file:->diff_files'
+  '--root=[produce diffs relative to subdirectory]:directory:_files -/' \
+  '*:file:->diff_files' && ret=0
 
   if [[ $state == 'diff_files' ]]
   then
-    if [[ -n $opt_args[-r] ]]
+    if [[ -n ${opt_args[(I)-r|--rev]} ]]
     then
-      _hg_files
+      _hg_files && ret=0
     else
-      _hg_modified
+      _hg_committable && ret=0
     fi
   fi
+
+  return ret
 }
 
 _hg_cmd_export() {
-  _arguments -s -w : $_hg_global_opts $_hg_diff_opts \
-  '(--outout -o)'{-o+,--output=}'[print output to file with formatted name]:filespec:' \
+  _arguments -s -S : $_hg_global_opts $_hg_diff_opts \
+  '(--bookmark -B)'{-B+,--bookmark=}'[export changes only reachable by given bookmark]:bookmark:_hg_bookmarks' \
+  '(--output -o)'{-o+,--output=}'[print output to file with formatted name]:format string' \
   '--switch-parent[diff against the second parent]' \
-  '(--rev -r)'{-r+,--rev=}'[revision]:revision:_hg_labels' \
+  '*'{-r+,--rev=}'[revisions to export]:revision:_hg_labels' \
   '*:revision:_hg_labels'
 }
 
+_hg_cmd_files() {
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_subrepos_opts \
+  '(--rev -r)'{-r+,--rev=}'[search the repository as it is in revision]:revision:_hg_labels' \
+  '(--print0 -0)'{-0,--print0}'[end filenames with NUL, for use with xargs]' \
+  '*:file:_hg_files'
+}
+
 _hg_cmd_forget() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+  '(--interactive -i)'{-i,--interactive}'[use interactive mode]' \
   '*:file:_hg_files'
 }
 
 _hg_cmd_graft() {
-  _arguments -s -w : $_hg_global_opts $_hg_dryrun_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_dryrun_opts \
                      $_hg_date_user_opts $_hg_mergetool_opts \
-  '(--continue -c)'{-c,--continue}'[resume interrupted graft]' \
+  '*'{-r+,--rev=}'[revisions to graft]:revision:_hg_labels' \
+  '(--continue -c --abort -a)'{-c,--continue}'[resume interrupted graft]' \
+  '(--continue -c --abort -a)'{-a,--abort}'[abort interrupted graft]' \
   '(--edit -e)'{-e,--edit}'[invoke editor on commit messages]' \
   '--log[append graft info to log message]' \
+  "--no-commit[don't commit, just apply the changes in working directory]" \
+  '(--force -f)'{-f,--force}'[force graft]' \
   '*:revision:_hg_labels'
 }
 
 _hg_cmd_grep() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
-  '(--print0 -0)'{-0,--print0}'[end filenames with NUL]' \
-  '--all[print all revisions with matches]' \
-  '(--follow -f)'{-f,--follow}'[follow changeset or file history]' \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts \
+  '(--print0 -0)'{-0,--print0}'[end fields with NUL]' \
+  '--diff[print all revisions when the term was introduced or removed]' \
+  '(--text -a)'{-a,--text}'[treat all files as text]' \
+  '(--follow -f)'{-f,--follow}'[follow changeset history, or file history across copies and renames]' \
   '(--ignore-case -i)'{-i,--ignore-case}'[ignore case when matching]' \
-  '(--files-with-matches -l)'{-l,--files-with-matches}'[print only filenames and revs that match]' \
+  '(--files-with-matches -l)'{-l,--files-with-matches}'[print only filenames and revisions that match]' \
   '(--line-number -n)'{-n,--line-number}'[print matching line numbers]' \
-  '*'{-r+,--rev=}'[search in given revision range]:revision:_hg_revrange' \
-  '(--user -u)'{-u,--user}'[print user who committed change]' \
-  '(--date -d)'{-d,--date}'[print date of a changeset]' \
+  '*'{-r+,--rev=}'[only search files changed within revision range]:revision:_hg_revrange' \
+  '(--user -u)'{-u,--user}'[list the author (long with -v)]' \
+  '(--date -d)'{-d,--date}'[list the date (short with -q)]' \
   '1:search pattern:' \
   '*:files:_hg_files'
 }
 
 _hg_cmd_heads() {
-  _arguments -s -w : $_hg_global_opts $_hg_style_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_template_opts \
   '(--topo -t)'{-t,--topo}'[show topological heads only]' \
   '(--closed -c)'{-c,--closed}'[show normal and closed branch heads]' \
-  '(--rev -r)'{-r+,--rev=}'[show only heads which are descendants of rev]:revision:_hg_labels'
+  '(--rev -r)'{-r+,--rev=}'[show only heads which are descendants of revision]:revision:_hg_labels'
 }
 
 _hg_cmd_help() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--extension -e)'{-e,--extension}'[show only help for extensions]' \
   '(--command -c)'{-c,--command}'[show only help for commands]' \
-  '(--keyword -k)'{-k+,--keyword}'[show topics matching keyword]' \
+  '(--keyword -k)'{-k,--keyword}'[show topics matching keyword]' \
+  '*'{-s+,--system=}'[show help for specific platform(s)]:platform:(windows vms plan9 unix)' \
   '*:mercurial help topic:_hg_help_topics'
 }
 
 _hg_cmd_identify() {
-  _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
-  '(--rev -r)'{-r+,--rev=}'[identify the specified rev]:revision:_hg_labels' \
+  _arguments -s -S : $_hg_global_opts $_hg_remote_opts \
+  '(--rev -r)'{-r+,--rev=}'[identify the specified revision]:revision:_hg_labels' \
   '(--num -n)'{-n,--num}'[show local revision number]' \
   '(--id -i)'{-i,--id}'[show global revision id]' \
   '(--branch -b)'{-b,--branch}'[show branch]' \
-  '(--bookmark -B)'{-B,--bookmark}'[show bookmarks]' \
+  '(--bookmarks -B)'{-B,--bookmarks}'[show bookmarks]' \
   '(--tags -t)'{-t,--tags}'[show tags]'
 }
 
 _hg_cmd_import() {
-  _arguments -s -w : $_hg_global_opts $_hg_commit_opts \
-  '(--strip -p)'{-p+,--strip=}'[directory strip option for patch (default: 1)]:count:' \
-  '(--force -f)'{-f,--force}'[skip check for outstanding uncommitted changes]' \
+  _arguments -s -S : $_hg_global_opts $_hg_commit_opts \
+  '(--strip -p)'{-p+,--strip=}'[directory strip option for patch (default: 1)]:count' \
   '--bypass[apply patch without touching the working directory]' \
   '--no-commit[do not commit, just update the working directory]' \
   '--partial[commit even if some hunks fail]' \
-  '--exact[apply patch to the nodes from which it was generated]' \
+  '--exact[abort if patch would apply lossily]' \
+  '--prefix=[apply patch to subdirectory]:directory:_files -/' \
   '--import-branch[use any branch information in patch (implied by --exact)]' \
-  '(--date -d)'{-d+,--date=}'[record datecode as commit date]:date code:' \
-  '(--user -u)'{-u+,--user=}'[record user as commiter]:user:' \
-  '(--similarity -s)'{-s+,--similarity=}'[guess renamed files by similarity (0<=s<=100)]:' \
+  '(--date -d)'{-d+,--date=}'[record the specified date as commit date]:date' \
+  '(--user -u)'{-u+,--user=}'[record the specified user as committer]:user' \
+  '(--similarity -s)'{-s+,--similarity=}'[guess renamed files by similarity (0<=s<=100)]:similarity' \
   '*:patch:_files'
 }
 
 _hg_cmd_incoming() {
-  _arguments -s -w : $_hg_log_opts $_hg_branch_bmark_opts $_hg_remote_opts \
-                     $_hg_subrepos_opts \
-  '(--force -f)'{-f,--force}'[run even when the remote repository is unrelated]' \
-  '(--rev -r)'{-r+,--rev=}'[a specific revision up to which you would like to pull]:revision:_hg_labels' \
+  _arguments -s -S : $_hg_log_opts $_hg_remote_opts $_hg_subrepos_opts \
+  '(--force -f)'{-f,--force}'[run even if remote repository is unrelated]' \
+  '*'{-r+,--rev=}'[a remote changeset intended to be added]:revision:_hg_labels' \
   '(--newest-first -n)'{-n,--newest-first}'[show newest record first]' \
-  '--bundle[file to store the bundles into]:bundle file:_files' \
+  '--bundle=[file to store the bundles into]:bundle file:_files' \
+  '(--bookmarks -B)'{-B,--bookmarks}'[compare bookmarks]' \
+  '*'{-b+,--branch=}'[a specific branch you would like to pull]:branch:_hg_branches' \
   ':source:_hg_remote'
 }
 
 _hg_cmd_init() {
-  _arguments -s -w : $_hg_global_opts $_hg_remote_opts \
-  ':dir:_files -/'
+  _arguments -s -S : $_hg_global_opts $_hg_remote_opts \
+  ':directory:_files -/'
 }
 
 _hg_cmd_locate() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
-  '(--rev -r)'{-r+,--rev=}'[search repository as it stood at revision]:revision:_hg_labels' \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts \
+  '(--rev -r)'{-r+,--rev=}'[search the repository as it is in revision]:revision:_hg_labels' \
   '(--print0 -0)'{-0,--print0}'[end filenames with NUL, for use with xargs]' \
-  '(--fullpath -f)'{-f,--fullpath}'[print complete paths]' \
+  '(--fullpath -f)'{-f,--fullpath}'[print complete paths from the filesystem root]' \
   '*:search pattern:_hg_files'
 }
 
 _hg_cmd_log() {
-  _arguments -s -w : $_hg_log_opts $_hg_pat_opts \
-  '(--follow --follow-first -f)'{-f,--follow}'[follow changeset or history]' \
-  '(-f --follow)--follow-first[only follow the first parent of merge changesets]' \
+  _arguments -s -S : $_hg_log_opts $_hg_pat_opts \
+  '(--follow -f)'{-f,--follow}'[follow changeset history, or file history across copies and renames]' \
   '(--copies -C)'{-C,--copies}'[show copied files]' \
-  '(--keyword -k)'{-k+,--keyword}'[search for a keyword]:' \
+  '*'{-k+,--keyword=}'[search for a keyword]:keyword' \
   '*'{-r+,--rev=}'[show the specified revision or revset]:revision:_hg_revrange' \
+  '--removed[include revisions where files were removed]' \
   '(--only-merges -m)'{-m,--only-merges}'[show only merges]' \
-  '(--prune -P)'{-P+,--prune=}'[do not display revision or any of its ancestors]:revision:_hg_labels' \
-  '(--graph -G)'{-G,--graph}'[show the revision DAG]' \
-  '(--branch -b)'{-b+,--branch=}'[show changesets within the given named branch]:branch:_hg_branches' \
-  '(--user -u)'{-u+,--user=}'[revisions committed by user]:user:' \
-  '(--date -d)'{-d+,--date=}'[show revisions matching date spec]:date:' \
+  '*'{-P+,--prune=}'[do not display revision or any of its ancestors]:revision:_hg_labels' \
+  '*'{-b+,--branch=}'[show changesets within the given named branch]:branch:_hg_branches' \
+  '*'{-u+,--user=}'[revisions committed by user]:user' \
+  '(--date -d)'{-d+,--date=}'[show revisions matching date spec]:date' \
   '*:files:_hg_files'
 }
 
 _hg_cmd_manifest() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '--all[list files from all revisions]' \
   '(--rev -r)'{-r+,--rev=}'[revision to display]:revision:_hg_labels' \
   ':revision:_hg_labels'
 }
 
 _hg_cmd_merge() {
-  _arguments -s -w : $_hg_global_opts $_hg_mergetool_opts \
-  '(--force -f)'{-f,--force}'[force a merge with outstanding changes]' \
+  _arguments -s -S : $_hg_global_opts $_hg_mergetool_opts \
   '(--rev -r 1)'{-r+,--rev=}'[revision to merge]:revision:_hg_mergerevs' \
   '(--preview -P)'{-P,--preview}'[review revisions to merge (no merge is performed)]' \
+  '(- :)--abort[abort the ongoing merge]' \
   ':revision:_hg_mergerevs'
 }
 
 _hg_cmd_outgoing() {
-  _arguments -s -w : $_hg_log_opts $_hg_branch_bmark_opts $_hg_remote_opts \
-                     $_hg_subrepos_opts \
-  '(--force -f)'{-f,--force}'[run even when the remote repository is unrelated]' \
-  '*'{-r+,--rev=}'[a specific revision you would like to push]:revision:_hg_revrange' \
+  _arguments -s -S : $_hg_log_opts $_hg_remote_opts $_hg_subrepos_opts \
+  '(--force -f)'{-f,--force}'[run even when the destination is unrelated]' \
+  '*'{-r+,--rev=}'[a changeset intended to be included in the destination]:revision:_hg_revrange' \
   '(--newest-first -n)'{-n,--newest-first}'[show newest record first]' \
+  '(--bookmarks -B)'{-B,--bookmarks}'[compare bookmarks]' \
+  '*'{-b+,--branch=}'[a specific branch you would like to push]:branch:_hg_branches' \
   ':destination:_hg_remote'
 }
 
 _hg_cmd_parents() {
-  _arguments -s -w : $_hg_global_opts $_hg_style_opts \
-  '(--rev -r)'{-r+,--rev=}'[show parents of the specified rev]:revision:_hg_labels' \
+  _arguments -s -S : $_hg_global_opts $_hg_template_opts \
+  '(--rev -r)'{-r+,--rev=}'[show parents of the specified revision]:revision:_hg_labels' \
   ':last modified file:_hg_files'
 }
 
 _hg_cmd_paths() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   ':path:_hg_paths'
 }
 
 _hg_cmd_phase() {
-  _arguments -s -w : $_hg_global_opts \
-  '(--public -p)'{-p,--public}'[set changeset phase to public]' \
-  '(--draft -d)'{-d,--draft}'[set changeset phase to draft]' \
-  '(--secret -s)'{-s,--secret}'[set changeset phase to secret]' \
+  _arguments -s -S : $_hg_global_opts \
+  '(--public -p --draft -d --secret -s)'{-p,--public}'[set changeset phase to public]' \
+  '(--public -p --draft -d --secret -s)'{-d,--draft}'[set changeset phase to draft]' \
+  '(--public -p --draft -d --secret -s)'{-s,--secret}'[set changeset phase to secret]' \
   '(--force -f)'{-f,--force}'[allow to move boundary backward]' \
-  '(--rev -r)'{-r+,--rev=}'[target revision]:revision:_hg_labels' \
-  ':revision:_hg_labels'
+  '*'{-r+,--rev=}'[target revision]:revision:_hg_labels' \
+  '*:revision:_hg_labels'
 }
 
 _hg_cmd_pull() {
-  _arguments -s -w : $_hg_global_opts $_hg_branch_bmark_opts $_hg_remote_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_remote_opts \
   '(--force -f)'{-f,--force}'[run even when the remote repository is unrelated]' \
-  '(--update -u)'{-u,--update}'[update to new tip if changesets were pulled]' \
-  '(--rev -r)'{-r+,--rev}'[a specific revision up to which you would like to pull]:revision:' \
+  '(--update -u)'{-u,--update}'[update to new branch head if new descendants were pulled]' \
+  '*'{-r+,--rev=}'[a remote changeset intended to be added]:revision:_hg_labels' \
+  '*'{-B+,--bookmark=}'[bookmark to pull]:bookmark:_hg_bookmarks' \
+  '*'{-b+,--branch=}'[a specific branch you would like to pull]:branch:_hg_branches' \
   ':source:_hg_remote'
 }
 
 _hg_cmd_push() {
-  _arguments -s -w : $_hg_global_opts $_hg_branch_bmark_opts $_hg_remote_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_remote_opts \
   '(--force -f)'{-f,--force}'[force push]' \
-  '(--rev -r)'{-r+,--rev=}'[a specific revision you would like to push]:revision:_hg_labels' \
+  '*'{-r+,--rev=}'[a changeset intended to be included in the destination]:revision:_hg_labels' \
+  '*'{-B+,--bookmark=}'[bookmark to push]:bookmark:_hg_bookmarks' \
+  '*'{-b+,--branch=}'[a specific branch you would like to push]:branch:_hg_branches' \
   '--new-branch[allow pushing a new branch]' \
   ':destination:_hg_remote'
 }
 
 _hg_cmd_remove() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts \
-  '(--after -A)'{-A,--after}'[record remove that has already occurred]' \
-  '(--force -f)'{-f,--force}'[remove file even if modified]' \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts $_hg_subrepos_opts \
+  '(--after -A)'{-A,--after}'[record delete for missing files]' \
+  '(--force -f)'{-f,--force}'[forget added files, delete modified files]' \
   '*:file:_hg_files'
 }
 
 _hg_cmd_rename() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
   '(--after -A)'{-A,--after}'[record a rename that has already occurred]' \
   '(--force -f)'{-f,--force}'[forcibly copy over an existing managed file]' \
   '*:file:_hg_files'
 }
 
 _hg_cmd_resolve() {
-  local context state line
+  local context state state_descr line ret=1
   typeset -A opt_args
 
-  _arguments -s -w : $_hg_global_opts $_hg_mergetool_opts $_hg_pat_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_mergetool_opts $_hg_pat_opts \
   '(--all -a)'{-a,--all}'[select all unresolved files]' \
   '(--no-status -n)'{-n,--no-status}'[hide status prefix]' \
   '(--list -l --mark -m --unmark -u)'{-l,--list}'[list state of files needing merge]:*:merged files:->resolve_files' \
   '(--mark -m --list -l --unmark -u)'{-m,--mark}'[mark files as resolved]:*:unresolved files:_hg_unresolved' \
-  '(--unmark -u --list -l --mark -m)'{-u,--unmark}'[unmark files as resolved]:*:resolved files:_hg_resolved' \
-  '*:file:_hg_unresolved'
+  '(--unmark -u --list -l --mark -m)'{-u,--unmark}'[mark files as unresolved]:*:resolved files:_hg_resolved' \
+  '*:file:_hg_unresolved' && ret=0
 
   if [[ $state == 'resolve_files' ]]
   then
     _alternative 'files:resolved files:_hg_resolved' \
-      'files:unresolved files:_hg_unresolved'
+      'files:unresolved files:_hg_unresolved' && ret=0
   fi
+
+  return ret
 }
 
 _hg_cmd_revert() {
-  local context state line
+  local context state state_descr line ret=1
   typeset -A opt_args
 
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_dryrun_opts \
   '(--all -a :)'{-a,--all}'[revert all changes when no arguments given]' \
-  '(--rev -r)'{-r+,--rev=}'[revision to revert to]:revision:_hg_labels' \
+  '(--rev -r)'{-r+,--rev=}'[revert to the specified revision]:revision:_hg_labels' \
   '(--no-backup -C)'{-C,--no-backup}'[do not save backup copies of files]' \
-  '(--date -d)'{-d+,--date=}'[tipmost revision matching date]:date code:' \
-  '*:file:->diff_files'
+  '(--date -d)'{-d+,--date=}'[tipmost revision matching date]:date' \
+  '(--interactive -i)'{-i,--interactive}'[interactively select the changes]' \
+  '*:file:->revert_files' && ret=0
 
-  if [[ $state == 'diff_files' ]]
+  if [[ $state == 'revert_files' ]]
   then
-    if [[ -n $opt_args[-r] ]]
+    if [[ -n ${opt_args[(I)-r|--rev]} ]]
     then
-      _hg_files
+      _hg_files && ret=0
     else
       typeset -a status_files
       _hg_status mard
-      _wanted files expl 'modified, added, removed or deleted file' _multi_parts / status_files
+      _wanted files expl 'modified, added, removed or deleted file' _multi_parts / status_files && ret=0
     fi
   fi
+
+  return ret
 }
 
 _hg_cmd_rollback() {
-  _arguments -s -w : $_hg_global_opts $_hg_dryrun_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_dryrun_opts \
   '(--force -f)'{-f,--force}'[ignore safety measures]' \
 }
 
 _hg_cmd_serve() {
-  _arguments -s -w : $_hg_global_opts \
-  '(--accesslog -A)'{-A+,--accesslog=}'[name of access log file]:log file:_files' \
-  '(--errorlog -E)'{-E+,--errorlog=}'[name of error log file]:log file:_files' \
+  _arguments -s -S : $_hg_global_opts $_hg_subrepos_opts \
+  '(--accesslog -A)'{-A+,--accesslog=}'[name of access log file to write to]:log file:_files' \
+  '(--errorlog -E)'{-E+,--errorlog=}'[name of error log file to write to]:log file:_files' \
   '(--daemon -d)'{-d,--daemon}'[run server in background]' \
-  '(--port -p)'{-p+,--port=}'[listen port]:listen port:' \
-  '(--address -a)'{-a+,--address=}'[interface address]:interface address:' \
-  '--prefix[prefix path to serve from]:directory:_files' \
-  '(--name -n)'{-n+,--name=}'[name to show in web pages]:repository name:' \
-  '--web-conf[name of the hgweb config file]:webconf_file:_files' \
-  '--pid-file[name of file to write process ID to]:pid_file:_files' \
-  '--cmdserver[cmdserver mode]:mode:' \
-  '(--templates -t)'{-t,--templates}'[web template directory]:template dir:_files -/' \
-  '--style[web template style]:style' \
+  '(--port -p)'{-p+,--port=}'[port to listen on (default: 8000)]:listen port' \
+  '(--address -a)'{-a+,--address=}'[address to listen on (default: all interfaces)]:interface address' \
+  '--prefix=[prefix path to serve from (default: server root)]:directory:_files' \
+  '(--name -n)'{-n+,--name=}'[name to show in web pages (default: working directory)]:repository name' \
+  '--web-conf=[name of the hgweb config file]:config file:_files' \
+  '--pid-file=[name of file to write process ID to]:pid file:_files' \
+  '--cmdserver[for remote clients]' \
+  '(--templates -t)'{-t+,--templates=}'[web template directory]:template dir:_files -/' \
+  '--style=[template style to use]:style' \
   '--stdio[for remote clients]' \
-  '--certificate[certificate file]:cert_file:_files' \
-  '(--ipv6 -6)'{-6,--ipv6}'[use IPv6 in addition to IPv4]'
+  '(--ipv6 -6)'{-6,--ipv6}'[use IPv6 in addition to IPv4]' \
+  '--certificate=[SSL certificate file]:certificate file:_files' \
+  '--print-url[start and print only the URL]'
 }
 
 _hg_cmd_showconfig() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--untrusted -u)'{-u,--untrusted}'[show untrusted configuration options]' \
-  ':config item:_hg_config'
+  '(--edit -e)'{-e,--edit}'[edit user config]' \
+  '(--local -l --global -g)'{-l,--local}'[edit repository config]' \
+  '(--local -l --global -g)'{-g,--global}'[edit global config]' \
+  '*:config item:_hg_config'
 }
 
 _hg_cmd_status() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_subrepos_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_subrepos_opts \
   '(--all -A)'{-A,--all}'[show status of all files]' \
   '(--modified -m)'{-m,--modified}'[show only modified files]' \
   '(--added -a)'{-a,--added}'[show only added files]' \
   '(--removed -r)'{-r,--removed}'[show only removed files]' \
   '(--deleted -d)'{-d,--deleted}'[show only deleted (but tracked) files]' \
   '(--clean -c)'{-c,--clean}'[show only files without changes]' \
-  '(--unknown -u)'{-u,--unknown}'[show only unknown files]' \
-  '(--ignored -i)'{-i,--ignored}'[show ignored files]' \
+  '(--unknown -u)'{-u,--unknown}'[show only unknown (not tracked) files]' \
+  '(--ignored -i)'{-i,--ignored}'[show only ignored files]' \
   '(--no-status -n)'{-n,--no-status}'[hide status prefix]' \
   '(--copies -C)'{-C,--copies}'[show source of copied files]' \
   '(--print0 -0)'{-0,--print0}'[end filenames with NUL, for use with xargs]' \
-  '--rev[show difference from revision]:revision:_hg_labels' \
-  '--change[list the changed files of a revision]:revision:_hg_labels' \
+  '*--rev=[show difference from revision]:revision:_hg_labels' \
+  '--change=[list the changed files of a revision]:revision:_hg_labels' \
   '*:files:_files'
 }
 
 _hg_cmd_summary() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '--remote[check for push and pull]'
 }
 
 _hg_cmd_tag() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--local -l)'{-l,--local}'[make the tag local]' \
-  '(--message -m)'{-m+,--message=}'[message for tag commit log entry]:message:' \
-  '(--date -d)'{-d+,--date=}'[record datecode as commit date]:date code:' \
-  '(--user -u)'{-u+,--user=}'[record user as commiter]:user:' \
+  '(--message -m)'{-m+,--message=}'[message for tag commit log entry]:message' \
+  '(--date -d)'{-d+,--date=}'[record the specified date as commit date]:date' \
+  '(--user -u)'{-u+,--user=}'[record the specified user as committer]:user' \
   '(--rev -r)'{-r+,--rev=}'[revision to tag]:revision:_hg_labels' \
   '(--force -f)'{-f,--force}'[force tag]' \
   '--remove[remove a tag]' \
@@ -905,22 +958,23 @@
 }
 
 _hg_cmd_tip() {
-  _arguments -s -w : $_hg_global_opts $_hg_gitlike_opts $_hg_style_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_gitlike_opts $_hg_template_opts \
   '(--patch -p)'{-p,--patch}'[show patch]'
 }
 
 _hg_cmd_unbundle() {
-  _arguments -s -w : $_hg_global_opts \
-  '(--update -u)'{-u,--update}'[update to new tip if changesets were unbundled]' \
-  ':files:_files'
+  _arguments -s -S : $_hg_global_opts \
+  '(--update -u)'{-u,--update}'[update to new branch head if changesets were unbundled]' \
+  '*:files:_files'
 }
 
 _hg_cmd_update() {
-  _arguments -s -w : $_hg_global_opts \
-  '(--clean -C)'{-C,--clean}'[overwrite locally modified files]' \
-  '(--rev -r)'{-r+,--rev=}'[revision]:revision:_hg_labels' \
-  '(--check -c)'{-c,--check}'[update across branches if no uncommitted changes]' \
-  '(--date -d)'{-d+,--date=}'[tipmost revision matching date]:' \
+  _arguments -s -S : $_hg_global_opts $_hg_mergetool_opts \
+  '(--clean -C)'{-C,--clean}'[discard uncommitted changes (no backup)]' \
+  '(--check -c)'{-c,--check}'[require clean working directory]' \
+  '(--merge -m)'{-m,--merge}'[merge uncommitted changes]' \
+  '(--date -d)'{-d+,--date=}'[tipmost revision matching date]:date' \
+  '(--rev -r 1)'{-r+,--rev=}'[revision]:revision:_hg_labels' \
   ':revision:_hg_labels'
 }
 
@@ -928,8 +982,8 @@
 
 # HGK
 _hg_cmd_view() {
-  _arguments -s -w : $_hg_global_opts \
-  '(--limit -l)'{-l+,--limit=}'[limit number of changes displayed]:' \
+  _arguments -s -S : $_hg_global_opts \
+  '(--limit -l)'{-l+,--limit=}'[limit number of changes displayed]:limit' \
   ':revision range:_hg_labels'
 }
 
@@ -983,54 +1037,55 @@
   '(--summary -s)'{-s,--summary}'[print first line of patch header]')
 
 _hg_cmd_qapplied() {
-  _arguments -s -w : $_hg_global_opts $_hg_qseries_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_qseries_opts \
   '(--last -1)'{-1,--last}'[show only the preceding applied patch]' \
   '*:patch:_hg_qapplied'
 }
 
 _hg_cmd_qclone() {
-  _arguments -s -w : $_hg_global_opts $_hg_remote_opts $_hg_clone_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_remote_opts $_hg_clone_opts \
   '(--patches -p)'{-p+,--patches=}'[location of source patch repository]:' \
   ':source repository:_hg_remote' \
   ':destination:_hg_clone_dest'
 }
 
 _hg_cmd_qdelete() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--keep -k)'{-k,--keep}'[keep patch file]' \
   '*'{-r+,--rev=}'[stop managing a revision]:applied patch:_hg_revrange' \
   '*:unapplied patch:_hg_qdeletable'
 }
 
 _hg_cmd_qdiff() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_diff_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_diff_opts \
                      $_hg_ignore_space_opts \
   '*:pattern:_hg_files'
 }
 
 _hg_cmd_qfinish() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--applied -a)'{-a,--applied}'[finish all applied patches]' \
   '*:patch:_hg_qapplied'
 }
 
 _hg_cmd_qfold() {
-  _arguments -s -w : $_hg_global_opts $_h_commit_opts \
-  '(--keep,-k)'{-k,--keep}'[keep folded patch files]' \
+  _arguments -s -S : $_hg_global_opts $_hg_commit_opts \
+  '(--keep -k)'{-k,--keep}'[keep folded patch files]' \
   '(--force -f)'{-f,--force}'[overwrite any local changes]' \
   '--no-backup[do not save backup copies of files]' \
   '*:unapplied patch:_hg_qunapplied'
 }
 
 _hg_cmd_qgoto() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--force -f)'{-f,--force}'[overwrite any local changes]' \
   '--keep-changes[tolerate non-conflicting local changes]' \
+  '--no-backup[do not save backup copies of files]' \
   ':patch:_hg_qseries'
 }
 
 _hg_cmd_qguard() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--list -l)'{-l,--list}'[list all patches and guards]' \
   '(--none -n)'{-n,--none}'[drop all guards]' \
   ':patch:_hg_qseries' \
@@ -1038,14 +1093,14 @@
 }
 
 _hg_cmd_qheader() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   ':patch:_hg_qseries'
 }
 
 _hg_cmd_qimport() {
-  _arguments -s -w : $_hg_global_opts $_hg_gitlike_opts \
-  '(--existing -e)'{-e,--existing}'[import file in patch dir]' \
-  '(--name -n 2)'{-n+,--name}'[patch file name]:name:' \
+  _arguments -s -S : $_hg_global_opts $_hg_gitlike_opts \
+  '(--existing -e)'{-e,--existing}'[import file in patch directory]' \
+  '(--name -n 2)'{-n+,--name=}'[name of patch file]:name' \
   '(--force -f)'{-f,--force}'[overwrite existing files]' \
   '*'{-r+,--rev=}'[place existing revisions under mq control]:revision:_hg_revrange' \
   '(--push -P)'{-P,--push}'[qpush after importing]' \
@@ -1053,32 +1108,32 @@
 }
 
 _hg_cmd_qnew() {
-  _arguments -s -w : $_hg_global_opts $_hg_commit_opts $_hg_date_user_opts $_hg_gitlike_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_commit_opts $_hg_date_user_opts $_hg_gitlike_opts \
   ':patch:'
 }
 
 _hg_cmd_qnext() {
-  _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+  _arguments -s -S : $_hg_global_opts $_hg_qseries_opts
 }
 
 _hg_cmd_qpop() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--all -a :)'{-a,--all}'[pop all patches]' \
-  '(--force -f)'{-f,--force}'[forget any local changes]' \
+  '(--force -f)'{-f,--force}'[forget any local changes to patched files]' \
   '--keep-changes[tolerate non-conflicting local changes]' \
   '--no-backup[do not save backup copies of files]' \
   ':patch:_hg_qapplied'
 }
 
 _hg_cmd_qprev() {
-  _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+  _arguments -s -S : $_hg_global_opts $_hg_qseries_opts
 }
 
 _hg_cmd_qpush() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--all -a :)'{-a,--all}'[apply all patches]' \
   '(--list -l)'{-l,--list}'[list patch name in commit text]' \
-  '(--force -f)'{-f,--force}'[apply if the patch has rejects]' \
+  '(--force -f)'{-f,--force}'[apply on top of local changes]' \
   '(--exact -e)'{-e,--exact}'[apply the target patch to its recorded parent]' \
   '--move[reorder patch series and apply only the patch]' \
   '--keep-changes[tolerate non-conflicting local changes]' \
@@ -1087,137 +1142,182 @@
 }
 
 _hg_cmd_qrefresh() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_commit_opts $_hg_gitlike_opts \
-  '(--short -s)'{-s,--short}'[short refresh]' \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts $_hg_commit_opts $_hg_date_user_opts $_hg_gitlike_opts \
+  '(--short -s)'{-s,--short}'[refresh only files already in the patch and specified files]' \
   '*:files:_hg_files'
 }
 
 _hg_cmd_qrename() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   ':patch:_hg_qunapplied' \
   ':destination:'
 }
 
 _hg_cmd_qselect() {
-  _arguments -s -w : $_hg_global_opts \
+  _arguments -s -S : $_hg_global_opts \
   '(--none -n :)'{-n,--none}'[disable all guards]' \
   '(--series -s :)'{-s,--series}'[list all guards in series file]' \
   '--pop[pop to before first guarded applied patch]' \
-  '--reapply[pop and reapply patches]' \
+  '--reapply[pop, then reapply patches]' \
   '*:guards:_hg_qguards'
 }
 
 _hg_cmd_qseries() {
-  _arguments -s -w : $_hg_global_opts $_hg_qseries_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_qseries_opts \
   '(--missing -m)'{-m,--missing}'[print patches not in series]'
 }
 
 _hg_cmd_qunapplied() {
-  _arguments -s -w : $_hg_global_opts $_hg_qseries_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_qseries_opts \
   '(--first -1)'{-1,--first}'[show only the first patch]'
 }
 
 _hg_cmd_qtop() {
-  _arguments -s -w : $_hg_global_opts $_hg_qseries_opts
+  _arguments -s -S : $_hg_global_opts $_hg_qseries_opts
 }
 
 _hg_cmd_strip() {
-  _arguments -s -w : $_hg_global_opts \
-  '(--force -f)'{-f,--force}'[force removal, discard uncommitted changes, no backup]' \
-  '(--no-backup -n)'{-n,--no-backup}'[no backups]' \
-  '(--keep -k)'{-k,--keep}'[do not modify working copy during strip]' \
-  '(--bookmark -B)'{-B+,--bookmark=}'[remove revs only reachable from given bookmark]:bookmark:_hg_bookmarks' \
-  '(--rev -r)'{-r+,--rev=}'[revision]:revision:_hg_labels' \
-  ':revision:_hg_labels'
+  _arguments -s -S : $_hg_global_opts \
+  '(--force -f)'{-f,--force}'[force removal of changesets, discard uncommitted changes (no backup)]' \
+  '--no-backup[do not save backup bundle]' \
+  '(--keep -k)'{-k,--keep}'[do not modify working directory during strip]' \
+  '*'{-B+,--bookmark=}'[remove revisions only reachable from given bookmark]:bookmark:_hg_bookmarks' \
+  '*'{-r+,--rev=}'[strip specified revision]:revision:_hg_labels' \
+  '*:revision:_hg_labels'
 }
 
 # Patchbomb
 _hg_cmd_email() {
-  _arguments -s -w : $_hg_global_opts $_hg_remote_opts $_hg_gitlike_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_remote_opts $_hg_gitlike_opts \
   '--plain[omit hg patch header]' \
   '--body[send patches as inline message text (default)]' \
   '(--outgoing -o)'{-o,--outgoing}'[send changes not found in the target repository]' \
   '(--bundle -b)'{-b,--bundle}'[send changes not in target as a binary bundle]' \
-  '--bundlename[name of the bundle attachment file (default: bundle)]:' \
-  '*'{-r+,--rev=}'[search in given revision range]:revision:_hg_revrange' \
+  '(--bookmark -B)'{-B+,--bookmark=}'[send changes only reachable by given bookmark]:bookmark:_hg_bookmarks' \
+  '--bundlename=[name of the bundle attachment file (default: bundle)]:name' \
+  '*'{-r+,--rev=}'[a revision to send]:revision:_hg_revrange' \
   '--force[run even when remote repository is unrelated (with -b/--bundle)]' \
-  '*--base[a base changeset to specify instead of a destination (with -b/--bundle)]:revision:_hg_labels' \
+  '*--base=[a base changeset to specify instead of a destination (with -b/--bundle)]:revision:_hg_labels' \
   '--intro[send an introduction email for a single patch]' \
   '(--inline -i --attach -a)'{-a,--attach}'[send patches as attachments]' \
   '(--attach -a --inline -i)'{-i,--inline}'[send patches as inline attachments]' \
-  '*--bcc[email addresses of blind carbon copy recipients]:email:' \
-  '*'{-c+,--cc}'[email addresses of copy recipients]:email:' \
+  '*--bcc=[email addresses of blind carbon copy recipients]:email' \
+  '*'{-c+,--cc=}'[email addresses of copy recipients]:email' \
+  '--confirm[ask for confirmation before sending]' \
   '(--diffstat -d)'{-d,--diffstat}'[add diffstat output to messages]' \
-  '--date[use the given date as the sending date]:date:' \
-  '--desc[use the given file as the series description]:files:_files' \
-  '(--from -f)'{-f,--from}'[email address of sender]:email:' \
+  '--date=[use the given date as the sending date]:date' \
+  '--desc=[use the given file as the series description]:files:_files' \
+  '(--from -f)'{-f+,--from=}'[email address of sender]:email' \
   '(--test -n)'{-n,--test}'[print messages that would be sent]' \
-  '(--mbox -m)'{-m,--mbox}'[write messages to mbox file instead of sending them]:file:' \
-  '*--reply-to[email addresses replies should be sent to]:email:' \
-  '(--subject -s)'{-s,--subject}'[subject of first message (intro or single patch)]:subject:' \
-  '--in-reply-to[message identifier to reply to]:msgid:' \
-  '*--flag[flags to add in subject prefixes]:flag:' \
-  '*'{-t,--to}'[email addresses of recipients]:email:' \
+  '(--mbox -m)'{-m+,--mbox=}'[write messages to mbox file instead of sending them]:file:_files' \
+  '*--reply-to=[email addresses replies should be sent to]:email' \
+  '(--subject -s)'{-s+,--subject=}'[subject of first message (intro or single patch)]:subject' \
+  '--in-reply-to=[message identifier to reply to]:msgid' \
+  '*--flag=[flags to add in subject prefixes]:flag' \
+  '*'{-t+,--to=}'[email addresses of recipients]:email' \
   ':revision:_hg_revrange'
 }
 
 # Rebase
 _hg_cmd_rebase() {
-  _arguments -s -w : $_hg_global_opts $_hg_commit_opts $_hg_mergetool_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_commit_opts $_hg_mergetool_opts $_hg_dryrun_opts  \
   '*'{-r+,--rev=}'[rebase these revisions]:revision:_hg_revrange' \
-  '(--source -s)'{-s+,--source=}'[rebase from the specified changeset]:revision:_hg_labels' \
-  '(--base -b)'{-b+,--base=}'[rebase from the base of the specified changeset]:revision:_hg_labels' \
+  '(--source -s --base -b)'{-s+,--source=}'[rebase the specified changeset and descendants]:revision:_hg_labels' \
+  '(--source -s --base -b)'{-b+,--base=}'[rebase everything from branching point of specified changeset]:revision:_hg_labels' \
   '(--dest -d)'{-d+,--dest=}'[rebase onto the specified changeset]:revision:_hg_labels' \
-  '--collapse[collapse the rebased changeset]' \
-  '--keep[keep original changeset]' \
-  '--keepbranches[keep original branch name]' \
-  '(--continue -c)'{-c,--continue}'[continue an interrupted rebase]' \
-  '(--abort -a)'{-a,--abort}'[abort an interrupted rebase]' \
+  '--collapse[collapse the rebased changesets]' \
+  '(--keep -k)'{-k,--keep}'[keep original changesets]' \
+  '--keepbranches[keep original branch names]' \
+  '(--continue -c --abort -a)'{-c,--continue}'[continue an interrupted rebase]' \
+  '(--continue -c --abort -a)'{-a,--abort}'[abort an interrupted rebase]' \
 }
 
 # Record
 _hg_cmd_record() {
-  _arguments -s -w : $_hg_global_opts $_hg_commit_opts $_hg_pat_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_commit_opts $_hg_pat_opts \
                      $_hg_ignore_space_opts $_hg_subrepos_opts \
   '(--addremove -A)'{-A,--addremove}'[mark new/missing files as added/removed before committing]' \
   '--close-branch[mark a branch as closed, hiding it from the branch list]' \
   '--amend[amend the parent of the working dir]' \
-  '(--date -d)'{-d+,--date=}'[record the specified date as commit date]:date:' \
-  '(--user -u)'{-u+,--user=}'[record the specified user as committer]:user:'
+  '(--date -d)'{-d+,--date=}'[record the specified date as commit date]:date' \
+  '(--user -u)'{-u+,--user=}'[record the specified user as committer]:user'
 }
 
 _hg_cmd_qrecord() {
-  _arguments -s -w : $_hg_global_opts $_hg_commit_opts $_hg_date_user_opts $_hg_gitlike_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_commit_opts $_hg_date_user_opts $_hg_gitlike_opts \
                      $_hg_pat_opts $_hg_ignore_space_opts $_hg_subrepos_opts
 }
 
 # Convert
 _hg_cmd_convert() {
-_arguments -s -w : $_hg_global_opts \
-  '(--source-type -s)'{-s,--source-type}'[source repository type]' \
-  '(--dest-type -d)'{-d,--dest-type}'[destination repository type]' \
-  '(--rev -r)'{-r+,--rev=}'[import up to target revision]:revision:' \
+_arguments -s -S : $_hg_global_opts \
+  '(--source-type -s)'{-s+,--source-type=}'[source repository type]:type:(hg cvs darcs git svn mtn gnuarch bzr p4)' \
+  '(--dest-type -d)'{-d+,--dest-type=}'[destination repository type]:type:(hg svn)' \
+  '*'{-r+,--rev=}'[import up to source revision]:revision' \
   '(--authormap -A)'{-A+,--authormap=}'[remap usernames using this file]:file:_files' \
-  '--filemap[remap file names using contents of file]:file:_files' \
-  '--splicemap[splice synthesized history into place]:file:_files' \
-  '--branchmap[change branch names while converting]:file:_files' \
+  '--filemap=[remap file names using contents of file]:file:_files' \
+  '--full[apply filemap changes by converting all files again]' \
+  '--splicemap=[splice synthesized history into place]:file:_files' \
+  '--branchmap=[change branch names while converting]:file:_files' \
   '--branchsort[try to sort changesets by branches]' \
   '--datesort[try to sort changesets by date]' \
-  '--sourcesort[preserve source changesets order]'
-}
-
-# Graphlog
-_hg_cmd_glog() {
-  _hg_cmd_log $@
+  '--sourcesort[preserve source changesets order]' \
+  '--closesort[try to reorder closed revisions]'
 }
 
 # Purge
 _hg_cmd_purge() {
-  _arguments -s -w : $_hg_global_opts $_hg_pat_opts $_hg_subrepos_opts \
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts \
   '(--abort-on-err -a)'{-a,--abort-on-err}'[abort if an error occurs]' \
   '--all[purge ignored files too]' \
+  '--dirs[purge empty directories]' \
+  '--files[purge files]' \
   '(--print -p)'{-p,--print}'[print filenames instead of deleting them]' \
   '(--print0 -0)'{-0,--print0}'[end filenames with NUL, for use with xargs (implies -p/--print)]'
 }
 
+# Shelve
+_hg_cmd_shelve() {
+  local context state state_descr line ret=1
+  typeset -A opt_args
+
+  _arguments -s -S : $_hg_global_opts $_hg_pat_opts \
+  '(--addremove -A)'{-A,--addremove}'[mark new/missing files as added/removed before shelving]' \
+  '(--unknown -u)'{-u,--unknown}'[store unknown files in the shelve]' \
+  '(--name -n :)--cleanup[delete all shelved changes]' \
+  '--date=[shelve with the specified commit date]:date' \
+  '(--delete -d)'{-d,--delete}'[delete the named shelved change(s)]' \
+  '(--edit -e)'{-e,--edit}'[invoke editor on commit messages]' \
+  '(--list -l)'{-l,--list}'[list current shelves]' \
+  '(--message -m)'{-m+,--message=}'[use text as shelve message]:text' \
+  '(--name -n)'{-n+,--name=}'[use the given name for the shelved commit]:name' \
+  '(--patch -p)'{-p,--patch}'[output patches for changes]' \
+  '(--interactive -i)'{-i,--interactive}'[interactive mode, only works while creating a shelve]' \
+  '--stat[output diffstat-style summary of changes]' \
+  '*:file:->shelve_files' && ret=0
+
+  if [[ $state == 'shelve_files' ]]
+  then
+    if [[ -n ${opt_args[(I)-d|--delete|-l|--list|-p|--patch|--stat]} ]]
+    then
+      _hg_shelves && ret=0
+    else
+      typeset -a status_files
+      _hg_status mard
+      _wanted files expl 'modified, added, removed or deleted file' _multi_parts / status_files && ret=0
+    fi
+  fi
+
+  return ret
+}
+
+_hg_cmd_unshelve() {
+  _arguments -s -S : $_hg_global_opts $_hg_mergetool_opts \
+  '(--abort -a --continue -c --name -n :)'{-a,--abort}'[abort an incomplete unshelve operation]' \
+  '(--abort -a --continue -c --name -n :)'{-c,--continue}'[continue an incomplete unshelve operation]' \
+  '(--keep -k)'{-k,--keep}'[keep shelve after unshelving]' \
+  '(--name -n :)'{-n+,--name=}'[restore shelved change with given name]:shelve:_hg_shelves' \
+  ':shelve:_hg_shelves'
+}
+
 _hg "$@"
--- a/doc/runrst	Tue Sep 25 16:32:38 2018 -0400
+++ b/doc/runrst	Wed Sep 26 20:33:09 2018 +0900
@@ -12,11 +12,14 @@
 where WRITER is the name of a Docutils writer such as 'html' or 'manpage'
 """
 
+from __future__ import absolute_import
+
 import sys
 try:
-    from docutils.parsers.rst import roles
-    from docutils.core import publish_cmdline
-    from docutils import nodes, utils
+    import docutils.core as core
+    import docutils.nodes as nodes
+    import docutils.utils as utils
+    import docutils.parsers.rst.roles as roles
 except ImportError:
     sys.stderr.write("abort: couldn't generate documentation: docutils "
                      "module is missing\n")
@@ -58,4 +61,4 @@
     writer = sys.argv[1]
     del sys.argv[1]
 
-    publish_cmdline(writer_name=writer)
+    core.publish_cmdline(writer_name=writer)
--- a/hg	Tue Sep 25 16:32:38 2018 -0400
+++ b/hg	Wed Sep 26 20:33:09 2018 +0900
@@ -27,15 +27,17 @@
         libdir = os.path.abspath(libdir)
     sys.path.insert(0, libdir)
 
-# enable importing on demand to reduce startup time
-try:
-    if sys.version_info[0] < 3 or sys.version_info >= (3, 6):
-        import hgdemandimport; hgdemandimport.enable()
-except ImportError:
-    sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
-                     ' '.join(sys.path))
-    sys.stderr.write("(check your install and PYTHONPATH)\n")
-    sys.exit(-1)
+from hgdemandimport import tracing
+with tracing.log('hg script'):
+    # enable importing on demand to reduce startup time
+    try:
+        if sys.version_info[0] < 3 or sys.version_info >= (3, 6):
+            import hgdemandimport; hgdemandimport.enable()
+    except ImportError:
+        sys.stderr.write("abort: couldn't find mercurial libraries in [%s]\n" %
+                         ' '.join(sys.path))
+        sys.stderr.write("(check your install and PYTHONPATH)\n")
+        sys.exit(-1)
 
-from mercurial import dispatch
-dispatch.run()
+    from mercurial import dispatch
+    dispatch.run()
--- a/hgdemandimport/demandimportpy2.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgdemandimport/demandimportpy2.py	Wed Sep 26 20:33:09 2018 +0900
@@ -30,6 +30,8 @@
 import contextlib
 import sys
 
+from . import tracing
+
 contextmanager = contextlib.contextmanager
 
 _origimport = __import__
@@ -86,52 +88,55 @@
 
     def _load(self):
         if not self._module:
-            head, globals, locals, after, level, modrefs = self._data
-            mod = _hgextimport(_origimport, head, globals, locals, None, level)
-            if mod is self:
-                # In this case, _hgextimport() above should imply
-                # _demandimport(). Otherwise, _hgextimport() never
-                # returns _demandmod. This isn't intentional behavior,
-                # in fact. (see also issue5304 for detail)
-                #
-                # If self._module is already bound at this point, self
-                # should be already _load()-ed while _hgextimport().
-                # Otherwise, there is no way to import actual module
-                # as expected, because (re-)invoking _hgextimport()
-                # should cause same result.
-                # This is reason why _load() returns without any more
-                # setup but assumes self to be already bound.
-                mod = self._module
-                assert mod and mod is not self, "%s, %s" % (self, mod)
-                return
+            with tracing.log('demandimport %s', self._data[0]):
+                head, globals, locals, after, level, modrefs = self._data
+                mod = _hgextimport(
+                    _origimport, head, globals, locals, None, level)
+                if mod is self:
+                    # In this case, _hgextimport() above should imply
+                    # _demandimport(). Otherwise, _hgextimport() never
+                    # returns _demandmod. This isn't intentional behavior,
+                    # in fact. (see also issue5304 for detail)
+                    #
+                    # If self._module is already bound at this point, self
+                    # should be already _load()-ed while _hgextimport().
+                    # Otherwise, there is no way to import actual module
+                    # as expected, because (re-)invoking _hgextimport()
+                    # should cause same result.
+                    # This is reason why _load() returns without any more
+                    # setup but assumes self to be already bound.
+                    mod = self._module
+                    assert mod and mod is not self, "%s, %s" % (self, mod)
+                    return
 
-            # load submodules
-            def subload(mod, p):
-                h, t = p, None
-                if '.' in p:
-                    h, t = p.split('.', 1)
-                if getattr(mod, h, nothing) is nothing:
-                    setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__,
-                                               level=1))
-                elif t:
-                    subload(getattr(mod, h), t)
+                # load submodules
+                def subload(mod, p):
+                    h, t = p, None
+                    if '.' in p:
+                        h, t = p.split('.', 1)
+                    if getattr(mod, h, nothing) is nothing:
+                        setattr(mod, h, _demandmod(
+                            p, mod.__dict__, mod.__dict__, level=1))
+                    elif t:
+                        subload(getattr(mod, h), t)
 
-            for x in after:
-                subload(mod, x)
+                for x in after:
+                    subload(mod, x)
 
-            # Replace references to this proxy instance with the actual module.
-            if locals:
-                if locals.get(head) is self:
-                    locals[head] = mod
-                elif locals.get(head + r'mod') is self:
-                    locals[head + r'mod'] = mod
+                # Replace references to this proxy instance with the
+                # actual module.
+                if locals:
+                    if locals.get(head) is self:
+                        locals[head] = mod
+                    elif locals.get(head + r'mod') is self:
+                        locals[head + r'mod'] = mod
 
-            for modname in modrefs:
-                modref = sys.modules.get(modname, None)
-                if modref and getattr(modref, head, None) is self:
-                    setattr(modref, head, mod)
+                for modname in modrefs:
+                    modref = sys.modules.get(modname, None)
+                    if modref and getattr(modref, head, None) is self:
+                        setattr(modref, head, mod)
 
-            object.__setattr__(self, r"_module", mod)
+                object.__setattr__(self, r"_module", mod)
 
     def __repr__(self):
         if self._module:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgdemandimport/tracing.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,44 @@
+# Support code for event tracing in Mercurial. Lives in demandimport
+# so it can also be used in demandimport.
+#
+# Copyright 2018 Google LLC.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
+
+import contextlib
+import os
+
+_pipe = None
+_checked = False
+
+@contextlib.contextmanager
+def log(whencefmt, *whenceargs):
+    global _pipe, _session, _checked
+    if _pipe is None:
+        if _checked:
+            yield
+            return
+        _checked = True
+        if 'HGCATAPULTSERVERPIPE' not in os.environ:
+            yield
+            return
+        _pipe = open(os.environ['HGCATAPULTSERVERPIPE'], 'w', 1)
+        _session = os.environ.get('HGCATAPULTSESSION', 'none')
+    whence = whencefmt % whenceargs
+    try:
+        # Both writes to the pipe are wrapped in try/except to ignore
+        # errors, as we can see mysterious errors in here if the pager
+        # is active. Presumably other conditions could trigger
+        # problems too.
+        try:
+            _pipe.write('START %s %s\n' % (_session, whence))
+        except IOError:
+            pass
+        yield
+    finally:
+        try:
+            _pipe.write('END %s %s\n' % (_session, whence))
+        except IOError:
+            pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/absorb.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,978 @@
+# absorb.py
+#
+# Copyright 2016 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""apply working directory changes to changesets (EXPERIMENTAL)
+
+The absorb extension provides a command to use annotate information to
+amend modified chunks into the corresponding non-public changesets.
+
+::
+
+    [absorb]
+    # only check 50 recent non-public changesets at most
+    max-stack-size = 50
+    # whether to add noise to new commits to avoid obsolescence cycle
+    add-noise = 1
+    # make `amend --correlated` a shortcut to the main command
+    amend-flag = correlated
+
+    [color]
+    absorb.node = blue bold
+    absorb.path = bold
+"""
+
+# TODO:
+#  * Rename config items to [commands] namespace
+#  * Converge getdraftstack() with other code in core
+#  * move many attributes on fixupstate to be private
+
+from __future__ import absolute_import
+
+import collections
+
+from mercurial.i18n import _
+from mercurial import (
+    cmdutil,
+    commands,
+    context,
+    crecord,
+    error,
+    linelog,
+    mdiff,
+    node,
+    obsolete,
+    patch,
+    phases,
+    pycompat,
+    registrar,
+    repair,
+    scmutil,
+    util,
+)
+from mercurial.utils import (
+    stringutil,
+)
+
+# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
+# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
+# be specifying the version(s) of Mercurial they are tested with, or
+# leave the attribute unspecified.
+testedwith = 'ships-with-hg-core'
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('absorb', 'add-noise', default=True)
+configitem('absorb', 'amend-flag', default=None)
+configitem('absorb', 'max-stack-size', default=50)
+
+colortable = {
+    'absorb.node': 'blue bold',
+    'absorb.path': 'bold',
+}
+
+defaultdict = collections.defaultdict
+
+class nullui(object):
+    """blank ui object doing nothing"""
+    debugflag = False
+    verbose = False
+    quiet = True
+
+    def __getitem__(name):
+        def nullfunc(*args, **kwds):
+            return
+        return nullfunc
+
+class emptyfilecontext(object):
+    """minimal filecontext representing an empty file"""
+    def data(self):
+        return ''
+
+    def node(self):
+        return node.nullid
+
+def uniq(lst):
+    """list -> list. remove duplicated items without changing the order"""
+    seen = set()
+    result = []
+    for x in lst:
+        if x not in seen:
+            seen.add(x)
+            result.append(x)
+    return result
+
+def getdraftstack(headctx, limit=None):
+    """(ctx, int?) -> [ctx]. get a linear stack of non-public changesets.
+
+    changesets are sorted in topo order, oldest first.
+    return at most limit items, if limit is a positive number.
+
+    merges are considered as non-draft as well. i.e. every commit
+    returned has and only has 1 parent.
+    """
+    ctx = headctx
+    result = []
+    while ctx.phase() != phases.public:
+        if limit and len(result) >= limit:
+            break
+        parents = ctx.parents()
+        if len(parents) != 1:
+            break
+        result.append(ctx)
+        ctx = parents[0]
+    result.reverse()
+    return result
+
+def getfilestack(stack, path, seenfctxs=None):
+    """([ctx], str, set) -> [fctx], {ctx: fctx}
+
+    stack is a list of contexts, from old to new. usually they are what
+    "getdraftstack" returns.
+
+    follows renames, but not copies.
+
+    seenfctxs is a set of filecontexts that will be considered "immutable".
+    they are usually what this function returned in earlier calls, useful
+    to avoid issues that a file was "moved" to multiple places and was then
+    modified differently, like: "a" was copied to "b", "a" was also copied to
+    "c" and then "a" was deleted, then both "b" and "c" were "moved" from "a"
+    and we enforce only one of them to be able to affect "a"'s content.
+
+    return an empty list and an empty dict, if the specified path does not
+    exist in stack[-1] (the top of the stack).
+
+    otherwise, return a list of de-duplicated filecontexts, and the map to
+    convert ctx in the stack to fctx, for possible mutable fctxs. the first item
+    of the list would be outside the stack and should be considered immutable.
+    the remaining items are within the stack.
+
+    for example, given the following changelog and corresponding filelog
+    revisions:
+
+      changelog: 3----4----5----6----7
+      filelog:   x    0----1----1----2 (x: no such file yet)
+
+    - if stack = [5, 6, 7], returns ([0, 1, 2], {5: 1, 6: 1, 7: 2})
+    - if stack = [3, 4, 5], returns ([e, 0, 1], {4: 0, 5: 1}), where "e" is a
+      dummy empty filecontext.
+    - if stack = [2], returns ([], {})
+    - if stack = [7], returns ([1, 2], {7: 2})
+    - if stack = [6, 7], returns ([1, 2], {6: 1, 7: 2}), although {6: 1} can be
+      removed, since 1 is immutable.
+    """
+    if seenfctxs is None:
+        seenfctxs = set()
+    assert stack
+
+    if path not in stack[-1]:
+        return [], {}
+
+    fctxs = []
+    fctxmap = {}
+
+    pctx = stack[0].p1() # the public (immutable) ctx we stop at
+    for ctx in reversed(stack):
+        if path not in ctx: # the file is added in the next commit
+            pctx = ctx
+            break
+        fctx = ctx[path]
+        fctxs.append(fctx)
+        if fctx in seenfctxs: # treat fctx as the immutable one
+            pctx = None # do not add another immutable fctx
+            break
+        fctxmap[ctx] = fctx # only for mutable fctxs
+        renamed = fctx.renamed()
+        if renamed:
+            path = renamed[0] # follow rename
+            if path in ctx: # but do not follow copy
+                pctx = ctx.p1()
+                break
+
+    if pctx is not None: # need an extra immutable fctx
+        if path in pctx:
+            fctxs.append(pctx[path])
+        else:
+            fctxs.append(emptyfilecontext())
+
+    fctxs.reverse()
+    # note: we rely on a property of hg: filerev is not reused for linear
+    # history. i.e. it's impossible to have:
+    #   changelog:  4----5----6 (linear, no merges)
+    #   filelog:    1----2----1
+    #                         ^ reuse filerev (impossible)
+    # because parents are part of the hash. if that's not true, we need to
+    # remove uniq and find a different way to identify fctxs.
+    return uniq(fctxs), fctxmap
+
+class overlaystore(patch.filestore):
+    """read-only, hybrid store based on a dict and ctx.
+    memworkingcopy: {path: content}, overrides file contents.
+    """
+    def __init__(self, basectx, memworkingcopy):
+        self.basectx = basectx
+        self.memworkingcopy = memworkingcopy
+
+    def getfile(self, path):
+        """comply with mercurial.patch.filestore.getfile"""
+        if path not in self.basectx:
+            return None, None, None
+        fctx = self.basectx[path]
+        if path in self.memworkingcopy:
+            content = self.memworkingcopy[path]
+        else:
+            content = fctx.data()
+        mode = (fctx.islink(), fctx.isexec())
+        renamed = fctx.renamed() # False or (path, node)
+        return content, mode, (renamed and renamed[0])
+
+def overlaycontext(memworkingcopy, ctx, parents=None, extra=None):
+    """({path: content}, ctx, (p1node, p2node)?, {}?) -> memctx
+    memworkingcopy overrides file contents.
+    """
+    # parents must contain 2 items: (node1, node2)
+    if parents is None:
+        parents = ctx.repo().changelog.parents(ctx.node())
+    if extra is None:
+        extra = ctx.extra()
+    date = ctx.date()
+    desc = ctx.description()
+    user = ctx.user()
+    files = set(ctx.files()).union(memworkingcopy)
+    store = overlaystore(ctx, memworkingcopy)
+    return context.memctx(
+        repo=ctx.repo(), parents=parents, text=desc,
+        files=files, filectxfn=store, user=user, date=date,
+        branch=None, extra=extra)
+
+class filefixupstate(object):
+    """state needed to apply fixups to a single file
+
+    internally, it keeps file contents of several revisions and a linelog.
+
+    the linelog uses odd revision numbers for original contents (fctxs passed
+    to __init__), and even revision numbers for fixups, like:
+
+        linelog rev 1: self.fctxs[0] (from an immutable "public" changeset)
+        linelog rev 2: fixups made to self.fctxs[0]
+        linelog rev 3: self.fctxs[1] (a child of fctxs[0])
+        linelog rev 4: fixups made to self.fctxs[1]
+        ...
+
+    a typical use is like:
+
+        1. call diffwith, to calculate self.fixups
+        2. (optionally), present self.fixups to the user, or change it
+        3. call apply, to apply changes
+        4. read results from "finalcontents", or call getfinalcontent
+    """
+
+    def __init__(self, fctxs, ui=None, opts=None):
+        """([fctx], ui or None) -> None
+
+        fctxs should be linear, and sorted by topo order - oldest first.
+        fctxs[0] will be considered as "immutable" and will not be changed.
+        """
+        self.fctxs = fctxs
+        self.ui = ui or nullui()
+        self.opts = opts or {}
+
+        # following fields are built from fctxs. they exist for perf reason
+        self.contents = [f.data() for f in fctxs]
+        self.contentlines = pycompat.maplist(mdiff.splitnewlines, self.contents)
+        self.linelog = self._buildlinelog()
+        if self.ui.debugflag:
+            assert self._checkoutlinelog() == self.contents
+
+        # following fields will be filled later
+        self.chunkstats = [0, 0] # [adopted, total : int]
+        self.targetlines = [] # [str]
+        self.fixups = [] # [(linelog rev, a1, a2, b1, b2)]
+        self.finalcontents = [] # [str]
+
+    def diffwith(self, targetfctx, showchanges=False):
+        """calculate fixups needed by examining the differences between
+        self.fctxs[-1] and targetfctx, chunk by chunk.
+
+        targetfctx is the target state we move towards. we may or may not be
+        able to get there because not all modified chunks can be amended into
+        a non-public fctx unambiguously.
+
+        call this only once, before apply().
+
+        update self.fixups, self.chunkstats, and self.targetlines.
+        """
+        a = self.contents[-1]
+        alines = self.contentlines[-1]
+        b = targetfctx.data()
+        blines = mdiff.splitnewlines(b)
+        self.targetlines = blines
+
+        self.linelog.annotate(self.linelog.maxrev)
+        annotated = self.linelog.annotateresult # [(linelog rev, linenum)]
+        assert len(annotated) == len(alines)
+        # add a dummy end line to make insertion at the end easier
+        if annotated:
+            dummyendline = (annotated[-1][0], annotated[-1][1] + 1)
+            annotated.append(dummyendline)
+
+        # analyse diff blocks
+        for chunk in self._alldiffchunks(a, b, alines, blines):
+            newfixups = self._analysediffchunk(chunk, annotated)
+            self.chunkstats[0] += bool(newfixups) # 1 or 0
+            self.chunkstats[1] += 1
+            self.fixups += newfixups
+            if showchanges:
+                self._showchanges(alines, blines, chunk, newfixups)
+
+    def apply(self):
+        """apply self.fixups. update self.linelog, self.finalcontents.
+
+        call this only once, before getfinalcontent(), after diffwith().
+        """
+        # the following is unnecessary, as it's done by "diffwith":
+        #   self.linelog.annotate(self.linelog.maxrev)
+        for rev, a1, a2, b1, b2 in reversed(self.fixups):
+            blines = self.targetlines[b1:b2]
+            if self.ui.debugflag:
+                idx = (max(rev - 1, 0)) // 2
+                self.ui.write(_('%s: chunk %d:%d -> %d lines\n')
+                              % (node.short(self.fctxs[idx].node()),
+                                 a1, a2, len(blines)))
+            self.linelog.replacelines(rev, a1, a2, b1, b2)
+        if self.opts.get('edit_lines', False):
+            self.finalcontents = self._checkoutlinelogwithedits()
+        else:
+            self.finalcontents = self._checkoutlinelog()
+
+    def getfinalcontent(self, fctx):
+        """(fctx) -> str. get modified file content for a given filecontext"""
+        idx = self.fctxs.index(fctx)
+        return self.finalcontents[idx]
+
+    def _analysediffchunk(self, chunk, annotated):
+        """analyse a different chunk and return new fixups found
+
+        return [] if no lines from the chunk can be safely applied.
+
+        the chunk (or lines) cannot be safely applied, if, for example:
+          - the modified (deleted) lines belong to a public changeset
+            (self.fctxs[0])
+          - the chunk is a pure insertion and the adjacent lines (at most 2
+            lines) belong to different non-public changesets, or do not belong
+            to any non-public changesets.
+          - the chunk is modifying lines from different changesets.
+            in this case, if the number of lines deleted equals to the number
+            of lines added, assume it's a simple 1:1 map (could be wrong).
+            otherwise, give up.
+          - the chunk is modifying lines from a single non-public changeset,
+            but other revisions touch the area as well. i.e. the lines are
+            not continuous as seen from the linelog.
+        """
+        a1, a2, b1, b2 = chunk
+        # find involved indexes from annotate result
+        involved = annotated[a1:a2]
+        if not involved and annotated: # a1 == a2 and a is not empty
+            # pure insertion, check nearby lines. ignore lines belong
+            # to the public (first) changeset (i.e. annotated[i][0] == 1)
+            nearbylinenums = {a2, max(0, a1 - 1)}
+            involved = [annotated[i]
+                        for i in nearbylinenums if annotated[i][0] != 1]
+        involvedrevs = list(set(r for r, l in involved))
+        newfixups = []
+        if len(involvedrevs) == 1 and self._iscontinuous(a1, a2 - 1, True):
+            # chunk belongs to a single revision
+            rev = involvedrevs[0]
+            if rev > 1:
+                fixuprev = rev + 1
+                newfixups.append((fixuprev, a1, a2, b1, b2))
+        elif a2 - a1 == b2 - b1 or b1 == b2:
+            # 1:1 line mapping, or chunk was deleted
+            for i in pycompat.xrange(a1, a2):
+                rev, linenum = annotated[i]
+                if rev > 1:
+                    if b1 == b2: # deletion, simply remove that single line
+                        nb1 = nb2 = 0
+                    else: # 1:1 line mapping, change the corresponding rev
+                        nb1 = b1 + i - a1
+                        nb2 = nb1 + 1
+                    fixuprev = rev + 1
+                    newfixups.append((fixuprev, i, i + 1, nb1, nb2))
+        return self._optimizefixups(newfixups)
+
+    @staticmethod
+    def _alldiffchunks(a, b, alines, blines):
+        """like mdiff.allblocks, but only care about differences"""
+        blocks = mdiff.allblocks(a, b, lines1=alines, lines2=blines)
+        for chunk, btype in blocks:
+            if btype != '!':
+                continue
+            yield chunk
+
+    def _buildlinelog(self):
+        """calculate the initial linelog based on self.content{,line}s.
+        this is similar to running a partial "annotate".
+        """
+        llog = linelog.linelog()
+        a, alines = '', []
+        for i in pycompat.xrange(len(self.contents)):
+            b, blines = self.contents[i], self.contentlines[i]
+            llrev = i * 2 + 1
+            chunks = self._alldiffchunks(a, b, alines, blines)
+            for a1, a2, b1, b2 in reversed(list(chunks)):
+                llog.replacelines(llrev, a1, a2, b1, b2)
+            a, alines = b, blines
+        return llog
+
+    def _checkoutlinelog(self):
+        """() -> [str]. check out file contents from linelog"""
+        contents = []
+        for i in pycompat.xrange(len(self.contents)):
+            rev = (i + 1) * 2
+            self.linelog.annotate(rev)
+            content = ''.join(map(self._getline, self.linelog.annotateresult))
+            contents.append(content)
+        return contents
+
+    def _checkoutlinelogwithedits(self):
+        """() -> [str]. prompt all lines for edit"""
+        alllines = self.linelog.getalllines()
+        # header
+        editortext = (_('HG: editing %s\nHG: "y" means the line to the right '
+                        'exists in the changeset to the top\nHG:\n')
+                      % self.fctxs[-1].path())
+        # [(idx, fctx)]. hide the dummy emptyfilecontext
+        visiblefctxs = [(i, f)
+                        for i, f in enumerate(self.fctxs)
+                        if not isinstance(f, emptyfilecontext)]
+        for i, (j, f) in enumerate(visiblefctxs):
+            editortext += (_('HG: %s/%s %s %s\n') %
+                           ('|' * i, '-' * (len(visiblefctxs) - i + 1),
+                            node.short(f.node()),
+                            f.description().split('\n',1)[0]))
+        editortext += _('HG: %s\n') % ('|' * len(visiblefctxs))
+        # figure out the lifetime of a line, this is relatively inefficient,
+        # but probably fine
+        lineset = defaultdict(lambda: set()) # {(llrev, linenum): {llrev}}
+        for i, f in visiblefctxs:
+            self.linelog.annotate((i + 1) * 2)
+            for l in self.linelog.annotateresult:
+                lineset[l].add(i)
+        # append lines
+        for l in alllines:
+            editortext += ('    %s : %s' %
+                           (''.join([('y' if i in lineset[l] else ' ')
+                                     for i, _f in visiblefctxs]),
+                            self._getline(l)))
+        # run editor
+        editedtext = self.ui.edit(editortext, '', action='absorb')
+        if not editedtext:
+            raise error.Abort(_('empty editor text'))
+        # parse edited result
+        contents = ['' for i in self.fctxs]
+        leftpadpos = 4
+        colonpos = leftpadpos + len(visiblefctxs) + 1
+        for l in mdiff.splitnewlines(editedtext):
+            if l.startswith('HG:'):
+                continue
+            if l[colonpos - 1:colonpos + 2] != ' : ':
+                raise error.Abort(_('malformed line: %s') % l)
+            linecontent = l[colonpos + 2:]
+            for i, ch in enumerate(l[leftpadpos:colonpos - 1]):
+                if ch == 'y':
+                    contents[visiblefctxs[i][0]] += linecontent
+        # chunkstats is hard to calculate if anything changes, therefore
+        # set them to just a simple value (1, 1).
+        if editedtext != editortext:
+            self.chunkstats = [1, 1]
+        return contents
+
+    def _getline(self, lineinfo):
+        """((rev, linenum)) -> str. convert rev+line number to line content"""
+        rev, linenum = lineinfo
+        if rev & 1: # odd: original line taken from fctxs
+            return self.contentlines[rev // 2][linenum]
+        else: # even: fixup line from targetfctx
+            return self.targetlines[linenum]
+
+    def _iscontinuous(self, a1, a2, closedinterval=False):
+        """(a1, a2 : int) -> bool
+
+        check if these lines are continuous. i.e. no other insertions or
+        deletions (from other revisions) among these lines.
+
+        closedinterval decides whether a2 should be included or not. i.e. is
+        it [a1, a2), or [a1, a2] ?
+        """
+        if a1 >= a2:
+            return True
+        llog = self.linelog
+        offset1 = llog.getoffset(a1)
+        offset2 = llog.getoffset(a2) + int(closedinterval)
+        linesinbetween = llog.getalllines(offset1, offset2)
+        return len(linesinbetween) == a2 - a1 + int(closedinterval)
+
+    def _optimizefixups(self, fixups):
+        """[(rev, a1, a2, b1, b2)] -> [(rev, a1, a2, b1, b2)].
+        merge adjacent fixups to make them less fragmented.
+        """
+        result = []
+        pcurrentchunk = [[-1, -1, -1, -1, -1]]
+
+        def pushchunk():
+            if pcurrentchunk[0][0] != -1:
+                result.append(tuple(pcurrentchunk[0]))
+
+        for i, chunk in enumerate(fixups):
+            rev, a1, a2, b1, b2 = chunk
+            lastrev = pcurrentchunk[0][0]
+            lasta2 = pcurrentchunk[0][2]
+            lastb2 = pcurrentchunk[0][4]
+            if (a1 == lasta2 and b1 == lastb2 and rev == lastrev and
+                    self._iscontinuous(max(a1 - 1, 0), a1)):
+                # merge into currentchunk
+                pcurrentchunk[0][2] = a2
+                pcurrentchunk[0][4] = b2
+            else:
+                pushchunk()
+                pcurrentchunk[0] = list(chunk)
+        pushchunk()
+        return result
+
+    def _showchanges(self, alines, blines, chunk, fixups):
+        ui = self.ui
+
+        def label(line, label):
+            if line.endswith('\n'):
+                line = line[:-1]
+            return ui.label(line, label)
+
+        # this is not optimized for perf but _showchanges only gets executed
+        # with an extra command-line flag.
+        a1, a2, b1, b2 = chunk
+        aidxs, bidxs = [0] * (a2 - a1), [0] * (b2 - b1)
+        for idx, fa1, fa2, fb1, fb2 in fixups:
+            for i in pycompat.xrange(fa1, fa2):
+                aidxs[i - a1] = (max(idx, 1) - 1) // 2
+            for i in pycompat.xrange(fb1, fb2):
+                bidxs[i - b1] = (max(idx, 1) - 1) // 2
+
+        buf = [] # [(idx, content)]
+        buf.append((0, label('@@ -%d,%d +%d,%d @@'
+                             % (a1, a2 - a1, b1, b2 - b1), 'diff.hunk')))
+        buf += [(aidxs[i - a1], label('-' + alines[i], 'diff.deleted'))
+                for i in pycompat.xrange(a1, a2)]
+        buf += [(bidxs[i - b1], label('+' + blines[i], 'diff.inserted'))
+                for i in pycompat.xrange(b1, b2)]
+        for idx, line in buf:
+            shortnode = idx and node.short(self.fctxs[idx].node()) or ''
+            ui.write(ui.label(shortnode[0:7].ljust(8), 'absorb.node') +
+                     line + '\n')
+
+class fixupstate(object):
+    """state needed to run absorb
+
+    internally, it keeps paths and filefixupstates.
+
+    a typical use is like filefixupstates:
+
+        1. call diffwith, to calculate fixups
+        2. (optionally), present fixups to the user, or edit fixups
+        3. call apply, to apply changes to memory
+        4. call commit, to commit changes to hg database
+    """
+
+    def __init__(self, stack, ui=None, opts=None):
+        """([ctx], ui or None) -> None
+
+        stack: should be linear, and sorted by topo order - oldest first.
+        all commits in stack are considered mutable.
+        """
+        assert stack
+        self.ui = ui or nullui()
+        self.opts = opts or {}
+        self.stack = stack
+        self.repo = stack[-1].repo().unfiltered()
+
+        # following fields will be filled later
+        self.paths = [] # [str]
+        self.status = None # ctx.status output
+        self.fctxmap = {} # {path: {ctx: fctx}}
+        self.fixupmap = {} # {path: filefixupstate}
+        self.replacemap = {} # {oldnode: newnode or None}
+        self.finalnode = None # head after all fixups
+
+    def diffwith(self, targetctx, match=None, showchanges=False):
+        """diff and prepare fixups. update self.fixupmap, self.paths"""
+        # only care about modified files
+        self.status = self.stack[-1].status(targetctx, match)
+        self.paths = []
+        # but if --edit-lines is used, the user may want to edit files
+        # even if they are not modified
+        editopt = self.opts.get('edit_lines')
+        if not self.status.modified and editopt and match:
+            interestingpaths = match.files()
+        else:
+            interestingpaths = self.status.modified
+        # prepare the filefixupstate
+        seenfctxs = set()
+        # sorting is necessary to eliminate ambiguity for the "double move"
+        # case: "hg cp A B; hg cp A C; hg rm A", then only "B" can affect "A".
+        for path in sorted(interestingpaths):
+            self.ui.debug('calculating fixups for %s\n' % path)
+            targetfctx = targetctx[path]
+            fctxs, ctx2fctx = getfilestack(self.stack, path, seenfctxs)
+            # ignore symbolic links or binary, or unchanged files
+            if any(f.islink() or stringutil.binary(f.data())
+                   for f in [targetfctx] + fctxs
+                   if not isinstance(f, emptyfilecontext)):
+                continue
+            if targetfctx.data() == fctxs[-1].data() and not editopt:
+                continue
+            seenfctxs.update(fctxs[1:])
+            self.fctxmap[path] = ctx2fctx
+            fstate = filefixupstate(fctxs, ui=self.ui, opts=self.opts)
+            if showchanges:
+                colorpath = self.ui.label(path, 'absorb.path')
+                header = 'showing changes for ' + colorpath
+                self.ui.write(header + '\n')
+            fstate.diffwith(targetfctx, showchanges=showchanges)
+            self.fixupmap[path] = fstate
+            self.paths.append(path)
+
+    def apply(self):
+        """apply fixups to individual filefixupstates"""
+        for path, state in self.fixupmap.iteritems():
+            if self.ui.debugflag:
+                self.ui.write(_('applying fixups to %s\n') % path)
+            state.apply()
+
+    @property
+    def chunkstats(self):
+        """-> {path: chunkstats}. collect chunkstats from filefixupstates"""
+        return dict((path, state.chunkstats)
+                    for path, state in self.fixupmap.iteritems())
+
+    def commit(self):
+        """commit changes. update self.finalnode, self.replacemap"""
+        with self.repo.wlock(), self.repo.lock():
+            with self.repo.transaction('absorb') as tr:
+                self._commitstack()
+                self._movebookmarks(tr)
+                if self.repo['.'].node() in self.replacemap:
+                    self._moveworkingdirectoryparent()
+                if self._useobsolete:
+                    self._obsoleteoldcommits()
+            if not self._useobsolete: # strip must be outside transactions
+                self._stripoldcommits()
+        return self.finalnode
+
+    def printchunkstats(self):
+        """print things like '1 of 2 chunk(s) applied'"""
+        ui = self.ui
+        chunkstats = self.chunkstats
+        if ui.verbose:
+            # chunkstats for each file
+            for path, stat in chunkstats.iteritems():
+                if stat[0]:
+                    ui.write(_('%s: %d of %d chunk(s) applied\n')
+                             % (path, stat[0], stat[1]))
+        elif not ui.quiet:
+            # a summary for all files
+            stats = chunkstats.values()
+            applied, total = (sum(s[i] for s in stats) for i in (0, 1))
+            ui.write(_('%d of %d chunk(s) applied\n') % (applied, total))
+
+    def _commitstack(self):
+        """make new commits. update self.finalnode, self.replacemap.
+        it is splitted from "commit" to avoid too much indentation.
+        """
+        # last node (20-char) committed by us
+        lastcommitted = None
+        # p1 which overrides the parent of the next commit, "None" means use
+        # the original parent unchanged
+        nextp1 = None
+        for ctx in self.stack:
+            memworkingcopy = self._getnewfilecontents(ctx)
+            if not memworkingcopy and not lastcommitted:
+                # nothing changed, nothing commited
+                nextp1 = ctx
+                continue
+            msg = ''
+            if self._willbecomenoop(memworkingcopy, ctx, nextp1):
+                # changeset is no longer necessary
+                self.replacemap[ctx.node()] = None
+                msg = _('became empty and was dropped')
+            else:
+                # changeset needs re-commit
+                nodestr = self._commitsingle(memworkingcopy, ctx, p1=nextp1)
+                lastcommitted = self.repo[nodestr]
+                nextp1 = lastcommitted
+                self.replacemap[ctx.node()] = lastcommitted.node()
+                if memworkingcopy:
+                    msg = _('%d file(s) changed, became %s') % (
+                        len(memworkingcopy), self._ctx2str(lastcommitted))
+                else:
+                    msg = _('became %s') % self._ctx2str(lastcommitted)
+            if self.ui.verbose and msg:
+                self.ui.write(_('%s: %s\n') % (self._ctx2str(ctx), msg))
+        self.finalnode = lastcommitted and lastcommitted.node()
+
+    def _ctx2str(self, ctx):
+        if self.ui.debugflag:
+            return ctx.hex()
+        else:
+            return node.short(ctx.node())
+
+    def _getnewfilecontents(self, ctx):
+        """(ctx) -> {path: str}
+
+        fetch file contents from filefixupstates.
+        return the working copy overrides - files different from ctx.
+        """
+        result = {}
+        for path in self.paths:
+            ctx2fctx = self.fctxmap[path] # {ctx: fctx}
+            if ctx not in ctx2fctx:
+                continue
+            fctx = ctx2fctx[ctx]
+            content = fctx.data()
+            newcontent = self.fixupmap[path].getfinalcontent(fctx)
+            if content != newcontent:
+                result[fctx.path()] = newcontent
+        return result
+
+    def _movebookmarks(self, tr):
+        repo = self.repo
+        needupdate = [(name, self.replacemap[hsh])
+                      for name, hsh in repo._bookmarks.iteritems()
+                      if hsh in self.replacemap]
+        changes = []
+        for name, hsh in needupdate:
+            if hsh:
+                changes.append((name, hsh))
+                if self.ui.verbose:
+                    self.ui.write(_('moving bookmark %s to %s\n')
+                                  % (name, node.hex(hsh)))
+            else:
+                changes.append((name, None))
+                if self.ui.verbose:
+                    self.ui.write(_('deleting bookmark %s\n') % name)
+        repo._bookmarks.applychanges(repo, tr, changes)
+
+    def _moveworkingdirectoryparent(self):
+        if not self.finalnode:
+            # Find the latest not-{obsoleted,stripped} parent.
+            revs = self.repo.revs('max(::. - %ln)', self.replacemap.keys())
+            ctx = self.repo[revs.first()]
+            self.finalnode = ctx.node()
+        else:
+            ctx = self.repo[self.finalnode]
+
+        dirstate = self.repo.dirstate
+        # dirstate.rebuild invalidates fsmonitorstate, causing "hg status" to
+        # be slow. in absorb's case, no need to invalidate fsmonitorstate.
+        noop = lambda: 0
+        restore = noop
+        if util.safehasattr(dirstate, '_fsmonitorstate'):
+            bak = dirstate._fsmonitorstate.invalidate
+            def restore():
+                dirstate._fsmonitorstate.invalidate = bak
+            dirstate._fsmonitorstate.invalidate = noop
+        try:
+            with dirstate.parentchange():
+                dirstate.rebuild(ctx.node(), ctx.manifest(), self.paths)
+        finally:
+            restore()
+
+    @staticmethod
+    def _willbecomenoop(memworkingcopy, ctx, pctx=None):
+        """({path: content}, ctx, ctx) -> bool. test if a commit will be noop
+
+        if it will become an empty commit (does not change anything, after the
+        memworkingcopy overrides), return True. otherwise return False.
+        """
+        if not pctx:
+            parents = ctx.parents()
+            if len(parents) != 1:
+                return False
+            pctx = parents[0]
+        # ctx changes more files (not a subset of memworkingcopy)
+        if not set(ctx.files()).issubset(set(memworkingcopy)):
+            return False
+        for path, content in memworkingcopy.iteritems():
+            if path not in pctx or path not in ctx:
+                return False
+            fctx = ctx[path]
+            pfctx = pctx[path]
+            if pfctx.flags() != fctx.flags():
+                return False
+            if pfctx.data() != content:
+                return False
+        return True
+
+    def _commitsingle(self, memworkingcopy, ctx, p1=None):
+        """(ctx, {path: content}, node) -> node. make a single commit
+
+        the commit is a clone from ctx, with a (optionally) different p1, and
+        different file contents replaced by memworkingcopy.
+        """
+        parents = p1 and (p1, node.nullid)
+        extra = ctx.extra()
+        if self._useobsolete and self.ui.configbool('absorb', 'add-noise'):
+            extra['absorb_source'] = ctx.hex()
+        mctx = overlaycontext(memworkingcopy, ctx, parents, extra=extra)
+        # preserve phase
+        with mctx.repo().ui.configoverride({
+            ('phases', 'new-commit'): ctx.phase()}):
+            return mctx.commit()
+
+    @util.propertycache
+    def _useobsolete(self):
+        """() -> bool"""
+        return obsolete.isenabled(self.repo, obsolete.createmarkersopt)
+
+    def _obsoleteoldcommits(self):
+        relations = [(self.repo[k], v and (self.repo[v],) or ())
+                     for k, v in self.replacemap.iteritems()]
+        if relations:
+            obsolete.createmarkers(self.repo, relations)
+
+    def _stripoldcommits(self):
+        nodelist = self.replacemap.keys()
+        # make sure we don't strip innocent children
+        revs = self.repo.revs('%ln - (::(heads(%ln::)-%ln))', nodelist,
+                              nodelist, nodelist)
+        tonode = self.repo.changelog.node
+        nodelist = [tonode(r) for r in revs]
+        if nodelist:
+            repair.strip(self.repo.ui, self.repo, nodelist)
+
+def _parsechunk(hunk):
+    """(crecord.uihunk or patch.recordhunk) -> (path, (a1, a2, [bline]))"""
+    if type(hunk) not in (crecord.uihunk, patch.recordhunk):
+        return None, None
+    path = hunk.header.filename()
+    a1 = hunk.fromline + len(hunk.before) - 1
+    # remove before and after context
+    hunk.before = hunk.after = []
+    buf = util.stringio()
+    hunk.write(buf)
+    patchlines = mdiff.splitnewlines(buf.getvalue())
+    # hunk.prettystr() will update hunk.removed
+    a2 = a1 + hunk.removed
+    blines = [l[1:] for l in patchlines[1:] if l[0] != '-']
+    return path, (a1, a2, blines)
+
+def overlaydiffcontext(ctx, chunks):
+    """(ctx, [crecord.uihunk]) -> memctx
+
+    return a memctx with some [1] patches (chunks) applied to ctx.
+    [1]: modifications are handled. renames, mode changes, etc. are ignored.
+    """
+    # sadly the applying-patch logic is hardly reusable, and messy:
+    # 1. the core logic "_applydiff" is too heavy - it writes .rej files, it
+    #    needs a file stream of a patch and will re-parse it, while we have
+    #    structured hunk objects at hand.
+    # 2. a lot of different implementations about "chunk" (patch.hunk,
+    #    patch.recordhunk, crecord.uihunk)
+    # as we only care about applying changes to modified files, no mode
+    # change, no binary diff, and no renames, it's probably okay to
+    # re-invent the logic using much simpler code here.
+    memworkingcopy = {} # {path: content}
+    patchmap = defaultdict(lambda: []) # {path: [(a1, a2, [bline])]}
+    for path, info in map(_parsechunk, chunks):
+        if not path or not info:
+            continue
+        patchmap[path].append(info)
+    for path, patches in patchmap.iteritems():
+        if path not in ctx or not patches:
+            continue
+        patches.sort(reverse=True)
+        lines = mdiff.splitnewlines(ctx[path].data())
+        for a1, a2, blines in patches:
+            lines[a1:a2] = blines
+        memworkingcopy[path] = ''.join(lines)
+    return overlaycontext(memworkingcopy, ctx)
+
+def absorb(ui, repo, stack=None, targetctx=None, pats=None, opts=None):
+    """pick fixup chunks from targetctx, apply them to stack.
+
+    if targetctx is None, the working copy context will be used.
+    if stack is None, the current draft stack will be used.
+    return fixupstate.
+    """
+    if stack is None:
+        limit = ui.configint('absorb', 'max-stack-size')
+        stack = getdraftstack(repo['.'], limit)
+        if limit and len(stack) >= limit:
+            ui.warn(_('absorb: only the recent %d changesets will '
+                      'be analysed\n')
+                    % limit)
+    if not stack:
+        raise error.Abort(_('no mutable changeset to change'))
+    if targetctx is None: # default to working copy
+        targetctx = repo[None]
+    if pats is None:
+        pats = ()
+    if opts is None:
+        opts = {}
+    state = fixupstate(stack, ui=ui, opts=opts)
+    matcher = scmutil.match(targetctx, pats, opts)
+    if opts.get('interactive'):
+        diff = patch.diff(repo, stack[-1].node(), targetctx.node(), matcher)
+        origchunks = patch.parsepatch(diff)
+        chunks = cmdutil.recordfilter(ui, origchunks)[0]
+        targetctx = overlaydiffcontext(stack[-1], chunks)
+    state.diffwith(targetctx, matcher, showchanges=opts.get('print_changes'))
+    if not opts.get('dry_run'):
+        state.apply()
+        if state.commit():
+            state.printchunkstats()
+        elif not ui.quiet:
+            ui.write(_('nothing applied\n'))
+    return state
+
+@command('^absorb',
+         [('p', 'print-changes', None,
+           _('print which changesets are modified by which changes')),
+          ('i', 'interactive', None,
+           _('interactively select which chunks to apply (EXPERIMENTAL)')),
+          ('e', 'edit-lines', None,
+           _('edit what lines belong to which changesets before commit '
+             '(EXPERIMENTAL)')),
+         ] + commands.dryrunopts + commands.walkopts,
+         _('hg absorb [OPTION] [FILE]...'))
+def absorbcmd(ui, repo, *pats, **opts):
+    """incorporate corrections into the stack of draft changesets
+
+    absorb analyzes each change in your working directory and attempts to
+    amend the changed lines into the changesets in your stack that first
+    introduced those lines.
+
+    If absorb cannot find an unambiguous changeset to amend for a change,
+    that change will be left in the working directory, untouched. They can be
+    observed by :hg:`status` or :hg:`diff` afterwards. In other words,
+    absorb does not write to the working directory.
+
+    Changesets outside the revset `::. and not public() and not merge()` will
+    not be changed.
+
+    Changesets that become empty after applying the changes will be deleted.
+
+    If in doubt, run :hg:`absorb -pn` to preview what changesets will
+    be amended by what changed lines, without actually changing anything.
+
+    Returns 0 on success, 1 if all chunks were ignored and nothing amended.
+    """
+    opts = pycompat.byteskwargs(opts)
+    state = absorb(ui, repo, pats=pats, opts=opts)
+    if sum(s[0] for s in state.chunkstats.values()) == 0:
+        return 1
--- a/hgext/acl.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/acl.py	Wed Sep 26 20:33:09 2018 +0900
@@ -220,6 +220,7 @@
     error,
     extensions,
     match,
+    pycompat,
     registrar,
     util,
 )
@@ -374,9 +375,9 @@
         _txnhook(ui, repo, hooktype, node, source, user, **kwargs)
 
 def _pkhook(ui, repo, hooktype, node, source, user, **kwargs):
-    if kwargs['namespace'] == 'bookmarks':
-        bookmark = kwargs['key']
-        ctx = kwargs['new']
+    if kwargs[r'namespace'] == 'bookmarks':
+        bookmark = kwargs[r'key']
+        ctx = kwargs[r'new']
         allowbookmarks = buildmatch(ui, None, user, 'acl.allow.bookmarks')
         denybookmarks = buildmatch(ui, None, user, 'acl.deny.bookmarks')
 
@@ -403,7 +404,7 @@
     allow = buildmatch(ui, repo, user, 'acl.allow')
     deny = buildmatch(ui, repo, user, 'acl.deny')
 
-    for rev in xrange(repo[node].rev(), len(repo)):
+    for rev in pycompat.xrange(repo[node].rev(), len(repo)):
         ctx = repo[rev]
         branch = ctx.branch()
         if denybranches and denybranches(branch):
--- a/hgext/beautifygraph.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/beautifygraph.py	Wed Sep 26 20:33:09 2018 +0900
@@ -18,6 +18,7 @@
     encoding,
     extensions,
     graphmod,
+    pycompat,
     templatekw,
 )
 
@@ -53,8 +54,10 @@
 def convertedges(line):
     line = ' %s ' % line
     pretty = []
-    for idx in xrange(len(line) - 2):
-        pretty.append(prettyedge(line[idx], line[idx + 1], line[idx + 2]))
+    for idx in pycompat.xrange(len(line) - 2):
+        pretty.append(prettyedge(line[idx:idx + 1],
+                                 line[idx + 1:idx + 2],
+                                 line[idx + 2:idx + 3]))
     return ''.join(pretty)
 
 def getprettygraphnode(orig, *args, **kwargs):
@@ -84,7 +87,7 @@
         ui.warn(_('beautifygraph: unsupported encoding, UTF-8 required\n'))
         return
 
-    if 'A' in encoding._wide:
+    if r'A' in encoding._wide:
         ui.warn(_('beautifygraph: unsupported terminal settings, '
                   'monospace narrow text required\n'))
         return
--- a/hgext/blackbox.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/blackbox.py	Wed Sep 26 20:33:09 2018 +0900
@@ -45,6 +45,7 @@
 
 from mercurial import (
     encoding,
+    pycompat,
     registrar,
     ui as uimod,
     util,
@@ -111,7 +112,7 @@
             if st.st_size >= maxsize:
                 path = vfs.join(name)
                 maxfiles = ui.configint('blackbox', 'maxfiles')
-                for i in xrange(maxfiles - 1, 1, -1):
+                for i in pycompat.xrange(maxfiles - 1, 1, -1):
                     rotate(oldpath='%s.%d' % (path, i - 1),
                            newpath='%s.%d' % (path, i))
                 rotate(oldpath=path,
--- a/hgext/censor.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/censor.py	Wed Sep 26 20:33:09 2018 +0900
@@ -33,9 +33,7 @@
 from mercurial import (
     error,
     registrar,
-    revlog,
     scmutil,
-    util,
 )
 
 cmdtable = {}
@@ -82,8 +80,11 @@
         raise error.Abort(_('file does not exist at revision %s') % rev)
 
     fnode = fctx.filenode()
-    headctxs = [repo[c] for c in repo.heads()]
-    heads = [c for c in headctxs if path in c and c.filenode(path) == fnode]
+    heads = []
+    for headnode in repo.heads():
+        hc = repo[headnode]
+        if path in hc and hc.filenode(path) == fnode:
+            heads.append(hc)
     if heads:
         headlist = ', '.join([short(c.node()) for c in heads])
         raise error.Abort(_('cannot censor file in heads (%s)') % headlist,
@@ -94,90 +95,5 @@
         raise error.Abort(_('cannot censor working directory'),
             hint=_('clean/delete/update first'))
 
-    flogv = flog.version & 0xFFFF
-    if flogv != revlog.REVLOGV1:
-        raise error.Abort(
-            _('censor does not support revlog version %d') % (flogv,))
-
-    tombstone = revlog.packmeta({"censored": tombstone}, "")
-
-    crev = fctx.filerev()
-
-    if len(tombstone) > flog.rawsize(crev):
-        raise error.Abort(_(
-            'censor tombstone must be no longer than censored data'))
-
-    # Using two files instead of one makes it easy to rewrite entry-by-entry
-    idxread = repo.svfs(flog.indexfile, 'r')
-    idxwrite = repo.svfs(flog.indexfile, 'wb', atomictemp=True)
-    if flog.version & revlog.FLAG_INLINE_DATA:
-        dataread, datawrite = idxread, idxwrite
-    else:
-        dataread = repo.svfs(flog.datafile, 'r')
-        datawrite = repo.svfs(flog.datafile, 'wb', atomictemp=True)
-
-    # Copy all revlog data up to the entry to be censored.
-    rio = revlog.revlogio()
-    offset = flog.start(crev)
-
-    for chunk in util.filechunkiter(idxread, limit=crev * rio.size):
-        idxwrite.write(chunk)
-    for chunk in util.filechunkiter(dataread, limit=offset):
-        datawrite.write(chunk)
-
-    def rewriteindex(r, newoffs, newdata=None):
-        """Rewrite the index entry with a new data offset and optional new data.
-
-        The newdata argument, if given, is a tuple of three positive integers:
-        (new compressed, new uncompressed, added flag bits).
-        """
-        offlags, comp, uncomp, base, link, p1, p2, nodeid = flog.index[r]
-        flags = revlog.gettype(offlags)
-        if newdata:
-            comp, uncomp, nflags = newdata
-            flags |= nflags
-        offlags = revlog.offset_type(newoffs, flags)
-        e = (offlags, comp, uncomp, r, link, p1, p2, nodeid)
-        idxwrite.write(rio.packentry(e, None, flog.version, r))
-        idxread.seek(rio.size, 1)
-
-    def rewrite(r, offs, data, nflags=revlog.REVIDX_DEFAULT_FLAGS):
-        """Write the given full text to the filelog with the given data offset.
-
-        Returns:
-            The integer number of data bytes written, for tracking data offsets.
-        """
-        flag, compdata = flog.compress(data)
-        newcomp = len(flag) + len(compdata)
-        rewriteindex(r, offs, (newcomp, len(data), nflags))
-        datawrite.write(flag)
-        datawrite.write(compdata)
-        dataread.seek(flog.length(r), 1)
-        return newcomp
-
-    # Rewrite censored revlog entry with (padded) tombstone data.
-    pad = ' ' * (flog.rawsize(crev) - len(tombstone))
-    offset += rewrite(crev, offset, tombstone + pad, revlog.REVIDX_ISCENSORED)
-
-    # Rewrite all following filelog revisions fixing up offsets and deltas.
-    for srev in xrange(crev + 1, len(flog)):
-        if crev in flog.parentrevs(srev):
-            # Immediate children of censored node must be re-added as fulltext.
-            try:
-                revdata = flog.revision(srev)
-            except error.CensoredNodeError as e:
-                revdata = e.tombstone
-            dlen = rewrite(srev, offset, revdata)
-        else:
-            # Copy any other revision data verbatim after fixing up the offset.
-            rewriteindex(srev, offset)
-            dlen = flog.length(srev)
-            for chunk in util.filechunkiter(dataread, limit=dlen):
-                datawrite.write(chunk)
-        offset += dlen
-
-    idxread.close()
-    idxwrite.close()
-    if dataread is not idxread:
-        dataread.close()
-        datawrite.close()
+    with repo.transaction(b'censor') as tr:
+        flog.censorrevision(tr, fnode, tombstone=tombstone)
--- a/hgext/commitextras.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/commitextras.py	Wed Sep 26 20:33:09 2018 +0900
@@ -17,6 +17,7 @@
     error,
     extensions,
     registrar,
+    util,
 )
 
 cmdtable = {}
@@ -43,34 +44,29 @@
         _('set a changeset\'s extra values'), _("KEY=VALUE")))
 
 def _commit(orig, ui, repo, *pats, **opts):
-    origcommit = repo.commit
-    try:
-        def _wrappedcommit(*innerpats, **inneropts):
+    if util.safehasattr(repo, 'unfiltered'):
+        repo = repo.unfiltered()
+    class repoextra(repo.__class__):
+        def commit(self, *innerpats, **inneropts):
             extras = opts.get(r'extra')
-            if extras:
-                for raw in extras:
-                    if '=' not in raw:
-                        msg = _("unable to parse '%s', should follow "
-                                "KEY=VALUE format")
-                        raise error.Abort(msg % raw)
-                    k, v = raw.split('=', 1)
-                    if not k:
-                        msg = _("unable to parse '%s', keys can't be empty")
-                        raise error.Abort(msg % raw)
-                    if re.search('[^\w-]', k):
-                        msg = _("keys can only contain ascii letters, digits,"
-                                " '_' and '-'")
-                        raise error.Abort(msg)
-                    if k in usedinternally:
-                        msg = _("key '%s' is used internally, can't be set "
-                                "manually")
-                        raise error.Abort(msg % k)
-                    inneropts[r'extra'][k] = v
-            return origcommit(*innerpats, **inneropts)
-
-        # This __dict__ logic is needed because the normal
-        # extension.wrapfunction doesn't seem to work.
-        repo.__dict__[r'commit'] = _wrappedcommit
-        return orig(ui, repo, *pats, **opts)
-    finally:
-        del repo.__dict__[r'commit']
+            for raw in extras:
+                if '=' not in raw:
+                    msg = _("unable to parse '%s', should follow "
+                            "KEY=VALUE format")
+                    raise error.Abort(msg % raw)
+                k, v = raw.split('=', 1)
+                if not k:
+                    msg = _("unable to parse '%s', keys can't be empty")
+                    raise error.Abort(msg % raw)
+                if re.search('[^\w-]', k):
+                    msg = _("keys can only contain ascii letters, digits,"
+                            " '_' and '-'")
+                    raise error.Abort(msg)
+                if k in usedinternally:
+                    msg = _("key '%s' is used internally, can't be set "
+                            "manually")
+                    raise error.Abort(msg % k)
+                inneropts[r'extra'][k] = v
+            return super(repoextra, self).commit(*innerpats, **inneropts)
+    repo.__class__ = repoextra
+    return orig(ui, repo, *pats, **opts)
--- a/hgext/convert/common.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/convert/common.py	Wed Sep 26 20:33:09 2018 +0900
@@ -402,7 +402,8 @@
 
     def _run(self, cmd, *args, **kwargs):
         def popen(cmdline):
-            p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
+            p = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmdline),
+                                 shell=True, bufsize=-1,
                                  close_fds=procutil.closefds,
                                  stdout=subprocess.PIPE)
             return p
@@ -459,7 +460,7 @@
         # POSIX requires at least 4096 bytes for ARG_MAX
         argmax = 4096
         try:
-            argmax = os.sysconf("SC_ARG_MAX")
+            argmax = os.sysconf(r"SC_ARG_MAX")
         except (AttributeError, ValueError):
             pass
 
--- a/hgext/convert/cvs.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/convert/cvs.py	Wed Sep 26 20:33:09 2018 +0900
@@ -15,7 +15,6 @@
 from mercurial import (
     encoding,
     error,
-    pycompat,
     util,
 )
 from mercurial.utils import (
@@ -74,7 +73,7 @@
                 raise error.Abort(_('revision %s is not a patchset number')
                                  % self.revs[0])
 
-        d = pycompat.getcwd()
+        d = encoding.getcwd()
         try:
             os.chdir(self.path)
             id = None
--- a/hgext/convert/cvsps.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/convert/cvsps.py	Wed Sep 26 20:33:09 2018 +0900
@@ -763,7 +763,7 @@
             # branchpoints such that it is the latest possible
             # commit without any intervening, unrelated commits.
 
-            for candidate in xrange(i):
+            for candidate in pycompat.xrange(i):
                 if c.branch not in changesets[candidate].branchpoints:
                     if p is not None:
                         break
--- a/hgext/convert/gnuarch.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/convert/gnuarch.py	Wed Sep 26 20:33:09 2018 +0900
@@ -17,6 +17,7 @@
 from mercurial import (
     encoding,
     error,
+    pycompat,
 )
 from mercurial.utils import (
     dateutil,
@@ -201,7 +202,7 @@
         cmdline += ['>', os.devnull, '2>', os.devnull]
         cmdline = procutil.quotecommand(' '.join(cmdline))
         self.ui.debug(cmdline, '\n')
-        return os.system(cmdline)
+        return os.system(pycompat.rapply(procutil.tonativestr, cmdline))
 
     def _update(self, rev):
         self.ui.debug('applying revision %s...\n' % rev)
--- a/hgext/convert/hg.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/convert/hg.py	Wed Sep 26 20:33:09 2018 +0900
@@ -358,7 +358,7 @@
             p2 = node
 
         if self.filemapmode and nparents == 1:
-            man = self.repo.manifestlog._revlog
+            man = self.repo.manifestlog.getstorage(b'')
             mnode = self.repo.changelog.read(nodemod.bin(p2))[0]
             closed = 'close' in commit.extra
             if not closed and not man.cmp(m1node, man.revision(mnode)):
--- a/hgext/convert/monotone.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/convert/monotone.py	Wed Sep 26 20:33:09 2018 +0900
@@ -345,7 +345,7 @@
 
         if version >= 12.0:
             self.automatestdio = True
-            self.ui.debug("mtn automate version %s - using automate stdio\n" %
+            self.ui.debug("mtn automate version %f - using automate stdio\n" %
                 version)
 
             # launch the long-running automate stdio process
--- a/hgext/convert/subversion.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/convert/subversion.py	Wed Sep 26 20:33:09 2018 +0900
@@ -1127,7 +1127,7 @@
         self.delexec = []
         self.copies = []
         self.wc = None
-        self.cwd = pycompat.getcwd()
+        self.cwd = encoding.getcwd()
 
         created = False
         if os.path.isfile(os.path.join(path, '.svn', 'entries')):
@@ -1147,7 +1147,7 @@
                         path = '/' + path
                     path = 'file://' + path
 
-            wcpath = os.path.join(pycompat.getcwd(), os.path.basename(path) +
+            wcpath = os.path.join(encoding.getcwd(), os.path.basename(path) +
                                 '-wc')
             ui.status(_('initializing svn working copy %r\n')
                       % os.path.basename(wcpath))
@@ -1270,7 +1270,7 @@
         self.childmap[parent] = child
 
     def revid(self, rev):
-        return u"svn:%s@%s" % (self.uuid, rev)
+        return "svn:%s@%s" % (self.uuid, rev)
 
     def putcommit(self, files, copies, parents, commit, source, revmap, full,
                   cleanp2):
--- a/hgext/eol.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/eol.py	Wed Sep 26 20:33:09 2018 +0900
@@ -266,7 +266,7 @@
     ensureenabled(ui)
     files = set()
     revs = set()
-    for rev in xrange(repo[node].rev(), len(repo)):
+    for rev in pycompat.xrange(repo[node].rev(), len(repo)):
         revs.add(rev)
         if headsonly:
             ctx = repo[rev]
--- a/hgext/factotum.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/factotum.py	Wed Sep 26 20:33:09 2018 +0900
@@ -49,6 +49,9 @@
 
 import os
 from mercurial.i18n import _
+from mercurial.utils import (
+    procutil,
+)
 from mercurial import (
     error,
     httpconnection,
@@ -83,7 +86,7 @@
     if 'user=' not in params:
         params = '%s user?' % params
     params = '%s !password?' % params
-    os.system("%s -g '%s'" % (_executable, params))
+    os.system(procutil.tonativestr("%s -g '%s'" % (_executable, params)))
 
 def auth_getuserpasswd(self, getkey, params):
     params = 'proto=pass %s' % params
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/fastannotate/__init__.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,193 @@
+# Copyright 2016-present Facebook. All Rights Reserved.
+#
+# fastannotate: faster annotate implementation using linelog
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""yet another annotate implementation that might be faster (EXPERIMENTAL)
+
+The fastannotate extension provides a 'fastannotate' command that makes
+use of the linelog data structure as a cache layer and is expected to
+be faster than the vanilla 'annotate' if the cache is present.
+
+In most cases, fastannotate requires a setup that mainbranch is some pointer
+that always moves forward, to be most efficient.
+
+Using fastannotate together with linkrevcache would speed up building the
+annotate cache greatly. Run "debugbuildlinkrevcache" before
+"debugbuildannotatecache".
+
+::
+
+    [fastannotate]
+    # specify the main branch head. the internal linelog will only contain
+    # the linear (ignoring p2) "mainbranch". since linelog cannot move
+    # backwards without a rebuild, this should be something that always moves
+    # forward, usually it is "master" or "@".
+    mainbranch = master
+
+    # fastannotate supports different modes to expose its feature.
+    # a list of combination:
+    # - fastannotate: expose the feature via the "fastannotate" command which
+    #   deals with everything in a most efficient way, and provides extra
+    #   features like --deleted etc.
+    # - fctx: replace fctx.annotate implementation. note:
+    #     a. it is less efficient than the "fastannotate" command
+    #     b. it will make it practically impossible to access the old (disk
+    #        side-effect free) annotate implementation
+    #     c. it implies "hgweb".
+    # - hgweb: replace hgweb's annotate implementation. conflict with "fctx".
+    # (default: fastannotate)
+    modes = fastannotate
+
+    # default format when no format flags are used (default: number)
+    defaultformat = changeset, user, date
+
+    # serve the annotate cache via wire protocol (default: False)
+    # tip: the .hg/fastannotate directory is portable - can be rsynced
+    server = True
+
+    # build annotate cache on demand for every client request (default: True)
+    # disabling it could make server response faster, useful when there is a
+    # cronjob building the cache.
+    serverbuildondemand = True
+
+    # update local annotate cache from remote on demand
+    client = False
+
+    # path to use when connecting to the remote server (default: default)
+    remotepath = default
+
+    # minimal length of the history of a file required to fetch linelog from
+    # the server. (default: 10)
+    clientfetchthreshold = 10
+
+    # use flock instead of the file existence lock
+    # flock may not work well on some network filesystems, but they avoid
+    # creating and deleting files frequently, which is faster when updating
+    # the annotate cache in batch. if you have issues with this option, set it
+    # to False. (default: True if flock is supported, False otherwise)
+    useflock = True
+
+    # for "fctx" mode, always follow renames regardless of command line option.
+    # this is a BC with the original command but will reduced the space needed
+    # for annotate cache, and is useful for client-server setup since the
+    # server will only provide annotate cache with default options (i.e. with
+    # follow). do not affect "fastannotate" mode. (default: True)
+    forcefollow = True
+
+    # for "fctx" mode, always treat file as text files, to skip the "isbinary"
+    # check. this is consistent with the "fastannotate" command and could help
+    # to avoid a file fetch if remotefilelog is used. (default: True)
+    forcetext = True
+
+    # use unfiltered repo for better performance.
+    unfilteredrepo = True
+
+    # sacrifice correctness in some corner cases for performance. it does not
+    # affect the correctness of the annotate cache being built. the option
+    # is experimental and may disappear in the future (default: False)
+    perfhack = True
+"""
+
+# TODO from import:
+# * `branch` is probably the wrong term, throughout the code.
+#
+# * replace the fastannotate `modes` configuration with a collection
+#   of booleans.
+#
+# * Use the templater instead of bespoke formatting
+#
+# * rename the config knob for updating the local cache from a remote server
+#
+# * move `flock` based locking to a common area
+#
+# * revise wireprotocol for sharing annotate files
+#
+# * figure out a sensible default for `mainbranch` (with the caveat
+#   that we probably also want to figure out a better term than
+#   `branch`, see above)
+#
+# * format changes to the revmap file (maybe use length-encoding
+#   instead of null-terminated file paths at least?)
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial import (
+    configitems,
+    error as hgerror,
+    localrepo,
+    registrar,
+)
+
+from . import (
+    commands,
+    context,
+    protocol,
+)
+
+# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
+# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
+# be specifying the version(s) of Mercurial they are tested with, or
+# leave the attribute unspecified.
+testedwith = 'ships-with-hg-core'
+
+cmdtable = commands.cmdtable
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fastannotate', 'modes', default=['fastannotate'])
+configitem('fastannotate', 'server', default=False)
+configitem('fastannotate', 'useflock', default=configitems.dynamicdefault)
+configitem('fastannotate', 'client', default=False)
+configitem('fastannotate', 'unfilteredrepo', default=True)
+configitem('fastannotate', 'defaultformat', default=['number'])
+configitem('fastannotate', 'perfhack', default=False)
+configitem('fastannotate', 'mainbranch')
+configitem('fastannotate', 'forcetext', default=True)
+configitem('fastannotate', 'forcefollow', default=True)
+configitem('fastannotate', 'clientfetchthreshold', default=10)
+configitem('fastannotate', 'serverbuildondemand', default=True)
+configitem('fastannotate', 'remotepath', default='default')
+
+def _flockavailable():
+    try:
+        import fcntl
+        fcntl.flock
+    except StandardError:
+        return False
+    else:
+        return True
+
+def uisetup(ui):
+    modes = set(ui.configlist('fastannotate', 'modes'))
+    if 'fctx' in modes:
+        modes.discard('hgweb')
+    for name in modes:
+        if name == 'fastannotate':
+            commands.registercommand()
+        elif name == 'hgweb':
+            from . import support
+            support.replacehgwebannotate()
+        elif name == 'fctx':
+            from . import support
+            support.replacefctxannotate()
+            commands.wrapdefault()
+        else:
+            raise hgerror.Abort(_('fastannotate: invalid mode: %s') % name)
+
+    if ui.configbool('fastannotate', 'server'):
+        protocol.serveruisetup(ui)
+
+    if ui.configbool('fastannotate', 'useflock', _flockavailable()):
+        context.pathhelper.lock = context.pathhelper._lockflock
+
+def extsetup(ui):
+    # fastannotate has its own locking, without depending on repo lock
+    # TODO: avoid mutating this unless the specific repo has it enabled
+    localrepo.localrepository._wlockfreeprefix.add('fastannotate/')
+
+def reposetup(ui, repo):
+    if ui.configbool('fastannotate', 'client'):
+        protocol.clientreposetup(ui, repo)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/fastannotate/commands.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,285 @@
+# Copyright 2016-present Facebook. All Rights Reserved.
+#
+# commands: fastannotate commands
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import os
+
+from mercurial.i18n import _
+from mercurial import (
+    commands,
+    encoding,
+    error,
+    extensions,
+    patch,
+    pycompat,
+    registrar,
+    scmutil,
+    util,
+)
+
+from . import (
+    context as facontext,
+    error as faerror,
+    formatter as faformatter,
+)
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+def _matchpaths(repo, rev, pats, opts, aopts=facontext.defaultopts):
+    """generate paths matching given patterns"""
+    perfhack = repo.ui.configbool('fastannotate', 'perfhack')
+
+    # disable perfhack if:
+    # a) any walkopt is used
+    # b) if we treat pats as plain file names, some of them do not have
+    #    corresponding linelog files
+    if perfhack:
+        # cwd related to reporoot
+        reporoot = os.path.dirname(repo.path)
+        reldir = os.path.relpath(encoding.getcwd(), reporoot)
+        if reldir == '.':
+            reldir = ''
+        if any(opts.get(o[1]) for o in commands.walkopts): # a)
+            perfhack = False
+        else: # b)
+            relpats = [os.path.relpath(p, reporoot) if os.path.isabs(p) else p
+                       for p in pats]
+            # disable perfhack on '..' since it allows escaping from the repo
+            if any(('..' in f or
+                    not os.path.isfile(
+                        facontext.pathhelper(repo, f, aopts).linelogpath))
+                   for f in relpats):
+                perfhack = False
+
+    # perfhack: emit paths directory without checking with manifest
+    # this can be incorrect if the rev dos not have file.
+    if perfhack:
+        for p in relpats:
+            yield os.path.join(reldir, p)
+    else:
+        def bad(x, y):
+            raise error.Abort("%s: %s" % (x, y))
+        ctx = scmutil.revsingle(repo, rev)
+        m = scmutil.match(ctx, pats, opts, badfn=bad)
+        for p in ctx.walk(m):
+            yield p
+
+fastannotatecommandargs = {
+    r'options': [
+        ('r', 'rev', '.', _('annotate the specified revision'), _('REV')),
+        ('u', 'user', None, _('list the author (long with -v)')),
+        ('f', 'file', None, _('list the filename')),
+        ('d', 'date', None, _('list the date (short with -q)')),
+        ('n', 'number', None, _('list the revision number (default)')),
+        ('c', 'changeset', None, _('list the changeset')),
+        ('l', 'line-number', None, _('show line number at the first '
+                                     'appearance')),
+        ('e', 'deleted', None, _('show deleted lines (slow) (EXPERIMENTAL)')),
+        ('', 'no-content', None, _('do not show file content (EXPERIMENTAL)')),
+        ('', 'no-follow', None, _("don't follow copies and renames")),
+        ('', 'linear', None, _('enforce linear history, ignore second parent '
+                               'of merges (EXPERIMENTAL)')),
+        ('', 'long-hash', None, _('show long changeset hash (EXPERIMENTAL)')),
+        ('', 'rebuild', None, _('rebuild cache even if it exists '
+                                '(EXPERIMENTAL)')),
+    ] + commands.diffwsopts + commands.walkopts + commands.formatteropts,
+    r'synopsis': _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
+    r'inferrepo': True,
+}
+
+def fastannotate(ui, repo, *pats, **opts):
+    """show changeset information by line for each file
+
+    List changes in files, showing the revision id responsible for each line.
+
+    This command is useful for discovering when a change was made and by whom.
+
+    By default this command prints revision numbers. If you include --file,
+    --user, or --date, the revision number is suppressed unless you also
+    include --number. The default format can also be customized by setting
+    fastannotate.defaultformat.
+
+    Returns 0 on success.
+
+    .. container:: verbose
+
+        This command uses an implementation different from the vanilla annotate
+        command, which may produce slightly different (while still reasonable)
+        outputs for some cases.
+
+        Unlike the vanilla anootate, fastannotate follows rename regardless of
+        the existence of --file.
+
+        For the best performance when running on a full repo, use -c, -l,
+        avoid -u, -d, -n. Use --linear and --no-content to make it even faster.
+
+        For the best performance when running on a shallow (remotefilelog)
+        repo, avoid --linear, --no-follow, or any diff options. As the server
+        won't be able to populate annotate cache when non-default options
+        affecting results are used.
+    """
+    if not pats:
+        raise error.Abort(_('at least one filename or pattern is required'))
+
+    # performance hack: filtered repo can be slow. unfilter by default.
+    if ui.configbool('fastannotate', 'unfilteredrepo'):
+        repo = repo.unfiltered()
+
+    opts = pycompat.byteskwargs(opts)
+
+    rev = opts.get('rev', '.')
+    rebuild = opts.get('rebuild', False)
+
+    diffopts = patch.difffeatureopts(ui, opts, section='annotate',
+                                     whitespace=True)
+    aopts = facontext.annotateopts(
+        diffopts=diffopts,
+        followmerge=not opts.get('linear', False),
+        followrename=not opts.get('no_follow', False))
+
+    if not any(opts.get(s)
+               for s in ['user', 'date', 'file', 'number', 'changeset']):
+        # default 'number' for compatibility. but fastannotate is more
+        # efficient with "changeset", "line-number" and "no-content".
+        for name in ui.configlist('fastannotate', 'defaultformat', ['number']):
+            opts[name] = True
+
+    ui.pager('fastannotate')
+    template = opts.get('template')
+    if template == 'json':
+        formatter = faformatter.jsonformatter(ui, repo, opts)
+    else:
+        formatter = faformatter.defaultformatter(ui, repo, opts)
+    showdeleted = opts.get('deleted', False)
+    showlines = not bool(opts.get('no_content'))
+    showpath = opts.get('file', False)
+
+    # find the head of the main (master) branch
+    master = ui.config('fastannotate', 'mainbranch') or rev
+
+    # paths will be used for prefetching and the real annotating
+    paths = list(_matchpaths(repo, rev, pats, opts, aopts))
+
+    # for client, prefetch from the server
+    if util.safehasattr(repo, 'prefetchfastannotate'):
+        repo.prefetchfastannotate(paths)
+
+    for path in paths:
+        result = lines = existinglines = None
+        while True:
+            try:
+                with facontext.annotatecontext(repo, path, aopts, rebuild) as a:
+                    result = a.annotate(rev, master=master, showpath=showpath,
+                                        showlines=(showlines and
+                                                   not showdeleted))
+                    if showdeleted:
+                        existinglines = set((l[0], l[1]) for l in result)
+                        result = a.annotatealllines(
+                            rev, showpath=showpath, showlines=showlines)
+                break
+            except (faerror.CannotReuseError, faerror.CorruptedFileError):
+                # happens if master moves backwards, or the file was deleted
+                # and readded, or renamed to an existing name, or corrupted.
+                if rebuild: # give up since we have tried rebuild already
+                    raise
+                else: # try a second time rebuilding the cache (slow)
+                    rebuild = True
+                    continue
+
+        if showlines:
+            result, lines = result
+
+        formatter.write(result, lines, existinglines=existinglines)
+    formatter.end()
+
+_newopts = set([])
+_knownopts = set([opt[1].replace('-', '_') for opt in
+                  (fastannotatecommandargs[r'options'] + commands.globalopts)])
+
+def _annotatewrapper(orig, ui, repo, *pats, **opts):
+    """used by wrapdefault"""
+    # we need this hack until the obsstore has 0.0 seconds perf impact
+    if ui.configbool('fastannotate', 'unfilteredrepo'):
+        repo = repo.unfiltered()
+
+    # treat the file as text (skip the isbinary check)
+    if ui.configbool('fastannotate', 'forcetext'):
+        opts[r'text'] = True
+
+    # check if we need to do prefetch (client-side)
+    rev = opts.get(r'rev')
+    if util.safehasattr(repo, 'prefetchfastannotate') and rev is not None:
+        paths = list(_matchpaths(repo, rev, pats, pycompat.byteskwargs(opts)))
+        repo.prefetchfastannotate(paths)
+
+    return orig(ui, repo, *pats, **opts)
+
+def registercommand():
+    """register the fastannotate command"""
+    name = '^fastannotate|fastblame|fa'
+    command(name, **fastannotatecommandargs)(fastannotate)
+
+def wrapdefault():
+    """wrap the default annotate command, to be aware of the protocol"""
+    extensions.wrapcommand(commands.table, 'annotate', _annotatewrapper)
+
+@command('debugbuildannotatecache',
+         [('r', 'rev', '', _('build up to the specific revision'), _('REV'))
+         ] + commands.walkopts,
+         _('[-r REV] FILE...'))
+def debugbuildannotatecache(ui, repo, *pats, **opts):
+    """incrementally build fastannotate cache up to REV for specified files
+
+    If REV is not specified, use the config 'fastannotate.mainbranch'.
+
+    If fastannotate.client is True, download the annotate cache from the
+    server. Otherwise, build the annotate cache locally.
+
+    The annotate cache will be built using the default diff and follow
+    options and lives in '.hg/fastannotate/default'.
+    """
+    opts = pycompat.byteskwargs(opts)
+    rev = opts.get('REV') or ui.config('fastannotate', 'mainbranch')
+    if not rev:
+        raise error.Abort(_('you need to provide a revision'),
+                          hint=_('set fastannotate.mainbranch or use --rev'))
+    if ui.configbool('fastannotate', 'unfilteredrepo'):
+        repo = repo.unfiltered()
+    ctx = scmutil.revsingle(repo, rev)
+    m = scmutil.match(ctx, pats, opts)
+    paths = list(ctx.walk(m))
+    if util.safehasattr(repo, 'prefetchfastannotate'):
+        # client
+        if opts.get('REV'):
+            raise error.Abort(_('--rev cannot be used for client'))
+        repo.prefetchfastannotate(paths)
+    else:
+        # server, or full repo
+        for i, path in enumerate(paths):
+            ui.progress(_('building'), i, total=len(paths))
+            with facontext.annotatecontext(repo, path) as actx:
+                try:
+                    if actx.isuptodate(rev):
+                        continue
+                    actx.annotate(rev, rev)
+                except (faerror.CannotReuseError, faerror.CorruptedFileError):
+                    # the cache is broken (could happen with renaming so the
+                    # file history gets invalidated). rebuild and try again.
+                    ui.debug('fastannotate: %s: rebuilding broken cache\n'
+                             % path)
+                    actx.rebuild()
+                    try:
+                        actx.annotate(rev, rev)
+                    except Exception as ex:
+                        # possibly a bug, but should not stop us from building
+                        # cache for other files.
+                        ui.warn(_('fastannotate: %s: failed to '
+                                  'build cache: %r\n') % (path, ex))
+        # clear the progress bar
+        ui.write()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/fastannotate/context.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,828 @@
+# Copyright 2016-present Facebook. All Rights Reserved.
+#
+# context: context needed to annotate a file
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import collections
+import contextlib
+import hashlib
+import os
+
+from mercurial.i18n import _
+from mercurial import (
+    error,
+    linelog as linelogmod,
+    lock as lockmod,
+    mdiff,
+    node,
+    pycompat,
+    scmutil,
+    util,
+)
+from mercurial.utils import (
+    stringutil,
+)
+
+from . import (
+    error as faerror,
+    revmap as revmapmod,
+)
+
+# given path, get filelog, cached
+@util.lrucachefunc
+def _getflog(repo, path):
+    return repo.file(path)
+
+# extracted from mercurial.context.basefilectx.annotate
+def _parents(f, follow=True):
+    # Cut _descendantrev here to mitigate the penalty of lazy linkrev
+    # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
+    # from the topmost introrev (= srcrev) down to p.linkrev() if it
+    # isn't an ancestor of the srcrev.
+    f._changeid
+    pl = f.parents()
+
+    # Don't return renamed parents if we aren't following.
+    if not follow:
+        pl = [p for p in pl if p.path() == f.path()]
+
+    # renamed filectx won't have a filelog yet, so set it
+    # from the cache to save time
+    for p in pl:
+        if not '_filelog' in p.__dict__:
+            p._filelog = _getflog(f._repo, p.path())
+
+    return pl
+
+# extracted from mercurial.context.basefilectx.annotate. slightly modified
+# so it takes a fctx instead of a pair of text and fctx.
+def _decorate(fctx):
+    text = fctx.data()
+    linecount = text.count('\n')
+    if text and not text.endswith('\n'):
+        linecount += 1
+    return ([(fctx, i) for i in pycompat.xrange(linecount)], text)
+
+# extracted from mercurial.context.basefilectx.annotate. slightly modified
+# so it takes an extra "blocks" parameter calculated elsewhere, instead of
+# calculating diff here.
+def _pair(parent, child, blocks):
+    for (a1, a2, b1, b2), t in blocks:
+        # Changed blocks ('!') or blocks made only of blank lines ('~')
+        # belong to the child.
+        if t == '=':
+            child[0][b1:b2] = parent[0][a1:a2]
+    return child
+
+# like scmutil.revsingle, but with lru cache, so their states (like manifests)
+# could be reused
+_revsingle = util.lrucachefunc(scmutil.revsingle)
+
+def resolvefctx(repo, rev, path, resolverev=False, adjustctx=None):
+    """(repo, str, str) -> fctx
+
+    get the filectx object from repo, rev, path, in an efficient way.
+
+    if resolverev is True, "rev" is a revision specified by the revset
+    language, otherwise "rev" is a nodeid, or a revision number that can
+    be consumed by repo.__getitem__.
+
+    if adjustctx is not None, the returned fctx will point to a changeset
+    that introduces the change (last modified the file). if adjustctx
+    is 'linkrev', trust the linkrev and do not adjust it. this is noticeably
+    faster for big repos but is incorrect for some cases.
+    """
+    if resolverev and not isinstance(rev, int) and rev is not None:
+        ctx = _revsingle(repo, rev)
+    else:
+        ctx = repo[rev]
+
+    # If we don't need to adjust the linkrev, create the filectx using the
+    # changectx instead of using ctx[path]. This means it already has the
+    # changectx information, so blame -u will be able to look directly at the
+    # commitctx object instead of having to resolve it by going through the
+    # manifest. In a lazy-manifest world this can prevent us from downloading a
+    # lot of data.
+    if adjustctx is None:
+        # ctx.rev() is None means it's the working copy, which is a special
+        # case.
+        if ctx.rev() is None:
+            fctx = ctx[path]
+        else:
+            fctx = repo.filectx(path, changeid=ctx.rev())
+    else:
+        fctx = ctx[path]
+        if adjustctx == 'linkrev':
+            introrev = fctx.linkrev()
+        else:
+            introrev = fctx.introrev()
+        if introrev != ctx.rev():
+            fctx._changeid = introrev
+            fctx._changectx = repo[introrev]
+    return fctx
+
+# like mercurial.store.encodedir, but use linelog suffixes: .m, .l, .lock
+def encodedir(path):
+    return (path
+            .replace('.hg/', '.hg.hg/')
+            .replace('.l/', '.l.hg/')
+            .replace('.m/', '.m.hg/')
+            .replace('.lock/', '.lock.hg/'))
+
+def hashdiffopts(diffopts):
+    diffoptstr = stringutil.pprint(sorted(
+        (k, getattr(diffopts, k))
+        for k in mdiff.diffopts.defaults
+    ))
+    return hashlib.sha1(diffoptstr).hexdigest()[:6]
+
+_defaultdiffopthash = hashdiffopts(mdiff.defaultopts)
+
+class annotateopts(object):
+    """like mercurial.mdiff.diffopts, but is for annotate
+
+    followrename: follow renames, like "hg annotate -f"
+    followmerge: follow p2 of a merge changeset, otherwise p2 is ignored
+    """
+
+    defaults = {
+        'diffopts': None,
+        'followrename': True,
+        'followmerge': True,
+    }
+
+    def __init__(self, **opts):
+        for k, v in self.defaults.iteritems():
+            setattr(self, k, opts.get(k, v))
+
+    @util.propertycache
+    def shortstr(self):
+        """represent opts in a short string, suitable for a directory name"""
+        result = ''
+        if not self.followrename:
+            result += 'r0'
+        if not self.followmerge:
+            result += 'm0'
+        if self.diffopts is not None:
+            assert isinstance(self.diffopts, mdiff.diffopts)
+            diffopthash = hashdiffopts(self.diffopts)
+            if diffopthash != _defaultdiffopthash:
+                result += 'i' + diffopthash
+        return result or 'default'
+
+defaultopts = annotateopts()
+
+class _annotatecontext(object):
+    """do not use this class directly as it does not use lock to protect
+    writes. use "with annotatecontext(...)" instead.
+    """
+
+    def __init__(self, repo, path, linelogpath, revmappath, opts):
+        self.repo = repo
+        self.ui = repo.ui
+        self.path = path
+        self.opts = opts
+        self.linelogpath = linelogpath
+        self.revmappath = revmappath
+        self._linelog = None
+        self._revmap = None
+        self._node2path = {} # {str: str}
+
+    @property
+    def linelog(self):
+        if self._linelog is None:
+            if os.path.exists(self.linelogpath):
+                with open(self.linelogpath, 'rb') as f:
+                    try:
+                        self._linelog = linelogmod.linelog.fromdata(f.read())
+                    except linelogmod.LineLogError:
+                        self._linelog = linelogmod.linelog()
+            else:
+                self._linelog = linelogmod.linelog()
+        return self._linelog
+
+    @property
+    def revmap(self):
+        if self._revmap is None:
+            self._revmap = revmapmod.revmap(self.revmappath)
+        return self._revmap
+
+    def close(self):
+        if self._revmap is not None:
+            self._revmap.flush()
+            self._revmap = None
+        if self._linelog is not None:
+            with open(self.linelogpath, 'wb') as f:
+                f.write(self._linelog.encode())
+            self._linelog = None
+
+    __del__ = close
+
+    def rebuild(self):
+        """delete linelog and revmap, useful for rebuilding"""
+        self.close()
+        self._node2path.clear()
+        _unlinkpaths([self.revmappath, self.linelogpath])
+
+    @property
+    def lastnode(self):
+        """return last node in revmap, or None if revmap is empty"""
+        if self._revmap is None:
+            # fast path, read revmap without loading its full content
+            return revmapmod.getlastnode(self.revmappath)
+        else:
+            return self._revmap.rev2hsh(self._revmap.maxrev)
+
+    def isuptodate(self, master, strict=True):
+        """return True if the revmap / linelog is up-to-date, or the file
+        does not exist in the master revision. False otherwise.
+
+        it tries to be fast and could return false negatives, because of the
+        use of linkrev instead of introrev.
+
+        useful for both server and client to decide whether to update
+        fastannotate cache or not.
+
+        if strict is True, even if fctx exists in the revmap, but is not the
+        last node, isuptodate will return False. it's good for performance - no
+        expensive check was done.
+
+        if strict is False, if fctx exists in the revmap, this function may
+        return True. this is useful for the client to skip downloading the
+        cache if the client's master is behind the server's.
+        """
+        lastnode = self.lastnode
+        try:
+            f = self._resolvefctx(master, resolverev=True)
+            # choose linkrev instead of introrev as the check is meant to be
+            # *fast*.
+            linknode = self.repo.changelog.node(f.linkrev())
+            if not strict and lastnode and linknode != lastnode:
+                # check if f.node() is in the revmap. note: this loads the
+                # revmap and can be slow.
+                return self.revmap.hsh2rev(linknode) is not None
+            # avoid resolving old manifest, or slow adjustlinkrev to be fast,
+            # false negatives are acceptable in this case.
+            return linknode == lastnode
+        except LookupError:
+            # master does not have the file, or the revmap is ahead
+            return True
+
+    def annotate(self, rev, master=None, showpath=False, showlines=False):
+        """incrementally update the cache so it includes revisions in the main
+        branch till 'master'. and run annotate on 'rev', which may or may not be
+        included in the main branch.
+
+        if master is None, do not update linelog.
+
+        the first value returned is the annotate result, it is [(node, linenum)]
+        by default. [(node, linenum, path)] if showpath is True.
+
+        if showlines is True, a second value will be returned, it is a list of
+        corresponding line contents.
+        """
+
+        # the fast path test requires commit hash, convert rev number to hash,
+        # so it may hit the fast path. note: in the "fctx" mode, the "annotate"
+        # command could give us a revision number even if the user passes a
+        # commit hash.
+        if isinstance(rev, int):
+            rev = node.hex(self.repo.changelog.node(rev))
+
+        # fast path: if rev is in the main branch already
+        directly, revfctx = self.canannotatedirectly(rev)
+        if directly:
+            if self.ui.debugflag:
+                self.ui.debug('fastannotate: %s: using fast path '
+                              '(resolved fctx: %s)\n'
+                              % (self.path,
+                                 stringutil.pprint(util.safehasattr(revfctx,
+                                                                    'node'))))
+            return self.annotatedirectly(revfctx, showpath, showlines)
+
+        # resolve master
+        masterfctx = None
+        if master:
+            try:
+                masterfctx = self._resolvefctx(master, resolverev=True,
+                                               adjustctx=True)
+            except LookupError: # master does not have the file
+                pass
+            else:
+                if masterfctx in self.revmap: # no need to update linelog
+                    masterfctx = None
+
+        #                  ... - @ <- rev (can be an arbitrary changeset,
+        #                 /                not necessarily a descendant
+        #      master -> o                 of master)
+        #                |
+        #     a merge -> o         'o': new changesets in the main branch
+        #                |\        '#': revisions in the main branch that
+        #                o *            exist in linelog / revmap
+        #                | .       '*': changesets in side branches, or
+        # last master -> # .            descendants of master
+        #                | .
+        #                # *       joint: '#', and is a parent of a '*'
+        #                |/
+        #     a joint -> # ^^^^ --- side branches
+        #                |
+        #                ^ --- main branch (in linelog)
+
+        # these DFSes are similar to the traditional annotate algorithm.
+        # we cannot really reuse the code for perf reason.
+
+        # 1st DFS calculates merges, joint points, and needed.
+        # "needed" is a simple reference counting dict to free items in
+        # "hist", reducing its memory usage otherwise could be huge.
+        initvisit = [revfctx]
+        if masterfctx:
+            if masterfctx.rev() is None:
+                raise error.Abort(_('cannot update linelog to wdir()'),
+                                  hint=_('set fastannotate.mainbranch'))
+            initvisit.append(masterfctx)
+        visit = initvisit[:]
+        pcache = {}
+        needed = {revfctx: 1}
+        hist = {} # {fctx: ([(llrev or fctx, linenum)], text)}
+        while visit:
+            f = visit.pop()
+            if f in pcache or f in hist:
+                continue
+            if f in self.revmap: # in the old main branch, it's a joint
+                llrev = self.revmap.hsh2rev(f.node())
+                self.linelog.annotate(llrev)
+                result = self.linelog.annotateresult
+                hist[f] = (result, f.data())
+                continue
+            pl = self._parentfunc(f)
+            pcache[f] = pl
+            for p in pl:
+                needed[p] = needed.get(p, 0) + 1
+                if p not in pcache:
+                    visit.append(p)
+
+        # 2nd (simple) DFS calculates new changesets in the main branch
+        # ('o' nodes in # the above graph), so we know when to update linelog.
+        newmainbranch = set()
+        f = masterfctx
+        while f and f not in self.revmap:
+            newmainbranch.add(f)
+            pl = pcache[f]
+            if pl:
+                f = pl[0]
+            else:
+                f = None
+                break
+
+        # f, if present, is the position where the last build stopped at, and
+        # should be the "master" last time. check to see if we can continue
+        # building the linelog incrementally. (we cannot if diverged)
+        if masterfctx is not None:
+            self._checklastmasterhead(f)
+
+        if self.ui.debugflag:
+            if newmainbranch:
+                self.ui.debug('fastannotate: %s: %d new changesets in the main'
+                              ' branch\n' % (self.path, len(newmainbranch)))
+            elif not hist: # no joints, no updates
+                self.ui.debug('fastannotate: %s: linelog cannot help in '
+                              'annotating this revision\n' % self.path)
+
+        # prepare annotateresult so we can update linelog incrementally
+        self.linelog.annotate(self.linelog.maxrev)
+
+        # 3rd DFS does the actual annotate
+        visit = initvisit[:]
+        progress = 0
+        while visit:
+            f = visit[-1]
+            if f in hist:
+                visit.pop()
+                continue
+
+            ready = True
+            pl = pcache[f]
+            for p in pl:
+                if p not in hist:
+                    ready = False
+                    visit.append(p)
+            if not ready:
+                continue
+
+            visit.pop()
+            blocks = None # mdiff blocks, used for appending linelog
+            ismainbranch = (f in newmainbranch)
+            # curr is the same as the traditional annotate algorithm,
+            # if we only care about linear history (do not follow merge),
+            # then curr is not actually used.
+            assert f not in hist
+            curr = _decorate(f)
+            for i, p in enumerate(pl):
+                bs = list(self._diffblocks(hist[p][1], curr[1]))
+                if i == 0 and ismainbranch:
+                    blocks = bs
+                curr = _pair(hist[p], curr, bs)
+                if needed[p] == 1:
+                    del hist[p]
+                    del needed[p]
+                else:
+                    needed[p] -= 1
+
+            hist[f] = curr
+            del pcache[f]
+
+            if ismainbranch: # need to write to linelog
+                if not self.ui.quiet:
+                    progress += 1
+                    self.ui.progress(_('building cache'), progress,
+                                     total=len(newmainbranch))
+                bannotated = None
+                if len(pl) == 2 and self.opts.followmerge: # merge
+                    bannotated = curr[0]
+                if blocks is None: # no parents, add an empty one
+                    blocks = list(self._diffblocks('', curr[1]))
+                self._appendrev(f, blocks, bannotated)
+            elif showpath: # not append linelog, but we need to record path
+                self._node2path[f.node()] = f.path()
+
+        if progress: # clean progress bar
+            self.ui.write()
+
+        result = [
+            ((self.revmap.rev2hsh(fr) if isinstance(fr, int) else fr.node()), l)
+            for fr, l in hist[revfctx][0]] # [(node, linenumber)]
+        return self._refineannotateresult(result, revfctx, showpath, showlines)
+
+    def canannotatedirectly(self, rev):
+        """(str) -> bool, fctx or node.
+        return (True, f) if we can annotate without updating the linelog, pass
+        f to annotatedirectly.
+        return (False, f) if we need extra calculation. f is the fctx resolved
+        from rev.
+        """
+        result = True
+        f = None
+        if not isinstance(rev, int) and rev is not None:
+            hsh = {20: bytes, 40: node.bin}.get(len(rev), lambda x: None)(rev)
+            if hsh is not None and (hsh, self.path) in self.revmap:
+                f = hsh
+        if f is None:
+            adjustctx = 'linkrev' if self._perfhack else True
+            f = self._resolvefctx(rev, adjustctx=adjustctx, resolverev=True)
+            result = f in self.revmap
+            if not result and self._perfhack:
+                # redo the resolution without perfhack - as we are going to
+                # do write operations, we need a correct fctx.
+                f = self._resolvefctx(rev, adjustctx=True, resolverev=True)
+        return result, f
+
+    def annotatealllines(self, rev, showpath=False, showlines=False):
+        """(rev : str) -> [(node : str, linenum : int, path : str)]
+
+        the result has the same format with annotate, but include all (including
+        deleted) lines up to rev. call this after calling annotate(rev, ...) for
+        better performance and accuracy.
+        """
+        revfctx = self._resolvefctx(rev, resolverev=True, adjustctx=True)
+
+        # find a chain from rev to anything in the mainbranch
+        if revfctx not in self.revmap:
+            chain = [revfctx]
+            a = ''
+            while True:
+                f = chain[-1]
+                pl = self._parentfunc(f)
+                if not pl:
+                    break
+                if pl[0] in self.revmap:
+                    a = pl[0].data()
+                    break
+                chain.append(pl[0])
+
+            # both self.linelog and self.revmap is backed by filesystem. now
+            # we want to modify them but do not want to write changes back to
+            # files. so we create in-memory objects and copy them. it's like
+            # a "fork".
+            linelog = linelogmod.linelog()
+            linelog.copyfrom(self.linelog)
+            linelog.annotate(linelog.maxrev)
+            revmap = revmapmod.revmap()
+            revmap.copyfrom(self.revmap)
+
+            for f in reversed(chain):
+                b = f.data()
+                blocks = list(self._diffblocks(a, b))
+                self._doappendrev(linelog, revmap, f, blocks)
+                a = b
+        else:
+            # fastpath: use existing linelog, revmap as we don't write to them
+            linelog = self.linelog
+            revmap = self.revmap
+
+        lines = linelog.getalllines()
+        hsh = revfctx.node()
+        llrev = revmap.hsh2rev(hsh)
+        result = [(revmap.rev2hsh(r), l) for r, l in lines if r <= llrev]
+        # cannot use _refineannotateresult since we need custom logic for
+        # resolving line contents
+        if showpath:
+            result = self._addpathtoresult(result, revmap)
+        if showlines:
+            linecontents = self._resolvelines(result, revmap, linelog)
+            result = (result, linecontents)
+        return result
+
+    def _resolvelines(self, annotateresult, revmap, linelog):
+        """(annotateresult) -> [line]. designed for annotatealllines.
+        this is probably the most inefficient code in the whole fastannotate
+        directory. but we have made a decision that the linelog does not
+        store line contents. so getting them requires random accesses to
+        the revlog data, since they can be many, it can be very slow.
+        """
+        # [llrev]
+        revs = [revmap.hsh2rev(l[0]) for l in annotateresult]
+        result = [None] * len(annotateresult)
+        # {(rev, linenum): [lineindex]}
+        key2idxs = collections.defaultdict(list)
+        for i in pycompat.xrange(len(result)):
+            key2idxs[(revs[i], annotateresult[i][1])].append(i)
+        while key2idxs:
+            # find an unresolved line and its linelog rev to annotate
+            hsh = None
+            try:
+                for (rev, _linenum), idxs in key2idxs.iteritems():
+                    if revmap.rev2flag(rev) & revmapmod.sidebranchflag:
+                        continue
+                    hsh = annotateresult[idxs[0]][0]
+                    break
+            except StopIteration: # no more unresolved lines
+                return result
+            if hsh is None:
+                # the remaining key2idxs are not in main branch, resolving them
+                # using the hard way...
+                revlines = {}
+                for (rev, linenum), idxs in key2idxs.iteritems():
+                    if rev not in revlines:
+                        hsh = annotateresult[idxs[0]][0]
+                        if self.ui.debugflag:
+                            self.ui.debug('fastannotate: reading %s line #%d '
+                                          'to resolve lines %r\n'
+                                          % (node.short(hsh), linenum, idxs))
+                        fctx = self._resolvefctx(hsh, revmap.rev2path(rev))
+                        lines = mdiff.splitnewlines(fctx.data())
+                        revlines[rev] = lines
+                    for idx in idxs:
+                        result[idx] = revlines[rev][linenum]
+                assert all(x is not None for x in result)
+                return result
+
+            # run the annotate and the lines should match to the file content
+            self.ui.debug('fastannotate: annotate %s to resolve lines\n'
+                          % node.short(hsh))
+            linelog.annotate(rev)
+            fctx = self._resolvefctx(hsh, revmap.rev2path(rev))
+            annotated = linelog.annotateresult
+            lines = mdiff.splitnewlines(fctx.data())
+            if len(lines) != len(annotated):
+                raise faerror.CorruptedFileError('unexpected annotated lines')
+            # resolve lines from the annotate result
+            for i, line in enumerate(lines):
+                k = annotated[i]
+                if k in key2idxs:
+                    for idx in key2idxs[k]:
+                        result[idx] = line
+                    del key2idxs[k]
+        return result
+
+    def annotatedirectly(self, f, showpath, showlines):
+        """like annotate, but when we know that f is in linelog.
+        f can be either a 20-char str (node) or a fctx. this is for perf - in
+        the best case, the user provides a node and we don't need to read the
+        filelog or construct any filecontext.
+        """
+        if isinstance(f, str):
+            hsh = f
+        else:
+            hsh = f.node()
+        llrev = self.revmap.hsh2rev(hsh)
+        if not llrev:
+            raise faerror.CorruptedFileError('%s is not in revmap'
+                                             % node.hex(hsh))
+        if (self.revmap.rev2flag(llrev) & revmapmod.sidebranchflag) != 0:
+            raise faerror.CorruptedFileError('%s is not in revmap mainbranch'
+                                             % node.hex(hsh))
+        self.linelog.annotate(llrev)
+        result = [(self.revmap.rev2hsh(r), l)
+                  for r, l in self.linelog.annotateresult]
+        return self._refineannotateresult(result, f, showpath, showlines)
+
+    def _refineannotateresult(self, result, f, showpath, showlines):
+        """add the missing path or line contents, they can be expensive.
+        f could be either node or fctx.
+        """
+        if showpath:
+            result = self._addpathtoresult(result)
+        if showlines:
+            if isinstance(f, str): # f: node or fctx
+                llrev = self.revmap.hsh2rev(f)
+                fctx = self._resolvefctx(f, self.revmap.rev2path(llrev))
+            else:
+                fctx = f
+            lines = mdiff.splitnewlines(fctx.data())
+            if len(lines) != len(result): # linelog is probably corrupted
+                raise faerror.CorruptedFileError()
+            result = (result, lines)
+        return result
+
+    def _appendrev(self, fctx, blocks, bannotated=None):
+        self._doappendrev(self.linelog, self.revmap, fctx, blocks, bannotated)
+
+    def _diffblocks(self, a, b):
+        return mdiff.allblocks(a, b, self.opts.diffopts)
+
+    @staticmethod
+    def _doappendrev(linelog, revmap, fctx, blocks, bannotated=None):
+        """append a revision to linelog and revmap"""
+
+        def getllrev(f):
+            """(fctx) -> int"""
+            # f should not be a linelog revision
+            if isinstance(f, int):
+                raise error.ProgrammingError('f should not be an int')
+            # f is a fctx, allocate linelog rev on demand
+            hsh = f.node()
+            rev = revmap.hsh2rev(hsh)
+            if rev is None:
+                rev = revmap.append(hsh, sidebranch=True, path=f.path())
+            return rev
+
+        # append sidebranch revisions to revmap
+        siderevs = []
+        siderevmap = {} # node: int
+        if bannotated is not None:
+            for (a1, a2, b1, b2), op in blocks:
+                if op != '=':
+                    # f could be either linelong rev, or fctx.
+                    siderevs += [f for f, l in bannotated[b1:b2]
+                                 if not isinstance(f, int)]
+        siderevs = set(siderevs)
+        if fctx in siderevs: # mainnode must be appended seperately
+            siderevs.remove(fctx)
+        for f in siderevs:
+            siderevmap[f] = getllrev(f)
+
+        # the changeset in the main branch, could be a merge
+        llrev = revmap.append(fctx.node(), path=fctx.path())
+        siderevmap[fctx] = llrev
+
+        for (a1, a2, b1, b2), op in reversed(blocks):
+            if op == '=':
+                continue
+            if bannotated is None:
+                linelog.replacelines(llrev, a1, a2, b1, b2)
+            else:
+                blines = [((r if isinstance(r, int) else siderevmap[r]), l)
+                          for r, l in bannotated[b1:b2]]
+                linelog.replacelines_vec(llrev, a1, a2, blines)
+
+    def _addpathtoresult(self, annotateresult, revmap=None):
+        """(revmap, [(node, linenum)]) -> [(node, linenum, path)]"""
+        if revmap is None:
+            revmap = self.revmap
+
+        def _getpath(nodeid):
+            path = self._node2path.get(nodeid)
+            if path is None:
+                path = revmap.rev2path(revmap.hsh2rev(nodeid))
+                self._node2path[nodeid] = path
+            return path
+
+        return [(n, l, _getpath(n)) for n, l in annotateresult]
+
+    def _checklastmasterhead(self, fctx):
+        """check if fctx is the master's head last time, raise if not"""
+        if fctx is None:
+            llrev = 0
+        else:
+            llrev = self.revmap.hsh2rev(fctx.node())
+            if not llrev:
+                raise faerror.CannotReuseError()
+        if self.linelog.maxrev != llrev:
+            raise faerror.CannotReuseError()
+
+    @util.propertycache
+    def _parentfunc(self):
+        """-> (fctx) -> [fctx]"""
+        followrename = self.opts.followrename
+        followmerge = self.opts.followmerge
+        def parents(f):
+            pl = _parents(f, follow=followrename)
+            if not followmerge:
+                pl = pl[:1]
+            return pl
+        return parents
+
+    @util.propertycache
+    def _perfhack(self):
+        return self.ui.configbool('fastannotate', 'perfhack')
+
+    def _resolvefctx(self, rev, path=None, **kwds):
+        return resolvefctx(self.repo, rev, (path or self.path), **kwds)
+
+def _unlinkpaths(paths):
+    """silent, best-effort unlink"""
+    for path in paths:
+        try:
+            util.unlink(path)
+        except OSError:
+            pass
+
+class pathhelper(object):
+    """helper for getting paths for lockfile, linelog and revmap"""
+
+    def __init__(self, repo, path, opts=defaultopts):
+        # different options use different directories
+        self._vfspath = os.path.join('fastannotate',
+                                     opts.shortstr, encodedir(path))
+        self._repo = repo
+
+    @property
+    def dirname(self):
+        return os.path.dirname(self._repo.vfs.join(self._vfspath))
+
+    @property
+    def linelogpath(self):
+        return self._repo.vfs.join(self._vfspath + '.l')
+
+    def lock(self):
+        return lockmod.lock(self._repo.vfs, self._vfspath + '.lock')
+
+    @contextlib.contextmanager
+    def _lockflock(self):
+        """the same as 'lock' but use flock instead of lockmod.lock, to avoid
+        creating temporary symlinks."""
+        import fcntl
+        lockpath = self.linelogpath
+        util.makedirs(os.path.dirname(lockpath))
+        lockfd = os.open(lockpath, os.O_RDONLY | os.O_CREAT, 0o664)
+        fcntl.flock(lockfd, fcntl.LOCK_EX)
+        try:
+            yield
+        finally:
+            fcntl.flock(lockfd, fcntl.LOCK_UN)
+            os.close(lockfd)
+
+    @property
+    def revmappath(self):
+        return self._repo.vfs.join(self._vfspath + '.m')
+
+@contextlib.contextmanager
+def annotatecontext(repo, path, opts=defaultopts, rebuild=False):
+    """context needed to perform (fast) annotate on a file
+
+    an annotatecontext of a single file consists of two structures: the
+    linelog and the revmap. this function takes care of locking. only 1
+    process is allowed to write that file's linelog and revmap at a time.
+
+    when something goes wrong, this function will assume the linelog and the
+    revmap are in a bad state, and remove them from disk.
+
+    use this function in the following way:
+
+        with annotatecontext(...) as actx:
+            actx. ....
+    """
+    helper = pathhelper(repo, path, opts)
+    util.makedirs(helper.dirname)
+    revmappath = helper.revmappath
+    linelogpath = helper.linelogpath
+    actx = None
+    try:
+        with helper.lock():
+            actx = _annotatecontext(repo, path, linelogpath, revmappath, opts)
+            if rebuild:
+                actx.rebuild()
+            yield actx
+    except Exception:
+        if actx is not None:
+            actx.rebuild()
+        repo.ui.debug('fastannotate: %s: cache broken and deleted\n' % path)
+        raise
+    finally:
+        if actx is not None:
+            actx.close()
+
+def fctxannotatecontext(fctx, follow=True, diffopts=None, rebuild=False):
+    """like annotatecontext but get the context from a fctx. convenient when
+    used in fctx.annotate
+    """
+    repo = fctx._repo
+    path = fctx._path
+    if repo.ui.configbool('fastannotate', 'forcefollow', True):
+        follow = True
+    aopts = annotateopts(diffopts=diffopts, followrename=follow)
+    return annotatecontext(repo, path, aopts, rebuild)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/fastannotate/error.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,13 @@
+# Copyright 2016-present Facebook. All Rights Reserved.
+#
+# error: errors used in fastannotate
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
+
+class CorruptedFileError(Exception):
+    pass
+
+class CannotReuseError(Exception):
+    """cannot reuse or update the cache incrementally"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/fastannotate/formatter.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,161 @@
+# Copyright 2016-present Facebook. All Rights Reserved.
+#
+# format: defines the format used to output annotate result
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
+
+from mercurial import (
+    encoding,
+    node,
+    pycompat,
+    templatefilters,
+    util,
+)
+from mercurial.utils import (
+        dateutil,
+)
+
+# imitating mercurial.commands.annotate, not using the vanilla formatter since
+# the data structures are a bit different, and we have some fast paths.
+class defaultformatter(object):
+    """the default formatter that does leftpad and support some common flags"""
+
+    def __init__(self, ui, repo, opts):
+        self.ui = ui
+        self.opts = opts
+
+        if ui.quiet:
+            datefunc = dateutil.shortdate
+        else:
+            datefunc = dateutil.datestr
+        datefunc = util.cachefunc(datefunc)
+        getctx = util.cachefunc(lambda x: repo[x[0]])
+        hexfunc = self._hexfunc
+
+        # special handling working copy "changeset" and "rev" functions
+        if self.opts.get('rev') == 'wdir()':
+            orig = hexfunc
+            hexfunc = lambda x: None if x is None else orig(x)
+            wnode = hexfunc(repo[None].p1().node()) + '+'
+            wrev = str(repo[None].p1().rev())
+            wrevpad = ''
+            if not opts.get('changeset'): # only show + if changeset is hidden
+                wrev += '+'
+                wrevpad = ' '
+            revenc = lambda x: wrev if x is None else str(x) + wrevpad
+            csetenc = lambda x: wnode if x is None else str(x) + ' '
+        else:
+            revenc = csetenc = str
+
+        # opt name, separator, raw value (for json/plain), encoder (for plain)
+        opmap = [('user', ' ', lambda x: getctx(x).user(), ui.shortuser),
+                 ('number', ' ', lambda x: getctx(x).rev(), revenc),
+                 ('changeset', ' ', lambda x: hexfunc(x[0]), csetenc),
+                 ('date', ' ', lambda x: getctx(x).date(), datefunc),
+                 ('file', ' ', lambda x: x[2], str),
+                 ('line_number', ':', lambda x: x[1] + 1, str)]
+        fieldnamemap = {'number': 'rev', 'changeset': 'node'}
+        funcmap = [(get, sep, fieldnamemap.get(op, op), enc)
+                   for op, sep, get, enc in opmap
+                   if opts.get(op)]
+        # no separator for first column
+        funcmap[0] = list(funcmap[0])
+        funcmap[0][1] = ''
+        self.funcmap = funcmap
+
+    def write(self, annotatedresult, lines=None, existinglines=None):
+        """(annotateresult, [str], set([rev, linenum])) -> None. write output.
+        annotateresult can be [(node, linenum, path)], or [(node, linenum)]
+        """
+        pieces = [] # [[str]]
+        maxwidths = [] # [int]
+
+        # calculate padding
+        for f, sep, name, enc in self.funcmap:
+            l = [enc(f(x)) for x in annotatedresult]
+            pieces.append(l)
+            if name in ['node', 'date']: # node and date has fixed size
+                l = l[:1]
+            widths = pycompat.maplist(encoding.colwidth, set(l))
+            maxwidth = (max(widths) if widths else 0)
+            maxwidths.append(maxwidth)
+
+        # buffered output
+        result = ''
+        for i in pycompat.xrange(len(annotatedresult)):
+            for j, p in enumerate(pieces):
+                sep = self.funcmap[j][1]
+                padding = ' ' * (maxwidths[j] - len(p[i]))
+                result += sep + padding + p[i]
+            if lines:
+                if existinglines is None:
+                    result += ': ' + lines[i]
+                else: # extra formatting showing whether a line exists
+                    key = (annotatedresult[i][0], annotatedresult[i][1])
+                    if key in existinglines:
+                        result += ':  ' + lines[i]
+                    else:
+                        result += ': ' + self.ui.label('-' + lines[i],
+                                                       'diff.deleted')
+
+            if result[-1] != '\n':
+                result += '\n'
+
+        self.ui.write(result)
+
+    @util.propertycache
+    def _hexfunc(self):
+        if self.ui.debugflag or self.opts.get('long_hash'):
+            return node.hex
+        else:
+            return node.short
+
+    def end(self):
+        pass
+
+class jsonformatter(defaultformatter):
+    def __init__(self, ui, repo, opts):
+        super(jsonformatter, self).__init__(ui, repo, opts)
+        self.ui.write('[')
+        self.needcomma = False
+
+    def write(self, annotatedresult, lines=None, existinglines=None):
+        if annotatedresult:
+            self._writecomma()
+
+        pieces = [(name, map(f, annotatedresult))
+                  for f, sep, name, enc in self.funcmap]
+        if lines is not None:
+            pieces.append(('line', lines))
+        pieces.sort()
+
+        seps = [','] * len(pieces[:-1]) + ['']
+
+        result = ''
+        lasti = len(annotatedresult) - 1
+        for i in pycompat.xrange(len(annotatedresult)):
+            result += '\n {\n'
+            for j, p in enumerate(pieces):
+                k, vs = p
+                result += ('  "%s": %s%s\n'
+                           % (k, templatefilters.json(vs[i], paranoid=False),
+                              seps[j]))
+            result += ' }%s' % ('' if i == lasti else ',')
+        if lasti >= 0:
+            self.needcomma = True
+
+        self.ui.write(result)
+
+    def _writecomma(self):
+        if self.needcomma:
+            self.ui.write(',')
+            self.needcomma = False
+
+    @util.propertycache
+    def _hexfunc(self):
+        return node.hex
+
+    def end(self):
+        self.ui.write('\n]\n')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/fastannotate/protocol.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,228 @@
+# Copyright 2016-present Facebook. All Rights Reserved.
+#
+# protocol: logic for a server providing fastannotate support
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
+
+import contextlib
+import os
+
+from mercurial.i18n import _
+from mercurial import (
+    error,
+    extensions,
+    hg,
+    util,
+    wireprotov1peer,
+    wireprotov1server,
+)
+from . import context
+
+# common
+
+def _getmaster(ui):
+    """get the mainbranch, and enforce it is set"""
+    master = ui.config('fastannotate', 'mainbranch')
+    if not master:
+        raise error.Abort(_('fastannotate.mainbranch is required '
+                            'for both the client and the server'))
+    return master
+
+# server-side
+
+def _capabilities(orig, repo, proto):
+    result = orig(repo, proto)
+    result.append('getannotate')
+    return result
+
+def _getannotate(repo, proto, path, lastnode):
+    # output:
+    #   FILE := vfspath + '\0' + str(size) + '\0' + content
+    #   OUTPUT := '' | FILE + OUTPUT
+    result = ''
+    buildondemand = repo.ui.configbool('fastannotate', 'serverbuildondemand',
+                                       True)
+    with context.annotatecontext(repo, path) as actx:
+        if buildondemand:
+            # update before responding to the client
+            master = _getmaster(repo.ui)
+            try:
+                if not actx.isuptodate(master):
+                    actx.annotate(master, master)
+            except Exception:
+                # non-fast-forward move or corrupted. rebuild automically.
+                actx.rebuild()
+                try:
+                    actx.annotate(master, master)
+                except Exception:
+                    actx.rebuild() # delete files
+            finally:
+                # although the "with" context will also do a close/flush, we
+                # need to do it early so we can send the correct respond to
+                # client.
+                actx.close()
+        # send back the full content of revmap and linelog, in the future we
+        # may want to do some rsync-like fancy updating.
+        # the lastnode check is not necessary if the client and the server
+        # agree where the main branch is.
+        if actx.lastnode != lastnode:
+            for p in [actx.revmappath, actx.linelogpath]:
+                if not os.path.exists(p):
+                    continue
+                content = ''
+                with open(p, 'rb') as f:
+                    content = f.read()
+                vfsbaselen = len(repo.vfs.base + '/')
+                relpath = p[vfsbaselen:]
+                result += '%s\0%d\0%s' % (relpath, len(content), content)
+    return result
+
+def _registerwireprotocommand():
+    if 'getannotate' in wireprotov1server.commands:
+        return
+    wireprotov1server.wireprotocommand(
+        'getannotate', 'path lastnode')(_getannotate)
+
+def serveruisetup(ui):
+    _registerwireprotocommand()
+    extensions.wrapfunction(wireprotov1server, '_capabilities', _capabilities)
+
+# client-side
+
+def _parseresponse(payload):
+    result = {}
+    i = 0
+    l = len(payload) - 1
+    state = 0 # 0: vfspath, 1: size
+    vfspath = size = ''
+    while i < l:
+        ch = payload[i]
+        if ch == '\0':
+            if state == 1:
+                result[vfspath] = buffer(payload, i + 1, int(size))
+                i += int(size)
+                state = 0
+                vfspath = size = ''
+            elif state == 0:
+                state = 1
+        else:
+            if state == 1:
+                size += ch
+            elif state == 0:
+                vfspath += ch
+        i += 1
+    return result
+
+def peersetup(ui, peer):
+    class fastannotatepeer(peer.__class__):
+        @wireprotov1peer.batchable
+        def getannotate(self, path, lastnode=None):
+            if not self.capable('getannotate'):
+                ui.warn(_('remote peer cannot provide annotate cache\n'))
+                yield None, None
+            else:
+                args = {'path': path, 'lastnode': lastnode or ''}
+                f = wireprotov1peer.future()
+                yield args, f
+                yield _parseresponse(f.value)
+    peer.__class__ = fastannotatepeer
+
+@contextlib.contextmanager
+def annotatepeer(repo):
+    ui = repo.ui
+
+    remotepath = ui.expandpath(
+        ui.config('fastannotate', 'remotepath', 'default'))
+    peer = hg.peer(ui, {}, remotepath)
+
+    try:
+        yield peer
+    finally:
+        peer.close()
+
+def clientfetch(repo, paths, lastnodemap=None, peer=None):
+    """download annotate cache from the server for paths"""
+    if not paths:
+        return
+
+    if peer is None:
+        with annotatepeer(repo) as peer:
+            return clientfetch(repo, paths, lastnodemap, peer)
+
+    if lastnodemap is None:
+        lastnodemap = {}
+
+    ui = repo.ui
+    results = []
+    with peer.commandexecutor() as batcher:
+        ui.debug('fastannotate: requesting %d files\n' % len(paths))
+        for p in paths:
+            results.append(batcher.callcommand(
+                'getannotate',
+                {'path': p, 'lastnode':lastnodemap.get(p)}))
+
+        for result in results:
+            r = result.result()
+            # TODO: pconvert these paths on the server?
+            r = {util.pconvert(p): v for p, v in r.iteritems()}
+            for path in sorted(r):
+                # ignore malicious paths
+                if (not path.startswith('fastannotate/')
+                    or '/../' in (path + '/')):
+                    ui.debug('fastannotate: ignored malicious path %s\n' % path)
+                    continue
+                content = r[path]
+                if ui.debugflag:
+                    ui.debug('fastannotate: writing %d bytes to %s\n'
+                             % (len(content), path))
+                repo.vfs.makedirs(os.path.dirname(path))
+                with repo.vfs(path, 'wb') as f:
+                    f.write(content)
+
+def _filterfetchpaths(repo, paths):
+    """return a subset of paths whose history is long and need to fetch linelog
+    from the server. works with remotefilelog and non-remotefilelog repos.
+    """
+    threshold = repo.ui.configint('fastannotate', 'clientfetchthreshold', 10)
+    if threshold <= 0:
+        return paths
+
+    result = []
+    for path in paths:
+        try:
+            if len(repo.file(path)) >= threshold:
+                result.append(path)
+        except Exception: # file not found etc.
+            result.append(path)
+
+    return result
+
+def localreposetup(ui, repo):
+    class fastannotaterepo(repo.__class__):
+        def prefetchfastannotate(self, paths, peer=None):
+            master = _getmaster(self.ui)
+            needupdatepaths = []
+            lastnodemap = {}
+            try:
+                for path in _filterfetchpaths(self, paths):
+                    with context.annotatecontext(self, path) as actx:
+                        if not actx.isuptodate(master, strict=False):
+                            needupdatepaths.append(path)
+                            lastnodemap[path] = actx.lastnode
+                if needupdatepaths:
+                    clientfetch(self, needupdatepaths, lastnodemap, peer)
+            except Exception as ex:
+                # could be directory not writable or so, not fatal
+                self.ui.debug('fastannotate: prefetch failed: %r\n' % ex)
+    repo.__class__ = fastannotaterepo
+
+def clientreposetup(ui, repo):
+    _registerwireprotocommand()
+    if repo.local():
+        localreposetup(ui, repo)
+    # TODO: this mutates global state, but only if at least one repo
+    # has the extension enabled. This is probably bad for hgweb.
+    if peersetup not in hg.wirepeersetupfuncs:
+        hg.wirepeersetupfuncs.append(peersetup)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/fastannotate/revmap.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,254 @@
+# Copyright 2016-present Facebook. All Rights Reserved.
+#
+# revmap: trivial hg hash - linelog rev bidirectional map
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import bisect
+import os
+import struct
+
+from mercurial.node import hex
+from mercurial import (
+    error as hgerror,
+    pycompat,
+)
+from . import error
+
+# the revmap file format is straightforward:
+#
+#    8 bytes: header
+#    1 byte : flag for linelog revision 1
+#    ? bytes: (optional) '\0'-terminated path string
+#             only exists if (flag & renameflag) != 0
+#   20 bytes: hg hash for linelog revision 1
+#    1 byte : flag for linelog revision 2
+#    ? bytes: (optional) '\0'-terminated path string
+#   20 bytes: hg hash for linelog revision 2
+#   ....
+#
+# the implementation is kinda stupid: __init__ loads the whole revmap.
+# no laziness. benchmark shows loading 10000 revisions is about 0.015
+# seconds, which looks enough for our use-case. if this implementation
+# becomes a bottleneck, we can change it to lazily read the file
+# from the end.
+
+# whether the changeset is in the side branch. i.e. not in the linear main
+# branch but only got referenced by lines in merge changesets.
+sidebranchflag = 1
+
+# whether the changeset changes the file path (ie. is a rename)
+renameflag = 2
+
+# len(mercurial.node.nullid)
+_hshlen = 20
+
+class revmap(object):
+    """trivial hg bin hash - linelog rev bidirectional map
+
+    also stores a flag (uint8) for each revision, and track renames.
+    """
+
+    HEADER = b'REVMAP1\0'
+
+    def __init__(self, path=None):
+        """create or load the revmap, optionally associate to a file
+
+        if path is None, the revmap is entirely in-memory. the caller is
+        responsible for locking. concurrent writes to a same file is unsafe.
+        the caller needs to make sure one file is associated to at most one
+        revmap object at a time."""
+        self.path = path
+        self._rev2hsh = [None]
+        self._rev2flag = [None]
+        self._hsh2rev = {}
+        # since rename does not happen frequently, do not store path for every
+        # revision. self._renamerevs can be used for bisecting.
+        self._renamerevs = [0]
+        self._renamepaths = ['']
+        self._lastmaxrev = -1
+        if path:
+            if os.path.exists(path):
+                self._load()
+            else:
+                # write the header so "append" can do incremental updates
+                self.flush()
+
+    def copyfrom(self, rhs):
+        """copy the map data from another revmap. do not affect self.path"""
+        self._rev2hsh = rhs._rev2hsh[:]
+        self._rev2flag = rhs._rev2flag[:]
+        self._hsh2rev = rhs._hsh2rev.copy()
+        self._renamerevs = rhs._renamerevs[:]
+        self._renamepaths = rhs._renamepaths[:]
+        self._lastmaxrev = -1
+
+    @property
+    def maxrev(self):
+        """return max linelog revision number"""
+        return len(self._rev2hsh) - 1
+
+    def append(self, hsh, sidebranch=False, path=None, flush=False):
+        """add a binary hg hash and return the mapped linelog revision.
+        if flush is True, incrementally update the file.
+        """
+        if hsh in self._hsh2rev:
+            raise error.CorruptedFileError('%r is in revmap already' % hex(hsh))
+        if len(hsh) != _hshlen:
+            raise hgerror.ProgrammingError('hsh must be %d-char long' % _hshlen)
+        idx = len(self._rev2hsh)
+        flag = 0
+        if sidebranch:
+            flag |= sidebranchflag
+        if path is not None and path != self._renamepaths[-1]:
+            flag |= renameflag
+            self._renamerevs.append(idx)
+            self._renamepaths.append(path)
+        self._rev2hsh.append(hsh)
+        self._rev2flag.append(flag)
+        self._hsh2rev[hsh] = idx
+        if flush:
+            self.flush()
+        return idx
+
+    def rev2hsh(self, rev):
+        """convert linelog revision to hg hash. return None if not found."""
+        if rev > self.maxrev or rev < 0:
+            return None
+        return self._rev2hsh[rev]
+
+    def rev2flag(self, rev):
+        """get the flag (uint8) for a given linelog revision.
+        return None if revision does not exist.
+        """
+        if rev > self.maxrev or rev < 0:
+            return None
+        return self._rev2flag[rev]
+
+    def rev2path(self, rev):
+        """get the path for a given linelog revision.
+        return None if revision does not exist.
+        """
+        if rev > self.maxrev or rev < 0:
+            return None
+        idx = bisect.bisect_right(self._renamerevs, rev) - 1
+        return self._renamepaths[idx]
+
+    def hsh2rev(self, hsh):
+        """convert hg hash to linelog revision. return None if not found."""
+        return self._hsh2rev.get(hsh)
+
+    def clear(self, flush=False):
+        """make the map empty. if flush is True, write to disk"""
+        # rev 0 is reserved, real rev starts from 1
+        self._rev2hsh = [None]
+        self._rev2flag = [None]
+        self._hsh2rev = {}
+        self._rev2path = ['']
+        self._lastmaxrev = -1
+        if flush:
+            self.flush()
+
+    def flush(self):
+        """write the state down to the file"""
+        if not self.path:
+            return
+        if self._lastmaxrev == -1: # write the entire file
+            with open(self.path, 'wb') as f:
+                f.write(self.HEADER)
+                for i in pycompat.xrange(1, len(self._rev2hsh)):
+                    self._writerev(i, f)
+        else: # append incrementally
+            with open(self.path, 'ab') as f:
+                for i in pycompat.xrange(self._lastmaxrev + 1,
+                                         len(self._rev2hsh)):
+                    self._writerev(i, f)
+        self._lastmaxrev = self.maxrev
+
+    def _load(self):
+        """load state from file"""
+        if not self.path:
+            return
+        # use local variables in a loop. CPython uses LOAD_FAST for them,
+        # which is faster than both LOAD_CONST and LOAD_GLOBAL.
+        flaglen = 1
+        hshlen = _hshlen
+        with open(self.path, 'rb') as f:
+            if f.read(len(self.HEADER)) != self.HEADER:
+                raise error.CorruptedFileError()
+            self.clear(flush=False)
+            while True:
+                buf = f.read(flaglen)
+                if not buf:
+                    break
+                flag = ord(buf)
+                rev = len(self._rev2hsh)
+                if flag & renameflag:
+                    path = self._readcstr(f)
+                    self._renamerevs.append(rev)
+                    self._renamepaths.append(path)
+                hsh = f.read(hshlen)
+                if len(hsh) != hshlen:
+                    raise error.CorruptedFileError()
+                self._hsh2rev[hsh] = rev
+                self._rev2flag.append(flag)
+                self._rev2hsh.append(hsh)
+        self._lastmaxrev = self.maxrev
+
+    def _writerev(self, rev, f):
+        """append a revision data to file"""
+        flag = self._rev2flag[rev]
+        hsh = self._rev2hsh[rev]
+        f.write(struct.pack('B', flag))
+        if flag & renameflag:
+            path = self.rev2path(rev)
+            if path is None:
+                raise error.CorruptedFileError('cannot find path for %s' % rev)
+            f.write(path + '\0')
+        f.write(hsh)
+
+    @staticmethod
+    def _readcstr(f):
+        """read a C-language-like '\0'-terminated string"""
+        buf = ''
+        while True:
+            ch = f.read(1)
+            if not ch: # unexpected eof
+                raise error.CorruptedFileError()
+            if ch == '\0':
+                break
+            buf += ch
+        return buf
+
+    def __contains__(self, f):
+        """(fctx or (node, path)) -> bool.
+        test if (node, path) is in the map, and is not in a side branch.
+        f can be either a tuple of (node, path), or a fctx.
+        """
+        if isinstance(f, tuple): # f: (node, path)
+            hsh, path = f
+        else: # f: fctx
+            hsh, path = f.node(), f.path()
+        rev = self.hsh2rev(hsh)
+        if rev is None:
+            return False
+        if path is not None and path != self.rev2path(rev):
+            return False
+        return (self.rev2flag(rev) & sidebranchflag) == 0
+
+def getlastnode(path):
+    """return the last hash in a revmap, without loading its full content.
+    this is equivalent to `m = revmap(path); m.rev2hsh(m.maxrev)`, but faster.
+    """
+    hsh = None
+    try:
+        with open(path, 'rb') as f:
+            f.seek(-_hshlen, 2)
+            if f.tell() > len(revmap.HEADER):
+                hsh = f.read(_hshlen)
+    except IOError:
+        pass
+    return hsh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/fastannotate/support.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,122 @@
+# Copyright 2016-present Facebook. All Rights Reserved.
+#
+# support: fastannotate support for hgweb, and filectx
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+    context as hgcontext,
+    dagop,
+    extensions,
+    hgweb,
+    patch,
+    util,
+)
+
+from . import (
+    context,
+    revmap,
+)
+
+class _lazyfctx(object):
+    """delegates to fctx but do not construct fctx when unnecessary"""
+
+    def __init__(self, repo, node, path):
+        self._node = node
+        self._path = path
+        self._repo = repo
+
+    def node(self):
+        return self._node
+
+    def path(self):
+        return self._path
+
+    @util.propertycache
+    def _fctx(self):
+        return context.resolvefctx(self._repo, self._node, self._path)
+
+    def __getattr__(self, name):
+        return getattr(self._fctx, name)
+
+def _convertoutputs(repo, annotated, contents):
+    """convert fastannotate outputs to vanilla annotate format"""
+    # fastannotate returns: [(nodeid, linenum, path)], [linecontent]
+    # convert to what fctx.annotate returns: [annotateline]
+    results = []
+    fctxmap = {}
+    annotateline = dagop.annotateline
+    for i, (hsh, linenum, path) in enumerate(annotated):
+        if (hsh, path) not in fctxmap:
+            fctxmap[(hsh, path)] = _lazyfctx(repo, hsh, path)
+        # linenum: the user wants 1-based, we have 0-based.
+        lineno = linenum + 1
+        fctx = fctxmap[(hsh, path)]
+        line = contents[i]
+        results.append(annotateline(fctx=fctx, lineno=lineno, text=line))
+    return results
+
+def _getmaster(fctx):
+    """(fctx) -> str"""
+    return fctx._repo.ui.config('fastannotate', 'mainbranch') or 'default'
+
+def _doannotate(fctx, follow=True, diffopts=None):
+    """like the vanilla fctx.annotate, but do it via fastannotate, and make
+    the output format compatible with the vanilla fctx.annotate.
+    may raise Exception, and always return line numbers.
+    """
+    master = _getmaster(fctx)
+    annotated = contents = None
+
+    with context.fctxannotatecontext(fctx, follow, diffopts) as ac:
+        try:
+            annotated, contents = ac.annotate(fctx.rev(), master=master,
+                                              showpath=True, showlines=True)
+        except Exception:
+            ac.rebuild() # try rebuild once
+            fctx._repo.ui.debug('fastannotate: %s: rebuilding broken cache\n'
+                                % fctx._path)
+            try:
+                annotated, contents = ac.annotate(fctx.rev(), master=master,
+                                                  showpath=True, showlines=True)
+            except Exception:
+                raise
+
+    assert annotated and contents
+    return _convertoutputs(fctx._repo, annotated, contents)
+
+def _hgwebannotate(orig, fctx, ui):
+    diffopts = patch.difffeatureopts(ui, untrusted=True,
+                                     section='annotate', whitespace=True)
+    return _doannotate(fctx, diffopts=diffopts)
+
+def _fctxannotate(orig, self, follow=False, linenumber=False, skiprevs=None,
+                  diffopts=None):
+    if skiprevs:
+        # skiprevs is not supported yet
+        return orig(self, follow, linenumber, skiprevs=skiprevs,
+                    diffopts=diffopts)
+    try:
+        return _doannotate(self, follow, diffopts)
+    except Exception as ex:
+        self._repo.ui.debug('fastannotate: falling back to the vanilla '
+                            'annotate: %r\n' % ex)
+        return orig(self, follow=follow, skiprevs=skiprevs,
+                    diffopts=diffopts)
+
+def _remotefctxannotate(orig, self, follow=False, skiprevs=None, diffopts=None):
+    # skipset: a set-like used to test if a fctx needs to be downloaded
+    skipset = None
+    with context.fctxannotatecontext(self, follow, diffopts) as ac:
+        skipset = revmap.revmap(ac.revmappath)
+    return orig(self, follow, skiprevs=skiprevs, diffopts=diffopts,
+                prefetchskip=skipset)
+
+def replacehgwebannotate():
+    extensions.wrapfunction(hgweb.webutil, 'annotate', _hgwebannotate)
+
+def replacefctxannotate():
+    extensions.wrapfunction(hgcontext.basefilectx, 'annotate', _fctxannotate)
--- a/hgext/fix.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/fix.py	Wed Sep 26 20:33:09 2018 +0900
@@ -58,6 +58,10 @@
 from mercurial.node import nullrev
 from mercurial.node import wdirrev
 
+from mercurial.utils import (
+    procutil,
+)
+
 from mercurial import (
     cmdutil,
     context,
@@ -96,15 +100,16 @@
 # user.
 configitem('fix', 'maxfilesize', default='2MB')
 
-@command('fix',
-    [('', 'all', False, _('fix all non-public non-obsolete revisions')),
-     ('', 'base', [], _('revisions to diff against (overrides automatic '
-                        'selection, and applies to every revision being '
-                        'fixed)'), _('REV')),
-     ('r', 'rev', [], _('revisions to fix'), _('REV')),
-     ('w', 'working-dir', False, _('fix the working directory')),
-     ('', 'whole', False, _('always fix every line of a file'))],
-    _('[OPTION]... [FILE]...'))
+allopt = ('', 'all', False, _('fix all non-public non-obsolete revisions'))
+baseopt = ('', 'base', [], _('revisions to diff against (overrides automatic '
+                             'selection, and applies to every revision being '
+                             'fixed)'), _('REV'))
+revopt = ('r', 'rev', [], _('revisions to fix'), _('REV'))
+wdiropt = ('w', 'working-dir', False, _('fix the working directory'))
+wholeopt = ('', 'whole', False, _('always fix every line of a file'))
+usage = _('[OPTION]... [FILE]...')
+
+@command('fix', [allopt, baseopt, revopt, wdiropt, wholeopt], usage)
 def fix(ui, repo, *pats, **opts):
     """rewrite file content in changesets or working directory
 
@@ -161,6 +166,7 @@
         # it makes the results more easily reproducible.
         filedata = collections.defaultdict(dict)
         replacements = {}
+        wdirwritten = False
         commitorder = sorted(revstofix, reverse=True)
         with ui.makeprogress(topic=_('fixing'), unit=_('files'),
                              total=sum(numitems.values())) as progress:
@@ -178,12 +184,28 @@
                     ctx = repo[rev]
                     if rev == wdirrev:
                         writeworkingdir(repo, ctx, filedata[rev], replacements)
+                        wdirwritten = bool(filedata[rev])
                     else:
                         replacerev(ui, repo, ctx, filedata[rev], replacements)
                     del filedata[rev]
 
-        replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
-        scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
+        cleanup(repo, replacements, wdirwritten)
+
+def cleanup(repo, replacements, wdirwritten):
+    """Calls scmutil.cleanupnodes() with the given replacements.
+
+    "replacements" is a dict from nodeid to nodeid, with one key and one value
+    for every revision that was affected by fixing. This is slightly different
+    from cleanupnodes().
+
+    "wdirwritten" is a bool which tells whether the working copy was affected by
+    fixing, since it has no entry in "replacements".
+
+    Useful as a hook point for extending "hg fix" with output summarizing the
+    effects of the command, though we choose not to output anything here.
+    """
+    replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
+    scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
 
 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
     """"Constructs the list of files to be fixed at specific revisions
@@ -267,8 +289,8 @@
     """
     files = set()
     for basectx in basectxs:
-        stat = repo.status(
-            basectx, fixctx, match=match, clean=bool(pats), unknown=bool(pats))
+        stat = basectx.status(fixctx, match=match, listclean=bool(pats),
+                              listunknown=bool(pats))
         files.update(
             set(itertools.chain(stat.added, stat.modified, stat.clean,
                                 stat.unknown)))
@@ -417,27 +439,33 @@
     starting with the file's content in the fixctx. Fixers that support line
     ranges will affect lines that have changed relative to any of the basectxs
     (i.e. they will only avoid lines that are common to all basectxs).
+
+    A fixer tool's stdout will become the file's new content if and only if it
+    exits with code zero.
     """
     newdata = fixctx[path].data()
     for fixername, fixer in fixers.iteritems():
         if fixer.affects(opts, fixctx, path):
-            ranges = lineranges(opts, path, basectxs, fixctx, newdata)
-            command = fixer.command(ui, path, ranges)
+            rangesfn = lambda: lineranges(opts, path, basectxs, fixctx, newdata)
+            command = fixer.command(ui, path, rangesfn)
             if command is None:
                 continue
             ui.debug('subprocess: %s\n' % (command,))
             proc = subprocess.Popen(
-                command,
+                pycompat.rapply(procutil.tonativestr, command),
                 shell=True,
-                cwd='/',
+                cwd=procutil.tonativestr(b'/'),
                 stdin=subprocess.PIPE,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE)
             newerdata, stderr = proc.communicate(newdata)
             if stderr:
                 showstderr(ui, fixctx.rev(), fixername, stderr)
-            else:
+            if proc.returncode == 0:
                 newdata = newerdata
+            elif not stderr:
+                showstderr(ui, fixctx.rev(), fixername,
+                           _('exited with status %d\n') % (proc.returncode,))
     return newdata
 
 def showstderr(ui, rev, fixername, stderr):
@@ -567,7 +595,7 @@
         """Should this fixer run on the file at the given path and context?"""
         return scmutil.match(fixctx, [self._fileset], opts)(path)
 
-    def command(self, ui, path, ranges):
+    def command(self, ui, path, rangesfn):
         """A shell command to use to invoke this fixer on the given file/lines
 
         May return None if there is no appropriate command to run for the given
@@ -577,6 +605,7 @@
         parts = [expand(ui, self._command,
                         {'rootpath': path, 'basename': os.path.basename(path)})]
         if self._linerange:
+            ranges = rangesfn()
             if not ranges:
                 # No line ranges to fix, so don't run the fixer.
                 return None
--- a/hgext/fsmonitor/pywatchman/__init__.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/fsmonitor/pywatchman/__init__.py	Wed Sep 26 20:33:09 2018 +0900
@@ -48,6 +48,14 @@
 except ImportError:
     from . import pybser as bser
 
+from mercurial.utils import (
+    procutil,
+)
+
+from mercurial import (
+    pycompat,
+)
+
 from . import (
     capabilities,
     compat,
@@ -580,7 +588,8 @@
             '--no-pretty',
             '-j',
         ]
-        self.proc = subprocess.Popen(args,
+        self.proc = subprocess.Popen(pycompat.rapply(procutil.tonativestr,
+                                                     args),
                                      stdin=subprocess.PIPE,
                                      stdout=subprocess.PIPE)
         return self.proc
@@ -822,7 +831,8 @@
                 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
                 args['startupinfo'] = startupinfo
 
-            p = subprocess.Popen(cmd, **args)
+            p = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmd),
+                                 **args)
 
         except OSError as e:
             raise WatchmanError('"watchman" executable not in PATH (%s)' % e)
--- a/hgext/hgk.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/hgk.py	Wed Sep 26 20:33:09 2018 +0900
@@ -227,7 +227,7 @@
             else:
                 i -= chunk
 
-            for x in xrange(chunk):
+            for x in pycompat.xrange(chunk):
                 if i + x >= count:
                     l[chunk - x:] = [0] * (chunk - x)
                     break
@@ -238,7 +238,7 @@
                 else:
                     if (i + x) in repo:
                         l[x] = 1
-            for x in xrange(chunk - 1, -1, -1):
+            for x in pycompat.xrange(chunk - 1, -1, -1):
                 if l[x] != 0:
                     yield (i + x, full is not None and l[x] or None)
             if i == 0:
@@ -249,7 +249,7 @@
         if len(ar) == 0:
             return 1
         mask = 0
-        for i in xrange(len(ar)):
+        for i in pycompat.xrange(len(ar)):
             if sha in reachable[i]:
                 mask |= 1 << i
 
--- a/hgext/histedit.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/histedit.py	Wed Sep 26 20:33:09 2018 +0900
@@ -386,7 +386,7 @@
         rules = []
         rulelen = int(lines[index])
         index += 1
-        for i in xrange(rulelen):
+        for i in pycompat.xrange(rulelen):
             ruleaction = lines[index]
             index += 1
             rule = lines[index]
@@ -397,7 +397,7 @@
         replacements = []
         replacementlen = int(lines[index])
         index += 1
-        for i in xrange(replacementlen):
+        for i in pycompat.xrange(replacementlen):
             replacement = lines[index]
             original = node.bin(replacement[:40])
             succ = [node.bin(replacement[i:i + 40]) for i in
@@ -1084,7 +1084,7 @@
             raise error.Abort(_('only --commands argument allowed with '
                                '--edit-plan'))
     else:
-        if os.path.exists(os.path.join(repo.path, 'histedit-state')):
+        if state.inprogress():
             raise error.Abort(_('history edit already in progress, try '
                                '--continue or --abort'))
         if outg:
@@ -1624,8 +1624,8 @@
 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
     if isinstance(nodelist, str):
         nodelist = [nodelist]
-    if os.path.exists(os.path.join(repo.path, 'histedit-state')):
-        state = histeditstate(repo)
+    state = histeditstate(repo)
+    if state.inprogress():
         state.read()
         histedit_nodes = {action.node for action
                           in state.actions if action.node}
@@ -1638,9 +1638,9 @@
 extensions.wrapfunction(repair, 'strip', stripwrapper)
 
 def summaryhook(ui, repo):
-    if not os.path.exists(repo.vfs.join('histedit-state')):
+    state = histeditstate(repo)
+    if not state.inprogress():
         return
-    state = histeditstate(repo)
     state.read()
     if state.actions:
         # i18n: column positioning for "hg summary"
--- a/hgext/infinitepush/__init__.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/infinitepush/__init__.py	Wed Sep 26 20:33:09 2018 +0900
@@ -1182,5 +1182,6 @@
         cmdline = [util.hgexecutable(), 'debugfillinfinitepushmetadata',
                    '-R', root] + nodesargs
         # Process will run in background. We don't care about the return code
-        subprocess.Popen(cmdline, close_fds=True, shell=False,
+        subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmdline),
+                         close_fds=True, shell=False,
                          stdin=devnull, stdout=devnull, stderr=devnull)
--- a/hgext/infinitepush/store.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/infinitepush/store.py	Wed Sep 26 20:33:09 2018 +0900
@@ -11,6 +11,13 @@
 import subprocess
 import tempfile
 
+from mercurial import (
+    pycompat,
+)
+from mercurial.utils import (
+    procutil,
+)
+
 NamedTemporaryFile = tempfile.NamedTemporaryFile
 
 class BundleWriteException(Exception):
@@ -111,7 +118,8 @@
 
     def _call_binary(self, args):
         p = subprocess.Popen(
-            args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+            pycompat.rapply(procutil.tonativestr, args),
+            stdout=subprocess.PIPE, stderr=subprocess.PIPE,
             close_fds=True)
         stdout, stderr = p.communicate()
         returncode = p.returncode
--- a/hgext/journal.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/journal.py	Wed Sep 26 20:33:09 2018 +0900
@@ -477,6 +477,8 @@
         name = args[0]
 
     fm = ui.formatter('journal', opts)
+    def formatnodes(nodes):
+        return fm.formatlist(map(fm.hexfunc, nodes), name='node', sep=',')
 
     if opts.get("template") != "json":
         if name is None:
@@ -491,31 +493,32 @@
     for count, entry in enumerate(repo.journal.filtered(name=name)):
         if count == limit:
             break
-        newhashesstr = fm.formatlist(map(fm.hexfunc, entry.newhashes),
-                                     name='node', sep=',')
-        oldhashesstr = fm.formatlist(map(fm.hexfunc, entry.oldhashes),
-                                     name='node', sep=',')
 
         fm.startitem()
-        fm.condwrite(ui.verbose, 'oldhashes', '%s -> ', oldhashesstr)
-        fm.write('newhashes', '%s', newhashesstr)
+        fm.condwrite(ui.verbose, 'oldnodes', '%s -> ',
+                     formatnodes(entry.oldhashes))
+        fm.write('newnodes', '%s', formatnodes(entry.newhashes))
         fm.condwrite(ui.verbose, 'user', ' %-8s', entry.user)
         fm.condwrite(
             opts.get('all') or name.startswith('re:'),
             'name', '  %-8s', entry.name)
 
-        timestring = fm.formatdate(entry.timestamp, '%Y-%m-%d %H:%M %1%2')
-        fm.condwrite(ui.verbose, 'date', ' %s', timestring)
+        fm.condwrite(ui.verbose, 'date', ' %s',
+                     fm.formatdate(entry.timestamp, '%Y-%m-%d %H:%M %1%2'))
         fm.write('command', '  %s\n', entry.command)
 
         if opts.get("commits"):
-            displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
+            if fm.isplain():
+                displayer = logcmdutil.changesetdisplayer(ui, repo, opts)
+            else:
+                displayer = logcmdutil.changesetformatter(
+                    ui, repo, fm.nested('changesets'), diffopts=opts)
             for hash in entry.newhashes:
                 try:
                     ctx = repo[hash]
                     displayer.show(ctx)
                 except error.RepoLookupError as e:
-                    fm.write('repolookuperror', "%s\n\n", pycompat.bytestr(e))
+                    fm.plain("%s\n\n" % pycompat.bytestr(e))
             displayer.close()
 
     fm.end()
--- a/hgext/keyword.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/keyword.py	Wed Sep 26 20:33:09 2018 +0900
@@ -208,7 +208,7 @@
 def _shrinktext(text, subfunc):
     '''Helper for keyword expansion removal in text.
     Depending on subfunc also returns number of substitutions.'''
-    return subfunc(r'$\1$', text)
+    return subfunc(br'$\1$', text)
 
 def _preselect(wstatus, changed):
     '''Retrieves modified and added files from a working directory state
@@ -250,12 +250,12 @@
     @util.propertycache
     def rekw(self):
         '''Returns regex for unexpanded keywords.'''
-        return re.compile(r'\$(%s)\$' % self.escape)
+        return re.compile(br'\$(%s)\$' % self.escape)
 
     @util.propertycache
     def rekwexp(self):
         '''Returns regex for expanded keywords.'''
-        return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
+        return re.compile(br'\$(%s): [^$\n\r]*? \$' % self.escape)
 
     def substitute(self, data, path, ctx, subfunc):
         '''Replaces keywords in data with expanded template.'''
@@ -439,7 +439,7 @@
         baseui = ui
     else:
         baseui = repo.baseui
-    repo = localrepo.localrepository(baseui, tmpdir, True)
+    repo = localrepo.instance(baseui, tmpdir, create=True)
     ui.setconfig('keyword', fn, '', 'keyword')
     svn = ui.configbool('keywordset', 'svn')
     # explicitly set keywordset for demo output
@@ -567,7 +567,7 @@
         showfiles += ([f for f in files if f not in kwfiles],
                       [f for f in status.unknown if f not in kwunknown])
     kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
-    kwstates = zip(kwlabels, 'K!kIi', showfiles)
+    kwstates = zip(kwlabels, pycompat.bytestr('K!kIi'), showfiles)
     fm = ui.formatter('kwfiles', opts)
     fmt = '%.0s%s\n'
     if opts.get('all') or ui.verbose:
@@ -576,8 +576,8 @@
         label = 'kwfiles.' + kwstate
         for f in filenames:
             fm.startitem()
-            fm.write('kwstatus path', fmt, char,
-                     repo.pathto(f, cwd), label=label)
+            fm.data(kwstatus=char, path=f)
+            fm.plain(fmt % (char, repo.pathto(f, cwd)), label=label)
     fm.end()
 
 @command('kwshrink',
--- a/hgext/largefiles/basestore.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/largefiles/basestore.py	Wed Sep 26 20:33:09 2018 +0900
@@ -62,25 +62,24 @@
 
         at = 0
         available = self.exists(set(hash for (_filename, hash) in files))
-        progress = ui.makeprogress(_('getting largefiles'), unit=_('files'),
-                                   total=len(files))
-        for filename, hash in files:
-            progress.update(at)
-            at += 1
-            ui.note(_('getting %s:%s\n') % (filename, hash))
+        with ui.makeprogress(_('getting largefiles'), unit=_('files'),
+                             total=len(files)) as progress:
+            for filename, hash in files:
+                progress.update(at)
+                at += 1
+                ui.note(_('getting %s:%s\n') % (filename, hash))
 
-            if not available.get(hash):
-                ui.warn(_('%s: largefile %s not available from %s\n')
-                        % (filename, hash, util.hidepassword(self.url)))
-                missing.append(filename)
-                continue
+                if not available.get(hash):
+                    ui.warn(_('%s: largefile %s not available from %s\n')
+                            % (filename, hash, util.hidepassword(self.url)))
+                    missing.append(filename)
+                    continue
 
-            if self._gethash(filename, hash):
-                success.append((filename, hash))
-            else:
-                missing.append(filename)
+                if self._gethash(filename, hash):
+                    success.append((filename, hash))
+                else:
+                    missing.append(filename)
 
-        progress.complete()
         return (success, missing)
 
     def _gethash(self, filename, hash):
--- a/hgext/largefiles/lfcommands.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/largefiles/lfcommands.py	Wed Sep 26 20:33:09 2018 +0900
@@ -118,14 +118,13 @@
                 matcher = None
 
             lfiletohash = {}
-            progress = ui.makeprogress(_('converting revisions'),
-                                       unit=_('revisions'),
-                                       total=rsrc['tip'].rev())
-            for ctx in ctxs:
-                progress.update(ctx.rev())
-                _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
-                    lfiles, normalfiles, matcher, size, lfiletohash)
-            progress.complete()
+            with ui.makeprogress(_('converting revisions'),
+                                 unit=_('revisions'),
+                                 total=rsrc['tip'].rev()) as progress:
+                for ctx in ctxs:
+                    progress.update(ctx.rev())
+                    _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
+                        lfiles, normalfiles, matcher, size, lfiletohash)
 
             if rdst.wvfs.exists(lfutil.shortname):
                 rdst.wvfs.rmtree(lfutil.shortname)
@@ -210,6 +209,10 @@
             if f in ctx.manifest():
                 fctx = ctx.filectx(f)
                 renamed = fctx.renamed()
+                if renamed is None:
+                    # the code below assumes renamed to be a boolean or a list
+                    # and won't quite work with the value None
+                    renamed = False
                 renamedlfile = renamed and renamed[0] in lfiles
                 islfile |= renamedlfile
                 if 'l' in fctx.flags():
@@ -370,18 +373,17 @@
     files = [h for h in files if not retval[h]]
     ui.debug("%d largefiles need to be uploaded\n" % len(files))
 
-    progress = ui.makeprogress(_('uploading largefiles'), unit=_('files'),
-                               total=len(files))
-    for hash in files:
-        progress.update(at)
-        source = lfutil.findfile(rsrc, hash)
-        if not source:
-            raise error.Abort(_('largefile %s missing from store'
-                               ' (needs to be uploaded)') % hash)
-        # XXX check for errors here
-        store.put(source, hash)
-        at += 1
-    progress.complete()
+    with ui.makeprogress(_('uploading largefiles'), unit=_('files'),
+                         total=len(files)) as progress:
+        for hash in files:
+            progress.update(at)
+            source = lfutil.findfile(rsrc, hash)
+            if not source:
+                raise error.Abort(_('largefile %s missing from store'
+                                   ' (needs to be uploaded)') % hash)
+            # XXX check for errors here
+            store.put(source, hash)
+            at += 1
 
 def verifylfiles(ui, repo, all=False, contents=False):
     '''Verify that every largefile revision in the current changeset
--- a/hgext/largefiles/lfutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/largefiles/lfutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -501,37 +501,37 @@
     return filelist
 
 def getlfilestoupload(repo, missing, addfunc):
-    progress = repo.ui.makeprogress(_('finding outgoing largefiles'),
-                                    unit=_('revisions'), total=len(missing))
-    for i, n in enumerate(missing):
-        progress.update(i)
-        parents = [p for p in repo[n].parents() if p != node.nullid]
+    makeprogress = repo.ui.makeprogress
+    with makeprogress(_('finding outgoing largefiles'),
+                      unit=_('revisions'), total=len(missing)) as progress:
+        for i, n in enumerate(missing):
+            progress.update(i)
+            parents = [p for p in repo[n].parents() if p != node.nullid]
 
-        oldlfstatus = repo.lfstatus
-        repo.lfstatus = False
-        try:
-            ctx = repo[n]
-        finally:
-            repo.lfstatus = oldlfstatus
+            oldlfstatus = repo.lfstatus
+            repo.lfstatus = False
+            try:
+                ctx = repo[n]
+            finally:
+                repo.lfstatus = oldlfstatus
 
-        files = set(ctx.files())
-        if len(parents) == 2:
-            mc = ctx.manifest()
-            mp1 = ctx.parents()[0].manifest()
-            mp2 = ctx.parents()[1].manifest()
-            for f in mp1:
-                if f not in mc:
-                    files.add(f)
-            for f in mp2:
-                if f not in mc:
-                    files.add(f)
-            for f in mc:
-                if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
-                    files.add(f)
-        for fn in files:
-            if isstandin(fn) and fn in ctx:
-                addfunc(fn, readasstandin(ctx[fn]))
-    progress.complete()
+            files = set(ctx.files())
+            if len(parents) == 2:
+                mc = ctx.manifest()
+                mp1 = ctx.parents()[0].manifest()
+                mp2 = ctx.parents()[1].manifest()
+                for f in mp1:
+                    if f not in mc:
+                        files.add(f)
+                for f in mp2:
+                    if f not in mc:
+                        files.add(f)
+                for f in mc:
+                    if mc[f] != mp1.get(f, None) or mc[f] != mp2.get(f, None):
+                        files.add(f)
+            for fn in files:
+                if isstandin(fn) and fn in ctx:
+                    addfunc(fn, readasstandin(ctx[fn]))
 
 def updatestandinsbymatch(repo, match):
     '''Update standins in the working directory according to specified match
--- a/hgext/lfs/__init__.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/lfs/__init__.py	Wed Sep 26 20:33:09 2018 +0900
@@ -136,7 +136,7 @@
     exchange,
     extensions,
     filelog,
-    fileset,
+    filesetlang,
     hg,
     localrepo,
     minifileset,
@@ -261,7 +261,7 @@
         # deprecated config: lfs.threshold
         threshold = repo.ui.configbytes('lfs', 'threshold')
         if threshold:
-            fileset.parse(trackspec)  # make sure syntax errors are confined
+            filesetlang.parse(trackspec)  # make sure syntax errors are confined
             trackspec = "(%s) | size('>%d')" % (trackspec, threshold)
 
         return minifileset.compile(trackspec)
@@ -357,11 +357,11 @@
     # when writing a bundle via "hg bundle" command, upload related LFS blobs
     wrapfunction(bundle2, 'writenewbundle', wrapper.writenewbundle)
 
-@filesetpredicate('lfs()', callstatus=True)
+@filesetpredicate('lfs()')
 def lfsfileset(mctx, x):
     """File that uses LFS storage."""
     # i18n: "lfs" is a keyword
-    fileset.getargs(x, 0, 0, _("lfs takes no arguments"))
+    filesetlang.getargs(x, 0, 0, _("lfs takes no arguments"))
     ctx = mctx.ctx
     def lfsfilep(f):
         return wrapper.pointerfromctx(ctx, f, removed=True) is not None
--- a/hgext/lfs/blobstore.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/lfs/blobstore.py	Wed Sep 26 20:33:09 2018 +0900
@@ -168,6 +168,20 @@
 
         self._linktousercache(oid)
 
+    def linkfromusercache(self, oid):
+        """Link blobs found in the user cache into this store.
+
+        The server module needs to do this when it lets the client know not to
+        upload the blob, to ensure it is always available in this store.
+        Normally this is done implicitly when the client reads or writes the
+        blob, but that doesn't happen when the server tells the client that it
+        already has the blob.
+        """
+        if (not isinstance(self.cachevfs, nullvfs)
+            and not self.vfs.exists(oid)):
+            self.ui.note(_('lfs: found %s in the usercache\n') % oid)
+            lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid))
+
     def _linktousercache(self, oid):
         # XXX: should we verify the content of the cache, and hardlink back to
         # the local store on success, but truncate, write and link on failure?
@@ -405,8 +419,7 @@
         if len(objects) > 1:
             self.ui.note(_('lfs: need to transfer %d objects (%s)\n')
                          % (len(objects), util.bytecount(total)))
-        progress = self.ui.makeprogress(topic, total=total)
-        progress.update(0)
+
         def transfer(chunk):
             for obj in chunk:
                 objsize = obj.get('size', 0)
@@ -439,14 +452,15 @@
         else:
             oids = transfer(sorted(objects, key=lambda o: o.get('oid')))
 
-        processed = 0
-        blobs = 0
-        for _one, oid in oids:
-            processed += sizes[oid]
-            blobs += 1
-            progress.update(processed)
-            self.ui.note(_('lfs: processed: %s\n') % oid)
-        progress.complete()
+        with self.ui.makeprogress(topic, total=total) as progress:
+            progress.update(0)
+            processed = 0
+            blobs = 0
+            for _one, oid in oids:
+                processed += sizes[oid]
+                blobs += 1
+                progress.update(processed)
+                self.ui.note(_('lfs: processed: %s\n') % oid)
 
         if blobs > 0:
             if action == 'upload':
@@ -572,7 +586,7 @@
         raise error.Abort(_('lfs: unknown url scheme: %s') % scheme)
     return _storemap[scheme](repo, url)
 
-class LfsRemoteError(error.RevlogError):
+class LfsRemoteError(error.StorageError):
     pass
 
 class LfsCorruptionError(error.Abort):
--- a/hgext/lfs/pointer.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/lfs/pointer.py	Wed Sep 26 20:33:09 2018 +0900
@@ -19,7 +19,7 @@
     stringutil,
 )
 
-class InvalidPointer(error.RevlogError):
+class InvalidPointer(error.StorageError):
     pass
 
 class gitlfspointer(dict):
--- a/hgext/lfs/wireprotolfsserver.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/lfs/wireprotolfsserver.py	Wed Sep 26 20:33:09 2018 +0900
@@ -204,6 +204,10 @@
         # verified as the file is streamed to the caller.
         try:
             verifies = store.verify(oid)
+            if verifies and action == 'upload':
+                # The client will skip this upload, but make sure it remains
+                # available locally.
+                store.linkfromusercache(oid)
         except IOError as inst:
             if inst.errno != errno.ENOENT:
                 _logexception(req)
--- a/hgext/lfs/wrapper.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/lfs/wrapper.py	Wed Sep 26 20:33:09 2018 +0900
@@ -343,11 +343,15 @@
     """return a list of lfs pointers added by given revs"""
     repo.ui.debug('lfs: computing set of blobs to upload\n')
     pointers = {}
-    for r in revs:
-        ctx = repo[r]
-        for p in pointersfromctx(ctx).values():
-            pointers[p.oid()] = p
-    return sorted(pointers.values())
+
+    makeprogress = repo.ui.makeprogress
+    with makeprogress(_('lfs search'), _('changesets'), len(revs)) as progress:
+        for r in revs:
+            ctx = repo[r]
+            for p in pointersfromctx(ctx).values():
+                pointers[p.oid()] = p
+            progress.increment()
+        return sorted(pointers.values())
 
 def pointerfromctx(ctx, f, removed=False):
     """return a pointer for the named file from the given changectx, or None if
--- a/hgext/logtoprocess.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/logtoprocess.py	Wed Sep 26 20:33:09 2018 +0900
@@ -44,6 +44,10 @@
     pycompat,
 )
 
+from mercurial.utils import (
+    procutil,
+)
+
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -62,7 +66,8 @@
             # we can't use close_fds *and* redirect stdin. I'm not sure that we
             # need to because the detached process has no console connection.
             subprocess.Popen(
-                script, shell=True, env=env, close_fds=True,
+                pycompat.rapply(procutil.tonativestr, script),
+                shell=True, env=procutil.tonativeenv(env), close_fds=True,
                 creationflags=_creationflags)
     else:
         def runshellcommand(script, env):
@@ -82,7 +87,9 @@
                 # connect stdin to devnull to make sure the subprocess can't
                 # muck up that stream for mercurial.
                 subprocess.Popen(
-                    script, shell=True, stdin=open(os.devnull, 'r'), env=env,
+                    pycompat.rapply(procutil.tonativestr, script),
+                    shell=True, stdin=open(os.devnull, 'r'),
+                    env=procutil.tonativeenv(env),
                     close_fds=True, **newsession)
             finally:
                 # mission accomplished, this child needs to exit and not
--- a/hgext/mq.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/mq.py	Wed Sep 26 20:33:09 2018 +0900
@@ -414,7 +414,7 @@
         the field and a blank line.'''
         if self.message:
             subj = 'subject: ' + self.message[0].lower()
-            for i in xrange(len(self.comments)):
+            for i in pycompat.xrange(len(self.comments)):
                 if subj == self.comments[i].lower():
                     del self.comments[i]
                     self.message = self.message[2:]
@@ -662,13 +662,13 @@
         exactneg = [g for g in patchguards
                     if g.startswith('-') and g[1:] in guards]
         if exactneg:
-            return False, pycompat.byterepr(exactneg[0])
+            return False, stringutil.pprint(exactneg[0])
         pos = [g for g in patchguards if g.startswith('+')]
         exactpos = [g for g in pos if g[1:] in guards]
         if pos:
             if exactpos:
-                return True, pycompat.byterepr(exactpos[0])
-            return False, ' '.join([pycompat.byterepr(p) for p in pos])
+                return True, stringutil.pprint(exactpos[0])
+            return False, ' '.join([stringutil.pprint(p) for p in pos])
         return True, ''
 
     def explainpushable(self, idx, all_patches=False):
@@ -1800,7 +1800,7 @@
                 # if the patch excludes a modified file, mark that
                 # file with mtime=0 so status can see it.
                 mm = []
-                for i in xrange(len(m) - 1, -1, -1):
+                for i in pycompat.xrange(len(m) - 1, -1, -1):
                     if not match1(m[i]):
                         mm.append(m[i])
                         del m[i]
@@ -1908,7 +1908,7 @@
         else:
             start = self.series.index(patch) + 1
         unapplied = []
-        for i in xrange(start, len(self.series)):
+        for i in pycompat.xrange(start, len(self.series)):
             pushable, reason = self.pushable(i)
             if pushable:
                 unapplied.append((i, self.series[i]))
@@ -1946,7 +1946,7 @@
         if not missing:
             if self.ui.verbose:
                 idxwidth = len("%d" % (start + length - 1))
-            for i in xrange(start, start + length):
+            for i in pycompat.xrange(start, start + length):
                 patch = self.series[i]
                 if patch in applied:
                     char, state = 'A', 'applied'
@@ -2091,7 +2091,7 @@
         def nextpatch(start):
             if all_patches or start >= len(self.series):
                 return start
-            for i in xrange(start, len(self.series)):
+            for i in pycompat.xrange(start, len(self.series)):
                 p, reason = self.pushable(i)
                 if p:
                     return i
@@ -2876,7 +2876,7 @@
         if args or opts.get(r'none'):
             raise error.Abort(_('cannot mix -l/--list with options or '
                                'arguments'))
-        for i in xrange(len(q.series)):
+        for i in pycompat.xrange(len(q.series)):
             status(i)
         return
     if not args or args[0][0:1] in '-+':
@@ -3179,14 +3179,16 @@
     pushable = lambda i: q.pushable(q.applied[i].name)[0]
     if args or opts.get('none'):
         old_unapplied = q.unapplied(repo)
-        old_guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
+        old_guarded = [i for i in pycompat.xrange(len(q.applied))
+                       if not pushable(i)]
         q.setactive(args)
         q.savedirty()
         if not args:
             ui.status(_('guards deactivated\n'))
         if not opts.get('pop') and not opts.get('reapply'):
             unapplied = q.unapplied(repo)
-            guarded = [i for i in xrange(len(q.applied)) if not pushable(i)]
+            guarded = [i for i in pycompat.xrange(len(q.applied))
+                       if not pushable(i)]
             if len(unapplied) != len(old_unapplied):
                 ui.status(_('number of unguarded, unapplied patches has '
                             'changed from %d to %d\n') %
@@ -3225,7 +3227,7 @@
     reapply = opts.get('reapply') and q.applied and q.applied[-1].name
     popped = False
     if opts.get('pop') or opts.get('reapply'):
-        for i in xrange(len(q.applied)):
+        for i in pycompat.xrange(len(q.applied)):
             if not pushable(i):
                 ui.status(_('popping guarded patches\n'))
                 popped = True
@@ -3583,7 +3585,7 @@
             raise error.Abort(_('only a local queue repository '
                                'may be initialized'))
     else:
-        repopath = cmdutil.findrepo(pycompat.getcwd())
+        repopath = cmdutil.findrepo(encoding.getcwd())
         if not repopath:
             raise error.Abort(_('there is no Mercurial repository here '
                                '(.hg not found)'))
--- a/hgext/narrow/TODO.rst	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/narrow/TODO.rst	Wed Sep 26 20:33:09 2018 +0900
@@ -7,18 +7,11 @@
 came up the import to hgext, but nobody's got concrete improvement
 ideas as of then.
 
-Fold most (or preferably all) of narrowrevlog.py into core.
-
-Address commentary in narrowrevlog.excludedmanifestrevlog.add -
+Address commentary in manifest.excludedmanifestrevlog.add -
 specifically we should improve the collaboration with core so that
 add() never gets called on an excluded directory and we can improve
 the stand-in to raise a ProgrammingError.
 
-Figure out how to correctly produce narrowmanifestrevlog and
-narrowfilelog instances instead of monkeypatching regular revlogs at
-runtime to our subclass. Even better, merge the narrowing logic
-directly into core.
-
 Reason more completely about rename-filtering logic in
 narrowfilelog. There could be some surprises lurking there.
 
@@ -28,10 +21,3 @@
 
 narrowrepo.setnarrowpats() or narrowspec.save() need to make sure
 they're holding the wlock.
-
-Implement a simple version of the expandnarrow wireproto command for
-core. Having configurable shorthands for narrowspecs has been useful
-at Google (and sparse has a similar feature from Facebook), so it
-probably makes sense to implement the feature in core. (Google's
-handler is entirely custom to Google, with a custom format related to
-bazel's build language, so it's not in the narrowhg distribution.)
--- a/hgext/narrow/__init__.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/narrow/__init__.py	Wed Sep 26 20:33:09 2018 +0900
@@ -15,22 +15,19 @@
 testedwith = 'ships-with-hg-core'
 
 from mercurial import (
-    changegroup,
     extensions,
-    hg,
     localrepo,
     registrar,
+    repository,
     verify as verifymod,
 )
 
 from . import (
     narrowbundle2,
-    narrowchangegroup,
     narrowcommands,
     narrowcopies,
     narrowpatch,
     narrowrepo,
-    narrowrevlog,
     narrowtemplates,
     narrowwirepeer,
 )
@@ -55,15 +52,13 @@
 cmdtable = narrowcommands.table
 
 def featuresetup(ui, features):
-    features.add(changegroup.NARROW_REQUIREMENT)
+    features.add(repository.NARROW_REQUIREMENT)
 
 def uisetup(ui):
     """Wraps user-facing mercurial commands with narrow-aware versions."""
     localrepo.featuresetupfuncs.add(featuresetup)
-    narrowrevlog.setup()
     narrowbundle2.setup()
     narrowcommands.setup()
-    narrowchangegroup.setup()
     narrowwirepeer.uisetup()
 
 def reposetup(ui, repo):
@@ -71,7 +66,7 @@
     if not repo.local():
         return
 
-    if changegroup.NARROW_REQUIREMENT in repo.requirements:
+    if repository.NARROW_REQUIREMENT in repo.requirements:
         narrowrepo.wraprepo(repo)
         narrowcopies.setup(repo)
         narrowpatch.setup(repo)
@@ -86,8 +81,6 @@
 
 def extsetup(ui):
     extensions.wrapfunction(verifymod.verifier, '__init__', _verifierinit)
-    extensions.wrapfunction(hg, 'postshare', narrowrepo.wrappostshare)
-    extensions.wrapfunction(hg, 'copystore', narrowrepo.unsharenarrowspec)
 
 templatekeyword = narrowtemplates.templatekeyword
 revsetpredicate = narrowtemplates.revsetpredicate
--- a/hgext/narrow/narrowbundle2.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/narrow/narrowbundle2.py	Wed Sep 26 20:33:09 2018 +0900
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import collections
 import errno
 import struct
 
@@ -15,17 +14,17 @@
 from mercurial.node import (
     bin,
     nullid,
-    nullrev,
 )
 from mercurial import (
     bundle2,
     changegroup,
-    dagutil,
     error,
     exchange,
     extensions,
+    match as matchmod,
     narrowspec,
     repair,
+    repository,
     util,
     wireprototypes,
 )
@@ -52,171 +51,12 @@
     caps[NARROWCAP] = ['v0']
     return caps
 
-def _computeellipsis(repo, common, heads, known, match, depth=None):
-    """Compute the shape of a narrowed DAG.
-
-    Args:
-      repo: The repository we're transferring.
-      common: The roots of the DAG range we're transferring.
-              May be just [nullid], which means all ancestors of heads.
-      heads: The heads of the DAG range we're transferring.
-      match: The narrowmatcher that allows us to identify relevant changes.
-      depth: If not None, only consider nodes to be full nodes if they are at
-             most depth changesets away from one of heads.
-
-    Returns:
-      A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
-
-        visitnodes: The list of nodes (either full or ellipsis) which
-                    need to be sent to the client.
-        relevant_nodes: The set of changelog nodes which change a file inside
-                 the narrowspec. The client needs these as non-ellipsis nodes.
-        ellipsisroots: A dict of {rev: parents} that is used in
-                       narrowchangegroup to produce ellipsis nodes with the
-                       correct parents.
-    """
-    cl = repo.changelog
-    mfl = repo.manifestlog
-
-    cldag = dagutil.revlogdag(cl)
-    # dagutil does not like nullid/nullrev
-    commonrevs = cldag.internalizeall(common - set([nullid])) | set([nullrev])
-    headsrevs = cldag.internalizeall(heads)
-    if depth:
-        revdepth = {h: 0 for h in headsrevs}
-
-    ellipsisheads = collections.defaultdict(set)
-    ellipsisroots = collections.defaultdict(set)
-
-    def addroot(head, curchange):
-        """Add a root to an ellipsis head, splitting heads with 3 roots."""
-        ellipsisroots[head].add(curchange)
-        # Recursively split ellipsis heads with 3 roots by finding the
-        # roots' youngest common descendant which is an elided merge commit.
-        # That descendant takes 2 of the 3 roots as its own, and becomes a
-        # root of the head.
-        while len(ellipsisroots[head]) > 2:
-            child, roots = splithead(head)
-            splitroots(head, child, roots)
-            head = child  # Recurse in case we just added a 3rd root
-
-    def splitroots(head, child, roots):
-        ellipsisroots[head].difference_update(roots)
-        ellipsisroots[head].add(child)
-        ellipsisroots[child].update(roots)
-        ellipsisroots[child].discard(child)
-
-    def splithead(head):
-        r1, r2, r3 = sorted(ellipsisroots[head])
-        for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
-            mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',
-                            nr1, head, nr2, head)
-            for j in mid:
-                if j == nr2:
-                    return nr2, (nr1, nr2)
-                if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
-                    return j, (nr1, nr2)
-        raise error.Abort('Failed to split up ellipsis node! head: %d, '
-                          'roots: %d %d %d' % (head, r1, r2, r3))
+def getbundlechangegrouppart_widen(bundler, repo, source, bundlecaps=None,
+                                   b2caps=None, heads=None, common=None,
+                                   **kwargs):
+    """Handling changegroup changegroup generation on the server when user
+    is widening their narrowspec"""
 
-    missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
-    visit = reversed(missing)
-    relevant_nodes = set()
-    visitnodes = [cl.node(m) for m in missing]
-    required = set(headsrevs) | known
-    for rev in visit:
-        clrev = cl.changelogrevision(rev)
-        ps = cldag.parents(rev)
-        if depth is not None:
-            curdepth = revdepth[rev]
-            for p in ps:
-                revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
-        needed = False
-        shallow_enough = depth is None or revdepth[rev] <= depth
-        if shallow_enough:
-            curmf = mfl[clrev.manifest].read()
-            if ps:
-                # We choose to not trust the changed files list in
-                # changesets because it's not always correct. TODO: could
-                # we trust it for the non-merge case?
-                p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
-                needed = bool(curmf.diff(p1mf, match))
-                if not needed and len(ps) > 1:
-                    # For merge changes, the list of changed files is not
-                    # helpful, since we need to emit the merge if a file
-                    # in the narrow spec has changed on either side of the
-                    # merge. As a result, we do a manifest diff to check.
-                    p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
-                    needed = bool(curmf.diff(p2mf, match))
-            else:
-                # For a root node, we need to include the node if any
-                # files in the node match the narrowspec.
-                needed = any(curmf.walk(match))
-
-        if needed:
-            for head in ellipsisheads[rev]:
-                addroot(head, rev)
-            for p in ps:
-                required.add(p)
-            relevant_nodes.add(cl.node(rev))
-        else:
-            if not ps:
-                ps = [nullrev]
-            if rev in required:
-                for head in ellipsisheads[rev]:
-                    addroot(head, rev)
-                for p in ps:
-                    ellipsisheads[p].add(rev)
-            else:
-                for p in ps:
-                    ellipsisheads[p] |= ellipsisheads[rev]
-
-    # add common changesets as roots of their reachable ellipsis heads
-    for c in commonrevs:
-        for head in ellipsisheads[c]:
-            addroot(head, c)
-    return visitnodes, relevant_nodes, ellipsisroots
-
-def _packellipsischangegroup(repo, common, match, relevant_nodes,
-                             ellipsisroots, visitnodes, depth, source, version):
-    if version in ('01', '02'):
-        raise error.Abort(
-            'ellipsis nodes require at least cg3 on client and server, '
-            'but negotiated version %s' % version)
-    # We wrap cg1packer.revchunk, using a side channel to pass
-    # relevant_nodes into that area. Then if linknode isn't in the
-    # set, we know we have an ellipsis node and we should defer
-    # sending that node's data. We override close() to detect
-    # pending ellipsis nodes and flush them.
-    packer = changegroup.getbundler(version, repo)
-    # Let the packer have access to the narrow matcher so it can
-    # omit filelogs and dirlogs as needed
-    packer._narrow_matcher = lambda : match
-    # Give the packer the list of nodes which should not be
-    # ellipsis nodes. We store this rather than the set of nodes
-    # that should be an ellipsis because for very large histories
-    # we expect this to be significantly smaller.
-    packer.full_nodes = relevant_nodes
-    # Maps ellipsis revs to their roots at the changelog level.
-    packer.precomputed_ellipsis = ellipsisroots
-    # Maps CL revs to per-revlog revisions. Cleared in close() at
-    # the end of each group.
-    packer.clrev_to_localrev = {}
-    packer.next_clrev_to_localrev = {}
-    # Maps changelog nodes to changelog revs. Filled in once
-    # during changelog stage and then left unmodified.
-    packer.clnode_to_rev = {}
-    packer.changelog_done = False
-    # If true, informs the packer that it is serving shallow content and might
-    # need to pack file contents not introduced by the changes being packed.
-    packer.is_shallow = depth is not None
-
-    return packer.generate(common, visitnodes, False, source)
-
-# Serve a changegroup for a client with a narrow clone.
-def getbundlechangegrouppart_narrow(bundler, repo, source,
-                                    bundlecaps=None, b2caps=None, heads=None,
-                                    common=None, **kwargs):
     cgversions = b2caps.get('changegroup')
     if cgversions:  # 3.1 and 3.2 ship with an empty value
         cgversions = [v for v in cgversions
@@ -231,32 +71,53 @@
     include = sorted(filter(bool, kwargs.get(r'includepats', [])))
     exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
     newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
-    if not repo.ui.configbool("experimental", "narrowservebrokenellipses"):
-        outgoing = exchange._computeoutgoing(repo, heads, common)
-        if not outgoing.missing:
-            return
-        def wrappedgetbundler(orig, *args, **kwargs):
-            bundler = orig(*args, **kwargs)
-            bundler._narrow_matcher = lambda : newmatch
-            return bundler
-        with extensions.wrappedfunction(changegroup, 'getbundler',
-                                        wrappedgetbundler):
-            cg = changegroup.makestream(repo, outgoing, version, source)
-        part = bundler.newpart('changegroup', data=cg)
-        part.addparam('version', version)
-        if 'treemanifest' in repo.requirements:
-            part.addparam('treemanifest', '1')
+    oldinclude = sorted(filter(bool, kwargs.get(r'oldincludepats', [])))
+    oldexclude = sorted(filter(bool, kwargs.get(r'oldexcludepats', [])))
+    oldmatch = narrowspec.match(repo.root, include=oldinclude,
+                                exclude=oldexclude)
+    diffmatch = matchmod.differencematcher(newmatch, oldmatch)
+    common = set(common or [nullid])
+
+    if (oldinclude != include or oldexclude != exclude):
+        common = repo.revs("::%ln", common)
+        commonnodes = set()
+        cl = repo.changelog
+        for c in common:
+            commonnodes.add(cl.node(c))
+        if commonnodes:
+            # XXX: we should only send the filelogs (and treemanifest). user
+            # already has the changelog and manifest
+            packer = changegroup.getbundler(version, repo,
+                                            filematcher=diffmatch,
+                                            fullnodes=commonnodes)
+            cgdata = packer.generate(set([nullid]), list(commonnodes), False,
+                                     source, changelog=False)
 
-        if include or exclude:
-            narrowspecpart = bundler.newpart(_SPECPART)
-            if include:
-                narrowspecpart.addparam(
-                    _SPECPART_INCLUDE, '\n'.join(include), mandatory=True)
-            if exclude:
-                narrowspecpart.addparam(
-                    _SPECPART_EXCLUDE, '\n'.join(exclude), mandatory=True)
+            part = bundler.newpart('changegroup', data=cgdata)
+            part.addparam('version', version)
+            if 'treemanifest' in repo.requirements:
+                part.addparam('treemanifest', '1')
+
+# Serve a changegroup for a client with a narrow clone.
+def getbundlechangegrouppart_narrow(bundler, repo, source,
+                                    bundlecaps=None, b2caps=None, heads=None,
+                                    common=None, **kwargs):
+    assert repo.ui.configbool('experimental', 'narrowservebrokenellipses')
 
-        return
+    cgversions = b2caps.get('changegroup')
+    if cgversions:  # 3.1 and 3.2 ship with an empty value
+        cgversions = [v for v in cgversions
+                      if v in changegroup.supportedoutgoingversions(repo)]
+        if not cgversions:
+            raise ValueError(_('no common changegroup version'))
+        version = max(cgversions)
+    else:
+        raise ValueError(_("server does not advertise changegroup version,"
+                           " can't negotiate support for ellipsis nodes"))
+
+    include = sorted(filter(bool, kwargs.get(r'includepats', [])))
+    exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
+    newmatch = narrowspec.match(repo.root, include=include, exclude=exclude)
 
     depth = kwargs.get(r'depth', None)
     if depth is not None:
@@ -300,72 +161,49 @@
                 yield repo.changelog.node(r)
             yield _DONESIGNAL
         bundler.newpart(_CHANGESPECPART, data=genkills())
-        newvisit, newfull, newellipsis = _computeellipsis(
+        newvisit, newfull, newellipsis = exchange._computeellipsis(
             repo, set(), common, known, newmatch)
         if newvisit:
-            cg = _packellipsischangegroup(
-                repo, common, newmatch, newfull, newellipsis,
-                newvisit, depth, source, version)
-            part = bundler.newpart('changegroup', data=cg)
+            packer = changegroup.getbundler(version, repo,
+                                            filematcher=newmatch,
+                                            ellipses=True,
+                                            shallow=depth is not None,
+                                            ellipsisroots=newellipsis,
+                                            fullnodes=newfull)
+            cgdata = packer.generate(common, newvisit, False, source)
+
+            part = bundler.newpart('changegroup', data=cgdata)
             part.addparam('version', version)
             if 'treemanifest' in repo.requirements:
                 part.addparam('treemanifest', '1')
 
-    visitnodes, relevant_nodes, ellipsisroots = _computeellipsis(
+    visitnodes, relevant_nodes, ellipsisroots = exchange._computeellipsis(
         repo, common, heads, set(), newmatch, depth=depth)
 
     repo.ui.debug('Found %d relevant revs\n' % len(relevant_nodes))
     if visitnodes:
-        cg = _packellipsischangegroup(
-            repo, common, newmatch, relevant_nodes, ellipsisroots,
-            visitnodes, depth, source, version)
-        part = bundler.newpart('changegroup', data=cg)
+        packer = changegroup.getbundler(version, repo,
+                                        filematcher=newmatch,
+                                        ellipses=True,
+                                        shallow=depth is not None,
+                                        ellipsisroots=ellipsisroots,
+                                        fullnodes=relevant_nodes)
+        cgdata = packer.generate(common, visitnodes, False, source)
+
+        part = bundler.newpart('changegroup', data=cgdata)
         part.addparam('version', version)
         if 'treemanifest' in repo.requirements:
             part.addparam('treemanifest', '1')
 
-def applyacl_narrow(repo, kwargs):
-    ui = repo.ui
-    username = ui.shortuser(ui.environ.get('REMOTE_USER') or ui.username())
-    user_includes = ui.configlist(
-        _NARROWACL_SECTION, username + '.includes',
-        ui.configlist(_NARROWACL_SECTION, 'default.includes'))
-    user_excludes = ui.configlist(
-        _NARROWACL_SECTION, username + '.excludes',
-        ui.configlist(_NARROWACL_SECTION, 'default.excludes'))
-    if not user_includes:
-        raise error.Abort(_("{} configuration for user {} is empty")
-                          .format(_NARROWACL_SECTION, username))
-
-    user_includes = [
-        'path:.' if p == '*' else 'path:' + p for p in user_includes]
-    user_excludes = [
-        'path:.' if p == '*' else 'path:' + p for p in user_excludes]
-
-    req_includes = set(kwargs.get(r'includepats', []))
-    req_excludes = set(kwargs.get(r'excludepats', []))
-
-    req_includes, req_excludes, invalid_includes = narrowspec.restrictpatterns(
-        req_includes, req_excludes, user_includes, user_excludes)
-
-    if invalid_includes:
-        raise error.Abort(
-            _("The following includes are not accessible for {}: {}")
-            .format(username, invalid_includes))
-
-    new_args = {}
-    new_args.update(kwargs)
-    new_args['includepats'] = req_includes
-    if req_excludes:
-        new_args['excludepats'] = req_excludes
-    return new_args
-
 @bundle2.parthandler(_SPECPART, (_SPECPART_INCLUDE, _SPECPART_EXCLUDE))
 def _handlechangespec_2(op, inpart):
     includepats = set(inpart.params.get(_SPECPART_INCLUDE, '').splitlines())
     excludepats = set(inpart.params.get(_SPECPART_EXCLUDE, '').splitlines())
-    if not changegroup.NARROW_REQUIREMENT in op.repo.requirements:
-        op.repo.requirements.add(changegroup.NARROW_REQUIREMENT)
+    narrowspec.validatepatterns(includepats)
+    narrowspec.validatepatterns(excludepats)
+
+    if not repository.NARROW_REQUIREMENT in op.repo.requirements:
+        op.repo.requirements.add(repository.NARROW_REQUIREMENT)
         op.repo._writerequirements()
     op.repo.setnarrowpats(includepats, excludepats)
 
@@ -467,6 +305,7 @@
     getbundleargs = wireprototypes.GETBUNDLE_ARGUMENTS
 
     getbundleargs['narrow'] = 'boolean'
+    getbundleargs['widen'] = 'boolean'
     getbundleargs['depth'] = 'plain'
     getbundleargs['oldincludepats'] = 'csv'
     getbundleargs['oldexcludepats'] = 'csv'
@@ -479,27 +318,17 @@
     def wrappedcgfn(*args, **kwargs):
         repo = args[1]
         if repo.ui.has_section(_NARROWACL_SECTION):
-            getbundlechangegrouppart_narrow(
-                *args, **applyacl_narrow(repo, kwargs))
-        elif kwargs.get(r'narrow', False):
+            kwargs = exchange.applynarrowacl(repo, kwargs)
+
+        if (kwargs.get(r'narrow', False) and
+            repo.ui.configbool('experimental', 'narrowservebrokenellipses')):
             getbundlechangegrouppart_narrow(*args, **kwargs)
+        elif kwargs.get(r'widen', False) and kwargs.get(r'narrow', False):
+            getbundlechangegrouppart_widen(*args, **kwargs)
         else:
             origcgfn(*args, **kwargs)
     exchange.getbundle2partsmapping['changegroup'] = wrappedcgfn
 
-    # disable rev branch cache exchange when serving a narrow bundle
-    # (currently incompatible with that part)
-    origrbcfn = exchange.getbundle2partsmapping['cache:rev-branch-cache']
-    def wrappedcgfn(*args, **kwargs):
-        repo = args[1]
-        if repo.ui.has_section(_NARROWACL_SECTION):
-            return
-        elif kwargs.get(r'narrow', False):
-            return
-        else:
-            origrbcfn(*args, **kwargs)
-    exchange.getbundle2partsmapping['cache:rev-branch-cache'] = wrappedcgfn
-
     # Extend changegroup receiver so client can fixup after widen requests.
     origcghandler = bundle2.parthandlermapping['changegroup']
     def wrappedcghandler(op, inpart):
--- a/hgext/narrow/narrowchangegroup.py	Tue Sep 25 16:32:38 2018 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,373 +0,0 @@
-# narrowchangegroup.py - narrow clone changegroup creation and consumption
-#
-# Copyright 2017 Google, Inc.
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from mercurial.i18n import _
-from mercurial import (
-    changegroup,
-    error,
-    extensions,
-    manifest,
-    match as matchmod,
-    mdiff,
-    node,
-    revlog,
-    util,
-)
-
-def setup():
-
-    def _cgmatcher(cgpacker):
-        localmatcher = cgpacker._repo.narrowmatch()
-        remotematcher = getattr(cgpacker, '_narrow_matcher', lambda: None)()
-        if remotematcher:
-            return matchmod.intersectmatchers(localmatcher, remotematcher)
-        else:
-            return localmatcher
-
-    def prune(orig, self, revlog, missing, commonrevs):
-        if isinstance(revlog, manifest.manifestrevlog):
-            matcher = _cgmatcher(self)
-            if (matcher and
-                not matcher.visitdir(revlog._dir[:-1] or '.')):
-                return []
-        return orig(self, revlog, missing, commonrevs)
-
-    extensions.wrapfunction(changegroup.cg1packer, 'prune', prune)
-
-    def generatefiles(orig, self, changedfiles, linknodes, commonrevs,
-                      source):
-        matcher = _cgmatcher(self)
-        if matcher:
-            changedfiles = list(filter(matcher, changedfiles))
-        if getattr(self, 'is_shallow', False):
-            # See comment in generate() for why this sadness is a thing.
-            mfdicts = self._mfdicts
-            del self._mfdicts
-            # In a shallow clone, the linknodes callback needs to also include
-            # those file nodes that are in the manifests we sent but weren't
-            # introduced by those manifests.
-            commonctxs = [self._repo[c] for c in commonrevs]
-            oldlinknodes = linknodes
-            clrev = self._repo.changelog.rev
-            def linknodes(flog, fname):
-                for c in commonctxs:
-                    try:
-                        fnode = c.filenode(fname)
-                        self.clrev_to_localrev[c.rev()] = flog.rev(fnode)
-                    except error.ManifestLookupError:
-                        pass
-                links = oldlinknodes(flog, fname)
-                if len(links) != len(mfdicts):
-                    for mf, lr in mfdicts:
-                        fnode = mf.get(fname, None)
-                        if fnode in links:
-                            links[fnode] = min(links[fnode], lr, key=clrev)
-                        elif fnode:
-                            links[fnode] = lr
-                return links
-        return orig(self, changedfiles, linknodes, commonrevs, source)
-    extensions.wrapfunction(
-        changegroup.cg1packer, 'generatefiles', generatefiles)
-
-    def ellipsisdata(packer, rev, revlog_, p1, p2, data, linknode):
-        n = revlog_.node(rev)
-        p1n, p2n = revlog_.node(p1), revlog_.node(p2)
-        flags = revlog_.flags(rev)
-        flags |= revlog.REVIDX_ELLIPSIS
-        meta = packer.builddeltaheader(
-            n, p1n, p2n, node.nullid, linknode, flags)
-        # TODO: try and actually send deltas for ellipsis data blocks
-        diffheader = mdiff.trivialdiffheader(len(data))
-        l = len(meta) + len(diffheader) + len(data)
-        return ''.join((changegroup.chunkheader(l),
-                        meta,
-                        diffheader,
-                        data))
-
-    def close(orig, self):
-        getattr(self, 'clrev_to_localrev', {}).clear()
-        if getattr(self, 'next_clrev_to_localrev', {}):
-            self.clrev_to_localrev = self.next_clrev_to_localrev
-            del self.next_clrev_to_localrev
-        self.changelog_done = True
-        return orig(self)
-    extensions.wrapfunction(changegroup.cg1packer, 'close', close)
-
-    # In a perfect world, we'd generate better ellipsis-ified graphs
-    # for non-changelog revlogs. In practice, we haven't started doing
-    # that yet, so the resulting DAGs for the manifestlog and filelogs
-    # are actually full of bogus parentage on all the ellipsis
-    # nodes. This has the side effect that, while the contents are
-    # correct, the individual DAGs might be completely out of whack in
-    # a case like 882681bc3166 and its ancestors (back about 10
-    # revisions or so) in the main hg repo.
-    #
-    # The one invariant we *know* holds is that the new (potentially
-    # bogus) DAG shape will be valid if we order the nodes in the
-    # order that they're introduced in dramatis personae by the
-    # changelog, so what we do is we sort the non-changelog histories
-    # by the order in which they are used by the changelog.
-    def _sortgroup(orig, self, revlog, nodelist, lookup):
-        if not util.safehasattr(self, 'full_nodes') or not self.clnode_to_rev:
-            return orig(self, revlog, nodelist, lookup)
-        key = lambda n: self.clnode_to_rev[lookup(n)]
-        return [revlog.rev(n) for n in sorted(nodelist, key=key)]
-
-    extensions.wrapfunction(changegroup.cg1packer, '_sortgroup', _sortgroup)
-
-    def generate(orig, self, commonrevs, clnodes, fastpathlinkrev, source):
-        '''yield a sequence of changegroup chunks (strings)'''
-        # Note: other than delegating to orig, the only deviation in
-        # logic from normal hg's generate is marked with BEGIN/END
-        # NARROW HACK.
-        if not util.safehasattr(self, 'full_nodes'):
-            # not sending a narrow bundle
-            for x in orig(self, commonrevs, clnodes, fastpathlinkrev, source):
-                yield x
-            return
-
-        repo = self._repo
-        cl = repo.changelog
-        mfl = repo.manifestlog
-        mfrevlog = mfl._revlog
-
-        clrevorder = {}
-        mfs = {} # needed manifests
-        fnodes = {} # needed file nodes
-        changedfiles = set()
-
-        # Callback for the changelog, used to collect changed files and manifest
-        # nodes.
-        # Returns the linkrev node (identity in the changelog case).
-        def lookupcl(x):
-            c = cl.read(x)
-            clrevorder[x] = len(clrevorder)
-            # BEGIN NARROW HACK
-            #
-            # Only update mfs if x is going to be sent. Otherwise we
-            # end up with bogus linkrevs specified for manifests and
-            # we skip some manifest nodes that we should otherwise
-            # have sent.
-            if x in self.full_nodes or cl.rev(x) in self.precomputed_ellipsis:
-                n = c[0]
-                # record the first changeset introducing this manifest version
-                mfs.setdefault(n, x)
-                # Set this narrow-specific dict so we have the lowest manifest
-                # revnum to look up for this cl revnum. (Part of mapping
-                # changelog ellipsis parents to manifest ellipsis parents)
-                self.next_clrev_to_localrev.setdefault(cl.rev(x),
-                                                       mfrevlog.rev(n))
-            # We can't trust the changed files list in the changeset if the
-            # client requested a shallow clone.
-            if self.is_shallow:
-                changedfiles.update(mfl[c[0]].read().keys())
-            else:
-                changedfiles.update(c[3])
-            # END NARROW HACK
-            # Record a complete list of potentially-changed files in
-            # this manifest.
-            return x
-
-        self._verbosenote(_('uncompressed size of bundle content:\n'))
-        size = 0
-        for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
-            size += len(chunk)
-            yield chunk
-        self._verbosenote(_('%8.i (changelog)\n') % size)
-
-        # We need to make sure that the linkrev in the changegroup refers to
-        # the first changeset that introduced the manifest or file revision.
-        # The fastpath is usually safer than the slowpath, because the filelogs
-        # are walked in revlog order.
-        #
-        # When taking the slowpath with reorder=None and the manifest revlog
-        # uses generaldelta, the manifest may be walked in the "wrong" order.
-        # Without 'clrevorder', we would get an incorrect linkrev (see fix in
-        # cc0ff93d0c0c).
-        #
-        # When taking the fastpath, we are only vulnerable to reordering
-        # of the changelog itself. The changelog never uses generaldelta, so
-        # it is only reordered when reorder=True. To handle this case, we
-        # simply take the slowpath, which already has the 'clrevorder' logic.
-        # This was also fixed in cc0ff93d0c0c.
-        fastpathlinkrev = fastpathlinkrev and not self._reorder
-        # Treemanifests don't work correctly with fastpathlinkrev
-        # either, because we don't discover which directory nodes to
-        # send along with files. This could probably be fixed.
-        fastpathlinkrev = fastpathlinkrev and (
-            'treemanifest' not in repo.requirements)
-        # Shallow clones also don't work correctly with fastpathlinkrev
-        # because file nodes may need to be sent for a manifest even if they
-        # weren't introduced by that manifest.
-        fastpathlinkrev = fastpathlinkrev and not self.is_shallow
-
-        for chunk in self.generatemanifests(commonrevs, clrevorder,
-                fastpathlinkrev, mfs, fnodes, source):
-            yield chunk
-        # BEGIN NARROW HACK
-        mfdicts = None
-        if self.is_shallow:
-            mfdicts = [(self._repo.manifestlog[n].read(), lr)
-                       for (n, lr) in mfs.iteritems()]
-        # END NARROW HACK
-        mfs.clear()
-        clrevs = set(cl.rev(x) for x in clnodes)
-
-        if not fastpathlinkrev:
-            def linknodes(unused, fname):
-                return fnodes.get(fname, {})
-        else:
-            cln = cl.node
-            def linknodes(filerevlog, fname):
-                llr = filerevlog.linkrev
-                fln = filerevlog.node
-                revs = ((r, llr(r)) for r in filerevlog)
-                return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
-
-        # BEGIN NARROW HACK
-        #
-        # We need to pass the mfdicts variable down into
-        # generatefiles(), but more than one command might have
-        # wrapped generatefiles so we can't modify the function
-        # signature. Instead, we pass the data to ourselves using an
-        # instance attribute. I'm sorry.
-        self._mfdicts = mfdicts
-        # END NARROW HACK
-        for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
-                                        source):
-            yield chunk
-
-        yield self.close()
-
-        if clnodes:
-            repo.hook('outgoing', node=node.hex(clnodes[0]), source=source)
-    extensions.wrapfunction(changegroup.cg1packer, 'generate', generate)
-
-    def revchunk(orig, self, revlog, rev, prev, linknode):
-        if not util.safehasattr(self, 'full_nodes'):
-            # not sending a narrow changegroup
-            for x in orig(self, revlog, rev, prev, linknode):
-                yield x
-            return
-        # build up some mapping information that's useful later. See
-        # the local() nested function below.
-        if not self.changelog_done:
-            self.clnode_to_rev[linknode] = rev
-            linkrev = rev
-            self.clrev_to_localrev[linkrev] = rev
-        else:
-            linkrev = self.clnode_to_rev[linknode]
-            self.clrev_to_localrev[linkrev] = rev
-        # This is a node to send in full, because the changeset it
-        # corresponds to was a full changeset.
-        if linknode in self.full_nodes:
-            for x in orig(self, revlog, rev, prev, linknode):
-                yield x
-            return
-        # At this point, a node can either be one we should skip or an
-        # ellipsis. If it's not an ellipsis, bail immediately.
-        if linkrev not in self.precomputed_ellipsis:
-            return
-        linkparents = self.precomputed_ellipsis[linkrev]
-        def local(clrev):
-            """Turn a changelog revnum into a local revnum.
-
-            The ellipsis dag is stored as revnums on the changelog,
-            but when we're producing ellipsis entries for
-            non-changelog revlogs, we need to turn those numbers into
-            something local. This does that for us, and during the
-            changelog sending phase will also expand the stored
-            mappings as needed.
-            """
-            if clrev == node.nullrev:
-                return node.nullrev
-            if not self.changelog_done:
-                # If we're doing the changelog, it's possible that we
-                # have a parent that is already on the client, and we
-                # need to store some extra mapping information so that
-                # our contained ellipsis nodes will be able to resolve
-                # their parents.
-                if clrev not in self.clrev_to_localrev:
-                    clnode = revlog.node(clrev)
-                    self.clnode_to_rev[clnode] = clrev
-                return clrev
-            # Walk the ellipsis-ized changelog breadth-first looking for a
-            # change that has been linked from the current revlog.
-            #
-            # For a flat manifest revlog only a single step should be necessary
-            # as all relevant changelog entries are relevant to the flat
-            # manifest.
-            #
-            # For a filelog or tree manifest dirlog however not every changelog
-            # entry will have been relevant, so we need to skip some changelog
-            # nodes even after ellipsis-izing.
-            walk = [clrev]
-            while walk:
-                p = walk[0]
-                walk = walk[1:]
-                if p in self.clrev_to_localrev:
-                    return self.clrev_to_localrev[p]
-                elif p in self.full_nodes:
-                    walk.extend([pp for pp in self._repo.changelog.parentrevs(p)
-                                    if pp != node.nullrev])
-                elif p in self.precomputed_ellipsis:
-                    walk.extend([pp for pp in self.precomputed_ellipsis[p]
-                                    if pp != node.nullrev])
-                else:
-                    # In this case, we've got an ellipsis with parents
-                    # outside the current bundle (likely an
-                    # incremental pull). We "know" that we can use the
-                    # value of this same revlog at whatever revision
-                    # is pointed to by linknode. "Know" is in scare
-                    # quotes because I haven't done enough examination
-                    # of edge cases to convince myself this is really
-                    # a fact - it works for all the (admittedly
-                    # thorough) cases in our testsuite, but I would be
-                    # somewhat unsurprised to find a case in the wild
-                    # where this breaks down a bit. That said, I don't
-                    # know if it would hurt anything.
-                    for i in xrange(rev, 0, -1):
-                        if revlog.linkrev(i) == clrev:
-                            return i
-                    # We failed to resolve a parent for this node, so
-                    # we crash the changegroup construction.
-                    raise error.Abort(
-                        'unable to resolve parent while packing %r %r'
-                        ' for changeset %r' % (revlog.indexfile, rev, clrev))
-            return node.nullrev
-
-        if not linkparents or (
-            revlog.parentrevs(rev) == (node.nullrev, node.nullrev)):
-            p1, p2 = node.nullrev, node.nullrev
-        elif len(linkparents) == 1:
-            p1, = sorted(local(p) for p in linkparents)
-            p2 = node.nullrev
-        else:
-            p1, p2 = sorted(local(p) for p in linkparents)
-        n = revlog.node(rev)
-        yield ellipsisdata(
-            self, rev, revlog, p1, p2, revlog.revision(n), linknode)
-    extensions.wrapfunction(changegroup.cg1packer, 'revchunk', revchunk)
-
-    def deltaparent(orig, self, revlog, rev, p1, p2, prev):
-        if util.safehasattr(self, 'full_nodes'):
-            # TODO: send better deltas when in narrow mode.
-            #
-            # changegroup.group() loops over revisions to send,
-            # including revisions we'll skip. What this means is that
-            # `prev` will be a potentially useless delta base for all
-            # ellipsis nodes, as the client likely won't have it. In
-            # the future we should do bookkeeping about which nodes
-            # have been sent to the client, and try to be
-            # significantly smarter about delta bases. This is
-            # slightly tricky because this same code has to work for
-            # all revlogs, and we don't have the linkrev/linknode here.
-            return p1
-        return orig(self, revlog, rev, p1, p2, prev)
-    extensions.wrapfunction(changegroup.cg2packer, 'deltaparent', deltaparent)
--- a/hgext/narrow/narrowcommands.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/narrow/narrowcommands.py	Wed Sep 26 20:33:09 2018 +0900
@@ -7,13 +7,14 @@
 from __future__ import absolute_import
 
 import itertools
+import os
 
 from mercurial.i18n import _
 from mercurial import (
-    changegroup,
     cmdutil,
     commands,
     discovery,
+    encoding,
     error,
     exchange,
     extensions,
@@ -24,12 +25,14 @@
     pycompat,
     registrar,
     repair,
+    repository,
     repoview,
+    sparse,
     util,
 )
 
 from . import (
-    narrowbundle2,
+    narrowwirepeer,
 )
 
 table = {}
@@ -43,6 +46,8 @@
                      _("create a narrow clone of select files")))
     entry[1].append(('', 'depth', '',
                      _("limit the history fetched by distance from heads")))
+    entry[1].append(('', 'narrowspec', '',
+                     _("read narrowspecs from file")))
     # TODO(durin42): unify sparse/narrow --include/--exclude logic a bit
     if 'sparse' not in extensions.enabled():
         entry[1].append(('', 'include', [],
@@ -57,41 +62,36 @@
 
     extensions.wrapcommand(commands.table, 'archive', archivenarrowcmd)
 
-def expandpull(pullop, includepats, excludepats):
-    if not narrowspec.needsexpansion(includepats):
-        return includepats, excludepats
-
-    heads = pullop.heads or pullop.rheads
-    includepats, excludepats = pullop.remote.expandnarrow(
-        includepats, excludepats, heads)
-    pullop.repo.ui.debug('Expanded narrowspec to inc=%s, exc=%s\n' % (
-        includepats, excludepats))
-    return set(includepats), set(excludepats)
-
 def clonenarrowcmd(orig, ui, repo, *args, **opts):
     """Wraps clone command, so 'hg clone' first wraps localrepo.clone()."""
     opts = pycompat.byteskwargs(opts)
     wrappedextraprepare = util.nullcontextmanager()
-    opts_narrow = opts['narrow']
-    if opts_narrow:
-        def pullbundle2extraprepare_widen(orig, pullop, kwargs):
-            # Create narrow spec patterns from clone flags
-            includepats = narrowspec.parsepatterns(opts['include'])
-            excludepats = narrowspec.parsepatterns(opts['exclude'])
+    narrowspecfile = opts['narrowspec']
+
+    if narrowspecfile:
+        filepath = os.path.join(encoding.getcwd(), narrowspecfile)
+        ui.status(_("reading narrowspec from '%s'\n") % filepath)
+        try:
+            fdata = util.readfile(filepath)
+        except IOError as inst:
+            raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
+                              (filepath, encoding.strtolocal(inst.strerror)))
 
-            # If necessary, ask the server to expand the narrowspec.
-            includepats, excludepats = expandpull(
-                pullop, includepats, excludepats)
+        includes, excludes, profiles = sparse.parseconfig(ui, fdata, 'narrow')
+        if profiles:
+            raise error.Abort(_("cannot specify other files using '%include' in"
+                                " narrowspec"))
+
+        narrowspec.validatepatterns(includes)
+        narrowspec.validatepatterns(excludes)
 
-            if not includepats and excludepats:
-                # If nothing was included, we assume the user meant to include
-                # everything, except what they asked to exclude.
-                includepats = {'path:.'}
+        # narrowspec is passed so we should assume that user wants narrow clone
+        opts['narrow'] = True
+        opts['include'].extend(includes)
+        opts['exclude'].extend(excludes)
 
-            pullop.repo.setnarrowpats(includepats, excludepats)
-
-            # This will populate 'includepats' etc with the values from the
-            # narrowspec we just saved.
+    if opts['narrow']:
+        def pullbundle2extraprepare_widen(orig, pullop, kwargs):
             orig(pullop, kwargs)
 
             if opts.get('depth'):
@@ -99,22 +99,13 @@
         wrappedextraprepare = extensions.wrappedfunction(exchange,
             '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
 
-    def pullnarrow(orig, repo, *args, **kwargs):
-        if opts_narrow:
-            repo.requirements.add(changegroup.NARROW_REQUIREMENT)
-            repo._writerequirements()
-
-        return orig(repo, *args, **kwargs)
-
-    wrappedpull = extensions.wrappedfunction(exchange, 'pull', pullnarrow)
-
-    with wrappedextraprepare, wrappedpull:
+    with wrappedextraprepare:
         return orig(ui, repo, *args, **pycompat.strkwargs(opts))
 
 def pullnarrowcmd(orig, ui, repo, *args, **opts):
     """Wraps pull command to allow modifying narrow spec."""
     wrappedextraprepare = util.nullcontextmanager()
-    if changegroup.NARROW_REQUIREMENT in repo.requirements:
+    if repository.NARROW_REQUIREMENT in repo.requirements:
 
         def pullbundle2extraprepare_widen(orig, pullop, kwargs):
             orig(pullop, kwargs)
@@ -128,7 +119,7 @@
 
 def archivenarrowcmd(orig, ui, repo, *args, **opts):
     """Wraps archive command to narrow the default includes."""
-    if changegroup.NARROW_REQUIREMENT in repo.requirements:
+    if repository.NARROW_REQUIREMENT in repo.requirements:
         repo_includes, repo_excludes = repo.narrowpats
         includes = set(opts.get(r'include', []))
         excludes = set(opts.get(r'exclude', []))
@@ -142,10 +133,10 @@
 
 def pullbundle2extraprepare(orig, pullop, kwargs):
     repo = pullop.repo
-    if changegroup.NARROW_REQUIREMENT not in repo.requirements:
+    if repository.NARROW_REQUIREMENT not in repo.requirements:
         return orig(pullop, kwargs)
 
-    if narrowbundle2.NARROWCAP not in pullop.remotebundle2caps:
+    if narrowwirepeer.NARROWCAP not in pullop.remote.capabilities():
         raise error.Abort(_("server doesn't support narrow clones"))
     orig(pullop, kwargs)
     kwargs['narrow'] = True
@@ -154,18 +145,31 @@
     kwargs['oldexcludepats'] = exclude
     kwargs['includepats'] = include
     kwargs['excludepats'] = exclude
-    kwargs['known'] = [node.hex(ctx.node()) for ctx in
-                       repo.set('::%ln', pullop.common)
-                       if ctx.node() != node.nullid]
-    if not kwargs['known']:
-        # Mercurial serialized an empty list as '' and deserializes it as
-        # [''], so delete it instead to avoid handling the empty string on the
-        # server.
-        del kwargs['known']
+    # calculate known nodes only in ellipses cases because in non-ellipses cases
+    # we have all the nodes
+    if narrowwirepeer.ELLIPSESCAP in pullop.remote.capabilities():
+        kwargs['known'] = [node.hex(ctx.node()) for ctx in
+                           repo.set('::%ln', pullop.common)
+                           if ctx.node() != node.nullid]
+        if not kwargs['known']:
+            # Mercurial serializes an empty list as '' and deserializes it as
+            # [''], so delete it instead to avoid handling the empty string on
+            # the server.
+            del kwargs['known']
 
 extensions.wrapfunction(exchange,'_pullbundle2extraprepare',
                         pullbundle2extraprepare)
 
+# This is an extension point for filesystems that need to do something other
+# than just blindly unlink the files. It's not clear what arguments would be
+# useful, so we're passing in a fair number of them, some of them redundant.
+def _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes, newexcludes,
+                       oldmatch, newmatch):
+    for f in repo.dirstate:
+        if not newmatch(f):
+            repo.dirstate.drop(f)
+            repo.wvfs.unlinkpath(f)
+
 def _narrow(ui, repo, remote, commoninc, oldincludes, oldexcludes,
             newincludes, newexcludes, force):
     oldmatch = narrowspec.match(repo.root, oldincludes, oldexcludes)
@@ -241,10 +245,8 @@
                 util.unlinkpath(repo.svfs.join(f))
                 repo.store.markremoved(f)
 
-            for f in repo.dirstate:
-                if not newmatch(f):
-                    repo.dirstate.drop(f)
-                    repo.wvfs.unlinkpath(f)
+            _narrowcleanupwdir(repo, oldincludes, oldexcludes, newincludes,
+                               newexcludes, oldmatch, newmatch)
             repo.setnarrowpats(newincludes, newexcludes)
 
         repo.destroyed()
@@ -252,15 +254,12 @@
 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
     newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
 
-    # TODO(martinvonz): Get expansion working with widening/narrowing.
-    if narrowspec.needsexpansion(newincludes):
-        raise error.Abort('Expansion not yet supported on pull')
-
     def pullbundle2extraprepare_widen(orig, pullop, kwargs):
         orig(pullop, kwargs)
         # The old{in,ex}cludepats have already been set by orig()
         kwargs['includepats'] = newincludes
         kwargs['excludepats'] = newexcludes
+        kwargs['widen'] = True
     wrappedextraprepare = extensions.wrappedfunction(exchange,
         '_pullbundle2extraprepare', pullbundle2extraprepare_widen)
 
@@ -269,6 +268,8 @@
     def setnewnarrowpats():
         repo.setnarrowpats(newincludes, newexcludes)
     repo.setnewnarrowpats = setnewnarrowpats
+    # silence the devel-warning of applying an empty changegroup
+    overrides = {('devel', 'all-warnings'): False}
 
     with ui.uninterruptable():
         ds = repo.dirstate
@@ -276,11 +277,12 @@
         with ds.parentchange():
             ds.setparents(node.nullid, node.nullid)
         common = commoninc[0]
-        with wrappedextraprepare:
+        with wrappedextraprepare, repo.ui.configoverride(overrides, 'widen'):
             exchange.pull(repo, remote, heads=common)
         with ds.parentchange():
             ds.setparents(p1, p2)
 
+        repo.setnewnarrowpats()
         actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
         addgaction = actions['g'].append
 
@@ -299,6 +301,7 @@
     [('', 'addinclude', [], _('new paths to include')),
      ('', 'removeinclude', [], _('old paths to no longer include')),
      ('', 'addexclude', [], _('new paths to exclude')),
+     ('', 'import-rules', '', _('import narrowspecs from a file')),
      ('', 'removeexclude', [], _('old paths to no longer exclude')),
      ('', 'clear', False, _('whether to replace the existing narrowspec')),
      ('', 'force-delete-local-changes', False,
@@ -331,7 +334,7 @@
     empty and will not match any files.
     """
     opts = pycompat.byteskwargs(opts)
-    if changegroup.NARROW_REQUIREMENT not in repo.requirements:
+    if repository.NARROW_REQUIREMENT not in repo.requirements:
         ui.warn(_('The narrow command is only supported on respositories cloned'
                   ' with --narrow.\n'))
         return 1
@@ -342,8 +345,22 @@
         ui.warn(_('The --clear option is not yet supported.\n'))
         return 1
 
-    if narrowspec.needsexpansion(opts['addinclude'] + opts['addexclude']):
-        raise error.Abort('Expansion not yet supported on widen/narrow')
+    # import rules from a file
+    newrules = opts.get('import_rules')
+    if newrules:
+        try:
+            filepath = os.path.join(encoding.getcwd(), newrules)
+            fdata = util.readfile(filepath)
+        except IOError as inst:
+            raise error.Abort(_("cannot read narrowspecs from '%s': %s") %
+                              (filepath, encoding.strtolocal(inst.strerror)))
+        includepats, excludepats, profiles = sparse.parseconfig(ui, fdata,
+                                                                'narrow')
+        if profiles:
+            raise error.Abort(_("including other spec files using '%include' "
+                                "is not supported in narrowspec"))
+        opts['addinclude'].extend(includepats)
+        opts['addexclude'].extend(excludepats)
 
     addedincludes = narrowspec.parsepatterns(opts['addinclude'])
     removedincludes = narrowspec.parsepatterns(opts['removeinclude'])
--- a/hgext/narrow/narrowdirstate.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/narrow/narrowdirstate.py	Wed Sep 26 20:33:09 2018 +0900
@@ -11,8 +11,6 @@
 from mercurial import (
     error,
     match as matchmod,
-    narrowspec,
-    util as hgutil,
 )
 
 def wrapdirstate(repo, dirstate):
@@ -29,10 +27,6 @@
             return fn(self, *args)
         return _wrapper
 
-    def _narrowbackupname(backupname):
-        assert 'dirstate' in backupname
-        return backupname.replace('dirstate', narrowspec.FILENAME)
-
     class narrowdirstate(dirstate.__class__):
         def walk(self, match, subrepos, unknown, ignored, full=True,
                  narrowonly=True):
@@ -78,22 +72,5 @@
                 allfiles = [f for f in allfiles if repo.narrowmatch()(f)]
             super(narrowdirstate, self).rebuild(parent, allfiles, changedfiles)
 
-        def restorebackup(self, tr, backupname):
-            self._opener.rename(_narrowbackupname(backupname),
-                                narrowspec.FILENAME, checkambig=True)
-            super(narrowdirstate, self).restorebackup(tr, backupname)
-
-        def savebackup(self, tr, backupname):
-            super(narrowdirstate, self).savebackup(tr, backupname)
-
-            narrowbackupname = _narrowbackupname(backupname)
-            self._opener.tryunlink(narrowbackupname)
-            hgutil.copyfile(self._opener.join(narrowspec.FILENAME),
-                            self._opener.join(narrowbackupname), hardlink=True)
-
-        def clearbackup(self, tr, backupname):
-            super(narrowdirstate, self).clearbackup(tr, backupname)
-            self._opener.unlink(_narrowbackupname(backupname))
-
     dirstate.__class__ = narrowdirstate
     return dirstate
--- a/hgext/narrow/narrowrepo.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/narrow/narrowrepo.py	Wed Sep 26 20:33:09 2018 +0900
@@ -7,64 +7,24 @@
 
 from __future__ import absolute_import
 
-from mercurial import (
-    changegroup,
-    hg,
-    narrowspec,
-    scmutil,
-)
-
 from . import (
     narrowdirstate,
-    narrowrevlog,
+    narrowwirepeer,
 )
 
-def wrappostshare(orig, sourcerepo, destrepo, **kwargs):
-    orig(sourcerepo, destrepo, **kwargs)
-    if changegroup.NARROW_REQUIREMENT in sourcerepo.requirements:
-        with destrepo.wlock():
-            with destrepo.vfs('shared', 'a') as fp:
-                fp.write(narrowspec.FILENAME + '\n')
-
-def unsharenarrowspec(orig, ui, repo, repopath):
-    if (changegroup.NARROW_REQUIREMENT in repo.requirements
-        and repo.path == repopath and repo.shared()):
-        srcrepo = hg.sharedreposource(repo)
-        with srcrepo.vfs(narrowspec.FILENAME) as f:
-            spec = f.read()
-        with repo.vfs(narrowspec.FILENAME, 'w') as f:
-            f.write(spec)
-    return orig(ui, repo, repopath)
-
 def wraprepo(repo):
     """Enables narrow clone functionality on a single local repository."""
 
     class narrowrepository(repo.__class__):
 
-        def file(self, f):
-            fl = super(narrowrepository, self).file(f)
-            narrowrevlog.makenarrowfilelog(fl, self.narrowmatch())
-            return fl
-
-        # I'm not sure this is the right place to do this filter.
-        # context._manifestmatches() would probably be better, or perhaps
-        # move it to a later place, in case some of the callers do want to know
-        # which directories changed. This seems to work for now, though.
-        def status(self, *args, **kwargs):
-            s = super(narrowrepository, self).status(*args, **kwargs)
-            narrowmatch = self.narrowmatch()
-            modified = list(filter(narrowmatch, s.modified))
-            added = list(filter(narrowmatch, s.added))
-            removed = list(filter(narrowmatch, s.removed))
-            deleted = list(filter(narrowmatch, s.deleted))
-            unknown = list(filter(narrowmatch, s.unknown))
-            ignored = list(filter(narrowmatch, s.ignored))
-            clean = list(filter(narrowmatch, s.clean))
-            return scmutil.status(modified, added, removed, deleted, unknown,
-                                  ignored, clean)
-
         def _makedirstate(self):
             dirstate = super(narrowrepository, self)._makedirstate()
             return narrowdirstate.wrapdirstate(self, dirstate)
 
+        def peer(self):
+            peer = super(narrowrepository, self).peer()
+            peer._caps.add(narrowwirepeer.NARROWCAP)
+            peer._caps.add(narrowwirepeer.ELLIPSESCAP)
+            return peer
+
     repo.__class__ = narrowrepository
--- a/hgext/narrow/narrowrevlog.py	Tue Sep 25 16:32:38 2018 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,80 +0,0 @@
-# narrowrevlog.py - revlog storing irrelevant nodes as "ellipsis" nodes
-#
-# Copyright 2017 Google, Inc.
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from mercurial import (
-   revlog,
-   util,
-)
-
-def readtransform(self, text):
-    return text, False
-
-def writetransform(self, text):
-    return text, False
-
-def rawtransform(self, text):
-    return False
-
-revlog.addflagprocessor(revlog.REVIDX_ELLIPSIS,
-                        (readtransform, writetransform, rawtransform))
-
-def setup():
-    # We just wanted to add the flag processor, which is done at module
-    # load time.
-    pass
-
-def makenarrowfilelog(fl, narrowmatch):
-    class narrowfilelog(fl.__class__):
-        def renamed(self, node):
-            # Renames that come from outside the narrowspec are
-            # problematic at least for git-diffs, because we lack the
-            # base text for the rename. This logic was introduced in
-            # 3cd72b1 of narrowhg (authored by martinvonz, reviewed by
-            # adgar), but that revision doesn't have any additional
-            # commentary on what problems we can encounter.
-            m = super(narrowfilelog, self).renamed(node)
-            if m and not narrowmatch(m[0]):
-                return None
-            return m
-
-        def size(self, rev):
-            # We take advantage of the fact that remotefilelog
-            # lacks a node() method to just skip the
-            # rename-checking logic when on remotefilelog. This
-            # might be incorrect on other non-revlog-based storage
-            # engines, but for now this seems to be fine.
-            #
-            # TODO: when remotefilelog is in core, improve this to
-            # explicitly look for remotefilelog instead of cheating
-            # with a hasattr check.
-            if util.safehasattr(self, 'node'):
-                node = self.node(rev)
-                # Because renamed() is overridden above to
-                # sometimes return None even if there is metadata
-                # in the revlog, size can be incorrect for
-                # copies/renames, so we need to make sure we call
-                # the super class's implementation of renamed()
-                # for the purpose of size calculation.
-                if super(narrowfilelog, self).renamed(node):
-                    return len(self.read(node))
-            return super(narrowfilelog, self).size(rev)
-
-        def cmp(self, node, text):
-            different = super(narrowfilelog, self).cmp(node, text)
-            if different:
-                # Similar to size() above, if the file was copied from
-                # a file outside the narrowspec, the super class's
-                # would have returned True because we tricked it into
-                # thinking that the file was not renamed.
-                if super(narrowfilelog, self).renamed(node):
-                    t2 = self.read(node)
-                    return t2 != text
-            return different
-
-    fl.__class__ = narrowfilelog
--- a/hgext/narrow/narrowtemplates.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/narrow/narrowtemplates.py	Wed Sep 26 20:33:09 2018 +0900
@@ -42,7 +42,7 @@
             return 'outsidenarrow'
     return ''
 
-@revsetpredicate('ellipsis')
+@revsetpredicate('ellipsis()')
 def ellipsisrevset(repo, subset, x):
     """Changesets that are ellipsis nodes."""
     return subset.filter(lambda r: _isellipsis(repo, r))
--- a/hgext/narrow/narrowwirepeer.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/narrow/narrowwirepeer.py	Wed Sep 26 20:33:09 2018 +0900
@@ -7,36 +7,25 @@
 
 from __future__ import absolute_import
 
-from mercurial.i18n import _
 from mercurial import (
-    error,
     extensions,
     hg,
-    narrowspec,
-    node,
+    wireprotov1server,
 )
 
+NARROWCAP = 'exp-narrow-1'
+ELLIPSESCAP = 'exp-ellipses-1'
+
 def uisetup():
-    def peersetup(ui, peer):
-        # We must set up the expansion before reposetup below, since it's used
-        # at clone time before we have a repo.
-        class expandingpeer(peer.__class__):
-            def expandnarrow(self, narrow_include, narrow_exclude, nodes):
-                ui.status(_("expanding narrowspec\n"))
-                if not self.capable('exp-expandnarrow'):
-                    raise error.Abort(
-                        'peer does not support expanding narrowspecs')
+    extensions.wrapfunction(wireprotov1server, '_capabilities', addnarrowcap)
 
-                hex_nodes = (node.hex(n) for n in nodes)
-                new_narrowspec = self._call(
-                    'expandnarrow',
-                    includepats=','.join(narrow_include),
-                    excludepats=','.join(narrow_exclude),
-                    nodes=','.join(hex_nodes))
-
-                return narrowspec.parseserverpatterns(new_narrowspec)
-        peer.__class__ = expandingpeer
-    hg.wirepeersetupfuncs.append(peersetup)
+def addnarrowcap(orig, repo, proto):
+    """add the narrow capability to the server"""
+    caps = orig(repo, proto)
+    caps.append(NARROWCAP)
+    if repo.ui.configbool('experimental', 'narrowservebrokenellipses'):
+        caps.append(ELLIPSESCAP)
+    return caps
 
 def reposetup(repo):
     def wirereposetup(ui, peer):
--- a/hgext/patchbomb.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/patchbomb.py	Wed Sep 26 20:33:09 2018 +0900
@@ -73,7 +73,7 @@
 '''
 from __future__ import absolute_import
 
-import email as emailmod
+import email.encoders as emailencoders
 import email.generator as emailgen
 import email.mime.base as emimebase
 import email.mime.multipart as emimemultipart
@@ -139,6 +139,11 @@
     default=None,
 )
 
+if pycompat.ispy3:
+    _bytesgenerator = emailgen.BytesGenerator
+else:
+    _bytesgenerator = emailgen.Generator
+
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -273,10 +278,11 @@
                                                  seqno=idx, total=total)
             else:
                 patchname = cmdutil.makefilename(repo[node], '%b.patch')
-        disposition = 'inline'
+        disposition = r'inline'
         if opts.get('attach'):
-            disposition = 'attachment'
-        p['Content-Disposition'] = disposition + '; filename=' + patchname
+            disposition = r'attachment'
+        p[r'Content-Disposition'] = (
+            disposition + r'; filename=' + encoding.strfromlocal(patchname))
         msg.attach(p)
     else:
         msg = mail.mimetextpatch(body, display=opts.get('test'))
@@ -370,12 +376,12 @@
     msg = emimemultipart.MIMEMultipart()
     if body:
         msg.attach(mail.mimeencode(ui, body, _charsets, opts.get(r'test')))
-    datapart = emimebase.MIMEBase('application', 'x-mercurial-bundle')
+    datapart = emimebase.MIMEBase(r'application', r'x-mercurial-bundle')
     datapart.set_payload(bundle)
     bundlename = '%s.hg' % opts.get(r'bundlename', 'bundle')
-    datapart.add_header('Content-Disposition', 'attachment',
-                        filename=bundlename)
-    emailmod.Encoders.encode_base64(datapart)
+    datapart.add_header(r'Content-Disposition', r'attachment',
+                        filename=encoding.strfromlocal(bundlename))
+    emailencoders.encode_base64(datapart)
     msg.attach(datapart)
     msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get(r'test'))
     return [(msg, subj, None)]
@@ -463,6 +469,11 @@
         ui.status(_("no changes found\n"))
     return revs
 
+def _msgid(node, timestamp):
+    hostname = encoding.strtolocal(socket.getfqdn())
+    hostname = encoding.environ.get('HGHOSTNAME', hostname)
+    return '<%s.%d@%s>' % (node, timestamp, hostname)
+
 emailopts = [
     ('', 'body', None, _('send patches as inline message text (default)')),
     ('a', 'attach', None, _('send patches as attachments')),
@@ -671,8 +682,7 @@
         start_time = dateutil.makedate()
 
     def genmsgid(id):
-        return '<%s.%d@%s>' % (id[:20], int(start_time[0]),
-                               encoding.strtolocal(socket.getfqdn()))
+        return _msgid(id[:20], int(start_time[0]))
 
     # deprecated config: patchbomb.from
     sender = (opts.get('from') or ui.config('email', 'from') or
@@ -780,10 +790,27 @@
             m['Bcc'] = ', '.join(bcc)
         if replyto:
             m['Reply-To'] = ', '.join(replyto)
+        # Fix up all headers to be native strings.
+        # TODO(durin42): this should probably be cleaned up above in the future.
+        if pycompat.ispy3:
+            for hdr, val in list(m.items()):
+                change = False
+                if isinstance(hdr, bytes):
+                    del m[hdr]
+                    hdr = pycompat.strurl(hdr)
+                    change = True
+                if isinstance(val, bytes):
+                    val = pycompat.strurl(val)
+                    if not change:
+                        # prevent duplicate headers
+                        del m[hdr]
+                    change = True
+                if change:
+                    m[hdr] = val
         if opts.get('test'):
             ui.status(_('displaying '), subj, ' ...\n')
             ui.pager('email')
-            generator = emailgen.Generator(ui, mangle_from_=False)
+            generator = _bytesgenerator(ui, mangle_from_=False)
             try:
                 generator.flatten(m, 0)
                 ui.write('\n')
@@ -799,8 +826,10 @@
                 # Exim does not remove the Bcc field
                 del m['Bcc']
             fp = stringio()
-            generator = emailgen.Generator(fp, mangle_from_=False)
+            generator = _bytesgenerator(fp, mangle_from_=False)
             generator.flatten(m, 0)
-            sendmail(sender_addr, to + bcc + cc, fp.getvalue())
+            alldests = to + bcc + cc
+            alldests = [encoding.strfromlocal(d) for d in alldests]
+            sendmail(sender_addr, alldests, fp.getvalue())
 
     progress.complete()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/phabricator.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,1019 @@
+# phabricator.py - simple Phabricator integration
+#
+# Copyright 2017 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""simple Phabricator integration (EXPERIMENTAL)
+
+This extension provides a ``phabsend`` command which sends a stack of
+changesets to Phabricator, and a ``phabread`` command which prints a stack of
+revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
+to update statuses in batch.
+
+By default, Phabricator requires ``Test Plan`` which might prevent some
+changeset from being sent. The requirement could be disabled by changing
+``differential.require-test-plan-field`` config server side.
+
+Config::
+
+    [phabricator]
+    # Phabricator URL
+    url = https://phab.example.com/
+
+    # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
+    # callsign is "FOO".
+    callsign = FOO
+
+    # curl command to use. If not set (default), use builtin HTTP library to
+    # communicate. If set, use the specified curl command. This could be useful
+    # if you need to specify advanced options that is not easily supported by
+    # the internal library.
+    curlcmd = curl --connect-timeout 2 --retry 3 --silent
+
+    [auth]
+    example.schemes = https
+    example.prefix = phab.example.com
+
+    # API token. Get it from https://$HOST/conduit/login/
+    example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
+"""
+
+from __future__ import absolute_import
+
+import itertools
+import json
+import operator
+import re
+
+from mercurial.node import bin, nullid
+from mercurial.i18n import _
+from mercurial import (
+    cmdutil,
+    context,
+    encoding,
+    error,
+    httpconnection as httpconnectionmod,
+    mdiff,
+    obsutil,
+    parser,
+    patch,
+    registrar,
+    scmutil,
+    smartset,
+    tags,
+    templateutil,
+    url as urlmod,
+    util,
+)
+from mercurial.utils import (
+    procutil,
+    stringutil,
+)
+
+# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
+# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
+# be specifying the version(s) of Mercurial they are tested with, or
+# leave the attribute unspecified.
+testedwith = 'ships-with-hg-core'
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+# developer config: phabricator.batchsize
+configitem(b'phabricator', b'batchsize',
+    default=12,
+)
+configitem(b'phabricator', b'callsign',
+    default=None,
+)
+configitem(b'phabricator', b'curlcmd',
+    default=None,
+)
+# developer config: phabricator.repophid
+configitem(b'phabricator', b'repophid',
+    default=None,
+)
+configitem(b'phabricator', b'url',
+    default=None,
+)
+configitem(b'phabsend', b'confirm',
+    default=False,
+)
+
+colortable = {
+    b'phabricator.action.created': b'green',
+    b'phabricator.action.skipped': b'magenta',
+    b'phabricator.action.updated': b'magenta',
+    b'phabricator.desc': b'',
+    b'phabricator.drev': b'bold',
+    b'phabricator.node': b'',
+}
+
+_VCR_FLAGS = [
+    (b'', b'test-vcr', b'',
+     _(b'Path to a vcr file. If nonexistent, will record a new vcr transcript'
+       b', otherwise will mock all http requests using the specified vcr file.'
+       b' (ADVANCED)'
+     )),
+]
+
+def vcrcommand(name, flags, spec):
+    fullflags = flags + _VCR_FLAGS
+    def decorate(fn):
+        def inner(*args, **kwargs):
+            cassette = kwargs.pop(r'test_vcr', None)
+            if cassette:
+                import hgdemandimport
+                with hgdemandimport.deactivated():
+                    import vcr as vcrmod
+                    import vcr.stubs as stubs
+                vcr = vcrmod.VCR(
+                    serializer=r'json',
+                    custom_patches=[
+                        (urlmod, 'httpconnection', stubs.VCRHTTPConnection),
+                        (urlmod, 'httpsconnection', stubs.VCRHTTPSConnection),
+                    ])
+                with vcr.use_cassette(cassette):
+                    return fn(*args, **kwargs)
+            return fn(*args, **kwargs)
+        inner.__name__ = fn.__name__
+        return command(name, fullflags, spec)(inner)
+    return decorate
+
+def urlencodenested(params):
+    """like urlencode, but works with nested parameters.
+
+    For example, if params is {'a': ['b', 'c'], 'd': {'e': 'f'}}, it will be
+    flattened to {'a[0]': 'b', 'a[1]': 'c', 'd[e]': 'f'} and then passed to
+    urlencode. Note: the encoding is consistent with PHP's http_build_query.
+    """
+    flatparams = util.sortdict()
+    def process(prefix, obj):
+        items = {list: enumerate, dict: lambda x: x.items()}.get(type(obj))
+        if items is None:
+            flatparams[prefix] = obj
+        else:
+            for k, v in items(obj):
+                if prefix:
+                    process(b'%s[%s]' % (prefix, k), v)
+                else:
+                    process(k, v)
+    process(b'', params)
+    return util.urlreq.urlencode(flatparams)
+
+printed_token_warning = False
+
+def readlegacytoken(repo, url):
+    """Transitional support for old phabricator tokens.
+
+    Remove before the 4.7 release.
+    """
+    groups = {}
+    for key, val in repo.ui.configitems(b'phabricator.auth'):
+        if b'.' not in key:
+            repo.ui.warn(_(b"ignoring invalid [phabricator.auth] key '%s'\n")
+                         % key)
+            continue
+        group, setting = key.rsplit(b'.', 1)
+        groups.setdefault(group, {})[setting] = val
+
+    token = None
+    for group, auth in groups.iteritems():
+        if url != auth.get(b'url'):
+            continue
+        token = auth.get(b'token')
+        if token:
+            break
+
+    global printed_token_warning
+
+    if token and not printed_token_warning:
+        printed_token_warning = True
+        repo.ui.warn(_(b'phabricator.auth.token is deprecated - please '
+                       b'migrate to auth.phabtoken.\n'))
+    return token
+
+def readurltoken(repo):
+    """return conduit url, token and make sure they exist
+
+    Currently read from [auth] config section. In the future, it might
+    make sense to read from .arcconfig and .arcrc as well.
+    """
+    url = repo.ui.config(b'phabricator', b'url')
+    if not url:
+        raise error.Abort(_(b'config %s.%s is required')
+                          % (b'phabricator', b'url'))
+
+    res = httpconnectionmod.readauthforuri(repo.ui, url, util.url(url).user)
+    token = None
+
+    if res:
+        group, auth = res
+
+        repo.ui.debug(b"using auth.%s.* for authentication\n" % group)
+
+        token = auth.get(b'phabtoken')
+
+    if not token:
+        token = readlegacytoken(repo, url)
+        if not token:
+            raise error.Abort(_(b'Can\'t find conduit token associated to %s')
+                              % (url,))
+
+    return url, token
+
+def callconduit(repo, name, params):
+    """call Conduit API, params is a dict. return json.loads result, or None"""
+    host, token = readurltoken(repo)
+    url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
+    repo.ui.debug(b'Conduit Call: %s %s\n' % (url, params))
+    params = params.copy()
+    params[b'api.token'] = token
+    data = urlencodenested(params)
+    curlcmd = repo.ui.config(b'phabricator', b'curlcmd')
+    if curlcmd:
+        sin, sout = procutil.popen2(b'%s -d @- %s'
+                                    % (curlcmd, procutil.shellquote(url)))
+        sin.write(data)
+        sin.close()
+        body = sout.read()
+    else:
+        urlopener = urlmod.opener(repo.ui, authinfo)
+        request = util.urlreq.request(url, data=data)
+        body = urlopener.open(request).read()
+    repo.ui.debug(b'Conduit Response: %s\n' % body)
+    parsed = json.loads(body)
+    if parsed.get(r'error_code'):
+        msg = (_(b'Conduit Error (%s): %s')
+               % (parsed[r'error_code'], parsed[r'error_info']))
+        raise error.Abort(msg)
+    return parsed[r'result']
+
+@vcrcommand(b'debugcallconduit', [], _(b'METHOD'))
+def debugcallconduit(ui, repo, name):
+    """call Conduit API
+
+    Call parameters are read from stdin as a JSON blob. Result will be written
+    to stdout as a JSON blob.
+    """
+    params = json.loads(ui.fin.read())
+    result = callconduit(repo, name, params)
+    s = json.dumps(result, sort_keys=True, indent=2, separators=(b',', b': '))
+    ui.write(b'%s\n' % s)
+
+def getrepophid(repo):
+    """given callsign, return repository PHID or None"""
+    # developer config: phabricator.repophid
+    repophid = repo.ui.config(b'phabricator', b'repophid')
+    if repophid:
+        return repophid
+    callsign = repo.ui.config(b'phabricator', b'callsign')
+    if not callsign:
+        return None
+    query = callconduit(repo, b'diffusion.repository.search',
+                        {b'constraints': {b'callsigns': [callsign]}})
+    if len(query[r'data']) == 0:
+        return None
+    repophid = encoding.strtolocal(query[r'data'][0][r'phid'])
+    repo.ui.setconfig(b'phabricator', b'repophid', repophid)
+    return repophid
+
+_differentialrevisiontagre = re.compile(b'\AD([1-9][0-9]*)\Z')
+_differentialrevisiondescre = re.compile(
+    b'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M)
+
+def getoldnodedrevmap(repo, nodelist):
+    """find previous nodes that has been sent to Phabricator
+
+    return {node: (oldnode, Differential diff, Differential Revision ID)}
+    for node in nodelist with known previous sent versions, or associated
+    Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
+    be ``None``.
+
+    Examines commit messages like "Differential Revision:" to get the
+    association information.
+
+    If such commit message line is not found, examines all precursors and their
+    tags. Tags with format like "D1234" are considered a match and the node
+    with that tag, and the number after "D" (ex. 1234) will be returned.
+
+    The ``old node``, if not None, is guaranteed to be the last diff of
+    corresponding Differential Revision, and exist in the repo.
+    """
+    url, token = readurltoken(repo)
+    unfi = repo.unfiltered()
+    nodemap = unfi.changelog.nodemap
+
+    result = {} # {node: (oldnode?, lastdiff?, drev)}
+    toconfirm = {} # {node: (force, {precnode}, drev)}
+    for node in nodelist:
+        ctx = unfi[node]
+        # For tags like "D123", put them into "toconfirm" to verify later
+        precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
+        for n in precnodes:
+            if n in nodemap:
+                for tag in unfi.nodetags(n):
+                    m = _differentialrevisiontagre.match(tag)
+                    if m:
+                        toconfirm[node] = (0, set(precnodes), int(m.group(1)))
+                        continue
+
+        # Check commit message
+        m = _differentialrevisiondescre.search(ctx.description())
+        if m:
+            toconfirm[node] = (1, set(precnodes), int(m.group(b'id')))
+
+    # Double check if tags are genuine by collecting all old nodes from
+    # Phabricator, and expect precursors overlap with it.
+    if toconfirm:
+        drevs = [drev for force, precs, drev in toconfirm.values()]
+        alldiffs = callconduit(unfi, b'differential.querydiffs',
+                               {b'revisionIDs': drevs})
+        getnode = lambda d: bin(encoding.unitolocal(
+            getdiffmeta(d).get(r'node', b''))) or None
+        for newnode, (force, precset, drev) in toconfirm.items():
+            diffs = [d for d in alldiffs.values()
+                     if int(d[r'revisionID']) == drev]
+
+            # "precursors" as known by Phabricator
+            phprecset = set(getnode(d) for d in diffs)
+
+            # Ignore if precursors (Phabricator and local repo) do not overlap,
+            # and force is not set (when commit message says nothing)
+            if not force and not bool(phprecset & precset):
+                tagname = b'D%d' % drev
+                tags.tag(repo, tagname, nullid, message=None, user=None,
+                         date=None, local=True)
+                unfi.ui.warn(_(b'D%s: local tag removed - does not match '
+                               b'Differential history\n') % drev)
+                continue
+
+            # Find the last node using Phabricator metadata, and make sure it
+            # exists in the repo
+            oldnode = lastdiff = None
+            if diffs:
+                lastdiff = max(diffs, key=lambda d: int(d[r'id']))
+                oldnode = getnode(lastdiff)
+                if oldnode and oldnode not in nodemap:
+                    oldnode = None
+
+            result[newnode] = (oldnode, lastdiff, drev)
+
+    return result
+
+def getdiff(ctx, diffopts):
+    """plain-text diff without header (user, commit message, etc)"""
+    output = util.stringio()
+    for chunk, _label in patch.diffui(ctx.repo(), ctx.p1().node(), ctx.node(),
+                                      None, opts=diffopts):
+        output.write(chunk)
+    return output.getvalue()
+
+def creatediff(ctx):
+    """create a Differential Diff"""
+    repo = ctx.repo()
+    repophid = getrepophid(repo)
+    # Create a "Differential Diff" via "differential.createrawdiff" API
+    params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
+    if repophid:
+        params[b'repositoryPHID'] = repophid
+    diff = callconduit(repo, b'differential.createrawdiff', params)
+    if not diff:
+        raise error.Abort(_(b'cannot create diff for %s') % ctx)
+    return diff
+
+def writediffproperties(ctx, diff):
+    """write metadata to diff so patches could be applied losslessly"""
+    params = {
+        b'diff_id': diff[r'id'],
+        b'name': b'hg:meta',
+        b'data': json.dumps({
+            b'user': ctx.user(),
+            b'date': b'%d %d' % ctx.date(),
+            b'node': ctx.hex(),
+            b'parent': ctx.p1().hex(),
+        }),
+    }
+    callconduit(ctx.repo(), b'differential.setdiffproperty', params)
+
+    params = {
+        b'diff_id': diff[r'id'],
+        b'name': b'local:commits',
+        b'data': json.dumps({
+            ctx.hex(): {
+                b'author': stringutil.person(ctx.user()),
+                b'authorEmail': stringutil.email(ctx.user()),
+                b'time': ctx.date()[0],
+            },
+        }),
+    }
+    callconduit(ctx.repo(), b'differential.setdiffproperty', params)
+
+def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
+                               olddiff=None, actions=None):
+    """create or update a Differential Revision
+
+    If revid is None, create a new Differential Revision, otherwise update
+    revid. If parentrevid is not None, set it as a dependency.
+
+    If oldnode is not None, check if the patch content (without commit message
+    and metadata) has changed before creating another diff.
+
+    If actions is not None, they will be appended to the transaction.
+    """
+    repo = ctx.repo()
+    if oldnode:
+        diffopts = mdiff.diffopts(git=True, context=32767)
+        oldctx = repo.unfiltered()[oldnode]
+        neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
+    else:
+        neednewdiff = True
+
+    transactions = []
+    if neednewdiff:
+        diff = creatediff(ctx)
+        transactions.append({b'type': b'update', b'value': diff[r'phid']})
+    else:
+        # Even if we don't need to upload a new diff because the patch content
+        # does not change. We might still need to update its metadata so
+        # pushers could know the correct node metadata.
+        assert olddiff
+        diff = olddiff
+    writediffproperties(ctx, diff)
+
+    # Use a temporary summary to set dependency. There might be better ways but
+    # I cannot find them for now. But do not do that if we are updating an
+    # existing revision (revid is not None) since that introduces visible
+    # churns (someone edited "Summary" twice) on the web page.
+    if parentrevid and revid is None:
+        summary = b'Depends on D%s' % parentrevid
+        transactions += [{b'type': b'summary', b'value': summary},
+                         {b'type': b'summary', b'value': b' '}]
+
+    if actions:
+        transactions += actions
+
+    # Parse commit message and update related fields.
+    desc = ctx.description()
+    info = callconduit(repo, b'differential.parsecommitmessage',
+                       {b'corpus': desc})
+    for k, v in info[r'fields'].items():
+        if k in [b'title', b'summary', b'testPlan']:
+            transactions.append({b'type': k, b'value': v})
+
+    params = {b'transactions': transactions}
+    if revid is not None:
+        # Update an existing Differential Revision
+        params[b'objectIdentifier'] = revid
+
+    revision = callconduit(repo, b'differential.revision.edit', params)
+    if not revision:
+        raise error.Abort(_(b'cannot create revision for %s') % ctx)
+
+    return revision, diff
+
+def userphids(repo, names):
+    """convert user names to PHIDs"""
+    query = {b'constraints': {b'usernames': names}}
+    result = callconduit(repo, b'user.search', query)
+    # username not found is not an error of the API. So check if we have missed
+    # some names here.
+    data = result[r'data']
+    resolved = set(entry[r'fields'][r'username'] for entry in data)
+    unresolved = set(names) - resolved
+    if unresolved:
+        raise error.Abort(_(b'unknown username: %s')
+                          % b' '.join(sorted(unresolved)))
+    return [entry[r'phid'] for entry in data]
+
+@vcrcommand(b'phabsend',
+         [(b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
+          (b'', b'amend', True, _(b'update commit messages')),
+          (b'', b'reviewer', [], _(b'specify reviewers')),
+          (b'', b'confirm', None, _(b'ask for confirmation before sending'))],
+         _(b'REV [OPTIONS]'))
+def phabsend(ui, repo, *revs, **opts):
+    """upload changesets to Phabricator
+
+    If there are multiple revisions specified, they will be send as a stack
+    with a linear dependencies relationship using the order specified by the
+    revset.
+
+    For the first time uploading changesets, local tags will be created to
+    maintain the association. After the first time, phabsend will check
+    obsstore and tags information so it can figure out whether to update an
+    existing Differential Revision, or create a new one.
+
+    If --amend is set, update commit messages so they have the
+    ``Differential Revision`` URL, remove related tags. This is similar to what
+    arcanist will do, and is more desired in author-push workflows. Otherwise,
+    use local tags to record the ``Differential Revision`` association.
+
+    The --confirm option lets you confirm changesets before sending them. You
+    can also add following to your configuration file to make it default
+    behaviour::
+
+        [phabsend]
+        confirm = true
+
+    phabsend will check obsstore and the above association to decide whether to
+    update an existing Differential Revision, or create a new one.
+    """
+    revs = list(revs) + opts.get(b'rev', [])
+    revs = scmutil.revrange(repo, revs)
+
+    if not revs:
+        raise error.Abort(_(b'phabsend requires at least one changeset'))
+    if opts.get(b'amend'):
+        cmdutil.checkunfinished(repo)
+
+    # {newnode: (oldnode, olddiff, olddrev}
+    oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
+
+    confirm = ui.configbool(b'phabsend', b'confirm')
+    confirm |= bool(opts.get(b'confirm'))
+    if confirm:
+        confirmed = _confirmbeforesend(repo, revs, oldmap)
+        if not confirmed:
+            raise error.Abort(_(b'phabsend cancelled'))
+
+    actions = []
+    reviewers = opts.get(b'reviewer', [])
+    if reviewers:
+        phids = userphids(repo, reviewers)
+        actions.append({b'type': b'reviewers.add', b'value': phids})
+
+    drevids = [] # [int]
+    diffmap = {} # {newnode: diff}
+
+    # Send patches one by one so we know their Differential Revision IDs and
+    # can provide dependency relationship
+    lastrevid = None
+    for rev in revs:
+        ui.debug(b'sending rev %d\n' % rev)
+        ctx = repo[rev]
+
+        # Get Differential Revision ID
+        oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
+        if oldnode != ctx.node() or opts.get(b'amend'):
+            # Create or update Differential Revision
+            revision, diff = createdifferentialrevision(
+                ctx, revid, lastrevid, oldnode, olddiff, actions)
+            diffmap[ctx.node()] = diff
+            newrevid = int(revision[r'object'][r'id'])
+            if revid:
+                action = b'updated'
+            else:
+                action = b'created'
+
+            # Create a local tag to note the association, if commit message
+            # does not have it already
+            m = _differentialrevisiondescre.search(ctx.description())
+            if not m or int(m.group(b'id')) != newrevid:
+                tagname = b'D%d' % newrevid
+                tags.tag(repo, tagname, ctx.node(), message=None, user=None,
+                         date=None, local=True)
+        else:
+            # Nothing changed. But still set "newrevid" so the next revision
+            # could depend on this one.
+            newrevid = revid
+            action = b'skipped'
+
+        actiondesc = ui.label(
+            {b'created': _(b'created'),
+             b'skipped': _(b'skipped'),
+             b'updated': _(b'updated')}[action],
+            b'phabricator.action.%s' % action)
+        drevdesc = ui.label(b'D%s' % newrevid, b'phabricator.drev')
+        nodedesc = ui.label(bytes(ctx), b'phabricator.node')
+        desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
+        ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
+                                             desc))
+        drevids.append(newrevid)
+        lastrevid = newrevid
+
+    # Update commit messages and remove tags
+    if opts.get(b'amend'):
+        unfi = repo.unfiltered()
+        drevs = callconduit(repo, b'differential.query', {b'ids': drevids})
+        with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
+            wnode = unfi[b'.'].node()
+            mapping = {} # {oldnode: [newnode]}
+            for i, rev in enumerate(revs):
+                old = unfi[rev]
+                drevid = drevids[i]
+                drev = [d for d in drevs if int(d[r'id']) == drevid][0]
+                newdesc = getdescfromdrev(drev)
+                newdesc = encoding.unitolocal(newdesc)
+                # Make sure commit message contain "Differential Revision"
+                if old.description() != newdesc:
+                    parents = [
+                        mapping.get(old.p1().node(), (old.p1(),))[0],
+                        mapping.get(old.p2().node(), (old.p2(),))[0],
+                    ]
+                    new = context.metadataonlyctx(
+                        repo, old, parents=parents, text=newdesc,
+                        user=old.user(), date=old.date(), extra=old.extra())
+
+                    newnode = new.commit()
+
+                    mapping[old.node()] = [newnode]
+                    # Update diff property
+                    writediffproperties(unfi[newnode], diffmap[old.node()])
+                # Remove local tags since it's no longer necessary
+                tagname = b'D%d' % drevid
+                if tagname in repo.tags():
+                    tags.tag(repo, tagname, nullid, message=None, user=None,
+                             date=None, local=True)
+            scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
+            if wnode in mapping:
+                unfi.setparents(mapping[wnode][0])
+
+# Map from "hg:meta" keys to header understood by "hg import". The order is
+# consistent with "hg export" output.
+_metanamemap = util.sortdict([(r'user', b'User'), (r'date', b'Date'),
+                              (r'node', b'Node ID'), (r'parent', b'Parent ')])
+
+def _confirmbeforesend(repo, revs, oldmap):
+    url, token = readurltoken(repo)
+    ui = repo.ui
+    for rev in revs:
+        ctx = repo[rev]
+        desc = ctx.description().splitlines()[0]
+        oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
+        if drevid:
+            drevdesc = ui.label(b'D%s' % drevid, b'phabricator.drev')
+        else:
+            drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
+
+        ui.write(_(b'%s - %s: %s\n')
+                 % (drevdesc,
+                    ui.label(bytes(ctx), b'phabricator.node'),
+                    ui.label(desc, b'phabricator.desc')))
+
+    if ui.promptchoice(_(b'Send the above changes to %s (yn)?'
+                         b'$$ &Yes $$ &No') % url):
+        return False
+
+    return True
+
+_knownstatusnames = {b'accepted', b'needsreview', b'needsrevision', b'closed',
+                     b'abandoned'}
+
+def _getstatusname(drev):
+    """get normalized status name from a Differential Revision"""
+    return drev[r'statusName'].replace(b' ', b'').lower()
+
+# Small language to specify differential revisions. Support symbols: (), :X,
+# +, and -.
+
+_elements = {
+    # token-type: binding-strength, primary, prefix, infix, suffix
+    b'(':      (12, None, (b'group', 1, b')'), None, None),
+    b':':      (8, None, (b'ancestors', 8), None, None),
+    b'&':      (5,  None, None, (b'and_', 5), None),
+    b'+':      (4,  None, None, (b'add', 4), None),
+    b'-':      (4,  None, None, (b'sub', 4), None),
+    b')':      (0,  None, None, None, None),
+    b'symbol': (0, b'symbol', None, None, None),
+    b'end':    (0, None, None, None, None),
+}
+
+def _tokenize(text):
+    view = memoryview(text) # zero-copy slice
+    special = b'():+-& '
+    pos = 0
+    length = len(text)
+    while pos < length:
+        symbol = b''.join(itertools.takewhile(lambda ch: ch not in special,
+                                              view[pos:]))
+        if symbol:
+            yield (b'symbol', symbol, pos)
+            pos += len(symbol)
+        else: # special char, ignore space
+            if text[pos] != b' ':
+                yield (text[pos], None, pos)
+            pos += 1
+    yield (b'end', None, pos)
+
+def _parse(text):
+    tree, pos = parser.parser(_elements).parse(_tokenize(text))
+    if pos != len(text):
+        raise error.ParseError(b'invalid token', pos)
+    return tree
+
+def _parsedrev(symbol):
+    """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
+    if symbol.startswith(b'D') and symbol[1:].isdigit():
+        return int(symbol[1:])
+    if symbol.isdigit():
+        return int(symbol)
+
+def _prefetchdrevs(tree):
+    """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
+    drevs = set()
+    ancestordrevs = set()
+    op = tree[0]
+    if op == b'symbol':
+        r = _parsedrev(tree[1])
+        if r:
+            drevs.add(r)
+    elif op == b'ancestors':
+        r, a = _prefetchdrevs(tree[1])
+        drevs.update(r)
+        ancestordrevs.update(r)
+        ancestordrevs.update(a)
+    else:
+        for t in tree[1:]:
+            r, a = _prefetchdrevs(t)
+            drevs.update(r)
+            ancestordrevs.update(a)
+    return drevs, ancestordrevs
+
+def querydrev(repo, spec):
+    """return a list of "Differential Revision" dicts
+
+    spec is a string using a simple query language, see docstring in phabread
+    for details.
+
+    A "Differential Revision dict" looks like:
+
+        {
+            "id": "2",
+            "phid": "PHID-DREV-672qvysjcczopag46qty",
+            "title": "example",
+            "uri": "https://phab.example.com/D2",
+            "dateCreated": "1499181406",
+            "dateModified": "1499182103",
+            "authorPHID": "PHID-USER-tv3ohwc4v4jeu34otlye",
+            "status": "0",
+            "statusName": "Needs Review",
+            "properties": [],
+            "branch": null,
+            "summary": "",
+            "testPlan": "",
+            "lineCount": "2",
+            "activeDiffPHID": "PHID-DIFF-xoqnjkobbm6k4dk6hi72",
+            "diffs": [
+              "3",
+              "4",
+            ],
+            "commits": [],
+            "reviewers": [],
+            "ccs": [],
+            "hashes": [],
+            "auxiliary": {
+              "phabricator:projects": [],
+              "phabricator:depends-on": [
+                "PHID-DREV-gbapp366kutjebt7agcd"
+              ]
+            },
+            "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
+            "sourcePath": null
+        }
+    """
+    def fetch(params):
+        """params -> single drev or None"""
+        key = (params.get(r'ids') or params.get(r'phids') or [None])[0]
+        if key in prefetched:
+            return prefetched[key]
+        drevs = callconduit(repo, b'differential.query', params)
+        # Fill prefetched with the result
+        for drev in drevs:
+            prefetched[drev[r'phid']] = drev
+            prefetched[int(drev[r'id'])] = drev
+        if key not in prefetched:
+            raise error.Abort(_(b'cannot get Differential Revision %r')
+                              % params)
+        return prefetched[key]
+
+    def getstack(topdrevids):
+        """given a top, get a stack from the bottom, [id] -> [id]"""
+        visited = set()
+        result = []
+        queue = [{r'ids': [i]} for i in topdrevids]
+        while queue:
+            params = queue.pop()
+            drev = fetch(params)
+            if drev[r'id'] in visited:
+                continue
+            visited.add(drev[r'id'])
+            result.append(int(drev[r'id']))
+            auxiliary = drev.get(r'auxiliary', {})
+            depends = auxiliary.get(r'phabricator:depends-on', [])
+            for phid in depends:
+                queue.append({b'phids': [phid]})
+        result.reverse()
+        return smartset.baseset(result)
+
+    # Initialize prefetch cache
+    prefetched = {} # {id or phid: drev}
+
+    tree = _parse(spec)
+    drevs, ancestordrevs = _prefetchdrevs(tree)
+
+    # developer config: phabricator.batchsize
+    batchsize = repo.ui.configint(b'phabricator', b'batchsize')
+
+    # Prefetch Differential Revisions in batch
+    tofetch = set(drevs)
+    for r in ancestordrevs:
+        tofetch.update(range(max(1, r - batchsize), r + 1))
+    if drevs:
+        fetch({r'ids': list(tofetch)})
+    validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
+
+    # Walk through the tree, return smartsets
+    def walk(tree):
+        op = tree[0]
+        if op == b'symbol':
+            drev = _parsedrev(tree[1])
+            if drev:
+                return smartset.baseset([drev])
+            elif tree[1] in _knownstatusnames:
+                drevs = [r for r in validids
+                         if _getstatusname(prefetched[r]) == tree[1]]
+                return smartset.baseset(drevs)
+            else:
+                raise error.Abort(_(b'unknown symbol: %s') % tree[1])
+        elif op in {b'and_', b'add', b'sub'}:
+            assert len(tree) == 3
+            return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
+        elif op == b'group':
+            return walk(tree[1])
+        elif op == b'ancestors':
+            return getstack(walk(tree[1]))
+        else:
+            raise error.ProgrammingError(b'illegal tree: %r' % tree)
+
+    return [prefetched[r] for r in walk(tree)]
+
+def getdescfromdrev(drev):
+    """get description (commit message) from "Differential Revision"
+
+    This is similar to differential.getcommitmessage API. But we only care
+    about limited fields: title, summary, test plan, and URL.
+    """
+    title = drev[r'title']
+    summary = drev[r'summary'].rstrip()
+    testplan = drev[r'testPlan'].rstrip()
+    if testplan:
+        testplan = b'Test Plan:\n%s' % testplan
+    uri = b'Differential Revision: %s' % drev[r'uri']
+    return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
+
+def getdiffmeta(diff):
+    """get commit metadata (date, node, user, p1) from a diff object
+
+    The metadata could be "hg:meta", sent by phabsend, like:
+
+        "properties": {
+          "hg:meta": {
+            "date": "1499571514 25200",
+            "node": "98c08acae292b2faf60a279b4189beb6cff1414d",
+            "user": "Foo Bar <foo@example.com>",
+            "parent": "6d0abad76b30e4724a37ab8721d630394070fe16"
+          }
+        }
+
+    Or converted from "local:commits", sent by "arc", like:
+
+        "properties": {
+          "local:commits": {
+            "98c08acae292b2faf60a279b4189beb6cff1414d": {
+              "author": "Foo Bar",
+              "time": 1499546314,
+              "branch": "default",
+              "tag": "",
+              "commit": "98c08acae292b2faf60a279b4189beb6cff1414d",
+              "rev": "98c08acae292b2faf60a279b4189beb6cff1414d",
+              "local": "1000",
+              "parents": ["6d0abad76b30e4724a37ab8721d630394070fe16"],
+              "summary": "...",
+              "message": "...",
+              "authorEmail": "foo@example.com"
+            }
+          }
+        }
+
+    Note: metadata extracted from "local:commits" will lose time zone
+    information.
+    """
+    props = diff.get(r'properties') or {}
+    meta = props.get(r'hg:meta')
+    if not meta and props.get(r'local:commits'):
+        commit = sorted(props[r'local:commits'].values())[0]
+        meta = {
+            r'date': r'%d 0' % commit[r'time'],
+            r'node': commit[r'rev'],
+            r'user': r'%s <%s>' % (commit[r'author'], commit[r'authorEmail']),
+        }
+        if len(commit.get(r'parents', ())) >= 1:
+            meta[r'parent'] = commit[r'parents'][0]
+    return meta or {}
+
+def readpatch(repo, drevs, write):
+    """generate plain-text patch readable by 'hg import'
+
+    write is usually ui.write. drevs is what "querydrev" returns, results of
+    "differential.query".
+    """
+    # Prefetch hg:meta property for all diffs
+    diffids = sorted(set(max(int(v) for v in drev[r'diffs']) for drev in drevs))
+    diffs = callconduit(repo, b'differential.querydiffs', {b'ids': diffids})
+
+    # Generate patch for each drev
+    for drev in drevs:
+        repo.ui.note(_(b'reading D%s\n') % drev[r'id'])
+
+        diffid = max(int(v) for v in drev[r'diffs'])
+        body = callconduit(repo, b'differential.getrawdiff',
+                           {b'diffID': diffid})
+        desc = getdescfromdrev(drev)
+        header = b'# HG changeset patch\n'
+
+        # Try to preserve metadata from hg:meta property. Write hg patch
+        # headers that can be read by the "import" command. See patchheadermap
+        # and extract in mercurial/patch.py for supported headers.
+        meta = getdiffmeta(diffs[str(diffid)])
+        for k in _metanamemap.keys():
+            if k in meta:
+                header += b'# %s %s\n' % (_metanamemap[k], meta[k])
+
+        content = b'%s%s\n%s' % (header, desc, body)
+        write(encoding.unitolocal(content))
+
+@vcrcommand(b'phabread',
+         [(b'', b'stack', False, _(b'read dependencies'))],
+         _(b'DREVSPEC [OPTIONS]'))
+def phabread(ui, repo, spec, **opts):
+    """print patches from Phabricator suitable for importing
+
+    DREVSPEC could be a Differential Revision identity, like ``D123``, or just
+    the number ``123``. It could also have common operators like ``+``, ``-``,
+    ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
+    select a stack.
+
+    ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
+    could be used to filter patches by status. For performance reason, they
+    only represent a subset of non-status selections and cannot be used alone.
+
+    For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
+    D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
+    stack up to D9.
+
+    If --stack is given, follow dependencies information and read all patches.
+    It is equivalent to the ``:`` operator.
+    """
+    if opts.get(b'stack'):
+        spec = b':(%s)' % spec
+    drevs = querydrev(repo, spec)
+    readpatch(repo, drevs, ui.write)
+
+@vcrcommand(b'phabupdate',
+         [(b'', b'accept', False, _(b'accept revisions')),
+          (b'', b'reject', False, _(b'reject revisions')),
+          (b'', b'abandon', False, _(b'abandon revisions')),
+          (b'', b'reclaim', False, _(b'reclaim revisions')),
+          (b'm', b'comment', b'', _(b'comment on the last revision')),
+          ], _(b'DREVSPEC [OPTIONS]'))
+def phabupdate(ui, repo, spec, **opts):
+    """update Differential Revision in batch
+
+    DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
+    """
+    flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)]
+    if len(flags) > 1:
+        raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
+
+    actions = []
+    for f in flags:
+        actions.append({b'type': f, b'value': b'true'})
+
+    drevs = querydrev(repo, spec)
+    for i, drev in enumerate(drevs):
+        if i + 1 == len(drevs) and opts.get(b'comment'):
+            actions.append({b'type': b'comment', b'value': opts[b'comment']})
+        if actions:
+            params = {b'objectIdentifier': drev[r'phid'],
+                      b'transactions': actions}
+            callconduit(repo, b'differential.revision.edit', params)
+
+templatekeyword = registrar.templatekeyword()
+
+@templatekeyword(b'phabreview', requires={b'ctx'})
+def template_review(context, mapping):
+    """:phabreview: Object describing the review for this changeset.
+    Has attributes `url` and `id`.
+    """
+    ctx = context.resource(mapping, b'ctx')
+    m = _differentialrevisiondescre.search(ctx.description())
+    if m:
+        return templateutil.hybriddict({
+            b'url': m.group(b'url'),
+            b'id': b"D{}".format(m.group(b'id')),
+        })
--- a/hgext/purge.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/purge.py	Wed Sep 26 20:33:09 2018 +0900
@@ -25,16 +25,13 @@
 '''command to delete untracked files from the working directory'''
 from __future__ import absolute_import
 
-import os
-
 from mercurial.i18n import _
 from mercurial import (
     cmdutil,
-    error,
+    merge as mergemod,
     pycompat,
     registrar,
     scmutil,
-    util,
 )
 
 cmdtable = {}
@@ -86,44 +83,28 @@
     option.
     '''
     opts = pycompat.byteskwargs(opts)
+
     act = not opts.get('print')
     eol = '\n'
     if opts.get('print0'):
         eol = '\0'
         act = False # --print0 implies --print
+
     removefiles = opts.get('files')
     removedirs = opts.get('dirs')
+
     if not removefiles and not removedirs:
         removefiles = True
         removedirs = True
 
-    def remove(remove_func, name):
-        if act:
-            try:
-                remove_func(repo.wjoin(name))
-            except OSError:
-                m = _('%s cannot be removed') % name
-                if opts.get('abort_on_err'):
-                    raise error.Abort(m)
-                ui.warn(_('warning: %s\n') % m)
-        else:
-            ui.write('%s%s' % (name, eol))
+    match = scmutil.match(repo[None], dirs, opts)
 
-    match = scmutil.match(repo[None], dirs, opts)
-    if removedirs:
-        directories = []
-        match.explicitdir = match.traversedir = directories.append
-    status = repo.status(match=match, ignored=opts.get('all'), unknown=True)
+    paths = mergemod.purge(
+        repo, match, ignored=opts.get('all', False),
+        removeemptydirs=removedirs, removefiles=removefiles,
+        abortonerror=opts.get('abort_on_err'),
+        noop=not act)
 
-    if removefiles:
-        for f in sorted(status.unknown + status.ignored):
-            if act:
-                ui.note(_('removing file %s\n') % f)
-            remove(util.unlink, f)
-
-    if removedirs:
-        for f in sorted(directories, reverse=True):
-            if match(f) and not os.listdir(repo.wjoin(f)):
-                if act:
-                    ui.note(_('removing directory %s\n') % f)
-                remove(os.rmdir, f)
+    for path in paths:
+        if not act:
+            ui.write('%s%s' % (path, eol))
--- a/hgext/rebase.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/rebase.py	Wed Sep 26 20:33:09 2018 +0900
@@ -177,6 +177,7 @@
         if e:
             self.extrafns = [e]
 
+        self.backupf = ui.configbool('ui', 'history-editing-backup')
         self.keepf = opts.get('keep', False)
         self.keepbranchesf = opts.get('keepbranches', False)
         self.obsoletenotrebased = {}
@@ -343,7 +344,9 @@
                 msg = _('cannot continue inconsistent rebase')
                 hint = _('use "hg rebase --abort" to clear broken state')
                 raise error.Abort(msg, hint=hint)
+
         if isabort:
+            backup = backup and self.backupf
             return abort(self.repo, self.originalwd, self.destmap, self.state,
                          activebookmark=self.activebookmark, backup=backup,
                          suppwarns=suppwarns)
@@ -632,7 +635,7 @@
         if self.collapsef and not self.keepf:
             collapsedas = newnode
         clearrebased(ui, repo, self.destmap, self.state, self.skipped,
-                     collapsedas, self.keepf, fm=fm)
+                     collapsedas, self.keepf, fm=fm, backup=self.backupf)
 
         clearstatus(repo)
         clearcollapsemsg(repo)
@@ -670,6 +673,7 @@
     ('D', 'detach', False, _('(DEPRECATED)')),
     ('i', 'interactive', False, _('(DEPRECATED)')),
     ('t', 'tool', '', _('specify merge tool')),
+    ('', 'stop', False, _('stop interrupted rebase')),
     ('c', 'continue', False, _('continue an interrupted rebase')),
     ('a', 'abort', False, _('abort an interrupted rebase')),
     ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
@@ -729,7 +733,8 @@
     deleted, there is no hook presently available for this.
 
     If a rebase is interrupted to manually resolve a conflict, it can be
-    continued with --continue/-c or aborted with --abort/-a.
+    continued with --continue/-c, aborted with --abort/-a, or stopped with
+    --stop.
 
     .. container:: verbose
 
@@ -800,22 +805,20 @@
     opts = pycompat.byteskwargs(opts)
     inmemory = ui.configbool('rebase', 'experimental.inmemory')
     dryrun = opts.get('dry_run')
-    if dryrun:
-        if opts.get('abort'):
-            raise error.Abort(_('cannot specify both --dry-run and --abort'))
-        if opts.get('continue'):
-            raise error.Abort(_('cannot specify both --dry-run and --continue'))
-    if opts.get('confirm'):
-        dryrun = True
-        if opts.get('dry_run'):
-            raise error.Abort(_('cannot specify both --confirm and --dry-run'))
-        if opts.get('abort'):
-            raise error.Abort(_('cannot specify both --confirm and --abort'))
-        if opts.get('continue'):
-            raise error.Abort(_('cannot specify both --confirm and --continue'))
+    confirm = opts.get('confirm')
+    selactions = [k for k in ['abort', 'stop', 'continue'] if opts.get(k)]
+    if len(selactions) > 1:
+        raise error.Abort(_('cannot use --%s with --%s')
+                          % tuple(selactions[:2]))
+    action = selactions[0] if selactions else None
+    if dryrun and action:
+        raise error.Abort(_('cannot specify both --dry-run and --%s') % action)
+    if confirm and action:
+        raise error.Abort(_('cannot specify both --confirm and --%s') % action)
+    if dryrun and confirm:
+        raise error.Abort(_('cannot specify both --confirm and --dry-run'))
 
-    if (opts.get('continue') or opts.get('abort') or
-        repo.currenttransaction() is not None):
+    if action or repo.currenttransaction() is not None:
         # in-memory rebase is not compatible with resuming rebases.
         # (Or if it is run within a transaction, since the restart logic can
         # fail the entire transaction.)
@@ -830,24 +833,43 @@
         opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
         opts['dest'] = '_destautoorphanrebase(SRC)'
 
-    if dryrun:
-        return _dryrunrebase(ui, repo, opts)
+    if dryrun or confirm:
+        return _dryrunrebase(ui, repo, action, opts)
+    elif action == 'stop':
+        rbsrt = rebaseruntime(repo, ui)
+        with repo.wlock(), repo.lock():
+            rbsrt.restorestatus()
+            if rbsrt.collapsef:
+                raise error.Abort(_("cannot stop in --collapse session"))
+            allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
+            if not (rbsrt.keepf or allowunstable):
+                raise error.Abort(_("cannot remove original changesets with"
+                                    " unrebased descendants"),
+                    hint=_('either enable obsmarkers to allow unstable '
+                           'revisions or use --keep to keep original '
+                           'changesets'))
+            if needupdate(repo, rbsrt.state):
+                # update to the current working revision
+                # to clear interrupted merge
+                hg.updaterepo(repo, rbsrt.originalwd, overwrite=True)
+            rbsrt._finishrebase()
+            return 0
     elif inmemory:
         try:
             # in-memory merge doesn't support conflicts, so if we hit any, abort
             # and re-run as an on-disk merge.
             overrides = {('rebase', 'singletransaction'): True}
             with ui.configoverride(overrides, 'rebase'):
-                return _dorebase(ui, repo, opts, inmemory=inmemory)
+                return _dorebase(ui, repo, action, opts, inmemory=inmemory)
         except error.InMemoryMergeConflictsError:
             ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
                       ' merge\n'))
-            _dorebase(ui, repo, {'abort': True})
-            return _dorebase(ui, repo, opts, inmemory=False)
+            _dorebase(ui, repo, action='abort', opts={})
+            return _dorebase(ui, repo, action, opts, inmemory=False)
     else:
-        return _dorebase(ui, repo, opts)
+        return _dorebase(ui, repo, action, opts)
 
-def _dryrunrebase(ui, repo, opts):
+def _dryrunrebase(ui, repo, action, opts):
     rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
     confirm = opts.get('confirm')
     if confirm:
@@ -860,7 +882,7 @@
         try:
             overrides = {('rebase', 'singletransaction'): True}
             with ui.configoverride(overrides, 'rebase'):
-                _origrebase(ui, repo, opts, rbsrt, inmemory=True,
+                _origrebase(ui, repo, action, opts, rbsrt, inmemory=True,
                             leaveunfinished=True)
         except error.InMemoryMergeConflictsError:
             ui.status(_('hit a merge conflict\n'))
@@ -886,11 +908,13 @@
                 rbsrt._prepareabortorcontinue(isabort=True, backup=False,
                                               suppwarns=True)
 
-def _dorebase(ui, repo, opts, inmemory=False):
+def _dorebase(ui, repo, action, opts, inmemory=False):
     rbsrt = rebaseruntime(repo, ui, inmemory, opts)
-    return _origrebase(ui, repo, opts, rbsrt, inmemory=inmemory)
+    return _origrebase(ui, repo, action, opts, rbsrt, inmemory=inmemory)
 
-def _origrebase(ui, repo, opts, rbsrt, inmemory=False, leaveunfinished=False):
+def _origrebase(ui, repo, action, opts, rbsrt, inmemory=False,
+                leaveunfinished=False):
+    assert action != 'stop'
     with repo.wlock(), repo.lock():
         # Validate input and define rebasing points
         destf = opts.get('dest', None)
@@ -900,8 +924,6 @@
         # search default destination in this space
         # used in the 'hg pull --rebase' case, see issue 5214.
         destspace = opts.get('_destspace')
-        contf = opts.get('continue')
-        abortf = opts.get('abort')
         if opts.get('interactive'):
             try:
                 if extensions.find('histedit'):
@@ -917,22 +939,20 @@
             raise error.Abort(
                 _('message can only be specified with collapse'))
 
-        if contf or abortf:
-            if contf and abortf:
-                raise error.Abort(_('cannot use both abort and continue'))
+        if action:
             if rbsrt.collapsef:
                 raise error.Abort(
                     _('cannot use collapse with continue or abort'))
             if srcf or basef or destf:
                 raise error.Abort(
                     _('abort and continue do not allow specifying revisions'))
-            if abortf and opts.get('tool', False):
+            if action == 'abort' and opts.get('tool', False):
                 ui.warn(_('tool option will be ignored\n'))
-            if contf:
+            if action == 'continue':
                 ms = mergemod.mergestate.read(repo)
                 mergeutil.checkunresolved(ms)
 
-            retcode = rbsrt._prepareabortorcontinue(abortf)
+            retcode = rbsrt._prepareabortorcontinue(isabort=(action == 'abort'))
             if retcode is not None:
                 return retcode
         else:
@@ -1728,7 +1748,7 @@
     return originalwd, destmap, state
 
 def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
-                 keepf=False, fm=None):
+                 keepf=False, fm=None, backup=True):
     """dispose of rebased revision at the end of the rebase
 
     If `collapsedas` is not None, the rebase was a collapse whose result if the
@@ -1736,6 +1756,9 @@
 
     If `keepf` is not True, the rebase has --keep set and no nodes should be
     removed (but bookmarks still need to be moved).
+
+    If `backup` is False, no backup will be stored when stripping rebased
+    revisions.
     """
     tonode = repo.changelog.node
     replacements = {}
@@ -1751,7 +1774,7 @@
                 else:
                     succs = (newnode,)
                 replacements[oldnode] = succs
-    scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
+    scmutil.cleanupnodes(repo, replacements, 'rebase', moves, backup=backup)
     if fm:
         hf = fm.hexfunc
         fl = fm.formatlist
@@ -1868,7 +1891,7 @@
                 # If 'srcrev' has a successor in rebase set but none in
                 # destination (which would be catched above), we shall skip it
                 # and its descendants to avoid divergence.
-                if any(s in destmap for s in succrevs):
+                if srcrev in extinctrevs or any(s in destmap for s in succrevs):
                     obsoletewithoutsuccessorindestination.add(srcrev)
 
     return (
--- a/hgext/remotenames.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/remotenames.py	Wed Sep 26 20:33:09 2018 +0900
@@ -230,7 +230,7 @@
                     self._nodetohoists.setdefault(node[0], []).append(name)
         return self._nodetohoists
 
-def wrapprintbookmarks(orig, ui, repo, bmarks, **opts):
+def wrapprintbookmarks(orig, ui, repo, fm, bmarks):
     if 'remotebookmarks' not in repo.names:
         return
     ns = repo.names['remotebookmarks']
@@ -243,7 +243,7 @@
 
         bmarks[name] = (node, ' ', '')
 
-    return orig(ui, repo, bmarks, **opts)
+    return orig(ui, repo, fm, bmarks)
 
 def extsetup(ui):
     extensions.wrapfunction(bookmarks, '_printbookmarks', wrapprintbookmarks)
--- a/hgext/schemes.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/schemes.py	Wed Sep 26 20:33:09 2018 +0900
@@ -78,9 +78,10 @@
     def __repr__(self):
         return '<ShortRepository: %s>' % self.scheme
 
-    def instance(self, ui, url, create, intents=None):
+    def instance(self, ui, url, create, intents=None, createopts=None):
         url = self.resolve(url)
-        return hg._peerlookup(url).instance(ui, url, create, intents=intents)
+        return hg._peerlookup(url).instance(ui, url, create, intents=intents,
+                                            createopts=createopts)
 
     def resolve(self, url):
         # Should this use the util.url class, or is manual parsing better?
--- a/hgext/shelve.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/shelve.py	Wed Sep 26 20:33:09 2018 +0900
@@ -41,6 +41,7 @@
     lock as lockmod,
     mdiff,
     merge,
+    narrowspec,
     node as nodemod,
     patch,
     phases,
@@ -78,7 +79,7 @@
 
 backupdir = 'shelve-backup'
 shelvedir = 'shelved'
-shelvefileextensions = ['hg', 'patch', 'oshelve']
+shelvefileextensions = ['hg', 'patch', 'shelve']
 # universal extension is present in all types of shelves
 patchextension = 'patch'
 
@@ -139,17 +140,22 @@
     def applybundle(self):
         fp = self.opener()
         try:
+            targetphase = phases.internal
+            if not phases.supportinternal(self.repo):
+                targetphase = phases.secret
             gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
             bundle2.applybundle(self.repo, gen, self.repo.currenttransaction(),
                                 source='unshelve',
                                 url='bundle:' + self.vfs.join(self.fname),
-                                targetphase=phases.secret)
+                                targetphase=targetphase)
         finally:
             fp.close()
 
     def bundlerepo(self):
-        return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
-                                           self.vfs.join(self.fname))
+        path = self.vfs.join(self.fname)
+        return bundlerepo.instance(self.repo.baseui,
+                                   'bundle://%s+%s' % (self.repo.root, path))
+
     def writebundle(self, bases, node):
         cgversion = changegroup.safeversion(self.repo)
         if cgversion == '01':
@@ -159,18 +165,19 @@
             btype = 'HG20'
             compression = 'BZ'
 
-        outgoing = discovery.outgoing(self.repo, missingroots=bases,
+        repo = self.repo.unfiltered()
+
+        outgoing = discovery.outgoing(repo, missingroots=bases,
                                       missingheads=[node])
-        cg = changegroup.makechangegroup(self.repo, outgoing, cgversion,
-                                         'shelve')
+        cg = changegroup.makechangegroup(repo, outgoing, cgversion, 'shelve')
 
         bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
                                 compression=compression)
 
-    def writeobsshelveinfo(self, info):
+    def writeinfo(self, info):
         scmutil.simplekeyvaluefile(self.vfs, self.fname).write(info)
 
-    def readobsshelveinfo(self):
+    def readinfo(self):
         return scmutil.simplekeyvaluefile(self.vfs, self.fname).read()
 
 class shelvedstate(object):
@@ -314,16 +321,13 @@
     '''Abort current transaction for shelve/unshelve, but keep dirstate
     '''
     tr = repo.currenttransaction()
-    backupname = 'dirstate.shelve'
-    repo.dirstate.savebackup(tr, backupname)
+    dirstatebackupname = 'dirstate.shelve'
+    narrowspecbackupname = 'narrowspec.shelve'
+    repo.dirstate.savebackup(tr, dirstatebackupname)
+    narrowspec.savebackup(repo, narrowspecbackupname)
     tr.abort()
-    repo.dirstate.restorebackup(None, backupname)
-
-def createcmd(ui, repo, pats, opts):
-    """subcommand that creates a new shelve"""
-    with repo.wlock():
-        cmdutil.checkunfinished(repo)
-        return _docreatecmd(ui, repo, pats, opts)
+    narrowspec.restorebackup(repo, narrowspecbackupname)
+    repo.dirstate.restorebackup(None, dirstatebackupname)
 
 def getshelvename(repo, parent, opts):
     """Decide on the name this shelve is going to have"""
@@ -381,7 +385,11 @@
         hasmq = util.safehasattr(repo, 'mq')
         if hasmq:
             saved, repo.mq.checkapplied = repo.mq.checkapplied, False
-        overrides = {('phases', 'new-commit'): phases.secret}
+
+        targetphase = phases.internal
+        if not phases.supportinternal(repo):
+            targetphase = phases.secret
+        overrides = {('phases', 'new-commit'): targetphase}
         try:
             editor_ = False
             if editor:
@@ -411,6 +419,8 @@
         ui.status(_("nothing changed\n"))
 
 def _shelvecreatedcommit(repo, node, name):
+    info = {'node': nodemod.hex(node)}
+    shelvedfile(repo, name, 'shelve').writeinfo(info)
     bases = list(mutableancestors(repo[node]))
     shelvedfile(repo, name, 'hg').writebundle(bases, node)
     with shelvedfile(repo, name, patchextension).opener('wb') as fp:
@@ -424,7 +434,20 @@
         repo[None].add(s.unknown)
 
 def _finishshelve(repo):
-    _aborttransaction(repo)
+    if phases.supportinternal(repo):
+        backupname = 'dirstate.shelve'
+        tr = repo.currenttransaction()
+        repo.dirstate.savebackup(tr, backupname)
+        tr.close()
+        repo.dirstate.restorebackup(None, backupname)
+    else:
+        _aborttransaction(repo)
+
+def createcmd(ui, repo, pats, opts):
+    """subcommand that creates a new shelve"""
+    with repo.wlock():
+        cmdutil.checkunfinished(repo)
+        return _docreatecmd(ui, repo, pats, opts)
 
 def _docreatecmd(ui, repo, pats, opts):
     wctx = repo[None]
@@ -456,7 +479,7 @@
 
         name = getshelvename(repo, parent, opts)
         activebookmark = _backupactivebookmark(repo)
-        extra = {}
+        extra = {'internal': 'shelve'}
         if includeunknown:
             _includeunknownfiles(repo, pats, opts, extra)
 
@@ -636,8 +659,9 @@
                 rebase.clearstatus(repo)
 
             mergefiles(ui, repo, state.wctx, state.pendingctx)
-            repair.strip(ui, repo, state.nodestoremove, backup=False,
-                         topic='shelve')
+            if not phases.supportinternal(repo):
+                repair.strip(ui, repo, state.nodestoremove, backup=False,
+                             topic='shelve')
         finally:
             shelvedstate.clear(repo)
             ui.warn(_("unshelve of '%s' aborted\n") % state.name)
@@ -695,7 +719,10 @@
             repo.setparents(state.pendingctx.node(), nodemod.nullid)
             repo.dirstate.write(repo.currenttransaction())
 
-        overrides = {('phases', 'new-commit'): phases.secret}
+        targetphase = phases.internal
+        if not phases.supportinternal(repo):
+            targetphase = phases.secret
+        overrides = {('phases', 'new-commit'): targetphase}
         with repo.ui.configoverride(overrides, 'unshelve'):
             with repo.dirstate.parentchange():
                 repo.setparents(state.parents[0], nodemod.nullid)
@@ -727,8 +754,9 @@
         mergefiles(ui, repo, state.wctx, shelvectx)
         restorebranch(ui, repo, state.branchtorestore)
 
-        repair.strip(ui, repo, state.nodestoremove, backup=False,
-                     topic='shelve')
+        if not phases.supportinternal(repo):
+            repair.strip(ui, repo, state.nodestoremove, backup=False,
+                         topic='shelve')
         _restoreactivebookmark(repo, state.activebookmark)
         shelvedstate.clear(repo)
         unshelvecleanup(ui, repo, state.name, opts)
@@ -744,7 +772,8 @@
         return tmpwctx, addedbefore
     ui.status(_("temporarily committing pending changes "
                 "(restore with 'hg unshelve --abort')\n"))
-    commitfunc = getcommitfunc(extra=None, interactive=False,
+    extra = {'internal': 'shelve'}
+    commitfunc = getcommitfunc(extra=extra, interactive=False,
                                editor=False)
     tempopts = {}
     tempopts['message'] = "pending changes temporary commit"
@@ -756,9 +785,22 @@
 
 def _unshelverestorecommit(ui, repo, basename):
     """Recreate commit in the repository during the unshelve"""
-    with ui.configoverride({('ui', 'quiet'): True}):
-        shelvedfile(repo, basename, 'hg').applybundle()
+    repo = repo.unfiltered()
+    node = None
+    if shelvedfile(repo, basename, 'shelve').exists():
+        node = shelvedfile(repo, basename, 'shelve').readinfo()['node']
+    if node is None or node not in repo:
+        with ui.configoverride({('ui', 'quiet'): True}):
+            shelvedfile(repo, basename, 'hg').applybundle()
         shelvectx = repo['tip']
+        # We might not strip the unbundled changeset, so we should keep track of
+        # the unshelve node in case we need to reuse it (eg: unshelve --keep)
+        if node is None:
+            info = {'node': nodemod.hex(shelvectx.node())}
+            shelvedfile(repo, basename, 'shelve').writeinfo(info)
+    else:
+        shelvectx = repo[node]
+
     return repo, shelvectx
 
 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
@@ -783,7 +825,7 @@
             tr.close()
 
             nodestoremove = [repo.changelog.node(rev)
-                             for rev in xrange(oldtiprev, len(repo))]
+                             for rev in pycompat.xrange(oldtiprev, len(repo))]
             shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
                               branchtorestore, opts.get('keep'), activebookmark)
             raise error.InterventionRequired(
@@ -955,6 +997,7 @@
     if not shelvedfile(repo, basename, patchextension).exists():
         raise error.Abort(_("shelved change '%s' not found") % basename)
 
+    repo = repo.unfiltered()
     lock = tr = None
     try:
         lock = repo.lock()
--- a/hgext/transplant.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/transplant.py	Wed Sep 26 20:33:09 2018 +0900
@@ -503,7 +503,7 @@
 def hasnode(repo, node):
     try:
         return repo.changelog.rev(node) is not None
-    except error.RevlogError:
+    except error.StorageError:
         return False
 
 def browserevs(ui, repo, nodes, opts):
--- a/hgext/uncommit.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/uncommit.py	Wed Sep 26 20:33:09 2018 +0900
@@ -182,7 +182,7 @@
 
             with repo.dirstate.parentchange():
                 repo.dirstate.setparents(newid, node.nullid)
-                s = repo.status(old.p1(), old, match=match)
+                s = old.p1().status(old, match=match)
                 _fixdirstate(repo, old, repo[newid], s)
 
 def predecessormarkers(ctx):
--- a/hgext/win32text.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/hgext/win32text.py	Wed Sep 26 20:33:09 2018 +0900
@@ -49,6 +49,7 @@
     short,
 )
 from mercurial import (
+    pycompat,
     registrar,
 )
 from mercurial.utils import (
@@ -141,7 +142,8 @@
     # changegroup that contains an unacceptable commit followed later
     # by a commit that fixes the problem.
     tip = repo['tip']
-    for rev in xrange(repo.changelog.tiprev(), repo[node].rev() - 1, -1):
+    for rev in pycompat.xrange(repo.changelog.tiprev(),
+                               repo[node].rev() - 1, -1):
         c = repo[rev]
         for f in c.files():
             if f in seen or f not in tip or f not in c:
--- a/i18n/hggettext	Tue Sep 25 16:32:38 2018 -0400
+++ b/i18n/hggettext	Wed Sep 26 20:33:09 2018 +0900
@@ -63,7 +63,7 @@
 
 doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
 
-def offset(src, doc, name, default):
+def offset(src, doc, name, lineno, default):
     """Compute offset or issue a warning on stdout."""
     # remove doctest part, in order to avoid backslash mismatching
     m = doctestre.search(doc)
@@ -76,8 +76,9 @@
         # This can happen if the docstring contains unnecessary escape
         # sequences such as \" in a triple-quoted string. The problem
         # is that \" is turned into " and so doc wont appear in src.
-        sys.stderr.write("warning: unknown offset in %s, assuming %d lines\n"
-                         % (name, default))
+        sys.stderr.write("%s:%d:warning:"
+                         " unknown docstr offset, assuming %d lines\n"
+                         % (name, lineno, default))
         return default
     else:
         return src.count('\n', 0, end)
@@ -106,7 +107,7 @@
     if not path.startswith('mercurial/') and mod.__doc__:
         with open(path) as fobj:
             src = fobj.read()
-        lineno = 1 + offset(src, mod.__doc__, path, 7)
+        lineno = 1 + offset(src, mod.__doc__, path, 1, 7)
         print(poentry(path, lineno, mod.__doc__))
 
     functions = list(getattr(mod, 'i18nfunctions', []))
@@ -129,7 +130,6 @@
             actualpath = '%s%s.py' % (funcmod.__name__.replace('.', '/'), extra)
 
             src = inspect.getsource(func)
-            name = "%s.%s" % (actualpath, func.__name__)
             lineno = inspect.getsourcelines(func)[1]
             doc = docobj.__doc__
             origdoc = getattr(docobj, '_origdoc', '')
@@ -137,9 +137,9 @@
                 doc = doc.rstrip()
                 origdoc = origdoc.rstrip()
             if origdoc:
-                lineno += offset(src, origdoc, name, 1)
+                lineno += offset(src, origdoc, actualpath, lineno, 1)
             else:
-                lineno += offset(src, doc, name, 1)
+                lineno += offset(src, doc, actualpath, lineno, 1)
             print(poentry(actualpath, lineno, doc))
 
 
--- a/i18n/posplit	Tue Sep 25 16:32:38 2018 -0400
+++ b/i18n/posplit	Wed Sep 26 20:33:09 2018 +0900
@@ -15,6 +15,14 @@
     e = cache.get(entry.msgid)
     if e:
         e.occurrences.extend(entry.occurrences)
+
+        # merge comments from entry
+        for comment in entry.comment.split('\n'):
+            if comment and comment not in e.comment:
+                if not e.comment:
+                    e.comment = comment
+                else:
+                    e.comment += '\n' + comment
     else:
         po.append(entry)
         cache[entry.msgid] = entry
--- a/mercurial/__init__.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/__init__.py	Wed Sep 26 20:33:09 2018 +0900
@@ -182,7 +182,7 @@
                     continue
                 r, c = t.start
                 l = (b'; from mercurial.pycompat import '
-                     b'delattr, getattr, hasattr, setattr, xrange, '
+                     b'delattr, getattr, hasattr, setattr, '
                      b'open, unicode\n')
                 for u in tokenize.tokenize(io.BytesIO(l).readline):
                     if u.type in (tokenize.ENCODING, token.ENDMARKER):
@@ -223,7 +223,7 @@
     # ``replacetoken`` or any mechanism that changes semantics of module
     # loading is changed. Otherwise cached bytecode may get loaded without
     # the new transformation mechanisms applied.
-    BYTECODEHEADER = b'HG\x00\x0a'
+    BYTECODEHEADER = b'HG\x00\x0b'
 
     class hgloader(importlib.machinery.SourceFileLoader):
         """Custom module loader that transforms source code.
--- a/mercurial/ancestor.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/ancestor.py	Wed Sep 26 20:33:09 2018 +0900
@@ -7,10 +7,12 @@
 
 from __future__ import absolute_import
 
-import collections
 import heapq
 
 from .node import nullrev
+from . import (
+    pycompat,
+)
 
 def commonancestorsheads(pfunc, *nodes):
     """Returns a set with the heads of all common ancestors of all nodes,
@@ -174,7 +176,7 @@
             # no revs to consider
             return
 
-        for curr in xrange(start, min(revs) - 1, -1):
+        for curr in pycompat.xrange(start, min(revs) - 1, -1):
             if curr not in bases:
                 continue
             revs.discard(curr)
@@ -215,7 +217,7 @@
         # exit.
 
         missing = []
-        for curr in xrange(start, nullrev, -1):
+        for curr in pycompat.xrange(start, nullrev, -1):
             if not revsvisit:
                 break
 
@@ -257,6 +259,50 @@
         missing.reverse()
         return missing
 
+# Extracted from lazyancestors.__iter__ to avoid a reference cycle
+def _lazyancestorsiter(parentrevs, initrevs, stoprev, inclusive):
+    seen = {nullrev}
+    heappush = heapq.heappush
+    heappop = heapq.heappop
+    heapreplace = heapq.heapreplace
+    see = seen.add
+
+    if inclusive:
+        visit = [-r for r in initrevs]
+        seen.update(initrevs)
+        heapq.heapify(visit)
+    else:
+        visit = []
+        heapq.heapify(visit)
+        for r in initrevs:
+            p1, p2 = parentrevs(r)
+            if p1 not in seen:
+                heappush(visit, -p1)
+                see(p1)
+            if p2 not in seen:
+                heappush(visit, -p2)
+                see(p2)
+
+    while visit:
+        current = -visit[0]
+        if current < stoprev:
+            break
+        yield current
+        # optimize out heapq operation if p1 is known to be the next highest
+        # revision, which is quite common in linear history.
+        p1, p2 = parentrevs(current)
+        if p1 not in seen:
+            if current - p1 == 1:
+                visit[0] = -p1
+            else:
+                heapreplace(visit, -p1)
+            see(p1)
+        else:
+            heappop(visit)
+        if p2 not in seen:
+            heappush(visit, -p2)
+            see(p2)
+
 class lazyancestors(object):
     def __init__(self, pfunc, revs, stoprev=0, inclusive=False):
         """Create a new object generating ancestors for the given revs. Does
@@ -271,22 +317,15 @@
 
         Result does not include the null revision."""
         self._parentrevs = pfunc
-        self._initrevs = revs
+        self._initrevs = revs = [r for r in revs if r >= stoprev]
         self._stoprev = stoprev
         self._inclusive = inclusive
 
-        # Initialize data structures for __contains__.
-        # For __contains__, we use a heap rather than a deque because
-        # (a) it minimizes the number of parentrevs calls made
-        # (b) it makes the loop termination condition obvious
-        # Python's heap is a min-heap. Multiply all values by -1 to convert it
-        # into a max-heap.
-        self._containsvisit = [-rev for rev in revs]
-        heapq.heapify(self._containsvisit)
-        if inclusive:
-            self._containsseen = set(revs)
-        else:
-            self._containsseen = set()
+        self._containsseen = set()
+        self._containsiter = _lazyancestorsiter(self._parentrevs,
+                                                self._initrevs,
+                                                self._stoprev,
+                                                self._inclusive)
 
     def __nonzero__(self):
         """False if the set is empty, True otherwise."""
@@ -302,66 +341,41 @@
         """Generate the ancestors of _initrevs in reverse topological order.
 
         If inclusive is False, yield a sequence of revision numbers starting
-        with the parents of each revision in revs, i.e., each revision is *not*
-        considered an ancestor of itself.  Results are in breadth-first order:
-        parents of each rev in revs, then parents of those, etc.
+        with the parents of each revision in revs, i.e., each revision is
+        *not* considered an ancestor of itself. Results are emitted in reverse
+        revision number order. That order is also topological: a child is
+        always emitted before its parent.
 
-        If inclusive is True, yield all the revs first (ignoring stoprev),
-        then yield all the ancestors of revs as when inclusive is False.
-        If an element in revs is an ancestor of a different rev it is not
-        yielded again."""
-        seen = set()
-        revs = self._initrevs
-        if self._inclusive:
-            for rev in revs:
-                yield rev
-            seen.update(revs)
-
-        parentrevs = self._parentrevs
-        stoprev = self._stoprev
-        visit = collections.deque(revs)
-
-        see = seen.add
-        schedule = visit.append
-
-        while visit:
-            for parent in parentrevs(visit.popleft()):
-                if parent >= stoprev and parent not in seen:
-                    schedule(parent)
-                    see(parent)
-                    yield parent
+        If inclusive is True, the source revisions are also yielded. The
+        reverse revision number order is still enforced."""
+        return _lazyancestorsiter(self._parentrevs, self._initrevs,
+                                  self._stoprev, self._inclusive)
 
     def __contains__(self, target):
         """Test whether target is an ancestor of self._initrevs."""
-        # Trying to do both __iter__ and __contains__ using the same visit
-        # heap and seen set is complex enough that it slows down both. Keep
-        # them separate.
         seen = self._containsseen
         if target in seen:
             return True
+        iter = self._containsiter
+        if iter is None:
+            # Iterator exhausted
+            return False
         # Only integer target is valid, but some callers expect 'None in self'
         # to be False. So we explicitly allow it.
         if target is None:
             return False
 
-        parentrevs = self._parentrevs
-        visit = self._containsvisit
-        stoprev = self._stoprev
-        heappop = heapq.heappop
-        heappush = heapq.heappush
         see = seen.add
-
-        targetseen = False
-
-        while visit and -visit[0] > target and not targetseen:
-            for parent in parentrevs(-heappop(visit)):
-                if parent < stoprev or parent in seen:
-                    continue
-                # We need to make sure we push all parents into the heap so
-                # that we leave it in a consistent state for future calls.
-                heappush(visit, -parent)
-                see(parent)
-                if parent == target:
-                    targetseen = True
-
-        return targetseen
+        try:
+            while True:
+                rev = next(iter)
+                see(rev)
+                if rev == target:
+                    return True
+                if rev < target:
+                    return False
+        except StopIteration:
+            # Set to None to indicate fast-path can be used next time, and to
+            # free up memory.
+            self._containsiter = None
+            return False
--- a/mercurial/bookmarks.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/bookmarks.py	Wed Sep 26 20:33:09 2018 +0900
@@ -240,7 +240,7 @@
             if self.active:
                 return self.active
             else:
-                raise error.Abort(_("no active bookmark"))
+                raise error.RepoLookupError(_("no active bookmark"))
         return bname
 
     def checkconflict(self, mark, force=False, target=None):
@@ -915,22 +915,18 @@
     elif cur != tgt and newact == repo._activebookmark:
         deactivate(repo)
 
-def _printbookmarks(ui, repo, bmarks, **opts):
+def _printbookmarks(ui, repo, fm, bmarks):
     """private method to print bookmarks
 
     Provides a way for extensions to control how bookmarks are printed (e.g.
     prepend or postpend names)
     """
-    opts = pycompat.byteskwargs(opts)
-    fm = ui.formatter('bookmarks', opts)
-    contexthint = fm.contexthint('bookmark rev node active')
     hexfn = fm.hexfunc
     if len(bmarks) == 0 and fm.isplain():
         ui.status(_("no bookmarks set\n"))
     for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
         fm.startitem()
-        if 'ctx' in contexthint:
-            fm.context(ctx=repo[n])
+        fm.context(repo=repo)
         if not ui.quiet:
             fm.plain(' %s ' % prefix, label=label)
         fm.write('bookmark', '%s', bmark, label=label)
@@ -939,24 +935,25 @@
                      repo.changelog.rev(n), hexfn(n), label=label)
         fm.data(active=(activebookmarklabel in label))
         fm.plain('\n')
-    fm.end()
 
-def printbookmarks(ui, repo, **opts):
-    """print bookmarks to a formatter
+def printbookmarks(ui, repo, fm, names=None):
+    """print bookmarks by the given formatter
 
     Provides a way for extensions to control how bookmarks are printed.
     """
     marks = repo._bookmarks
     bmarks = {}
-    for bmark, n in sorted(marks.iteritems()):
+    for bmark in (names or marks):
+        if bmark not in marks:
+            raise error.Abort(_("bookmark '%s' does not exist") % bmark)
         active = repo._activebookmark
         if bmark == active:
             prefix, label = '*', activebookmarklabel
         else:
             prefix, label = ' ', ''
 
-        bmarks[bmark] = (n, prefix, label)
-    _printbookmarks(ui, repo, bmarks, **opts)
+        bmarks[bmark] = (marks[bmark], prefix, label)
+    _printbookmarks(ui, repo, fm, bmarks)
 
 def preparehookargs(name, old, new):
     if new is None:
--- a/mercurial/branchmap.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/branchmap.py	Wed Sep 26 20:33:09 2018 +0900
@@ -38,15 +38,11 @@
     return filename
 
 def read(repo):
+    f = None
     try:
         f = repo.cachevfs(_filename(repo))
-        lines = f.read().split('\n')
-        f.close()
-    except (IOError, OSError):
-        return None
-
-    try:
-        cachekey = lines.pop(0).split(" ", 2)
+        lineiter = iter(f)
+        cachekey = next(lineiter).rstrip('\n').split(" ", 2)
         last, lrev = cachekey[:2]
         last, lrev = bin(last), int(lrev)
         filteredhash = None
@@ -58,7 +54,8 @@
             # invalidate the cache
             raise ValueError(r'tip differs')
         cl = repo.changelog
-        for l in lines:
+        for l in lineiter:
+            l = l.rstrip('\n')
             if not l:
                 continue
             node, state, label = l.split(" ", 2)
@@ -72,6 +69,10 @@
             partial.setdefault(label, []).append(node)
             if state == 'c':
                 partial._closednodes.add(node)
+
+    except (IOError, OSError):
+        return None
+
     except Exception as inst:
         if repo.ui.debugflag:
             msg = 'invalid branchheads cache'
@@ -80,6 +81,11 @@
             msg += ': %s\n'
             repo.ui.debug(msg % pycompat.bytestr(inst))
         partial = None
+
+    finally:
+        if f:
+            f.close()
+
     return partial
 
 ### Nearest subset relation
--- a/mercurial/bundle2.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/bundle2.py	Wed Sep 26 20:33:09 2018 +0900
@@ -1532,7 +1532,7 @@
     if role == 'server':
         streamsupported = repo.ui.configbool('server', 'uncompressed',
                                              untrusted=True)
-        featuresupported = repo.ui.configbool('experimental', 'bundle2.stream')
+        featuresupported = repo.ui.configbool('server', 'bundle2.stream')
 
         if not streamsupported or not featuresupported:
             caps.pop('stream')
@@ -1672,7 +1672,7 @@
     return params
 
 def addpartbundlestream2(bundler, repo, **kwargs):
-    if not kwargs.get('stream', False):
+    if not kwargs.get(r'stream', False):
         return
 
     if not streamclone.allowservergeneration(repo):
@@ -1779,6 +1779,8 @@
     This is a very early implementation that will massive rework before being
     inflicted to any end-user.
     """
+    from . import localrepo
+
     tr = op.gettransaction()
     unpackerversion = inpart.params.get('version', '01')
     # We should raise an appropriate exception here
@@ -1795,7 +1797,8 @@
                 "bundle contains tree manifests, but local repo is "
                 "non-empty and does not use tree manifests"))
         op.repo.requirements.add('treemanifest')
-        op.repo._applyopenerreqs()
+        op.repo.svfs.options = localrepo.resolvestorevfsoptions(
+            op.repo.ui, op.repo.requirements)
         op.repo._writerequirements()
     extrakwargs = {}
     targetphase = inpart.params.get('targetphase')
@@ -2223,11 +2226,11 @@
         total += header[1] + header[2]
         utf8branch = inpart.read(header[0])
         branch = encoding.tolocal(utf8branch)
-        for x in xrange(header[1]):
+        for x in pycompat.xrange(header[1]):
             node = inpart.read(20)
             rev = cl.rev(node)
             cache.setdata(branch, rev, node, False)
-        for x in xrange(header[2]):
+        for x in pycompat.xrange(header[2]):
             node = inpart.read(20)
             rev = cl.rev(node)
             cache.setdata(branch, rev, node, True)
--- a/mercurial/bundlerepo.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/bundlerepo.py	Wed Sep 26 20:33:09 2018 +0900
@@ -25,6 +25,7 @@
     changelog,
     cmdutil,
     discovery,
+    encoding,
     error,
     exchange,
     filelog,
@@ -80,7 +81,7 @@
             # start, size, full unc. size, base (unused), link, p1, p2, node
             e = (revlog.offset_type(start, flags), size, -1, baserev, link,
                  self.rev(p1), self.rev(p2), node)
-            self.index.insert(-1, e)
+            self.index.append(e)
             self.nodemap[node] = n
             self.bundlerevs.add(n)
             n += 1
@@ -187,7 +188,7 @@
 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
     def __init__(self, opener, cgunpacker, linkmapper, dirlogstarts=None,
                  dir=''):
-        manifest.manifestrevlog.__init__(self, opener, dir=dir)
+        manifest.manifestrevlog.__init__(self, opener, tree=dir)
         bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
                               linkmapper)
         if dirlogstarts is None:
@@ -255,7 +256,7 @@
             pass
     return filespos
 
-class bundlerepository(localrepo.localrepository):
+class bundlerepository(object):
     """A repository instance that is a union of a local repo and a bundle.
 
     Instances represent a read-only repository composed of a local repository
@@ -263,25 +264,19 @@
     conceptually similar to the state of a repository after an
     ``hg unbundle`` operation. However, the contents of the bundle are never
     applied to the actual base repository.
+
+    Instances constructed directly are not usable as repository objects.
+    Use instance() or makebundlerepository() to create instances.
     """
-    def __init__(self, ui, repopath, bundlepath):
-        self._tempparent = None
-        try:
-            localrepo.localrepository.__init__(self, ui, repopath)
-        except error.RepoError:
-            self._tempparent = pycompat.mkdtemp()
-            localrepo.instance(ui, self._tempparent, 1)
-            localrepo.localrepository.__init__(self, ui, self._tempparent)
+    def __init__(self, bundlepath, url, tempparent):
+        self._tempparent = tempparent
+        self._url = url
+
         self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
 
-        if repopath:
-            self._url = 'bundle:' + util.expandpath(repopath) + '+' + bundlepath
-        else:
-            self._url = 'bundle:' + bundlepath
-
         self.tempfile = None
         f = util.posixfile(bundlepath, "rb")
-        bundle = exchange.readbundle(ui, f, bundlepath)
+        bundle = exchange.readbundle(self.ui, f, bundlepath)
 
         if isinstance(bundle, bundle2.unbundle20):
             self._bundlefile = bundle
@@ -311,7 +306,7 @@
             if bundle.compressed():
                 f = self._writetempbundle(bundle.read, '.hg10un',
                                           header='HG10UN')
-                bundle = exchange.readbundle(ui, f, bundlepath, self.vfs)
+                bundle = exchange.readbundle(self.ui, f, bundlepath, self.vfs)
 
             self._bundlefile = bundle
             self._cgunpacker = bundle
@@ -370,14 +365,16 @@
         self.manstart = self._cgunpacker.tell()
         return c
 
-    def _constructmanifest(self):
+    @localrepo.unfilteredpropertycache
+    def manifestlog(self):
         self._cgunpacker.seek(self.manstart)
         # consume the header if it exists
         self._cgunpacker.manifestheader()
         linkmapper = self.unfiltered().changelog.rev
-        m = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
+        rootstore = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
         self.filestart = self._cgunpacker.tell()
-        return m
+
+        return manifest.manifestlog(self.svfs, self, rootstore)
 
     def _consumemanifest(self):
         """Consumes the manifest portion of the bundle, setting filestart so the
@@ -436,7 +433,7 @@
         return bundlepeer(self)
 
     def getcwd(self):
-        return pycompat.getcwd() # always outside the repo
+        return encoding.getcwd() # always outside the repo
 
     # Check if parents exist in localrepo before setting
     def setparents(self, p1, p2=nullid):
@@ -449,20 +446,20 @@
             self.ui.warn(msg % nodemod.hex(p2))
         return super(bundlerepository, self).setparents(p1, p2)
 
-def instance(ui, path, create, intents=None):
+def instance(ui, path, create, intents=None, createopts=None):
     if create:
         raise error.Abort(_('cannot create new bundle repository'))
     # internal config: bundle.mainreporoot
     parentpath = ui.config("bundle", "mainreporoot")
     if not parentpath:
         # try to find the correct path to the working directory repo
-        parentpath = cmdutil.findrepo(pycompat.getcwd())
+        parentpath = cmdutil.findrepo(encoding.getcwd())
         if parentpath is None:
             parentpath = ''
     if parentpath:
         # Try to make the full path relative so we get a nice, short URL.
         # In particular, we don't want temp dir names in test outputs.
-        cwd = pycompat.getcwd()
+        cwd = encoding.getcwd()
         if parentpath == cwd:
             parentpath = ''
         else:
@@ -479,7 +476,46 @@
             repopath, bundlename = s
     else:
         repopath, bundlename = parentpath, path
-    return bundlerepository(ui, repopath, bundlename)
+
+    return makebundlerepository(ui, repopath, bundlename)
+
+def makebundlerepository(ui, repopath, bundlepath):
+    """Make a bundle repository object based on repo and bundle paths."""
+    if repopath:
+        url = 'bundle:%s+%s' % (util.expandpath(repopath), bundlepath)
+    else:
+        url = 'bundle:%s' % bundlepath
+
+    # Because we can't make any guarantees about the type of the base
+    # repository, we can't have a static class representing the bundle
+    # repository. We also can't make any guarantees about how to even
+    # call the base repository's constructor!
+    #
+    # So, our strategy is to go through ``localrepo.instance()`` to construct
+    # a repo instance. Then, we dynamically create a new type derived from
+    # both it and our ``bundlerepository`` class which overrides some
+    # functionality. We then change the type of the constructed repository
+    # to this new type and initialize the bundle-specific bits of it.
+
+    try:
+        parentrepo = localrepo.instance(ui, repopath, create=False)
+        tempparent = None
+    except error.RepoError:
+        tempparent = pycompat.mkdtemp()
+        try:
+            parentrepo = localrepo.instance(ui, tempparent, create=True)
+        except Exception:
+            shutil.rmtree(tempparent)
+            raise
+
+    class derivedbundlerepository(bundlerepository, parentrepo.__class__):
+        pass
+
+    repo = parentrepo
+    repo.__class__ = derivedbundlerepository
+    bundlerepository.__init__(repo, bundlepath, url, tempparent)
+
+    return repo
 
 class bundletransactionmanager(object):
     def transaction(self):
@@ -588,8 +624,10 @@
             bundle = None
         if not localrepo:
             # use the created uncompressed bundlerepo
-            localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
-                                                      fname)
+            localrepo = bundlerepo = makebundlerepository(repo. baseui,
+                                                          repo.root,
+                                                          fname)
+
             # this repo contains local and peer now, so filter out local again
             common = repo.heads()
     if localrepo:
--- a/mercurial/cext/osutil.c	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/cext/osutil.c	Wed Sep 26 20:33:09 2018 +0900
@@ -1217,7 +1217,9 @@
 	char fpmode[4];
 	int fppos = 0;
 	int plus;
+#ifndef IS_PY3K
 	FILE *fp;
+#endif
 
 	if (!PyArg_ParseTupleAndKeywords(args, kwds, PY23("et|si:posixfile",
 							  "et|yi:posixfile"),
--- a/mercurial/cext/parsers.c	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/cext/parsers.c	Wed Sep 26 20:33:09 2018 +0900
@@ -713,7 +713,7 @@
 void manifest_module_init(PyObject *mod);
 void revlog_module_init(PyObject *mod);
 
-static const int version = 10;
+static const int version = 11;
 
 static void module_init(PyObject *mod)
 {
--- a/mercurial/cext/revlog.c	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/cext/revlog.c	Wed Sep 26 20:33:09 2018 +0900
@@ -28,17 +28,33 @@
 #define PyInt_AsLong PyLong_AsLong
 #endif
 
+typedef struct indexObjectStruct indexObject;
+
+typedef struct {
+	int children[16];
+} nodetreenode;
+
 /*
  * A base-16 trie for fast node->rev mapping.
  *
  * Positive value is index of the next node in the trie
- * Negative value is a leaf: -(rev + 1)
+ * Negative value is a leaf: -(rev + 2)
  * Zero is empty
  */
 typedef struct {
-	int children[16];
+	indexObject *index;
+	nodetreenode *nodes;
+	unsigned length;     /* # nodes in use */
+	unsigned capacity;   /* # nodes allocated */
+	int depth;           /* maximum depth of tree */
+	int splits;          /* # splits performed */
 } nodetree;
 
+typedef struct {
+	PyObject_HEAD
+	nodetree nt;
+} nodetreeObject;
+
 /*
  * This class has two behaviors.
  *
@@ -51,7 +67,7 @@
  * With string keys, we lazily perform a reverse mapping from node to
  * rev, using a base-16 trie.
  */
-typedef struct {
+struct indexObjectStruct {
 	PyObject_HEAD
 	/* Type-specific fields go here. */
 	PyObject *data;        /* raw bytes of index */
@@ -63,16 +79,13 @@
 	PyObject *added;       /* populated on demand */
 	PyObject *headrevs;    /* cache, invalidated on changes */
 	PyObject *filteredrevs;/* filtered revs set */
-	nodetree *nt;          /* base-16 trie */
-	unsigned ntlength;          /* # nodes in use */
-	unsigned ntcapacity;        /* # nodes allocated */
-	int ntdepth;           /* maximum depth of tree */
-	int ntsplits;          /* # splits performed */
+	nodetree nt;           /* base-16 trie */
+	int ntinitialized;     /* 0 or 1 */
 	int ntrev;             /* last rev scanned */
 	int ntlookups;         /* # lookups */
 	int ntmisses;          /* # lookups that miss the cache */
 	int inlined;
-} indexObject;
+};
 
 static Py_ssize_t index_length(const indexObject *self)
 {
@@ -95,6 +108,36 @@
 /* A RevlogNG v1 index entry is 64 bytes long. */
 static const long v1_hdrsize = 64;
 
+static void raise_revlog_error(void)
+{
+	PyObject *mod = NULL, *dict = NULL, *errclass = NULL;
+
+	mod = PyImport_ImportModule("mercurial.error");
+	if (mod == NULL) {
+		goto cleanup;
+	}
+
+	dict = PyModule_GetDict(mod);
+	if (dict == NULL) {
+		goto cleanup;
+	}
+	Py_INCREF(dict);
+
+	errclass = PyDict_GetItemString(dict, "RevlogError");
+	if (errclass == NULL) {
+		PyErr_SetString(PyExc_SystemError,
+				"could not find RevlogError");
+		goto cleanup;
+	}
+
+	/* value of exception is ignored by callers */
+	PyErr_SetString(errclass, "RevlogError");
+
+cleanup:
+	Py_XDECREF(dict);
+	Py_XDECREF(mod);
+}
+
 /*
  * Return a pointer to the beginning of a RevlogNG record.
  */
@@ -117,9 +160,8 @@
 static inline int index_get_parents(indexObject *self, Py_ssize_t rev,
 				    int *ps, int maxrev)
 {
-	if (rev >= self->length - 1) {
-		PyObject *tuple = PyList_GET_ITEM(self->added,
-						  rev - self->length + 1);
+	if (rev >= self->length) {
+		PyObject *tuple = PyList_GET_ITEM(self->added, rev - self->length);
 		ps[0] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 5));
 		ps[1] = (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 6));
 	} else {
@@ -158,22 +200,19 @@
 	Py_ssize_t length = index_length(self);
 	PyObject *entry;
 
-	if (pos < 0)
-		pos += length;
+	if (pos == -1) {
+		Py_INCREF(nullentry);
+		return nullentry;
+	}
 
 	if (pos < 0 || pos >= length) {
 		PyErr_SetString(PyExc_IndexError, "revlog index out of range");
 		return NULL;
 	}
 
-	if (pos == length - 1) {
-		Py_INCREF(nullentry);
-		return nullentry;
-	}
-
-	if (pos >= self->length - 1) {
+	if (pos >= self->length) {
 		PyObject *obj;
-		obj = PyList_GET_ITEM(self->added, pos - self->length + 1);
+		obj = PyList_GET_ITEM(self->added, pos - self->length);
 		Py_INCREF(obj);
 		return obj;
 	}
@@ -231,15 +270,15 @@
 	Py_ssize_t length = index_length(self);
 	const char *data;
 
-	if (pos == length - 1 || pos == INT_MAX)
+	if (pos == -1)
 		return nullid;
 
 	if (pos >= length)
 		return NULL;
 
-	if (pos >= self->length - 1) {
+	if (pos >= self->length) {
 		PyObject *tuple, *str;
-		tuple = PyList_GET_ITEM(self->added, pos - self->length + 1);
+		tuple = PyList_GET_ITEM(self->added, pos - self->length);
 		str = PyTuple_GetItem(tuple, 7);
 		return str ? PyBytes_AS_STRING(str) : NULL;
 	}
@@ -262,47 +301,34 @@
 	return node;
 }
 
-static int nt_insert(indexObject *self, const char *node, int rev);
+static int nt_insert(nodetree *self, const char *node, int rev);
 
-static int node_check(PyObject *obj, char **node, Py_ssize_t *nodelen)
+static int node_check(PyObject *obj, char **node)
 {
-	if (PyBytes_AsStringAndSize(obj, node, nodelen) == -1)
+	Py_ssize_t nodelen;
+	if (PyBytes_AsStringAndSize(obj, node, &nodelen) == -1)
 		return -1;
-	if (*nodelen == 20)
+	if (nodelen == 20)
 		return 0;
 	PyErr_SetString(PyExc_ValueError, "20-byte hash required");
 	return -1;
 }
 
-static PyObject *index_insert(indexObject *self, PyObject *args)
+static PyObject *index_append(indexObject *self, PyObject *obj)
 {
-	PyObject *obj;
 	char *node;
-	int index;
-	Py_ssize_t len, nodelen;
-
-	if (!PyArg_ParseTuple(args, "iO", &index, &obj))
-		return NULL;
+	Py_ssize_t len;
 
 	if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 8) {
 		PyErr_SetString(PyExc_TypeError, "8-tuple required");
 		return NULL;
 	}
 
-	if (node_check(PyTuple_GET_ITEM(obj, 7), &node, &nodelen) == -1)
+	if (node_check(PyTuple_GET_ITEM(obj, 7), &node) == -1)
 		return NULL;
 
 	len = index_length(self);
 
-	if (index < 0)
-		index += len;
-
-	if (index != len - 1) {
-		PyErr_SetString(PyExc_IndexError,
-				"insert only supported at index -1");
-		return NULL;
-	}
-
 	if (self->added == NULL) {
 		self->added = PyList_New(0);
 		if (self->added == NULL)
@@ -312,42 +338,13 @@
 	if (PyList_Append(self->added, obj) == -1)
 		return NULL;
 
-	if (self->nt)
-		nt_insert(self, node, index);
+	if (self->ntinitialized)
+		nt_insert(&self->nt, node, (int)len);
 
 	Py_CLEAR(self->headrevs);
 	Py_RETURN_NONE;
 }
 
-static void _index_clearcaches(indexObject *self)
-{
-	if (self->cache) {
-		Py_ssize_t i;
-
-		for (i = 0; i < self->raw_length; i++)
-			Py_CLEAR(self->cache[i]);
-		free(self->cache);
-		self->cache = NULL;
-	}
-	if (self->offsets) {
-		PyMem_Free(self->offsets);
-		self->offsets = NULL;
-	}
-	free(self->nt);
-	self->nt = NULL;
-	Py_CLEAR(self->headrevs);
-}
-
-static PyObject *index_clearcaches(indexObject *self)
-{
-	_index_clearcaches(self);
-	self->ntlength = self->ntcapacity = 0;
-	self->ntdepth = self->ntsplits = 0;
-	self->ntrev = -1;
-	self->ntlookups = self->ntmisses = 0;
-	Py_RETURN_NONE;
-}
-
 static PyObject *index_stats(indexObject *self)
 {
 	PyObject *obj = PyDict_New();
@@ -376,16 +373,18 @@
 		Py_DECREF(t);
 	}
 
-	if (self->raw_length != self->length - 1)
+	if (self->raw_length != self->length)
 		istat(raw_length, "revs on disk");
 	istat(length, "revs in memory");
-	istat(ntcapacity, "node trie capacity");
-	istat(ntdepth, "node trie depth");
-	istat(ntlength, "node trie count");
 	istat(ntlookups, "node trie lookups");
 	istat(ntmisses, "node trie misses");
 	istat(ntrev, "node trie last rev scanned");
-	istat(ntsplits, "node trie splits");
+	if (self->ntinitialized) {
+		istat(nt.capacity, "node trie capacity");
+		istat(nt.depth, "node trie depth");
+		istat(nt.length, "node trie count");
+		istat(nt.splits, "node trie splits");
+	}
 
 #undef istat
 
@@ -451,7 +450,7 @@
 {
 	PyObject *iter = NULL;
 	PyObject *iter_item = NULL;
-	Py_ssize_t min_idx = index_length(self) + 1;
+	Py_ssize_t min_idx = index_length(self) + 2;
 	long iter_item_long;
 
 	if (PyList_GET_SIZE(list) != 0) {
@@ -463,7 +462,7 @@
 			Py_DECREF(iter_item);
 			if (iter_item_long < min_idx)
 				min_idx = iter_item_long;
-			phases[iter_item_long] = marker;
+			phases[iter_item_long] = (char)marker;
 		}
 		Py_DECREF(iter);
 	}
@@ -493,7 +492,7 @@
 	PyObject *reachable = NULL;
 
 	PyObject *val;
-	Py_ssize_t len = index_length(self) - 1;
+	Py_ssize_t len = index_length(self);
 	long revnum;
 	Py_ssize_t k;
 	Py_ssize_t i;
@@ -615,7 +614,7 @@
 			      revstates[parents[1] + 1]) & RS_REACHABLE)
 			    && !(revstates[i + 1] & RS_REACHABLE)) {
 				revstates[i + 1] |= RS_REACHABLE;
-				val = PyInt_FromLong(i);
+				val = PyInt_FromSsize_t(i);
 				if (val == NULL)
 					goto bail;
 				r = PyList_Append(reachable, val);
@@ -645,7 +644,7 @@
 	PyObject *phaseset = NULL;
 	PyObject *phasessetlist = NULL;
 	PyObject *rev = NULL;
-	Py_ssize_t len = index_length(self) - 1;
+	Py_ssize_t len = index_length(self);
 	Py_ssize_t numphase = 0;
 	Py_ssize_t minrevallphases = 0;
 	Py_ssize_t minrevphase = 0;
@@ -702,7 +701,7 @@
 		}
 	}
 	/* Transform phase list to a python list */
-	phasessize = PyInt_FromLong(len);
+	phasessize = PyInt_FromSsize_t(len);
 	if (phasessize == NULL)
 		goto release;
 	for (i = 0; i < len; i++) {
@@ -711,7 +710,7 @@
 		 * is computed as a difference */
 		if (phase != 0) {
 			phaseset = PyList_GET_ITEM(phasessetlist, phase);
-			rev = PyInt_FromLong(i);
+			rev = PyInt_FromSsize_t(i);
 			if (rev == NULL)
 				goto release;
 			PySet_Add(phaseset, rev);
@@ -756,7 +755,7 @@
 		}
 	}
 
-	len = index_length(self) - 1;
+	len = index_length(self);
 	heads = PyList_New(0);
 	if (heads == NULL)
 		goto bail;
@@ -838,9 +837,8 @@
 {
 	const char *data;
 
-	if (rev >= self->length - 1) {
-		PyObject *tuple = PyList_GET_ITEM(self->added,
-			rev - self->length + 1);
+	if (rev >= self->length) {
+		PyObject *tuple = PyList_GET_ITEM(self->added, rev - self->length);
 		return (int)PyInt_AS_LONG(PyTuple_GET_ITEM(tuple, 3));
 	}
 	else {
@@ -881,7 +879,7 @@
 		return NULL;
 	}
 
-	if (rev < 0 || rev >= length - 1) {
+	if (rev < 0 || rev >= length) {
 		PyErr_SetString(PyExc_ValueError, "revlog index out of range");
 		return NULL;
 	}
@@ -924,7 +922,7 @@
 			break;
 		}
 
-		if (iterrev >= length - 1) {
+		if (iterrev >= length) {
 			PyErr_SetString(PyExc_IndexError, "revision outside index");
 			return NULL;
 		}
@@ -984,7 +982,7 @@
  *   -2: not found
  * rest: valid rev
  */
-static int nt_find(indexObject *self, const char *node, Py_ssize_t nodelen,
+static int nt_find(nodetree *self, const char *node, Py_ssize_t nodelen,
 		   int hex)
 {
 	int (*getnybble)(const char *, Py_ssize_t) = hex ? hexdigit : nt_level;
@@ -993,9 +991,6 @@
 	if (nodelen == 20 && node[0] == '\0' && memcmp(node, nullid, 20) == 0)
 		return -1;
 
-	if (self->nt == NULL)
-		return -2;
-
 	if (hex)
 		maxlevel = nodelen > 40 ? 40 : (int)nodelen;
 	else
@@ -1003,15 +998,15 @@
 
 	for (level = off = 0; level < maxlevel; level++) {
 		int k = getnybble(node, level);
-		nodetree *n = &self->nt[off];
+		nodetreenode *n = &self->nodes[off];
 		int v = n->children[k];
 
 		if (v < 0) {
 			const char *n;
 			Py_ssize_t i;
 
-			v = -(v + 1);
-			n = index_node(self, v);
+			v = -(v + 2);
+			n = index_node(self->index, v);
 			if (n == NULL)
 				return -2;
 			for (i = level; i < maxlevel; i++)
@@ -1027,65 +1022,67 @@
 	return -4;
 }
 
-static int nt_new(indexObject *self)
+static int nt_new(nodetree *self)
 {
-	if (self->ntlength == self->ntcapacity) {
-		if (self->ntcapacity >= INT_MAX / (sizeof(nodetree) * 2)) {
-			PyErr_SetString(PyExc_MemoryError,
-					"overflow in nt_new");
+	if (self->length == self->capacity) {
+		unsigned newcapacity;
+		nodetreenode *newnodes;
+		newcapacity = self->capacity * 2;
+		if (newcapacity >= INT_MAX / sizeof(nodetreenode)) {
+			PyErr_SetString(PyExc_MemoryError, "overflow in nt_new");
 			return -1;
 		}
-		self->ntcapacity *= 2;
-		self->nt = realloc(self->nt,
-				   self->ntcapacity * sizeof(nodetree));
-		if (self->nt == NULL) {
+		newnodes = realloc(self->nodes, newcapacity * sizeof(nodetreenode));
+		if (newnodes == NULL) {
 			PyErr_SetString(PyExc_MemoryError, "out of memory");
 			return -1;
 		}
-		memset(&self->nt[self->ntlength], 0,
-		       sizeof(nodetree) * (self->ntcapacity - self->ntlength));
+		self->capacity = newcapacity;
+		self->nodes = newnodes;
+		memset(&self->nodes[self->length], 0,
+		       sizeof(nodetreenode) * (self->capacity - self->length));
 	}
-	return self->ntlength++;
+	return self->length++;
 }
 
-static int nt_insert(indexObject *self, const char *node, int rev)
+static int nt_insert(nodetree *self, const char *node, int rev)
 {
 	int level = 0;
 	int off = 0;
 
 	while (level < 40) {
 		int k = nt_level(node, level);
-		nodetree *n;
+		nodetreenode *n;
 		int v;
 
-		n = &self->nt[off];
+		n = &self->nodes[off];
 		v = n->children[k];
 
 		if (v == 0) {
-			n->children[k] = -rev - 1;
+			n->children[k] = -rev - 2;
 			return 0;
 		}
 		if (v < 0) {
-			const char *oldnode = index_node_existing(self, -(v + 1));
+			const char *oldnode = index_node_existing(self->index, -(v + 2));
 			int noff;
 
 			if (oldnode == NULL)
 				return -1;
 			if (!memcmp(oldnode, node, 20)) {
-				n->children[k] = -rev - 1;
+				n->children[k] = -rev - 2;
 				return 0;
 			}
 			noff = nt_new(self);
 			if (noff == -1)
 				return -1;
-			/* self->nt may have been changed by realloc */
-			self->nt[off].children[k] = noff;
+			/* self->nodes may have been changed by realloc */
+			self->nodes[off].children[k] = noff;
 			off = noff;
-			n = &self->nt[off];
+			n = &self->nodes[off];
 			n->children[nt_level(oldnode, ++level)] = v;
-			if (level > self->ntdepth)
-				self->ntdepth = level;
-			self->ntsplits += 1;
+			if (level > self->depth)
+				self->depth = level;
+			self->splits += 1;
 		} else {
 			level += 1;
 			off = v;
@@ -1095,167 +1092,69 @@
 	return -1;
 }
 
-static int nt_init(indexObject *self)
+static PyObject *ntobj_insert(nodetreeObject *self, PyObject *args)
 {
-	if (self->nt == NULL) {
-		if ((size_t)self->raw_length > INT_MAX / sizeof(nodetree)) {
-			PyErr_SetString(PyExc_ValueError, "overflow in nt_init");
-			return -1;
-		}
-		self->ntcapacity = self->raw_length < 4
-			? 4 : (int)self->raw_length / 2;
+	Py_ssize_t rev;
+	const char *node;
+	Py_ssize_t length;
+	if (!PyArg_ParseTuple(args, "n", &rev))
+		return NULL;
+	length = index_length(self->nt.index);
+	if (rev < 0 || rev >= length) {
+		PyErr_SetString(PyExc_ValueError, "revlog index out of range");
+		return NULL;
+	}
+	node = index_node_existing(self->nt.index, rev);
+	if (nt_insert(&self->nt, node, (int)rev) == -1)
+		return NULL;
+	Py_RETURN_NONE;
+}
 
-		self->nt = calloc(self->ntcapacity, sizeof(nodetree));
-		if (self->nt == NULL) {
-			PyErr_NoMemory();
-			return -1;
-		}
-		self->ntlength = 1;
-		self->ntrev = (int)index_length(self) - 1;
-		self->ntlookups = 1;
-		self->ntmisses = 0;
-		if (nt_insert(self, nullid, INT_MAX) == -1)
-			return -1;
+static int nt_delete_node(nodetree *self, const char *node)
+{
+	/* rev==-2 happens to get encoded as 0, which is interpreted as not set */
+	return nt_insert(self, node, -2);
+}
+
+static int nt_init(nodetree *self, indexObject *index, unsigned capacity)
+{
+	/* Initialize before overflow-checking to avoid nt_dealloc() crash. */
+	self->nodes = NULL;
+
+	self->index = index;
+	/* The input capacity is in terms of revisions, while the field is in
+	 * terms of nodetree nodes. */
+	self->capacity = (capacity < 4 ? 4 : capacity / 2);
+	self->depth = 0;
+	self->splits = 0;
+	if ((size_t)self->capacity > INT_MAX / sizeof(nodetreenode)) {
+		PyErr_SetString(PyExc_ValueError, "overflow in init_nt");
+		return -1;
 	}
+	self->nodes = calloc(self->capacity, sizeof(nodetreenode));
+	if (self->nodes == NULL) {
+		PyErr_NoMemory();
+		return -1;
+	}
+	self->length = 1;
 	return 0;
 }
 
-/*
- * Return values:
- *
- *   -3: error (exception set)
- *   -2: not found (no exception set)
- * rest: valid rev
- */
-static int index_find_node(indexObject *self,
-			   const char *node, Py_ssize_t nodelen)
-{
-	int rev;
-
-	self->ntlookups++;
-	rev = nt_find(self, node, nodelen, 0);
-	if (rev >= -1)
-		return rev;
-
-	if (nt_init(self) == -1)
-		return -3;
+static PyTypeObject indexType;
 
-	/*
-	 * For the first handful of lookups, we scan the entire index,
-	 * and cache only the matching nodes. This optimizes for cases
-	 * like "hg tip", where only a few nodes are accessed.
-	 *
-	 * After that, we cache every node we visit, using a single
-	 * scan amortized over multiple lookups.  This gives the best
-	 * bulk performance, e.g. for "hg log".
-	 */
-	if (self->ntmisses++ < 4) {
-		for (rev = self->ntrev - 1; rev >= 0; rev--) {
-			const char *n = index_node_existing(self, rev);
-			if (n == NULL)
-				return -3;
-			if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
-				if (nt_insert(self, n, rev) == -1)
-					return -3;
-				break;
-			}
-		}
-	} else {
-		for (rev = self->ntrev - 1; rev >= 0; rev--) {
-			const char *n = index_node_existing(self, rev);
-			if (n == NULL)
-				return -3;
-			if (nt_insert(self, n, rev) == -1) {
-				self->ntrev = rev + 1;
-				return -3;
-			}
-			if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
-				break;
-			}
-		}
-		self->ntrev = rev;
-	}
-
-	if (rev >= 0)
-		return rev;
-	return -2;
+static int ntobj_init(nodetreeObject *self, PyObject *args)
+{
+	PyObject *index;
+	unsigned capacity;
+	if (!PyArg_ParseTuple(args, "O!I", &indexType, &index, &capacity))
+		return -1;
+	Py_INCREF(index);
+	return nt_init(&self->nt, (indexObject*)index, capacity);
 }
 
-static void raise_revlog_error(void)
-{
-	PyObject *mod = NULL, *dict = NULL, *errclass = NULL;
-
-	mod = PyImport_ImportModule("mercurial.error");
-	if (mod == NULL) {
-		goto cleanup;
-	}
-
-	dict = PyModule_GetDict(mod);
-	if (dict == NULL) {
-		goto cleanup;
-	}
-	Py_INCREF(dict);
-
-	errclass = PyDict_GetItemString(dict, "RevlogError");
-	if (errclass == NULL) {
-		PyErr_SetString(PyExc_SystemError,
-				"could not find RevlogError");
-		goto cleanup;
-	}
-
-	/* value of exception is ignored by callers */
-	PyErr_SetString(errclass, "RevlogError");
-
-cleanup:
-	Py_XDECREF(dict);
-	Py_XDECREF(mod);
-}
-
-static PyObject *index_getitem(indexObject *self, PyObject *value)
-{
-	char *node;
-	Py_ssize_t nodelen;
-	int rev;
-
-	if (PyInt_Check(value))
-		return index_get(self, PyInt_AS_LONG(value));
-
-	if (node_check(value, &node, &nodelen) == -1)
-		return NULL;
-	rev = index_find_node(self, node, nodelen);
-	if (rev >= -1)
-		return PyInt_FromLong(rev);
-	if (rev == -2)
-		raise_revlog_error();
-	return NULL;
-}
-
-/*
- * Fully populate the radix tree.
- */
-static int nt_populate(indexObject *self) {
-	int rev;
-	if (self->ntrev > 0) {
-		for (rev = self->ntrev - 1; rev >= 0; rev--) {
-			const char *n = index_node_existing(self, rev);
-			if (n == NULL)
-				return -1;
-			if (nt_insert(self, n, rev) == -1)
-				return -1;
-		}
-		self->ntrev = -1;
-	}
-	return 0;
-}
-
-static int nt_partialmatch(indexObject *self, const char *node,
+static int nt_partialmatch(nodetree *self, const char *node,
 			   Py_ssize_t nodelen)
 {
-	if (nt_init(self) == -1)
-		return -3;
-	if (nt_populate(self) == -1)
-		return -3;
-
 	return nt_find(self, node, nodelen, 1);
 }
 
@@ -1268,24 +1167,19 @@
  *   -2: not found (no exception set)
  * rest: length of shortest prefix
  */
-static int nt_shortest(indexObject *self, const char *node)
+static int nt_shortest(nodetree *self, const char *node)
 {
 	int level, off;
 
-	if (nt_init(self) == -1)
-		return -3;
-	if (nt_populate(self) == -1)
-		return -3;
-
 	for (level = off = 0; level < 40; level++) {
 		int k, v;
-		nodetree *n = &self->nt[off];
+		nodetreenode *n = &self->nodes[off];
 		k = nt_level(node, level);
 		v = n->children[k];
 		if (v < 0) {
 			const char *n;
-			v = -(v + 1);
-			n = index_node_existing(self, v);
+			v = -(v + 2);
+			n = index_node_existing(self->index, v);
 			if (n == NULL)
 				return -3;
 			if (memcmp(node, n, 20) != 0)
@@ -1310,6 +1204,204 @@
 	return -3;
 }
 
+static PyObject *ntobj_shortest(nodetreeObject *self, PyObject *args)
+{
+	PyObject *val;
+	char *node;
+	int length;
+
+	if (!PyArg_ParseTuple(args, "O", &val))
+		return NULL;
+	if (node_check(val, &node) == -1)
+		return NULL;
+
+	length = nt_shortest(&self->nt, node);
+	if (length == -3)
+		return NULL;
+	if (length == -2) {
+		raise_revlog_error();
+		return NULL;
+	}
+	return PyInt_FromLong(length);
+}
+
+static void nt_dealloc(nodetree *self)
+{
+	free(self->nodes);
+	self->nodes = NULL;
+}
+
+static void ntobj_dealloc(nodetreeObject *self)
+{
+	Py_XDECREF(self->nt.index);
+	nt_dealloc(&self->nt);
+	PyObject_Del(self);
+}
+
+static PyMethodDef ntobj_methods[] = {
+	{"insert", (PyCFunction)ntobj_insert, METH_VARARGS,
+	 "insert an index entry"},
+	{"shortest", (PyCFunction)ntobj_shortest, METH_VARARGS,
+	 "find length of shortest hex nodeid of a binary ID"},
+	{NULL} /* Sentinel */
+};
+
+static PyTypeObject nodetreeType = {
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
+	"parsers.nodetree",        /* tp_name */
+	sizeof(nodetreeObject) ,   /* tp_basicsize */
+	0,                         /* tp_itemsize */
+	(destructor)ntobj_dealloc, /* tp_dealloc */
+	0,                         /* tp_print */
+	0,                         /* tp_getattr */
+	0,                         /* tp_setattr */
+	0,                         /* tp_compare */
+	0,                         /* tp_repr */
+	0,                         /* tp_as_number */
+	0,                         /* tp_as_sequence */
+	0,                         /* tp_as_mapping */
+	0,                         /* tp_hash */
+	0,                         /* tp_call */
+	0,                         /* tp_str */
+	0,                         /* tp_getattro */
+	0,                         /* tp_setattro */
+	0,                         /* tp_as_buffer */
+	Py_TPFLAGS_DEFAULT,        /* tp_flags */
+	"nodetree",                /* tp_doc */
+	0,                         /* tp_traverse */
+	0,                         /* tp_clear */
+	0,                         /* tp_richcompare */
+	0,                         /* tp_weaklistoffset */
+	0,                         /* tp_iter */
+	0,                         /* tp_iternext */
+	ntobj_methods,             /* tp_methods */
+	0,                         /* tp_members */
+	0,                         /* tp_getset */
+	0,                         /* tp_base */
+	0,                         /* tp_dict */
+	0,                         /* tp_descr_get */
+	0,                         /* tp_descr_set */
+	0,                         /* tp_dictoffset */
+	(initproc)ntobj_init,      /* tp_init */
+	0,                         /* tp_alloc */
+};
+
+static int index_init_nt(indexObject *self)
+{
+	if (!self->ntinitialized) {
+		if (nt_init(&self->nt, self, (int)self->raw_length) == -1) {
+			nt_dealloc(&self->nt);
+			return -1;
+		}
+		if (nt_insert(&self->nt, nullid, -1) == -1) {
+			nt_dealloc(&self->nt);
+			return -1;
+		}
+		self->ntinitialized = 1;
+		self->ntrev = (int)index_length(self);
+		self->ntlookups = 1;
+		self->ntmisses = 0;
+	}
+	return 0;
+}
+
+/*
+ * Return values:
+ *
+ *   -3: error (exception set)
+ *   -2: not found (no exception set)
+ * rest: valid rev
+ */
+static int index_find_node(indexObject *self,
+			   const char *node, Py_ssize_t nodelen)
+{
+	int rev;
+
+	if (index_init_nt(self) == -1)
+		return -3;
+
+	self->ntlookups++;
+	rev = nt_find(&self->nt, node, nodelen, 0);
+	if (rev >= -1)
+		return rev;
+
+	/*
+	 * For the first handful of lookups, we scan the entire index,
+	 * and cache only the matching nodes. This optimizes for cases
+	 * like "hg tip", where only a few nodes are accessed.
+	 *
+	 * After that, we cache every node we visit, using a single
+	 * scan amortized over multiple lookups.  This gives the best
+	 * bulk performance, e.g. for "hg log".
+	 */
+	if (self->ntmisses++ < 4) {
+		for (rev = self->ntrev - 1; rev >= 0; rev--) {
+			const char *n = index_node_existing(self, rev);
+			if (n == NULL)
+				return -3;
+			if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
+				if (nt_insert(&self->nt, n, rev) == -1)
+					return -3;
+				break;
+			}
+		}
+	} else {
+		for (rev = self->ntrev - 1; rev >= 0; rev--) {
+			const char *n = index_node_existing(self, rev);
+			if (n == NULL)
+				return -3;
+			if (nt_insert(&self->nt, n, rev) == -1) {
+				self->ntrev = rev + 1;
+				return -3;
+			}
+			if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
+				break;
+			}
+		}
+		self->ntrev = rev;
+	}
+
+	if (rev >= 0)
+		return rev;
+	return -2;
+}
+
+static PyObject *index_getitem(indexObject *self, PyObject *value)
+{
+	char *node;
+	int rev;
+
+	if (PyInt_Check(value))
+		return index_get(self, PyInt_AS_LONG(value));
+
+	if (node_check(value, &node) == -1)
+		return NULL;
+	rev = index_find_node(self, node, 20);
+	if (rev >= -1)
+		return PyInt_FromLong(rev);
+	if (rev == -2)
+		raise_revlog_error();
+	return NULL;
+}
+
+/*
+ * Fully populate the radix tree.
+ */
+static int index_populate_nt(indexObject *self) {
+	int rev;
+	if (self->ntrev > 0) {
+		for (rev = self->ntrev - 1; rev >= 0; rev--) {
+			const char *n = index_node_existing(self, rev);
+			if (n == NULL)
+				return -1;
+			if (nt_insert(&self->nt, n, rev) == -1)
+				return -1;
+		}
+		self->ntrev = -1;
+	}
+	return 0;
+}
+
 static PyObject *index_partialmatch(indexObject *self, PyObject *args)
 {
 	const char *fullnode;
@@ -1338,12 +1430,15 @@
 		Py_RETURN_NONE;
 	}
 
-	rev = nt_partialmatch(self, node, nodelen);
+	if (index_init_nt(self) == -1)
+		return NULL;
+	if (index_populate_nt(self) == -1)
+		return NULL;
+	rev = nt_partialmatch(&self->nt, node, nodelen);
 
 	switch (rev) {
 	case -4:
 		raise_revlog_error();
-	case -3:
 		return NULL;
 	case -2:
 		Py_RETURN_NONE;
@@ -1360,18 +1455,21 @@
 
 static PyObject *index_shortest(indexObject *self, PyObject *args)
 {
-	Py_ssize_t nodelen;
 	PyObject *val;
 	char *node;
 	int length;
 
 	if (!PyArg_ParseTuple(args, "O", &val))
 		return NULL;
-	if (node_check(val, &node, &nodelen) == -1)
+	if (node_check(val, &node) == -1)
 		return NULL;
 
 	self->ntlookups++;
-	length = nt_shortest(self, node);
+	if (index_init_nt(self) == -1)
+		return NULL;
+	if (index_populate_nt(self) == -1)
+		return NULL;
+	length = nt_shortest(&self->nt, node);
 	if (length == -3)
 		return NULL;
 	if (length == -2) {
@@ -1383,16 +1481,15 @@
 
 static PyObject *index_m_get(indexObject *self, PyObject *args)
 {
-	Py_ssize_t nodelen;
 	PyObject *val;
 	char *node;
 	int rev;
 
 	if (!PyArg_ParseTuple(args, "O", &val))
 		return NULL;
-	if (node_check(val, &node, &nodelen) == -1)
+	if (node_check(val, &node) == -1)
 		return NULL;
-	rev = index_find_node(self, node, nodelen);
+	rev = index_find_node(self, node, 20);
 	if (rev == -3)
 		return NULL;
 	if (rev == -2)
@@ -1403,17 +1500,16 @@
 static int index_contains(indexObject *self, PyObject *value)
 {
 	char *node;
-	Py_ssize_t nodelen;
 
 	if (PyInt_Check(value)) {
 		long rev = PyInt_AS_LONG(value);
 		return rev >= -1 && rev < index_length(self);
 	}
 
-	if (node_check(value, &node, &nodelen) == -1)
+	if (node_check(value, &node) == -1)
 		return -1;
 
-	switch (index_find_node(self, node, nodelen)) {
+	switch (index_find_node(self, node, 20)) {
 	case -3:
 		return -1;
 	case -2:
@@ -1554,7 +1650,7 @@
 		goto bail;
 	}
 
-	interesting = calloc(sizeof(*interesting), 1 << revcount);
+	interesting = calloc(sizeof(*interesting), ((size_t)1) << revcount);
 	if (interesting == NULL) {
 		PyErr_NoMemory();
 		goto bail;
@@ -1687,7 +1783,7 @@
 	revs = PyMem_Malloc(argcount * sizeof(*revs));
 	if (argcount > 0 && revs == NULL)
 		return PyErr_NoMemory();
-	len = index_length(self) - 1;
+	len = index_length(self);
 
 	for (i = 0; i < argcount; i++) {
 		static const int capacity = 24;
@@ -1787,7 +1883,7 @@
 /*
  * Invalidate any trie entries introduced by added revs.
  */
-static void nt_invalidate_added(indexObject *self, Py_ssize_t start)
+static void index_invalidate_added(indexObject *self, Py_ssize_t start)
 {
 	Py_ssize_t i, len = PyList_GET_SIZE(self->added);
 
@@ -1795,7 +1891,7 @@
 		PyObject *tuple = PyList_GET_ITEM(self->added, i);
 		PyObject *node = PyTuple_GET_ITEM(tuple, 7);
 
-		nt_insert(self, PyBytes_AS_STRING(node), -1);
+		nt_delete_node(&self->nt, PyBytes_AS_STRING(node));
 	}
 
 	if (start == 0)
@@ -1809,16 +1905,17 @@
 static int index_slice_del(indexObject *self, PyObject *item)
 {
 	Py_ssize_t start, stop, step, slicelength;
-	Py_ssize_t length = index_length(self);
+	Py_ssize_t length = index_length(self) + 1;
 	int ret = 0;
 
 /* Argument changed from PySliceObject* to PyObject* in Python 3. */
 #ifdef IS_PY3K
 	if (PySlice_GetIndicesEx(item, length,
+				 &start, &stop, &step, &slicelength) < 0)
 #else
 	if (PySlice_GetIndicesEx((PySliceObject*)item, length,
+				 &start, &stop, &step, &slicelength) < 0)
 #endif
-				 &start, &stop, &step, &slicelength) < 0)
 		return -1;
 
 	if (slicelength <= 0)
@@ -1845,23 +1942,23 @@
 		return -1;
 	}
 
-	if (start < self->length - 1) {
-		if (self->nt) {
+	if (start < self->length) {
+		if (self->ntinitialized) {
 			Py_ssize_t i;
 
-			for (i = start + 1; i < self->length - 1; i++) {
+			for (i = start + 1; i < self->length; i++) {
 				const char *node = index_node_existing(self, i);
 				if (node == NULL)
 					return -1;
 
-				nt_insert(self, node, -1);
+				nt_delete_node(&self->nt, node);
 			}
 			if (self->added)
-				nt_invalidate_added(self, 0);
+				index_invalidate_added(self, 0);
 			if (self->ntrev > start)
 				self->ntrev = (int)start;
 		}
-		self->length = start + 1;
+		self->length = start;
 		if (start < self->raw_length) {
 			if (self->cache) {
 				Py_ssize_t i;
@@ -1873,13 +1970,13 @@
 		goto done;
 	}
 
-	if (self->nt) {
-		nt_invalidate_added(self, start - self->length + 1);
+	if (self->ntinitialized) {
+		index_invalidate_added(self, start - self->length);
 		if (self->ntrev > start)
 			self->ntrev = (int)start;
 	}
 	if (self->added)
-		ret = PyList_SetSlice(self->added, start - self->length + 1,
+		ret = PyList_SetSlice(self->added, start - self->length,
 				      PyList_GET_SIZE(self->added), NULL);
 done:
 	Py_CLEAR(self->headrevs);
@@ -1897,17 +1994,16 @@
 				  PyObject *value)
 {
 	char *node;
-	Py_ssize_t nodelen;
 	long rev;
 
 	if (PySlice_Check(item) && value == NULL)
 		return index_slice_del(self, item);
 
-	if (node_check(item, &node, &nodelen) == -1)
+	if (node_check(item, &node) == -1)
 		return -1;
 
 	if (value == NULL)
-		return self->nt ? nt_insert(self, node, -1) : 0;
+		return self->ntinitialized ? nt_delete_node(&self->nt, node) : 0;
 	rev = PyInt_AsLong(value);
 	if (rev > INT_MAX || rev < 0) {
 		if (!PyErr_Occurred())
@@ -1915,9 +2011,9 @@
 		return -1;
 	}
 
-	if (nt_init(self) == -1)
+	if (index_init_nt(self) == -1)
 		return -1;
-	return nt_insert(self, node, (int)rev);
+	return nt_insert(&self->nt, node, (int)rev);
 }
 
 /*
@@ -1966,7 +2062,7 @@
 	self->headrevs = NULL;
 	self->filteredrevs = Py_None;
 	Py_INCREF(Py_None);
-	self->nt = NULL;
+	self->ntinitialized = 0;
 	self->offsets = NULL;
 
 	if (!PyArg_ParseTuple(args, "OO", &data_obj, &inlined_obj))
@@ -1984,8 +2080,6 @@
 	self->inlined = inlined_obj && PyObject_IsTrue(inlined_obj);
 	self->data = data_obj;
 
-	self->ntlength = self->ntcapacity = 0;
-	self->ntdepth = self->ntsplits = 0;
 	self->ntlookups = self->ntmisses = 0;
 	self->ntrev = -1;
 	Py_INCREF(self->data);
@@ -1995,14 +2089,14 @@
 		if (len == -1)
 			goto bail;
 		self->raw_length = len;
-		self->length = len + 1;
+		self->length = len;
 	} else {
 		if (size % v1_hdrsize) {
 			PyErr_SetString(PyExc_ValueError, "corrupt index file");
 			goto bail;
 		}
 		self->raw_length = size / v1_hdrsize;
-		self->length = self->raw_length + 1;
+		self->length = self->raw_length;
 	}
 
 	return 0;
@@ -2016,6 +2110,35 @@
 	return (PyObject *)self;
 }
 
+static void _index_clearcaches(indexObject *self)
+{
+	if (self->cache) {
+		Py_ssize_t i;
+
+		for (i = 0; i < self->raw_length; i++)
+			Py_CLEAR(self->cache[i]);
+		free(self->cache);
+		self->cache = NULL;
+	}
+	if (self->offsets) {
+		PyMem_Free((void *)self->offsets);
+		self->offsets = NULL;
+	}
+	if (self->ntinitialized) {
+		nt_dealloc(&self->nt);
+	}
+	self->ntinitialized = 0;
+	Py_CLEAR(self->headrevs);
+}
+
+static PyObject *index_clearcaches(indexObject *self)
+{
+	_index_clearcaches(self);
+	self->ntrev = -1;
+	self->ntlookups = self->ntmisses = 0;
+	Py_RETURN_NONE;
+}
+
 static void index_dealloc(indexObject *self)
 {
 	_index_clearcaches(self);
@@ -2066,8 +2189,8 @@
 	 "get filtered head revisions"}, /* Can always do filtering */
 	{"deltachain", (PyCFunction)index_deltachain, METH_VARARGS,
 	 "determine revisions with deltas to reconstruct fulltext"},
-	{"insert", (PyCFunction)index_insert, METH_VARARGS,
-	 "insert an index entry"},
+	{"append", (PyCFunction)index_append, METH_O,
+	 "append an index entry"},
 	{"partialmatch", (PyCFunction)index_partialmatch, METH_VARARGS,
 	 "match a potentially ambiguous node ID"},
 	{"shortest", (PyCFunction)index_shortest, METH_VARARGS,
@@ -2175,6 +2298,12 @@
 	Py_INCREF(&indexType);
 	PyModule_AddObject(mod, "index", (PyObject *)&indexType);
 
+	nodetreeType.tp_new = PyType_GenericNew;
+	if (PyType_Ready(&nodetreeType) < 0)
+		return;
+	Py_INCREF(&nodetreeType);
+	PyModule_AddObject(mod, "nodetree", (PyObject *)&nodetreeType);
+
 	nullentry = Py_BuildValue(PY23("iiiiiiis#", "iiiiiiiy#"), 0, 0, 0,
 				  -1, -1, -1, -1, nullid, 20);
 	if (nullentry)
--- a/mercurial/changegroup.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/changegroup.py	Wed Sep 26 20:33:09 2018 +0900
@@ -14,33 +14,37 @@
 from .i18n import _
 from .node import (
     hex,
+    nullid,
     nullrev,
     short,
 )
 
+from .thirdparty import (
+    attr,
+)
+
 from . import (
-    dagutil,
+    dagop,
     error,
+    match as matchmod,
     mdiff,
     phases,
     pycompat,
+    repository,
     util,
 )
 
 from .utils import (
+    interfaceutil,
     stringutil,
 )
 
-_CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
-_CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
-_CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
+_CHANGEGROUPV1_DELTA_HEADER = struct.Struct("20s20s20s20s")
+_CHANGEGROUPV2_DELTA_HEADER = struct.Struct("20s20s20s20s20s")
+_CHANGEGROUPV3_DELTA_HEADER = struct.Struct(">20s20s20s20s20sH")
 
 LFS_REQUIREMENT = 'lfs'
 
-# When narrowing is finalized and no longer subject to format changes,
-# we should move this to just "narrow" or similar.
-NARROW_REQUIREMENT = 'narrowhg-experimental'
-
 readexactly = util.readexactly
 
 def getchunk(stream):
@@ -61,6 +65,10 @@
     """return a changegroup chunk header (string) for a zero-length chunk"""
     return struct.pack(">l", 0)
 
+def _fileheader(path):
+    """Obtain a changegroup chunk header for a named path."""
+    return chunkheader(len(path)) + path
+
 def writechunks(ui, chunks, filename, vfs=None):
     """Write chunks to a file and return its filename.
 
@@ -114,7 +122,7 @@
     bundlerepo and some debug commands - their use is discouraged.
     """
     deltaheader = _CHANGEGROUPV1_DELTA_HEADER
-    deltaheadersize = struct.calcsize(deltaheader)
+    deltaheadersize = deltaheader.size
     version = '01'
     _grouplistcount = 1 # One list of files after the manifests
 
@@ -187,7 +195,7 @@
         if not l:
             return {}
         headerdata = readexactly(self._stream, self.deltaheadersize)
-        header = struct.unpack(self.deltaheader, headerdata)
+        header = self.deltaheader.unpack(headerdata)
         delta = readexactly(self._stream, l - self.deltaheadersize)
         node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
         return (node, p1, p2, cs, deltabase, delta, flags)
@@ -245,7 +253,7 @@
         # be empty during the pull
         self.manifestheader()
         deltas = self.deltaiter()
-        repo.manifestlog.addgroup(deltas, revmap, trp)
+        repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
         prog.complete()
         self.callback = None
 
@@ -305,7 +313,7 @@
             efiles = len(efiles)
 
             if not cgnodes:
-                repo.ui.develwarn('applied empty changegroup',
+                repo.ui.develwarn('applied empty changelog from changegroup',
                                   config='warn-empty-changegroup')
             clend = len(cl)
             changesets = clend - clstart
@@ -325,7 +333,7 @@
                 cl = repo.changelog
                 ml = repo.manifestlog
                 # validate incoming csets have their manifests
-                for cset in xrange(clstart, clend):
+                for cset in pycompat.xrange(clstart, clend):
                     mfnode = cl.changelogrevision(cset).manifest
                     mfest = ml[mfnode].readdelta()
                     # store file cgnodes we must see
@@ -367,7 +375,7 @@
                 repo.hook('pretxnchangegroup',
                           throw=True, **pycompat.strkwargs(hookargs))
 
-            added = [cl.node(r) for r in xrange(clstart, clend)]
+            added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
             phaseall = None
             if srctype in ('push', 'serve'):
                 # Old servers can not push the boundary themselves.
@@ -446,7 +454,7 @@
     remain the same.
     """
     deltaheader = _CHANGEGROUPV2_DELTA_HEADER
-    deltaheadersize = struct.calcsize(deltaheader)
+    deltaheadersize = deltaheader.size
     version = '02'
 
     def _deltaheader(self, headertuple, prevnode):
@@ -462,7 +470,7 @@
     separating manifests and files.
     """
     deltaheader = _CHANGEGROUPV3_DELTA_HEADER
-    deltaheadersize = struct.calcsize(deltaheader)
+    deltaheadersize = deltaheader.size
     version = '03'
     _grouplistcount = 2 # One list of manifests and one list of files
 
@@ -476,9 +484,8 @@
             # If we get here, there are directory manifests in the changegroup
             d = chunkdata["filename"]
             repo.ui.debug("adding %s revisions\n" % d)
-            dirlog = repo.manifestlog._revlog.dirlog(d)
             deltas = self.deltaiter()
-            if not dirlog.addgroup(deltas, revmap, trp):
+            if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
                 raise error.Abort(_("received dir revlog group is empty"))
 
 class headerlessfixup(object):
@@ -493,139 +500,363 @@
             return d
         return readexactly(self._fh, n)
 
-class cg1packer(object):
-    deltaheader = _CHANGEGROUPV1_DELTA_HEADER
-    version = '01'
-    def __init__(self, repo, bundlecaps=None):
+@interfaceutil.implementer(repository.irevisiondeltarequest)
+@attr.s(slots=True, frozen=True)
+class revisiondeltarequest(object):
+    node = attr.ib()
+    linknode = attr.ib()
+    p1node = attr.ib()
+    p2node = attr.ib()
+    basenode = attr.ib()
+    ellipsis = attr.ib(default=False)
+
+def _revisiondeltatochunks(delta, headerfn):
+    """Serialize a revisiondelta to changegroup chunks."""
+
+    # The captured revision delta may be encoded as a delta against
+    # a base revision or as a full revision. The changegroup format
+    # requires that everything on the wire be deltas. So for full
+    # revisions, we need to invent a header that says to rewrite
+    # data.
+
+    if delta.delta is not None:
+        prefix, data = b'', delta.delta
+    elif delta.basenode == nullid:
+        data = delta.revision
+        prefix = mdiff.trivialdiffheader(len(data))
+    else:
+        data = delta.revision
+        prefix = mdiff.replacediffheader(delta.baserevisionsize,
+                                         len(data))
+
+    meta = headerfn(delta)
+
+    yield chunkheader(len(meta) + len(prefix) + len(data))
+    yield meta
+    if prefix:
+        yield prefix
+    yield data
+
+def _sortnodesnormal(store, nodes, reorder):
+    """Sort nodes for changegroup generation and turn into revnums."""
+    # for generaldelta revlogs, we linearize the revs; this will both be
+    # much quicker and generate a much smaller bundle
+    if (store._generaldelta and reorder is None) or reorder:
+        revs = set(store.rev(n) for n in nodes)
+        return dagop.linearize(revs, store.parentrevs)
+    else:
+        return sorted([store.rev(n) for n in nodes])
+
+def _sortnodesellipsis(store, nodes, cl, lookup):
+    """Sort nodes for changegroup generation and turn into revnums."""
+    # Ellipses serving mode.
+    #
+    # In a perfect world, we'd generate better ellipsis-ified graphs
+    # for non-changelog revlogs. In practice, we haven't started doing
+    # that yet, so the resulting DAGs for the manifestlog and filelogs
+    # are actually full of bogus parentage on all the ellipsis
+    # nodes. This has the side effect that, while the contents are
+    # correct, the individual DAGs might be completely out of whack in
+    # a case like 882681bc3166 and its ancestors (back about 10
+    # revisions or so) in the main hg repo.
+    #
+    # The one invariant we *know* holds is that the new (potentially
+    # bogus) DAG shape will be valid if we order the nodes in the
+    # order that they're introduced in dramatis personae by the
+    # changelog, so what we do is we sort the non-changelog histories
+    # by the order in which they are used by the changelog.
+    key = lambda n: cl.rev(lookup(n))
+    return [store.rev(n) for n in sorted(nodes, key=key)]
+
+def _makenarrowdeltarequest(cl, store, ischangelog, rev, node, linkrev,
+                            linknode, clrevtolocalrev, fullclnodes,
+                            precomputedellipsis):
+    linkparents = precomputedellipsis[linkrev]
+    def local(clrev):
+        """Turn a changelog revnum into a local revnum.
+
+        The ellipsis dag is stored as revnums on the changelog,
+        but when we're producing ellipsis entries for
+        non-changelog revlogs, we need to turn those numbers into
+        something local. This does that for us, and during the
+        changelog sending phase will also expand the stored
+        mappings as needed.
+        """
+        if clrev == nullrev:
+            return nullrev
+
+        if ischangelog:
+            return clrev
+
+        # Walk the ellipsis-ized changelog breadth-first looking for a
+        # change that has been linked from the current revlog.
+        #
+        # For a flat manifest revlog only a single step should be necessary
+        # as all relevant changelog entries are relevant to the flat
+        # manifest.
+        #
+        # For a filelog or tree manifest dirlog however not every changelog
+        # entry will have been relevant, so we need to skip some changelog
+        # nodes even after ellipsis-izing.
+        walk = [clrev]
+        while walk:
+            p = walk[0]
+            walk = walk[1:]
+            if p in clrevtolocalrev:
+                return clrevtolocalrev[p]
+            elif p in fullclnodes:
+                walk.extend([pp for pp in cl.parentrevs(p)
+                                if pp != nullrev])
+            elif p in precomputedellipsis:
+                walk.extend([pp for pp in precomputedellipsis[p]
+                                if pp != nullrev])
+            else:
+                # In this case, we've got an ellipsis with parents
+                # outside the current bundle (likely an
+                # incremental pull). We "know" that we can use the
+                # value of this same revlog at whatever revision
+                # is pointed to by linknode. "Know" is in scare
+                # quotes because I haven't done enough examination
+                # of edge cases to convince myself this is really
+                # a fact - it works for all the (admittedly
+                # thorough) cases in our testsuite, but I would be
+                # somewhat unsurprised to find a case in the wild
+                # where this breaks down a bit. That said, I don't
+                # know if it would hurt anything.
+                for i in pycompat.xrange(rev, 0, -1):
+                    if store.linkrev(i) == clrev:
+                        return i
+                # We failed to resolve a parent for this node, so
+                # we crash the changegroup construction.
+                raise error.Abort(
+                    'unable to resolve parent while packing %r %r'
+                    ' for changeset %r' % (store.indexfile, rev, clrev))
+
+        return nullrev
+
+    if not linkparents or (
+        store.parentrevs(rev) == (nullrev, nullrev)):
+        p1, p2 = nullrev, nullrev
+    elif len(linkparents) == 1:
+        p1, = sorted(local(p) for p in linkparents)
+        p2 = nullrev
+    else:
+        p1, p2 = sorted(local(p) for p in linkparents)
+
+    p1node, p2node = store.node(p1), store.node(p2)
+
+    # TODO: try and actually send deltas for ellipsis data blocks
+    return revisiondeltarequest(
+        node=node,
+        p1node=p1node,
+        p2node=p2node,
+        linknode=linknode,
+        basenode=nullid,
+        ellipsis=True,
+    )
+
+def deltagroup(repo, store, nodes, ischangelog, lookup, forcedeltaparentprev,
+               allowreorder,
+               topic=None,
+               ellipses=False, clrevtolocalrev=None, fullclnodes=None,
+               precomputedellipsis=None):
+    """Calculate deltas for a set of revisions.
+
+    Is a generator of ``revisiondelta`` instances.
+
+    If topic is not None, progress detail will be generated using this
+    topic name (e.g. changesets, manifests, etc).
+    """
+    if not nodes:
+        return
+
+    # We perform two passes over the revisions whose data we will emit.
+    #
+    # In the first pass, we obtain information about the deltas that will
+    # be generated. This involves computing linknodes and adjusting the
+    # request to take shallow fetching into account. The end result of
+    # this pass is a list of "request" objects stating which deltas
+    # to obtain.
+    #
+    # The second pass is simply resolving the requested deltas.
+
+    cl = repo.changelog
+
+    if ischangelog:
+        # Changelog doesn't benefit from reordering revisions. So send
+        # out revisions in store order.
+        # TODO the API would be cleaner if this were controlled by the
+        # store producing the deltas.
+        revs = sorted(cl.rev(n) for n in nodes)
+    elif ellipses:
+        revs = _sortnodesellipsis(store, nodes, cl, lookup)
+    else:
+        revs = _sortnodesnormal(store, nodes, allowreorder)
+
+    # In the first pass, collect info about the deltas we'll be
+    # generating.
+    requests = []
+
+    # Add the parent of the first rev.
+    revs.insert(0, store.parentrevs(revs[0])[0])
+
+    for i in pycompat.xrange(len(revs) - 1):
+        prev = revs[i]
+        curr = revs[i + 1]
+
+        node = store.node(curr)
+        linknode = lookup(node)
+        p1node, p2node = store.parents(node)
+
+        if ellipses:
+            linkrev = cl.rev(linknode)
+            clrevtolocalrev[linkrev] = curr
+
+            # This is a node to send in full, because the changeset it
+            # corresponds to was a full changeset.
+            if linknode in fullclnodes:
+                requests.append(revisiondeltarequest(
+                    node=node,
+                    p1node=p1node,
+                    p2node=p2node,
+                    linknode=linknode,
+                    basenode=None,
+                ))
+
+            elif linkrev not in precomputedellipsis:
+                pass
+            else:
+                requests.append(_makenarrowdeltarequest(
+                    cl, store, ischangelog, curr, node, linkrev, linknode,
+                    clrevtolocalrev, fullclnodes,
+                    precomputedellipsis))
+        else:
+            requests.append(revisiondeltarequest(
+                node=node,
+                p1node=p1node,
+                p2node=p2node,
+                linknode=linknode,
+                basenode=store.node(prev) if forcedeltaparentprev else None,
+            ))
+
+    # We expect the first pass to be fast, so we only engage the progress
+    # meter for constructing the revision deltas.
+    progress = None
+    if topic is not None:
+        progress = repo.ui.makeprogress(topic, unit=_('chunks'),
+                                        total=len(requests))
+
+    for i, delta in enumerate(store.emitrevisiondeltas(requests)):
+        if progress:
+            progress.update(i + 1)
+
+        yield delta
+
+    if progress:
+        progress.complete()
+
+class cgpacker(object):
+    def __init__(self, repo, filematcher, version, allowreorder,
+                 builddeltaheader, manifestsend,
+                 forcedeltaparentprev=False,
+                 bundlecaps=None, ellipses=False,
+                 shallow=False, ellipsisroots=None, fullnodes=None):
         """Given a source repo, construct a bundler.
 
+        filematcher is a matcher that matches on files to include in the
+        changegroup. Used to facilitate sparse changegroups.
+
+        allowreorder controls whether reordering of revisions is allowed.
+        This value is used when ``bundle.reorder`` is ``auto`` or isn't
+        set.
+
+        forcedeltaparentprev indicates whether delta parents must be against
+        the previous revision in a delta group. This should only be used for
+        compatibility with changegroup version 1.
+
+        builddeltaheader is a callable that constructs the header for a group
+        delta.
+
+        manifestsend is a chunk to send after manifests have been fully emitted.
+
+        ellipses indicates whether ellipsis serving mode is enabled.
+
         bundlecaps is optional and can be used to specify the set of
         capabilities which can be used to build the bundle. While bundlecaps is
         unused in core Mercurial, extensions rely on this feature to communicate
         capabilities to customize the changegroup packer.
+
+        shallow indicates whether shallow data might be sent. The packer may
+        need to pack file contents not introduced by the changes being packed.
+
+        fullnodes is the set of changelog nodes which should not be ellipsis
+        nodes. We store this rather than the set of nodes that should be
+        ellipsis because for very large histories we expect this to be
+        significantly smaller.
         """
+        assert filematcher
+        self._filematcher = filematcher
+
+        self.version = version
+        self._forcedeltaparentprev = forcedeltaparentprev
+        self._builddeltaheader = builddeltaheader
+        self._manifestsend = manifestsend
+        self._ellipses = ellipses
+
         # Set of capabilities we can use to build the bundle.
         if bundlecaps is None:
             bundlecaps = set()
         self._bundlecaps = bundlecaps
+        self._isshallow = shallow
+        self._fullclnodes = fullnodes
+
+        # Maps ellipsis revs to their roots at the changelog level.
+        self._precomputedellipsis = ellipsisroots
+
         # experimental config: bundle.reorder
         reorder = repo.ui.config('bundle', 'reorder')
         if reorder == 'auto':
-            reorder = None
+            self._reorder = allowreorder
         else:
-            reorder = stringutil.parsebool(reorder)
+            self._reorder = stringutil.parsebool(reorder)
+
         self._repo = repo
-        self._reorder = reorder
+
         if self._repo.ui.verbose and not self._repo.ui.debugflag:
             self._verbosenote = self._repo.ui.note
         else:
             self._verbosenote = lambda s: None
 
-    def close(self):
-        return closechunk()
-
-    def fileheader(self, fname):
-        return chunkheader(len(fname)) + fname
-
-    # Extracted both for clarity and for overriding in extensions.
-    def _sortgroup(self, revlog, nodelist, lookup):
-        """Sort nodes for change group and turn them into revnums."""
-        # for generaldelta revlogs, we linearize the revs; this will both be
-        # much quicker and generate a much smaller bundle
-        if (revlog._generaldelta and self._reorder is None) or self._reorder:
-            dag = dagutil.revlogdag(revlog)
-            return dag.linearize(set(revlog.rev(n) for n in nodelist))
-        else:
-            return sorted([revlog.rev(n) for n in nodelist])
-
-    def group(self, nodelist, revlog, lookup, units=None):
-        """Calculate a delta group, yielding a sequence of changegroup chunks
-        (strings).
-
-        Given a list of changeset revs, return a set of deltas and
-        metadata corresponding to nodes. The first delta is
-        first parent(nodelist[0]) -> nodelist[0], the receiver is
-        guaranteed to have this parent as it has all history before
-        these changesets. In the case firstparent is nullrev the
-        changegroup starts with a full revision.
-
-        If units is not None, progress detail will be generated, units specifies
-        the type of revlog that is touched (changelog, manifest, etc.).
+    def generate(self, commonrevs, clnodes, fastpathlinkrev, source,
+                 changelog=True):
+        """Yield a sequence of changegroup byte chunks.
+        If changelog is False, changelog data won't be added to changegroup
         """
-        # if we don't have any revisions touched by these changesets, bail
-        if len(nodelist) == 0:
-            yield self.close()
-            return
-
-        revs = self._sortgroup(revlog, nodelist, lookup)
 
-        # add the parent of the first rev
-        p = revlog.parentrevs(revs[0])[0]
-        revs.insert(0, p)
-
-        # build deltas
-        progress = None
-        if units is not None:
-            progress = self._repo.ui.makeprogress(_('bundling'), unit=units,
-                                                  total=(len(revs) - 1))
-        for r in xrange(len(revs) - 1):
-            if progress:
-                progress.update(r + 1)
-            prev, curr = revs[r], revs[r + 1]
-            linknode = lookup(revlog.node(curr))
-            for c in self.revchunk(revlog, curr, prev, linknode):
-                yield c
-
-        if progress:
-            progress.complete()
-        yield self.close()
-
-    # filter any nodes that claim to be part of the known set
-    def prune(self, revlog, missing, commonrevs):
-        rr, rl = revlog.rev, revlog.linkrev
-        return [n for n in missing if rl(rr(n)) not in commonrevs]
-
-    def _packmanifests(self, dir, mfnodes, lookuplinknode):
-        """Pack flat manifests into a changegroup stream."""
-        assert not dir
-        for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
-                                lookuplinknode, units=_('manifests')):
-            yield chunk
-
-    def _manifestsdone(self):
-        return ''
-
-    def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
-        '''yield a sequence of changegroup chunks (strings)'''
         repo = self._repo
         cl = repo.changelog
 
-        clrevorder = {}
-        mfs = {} # needed manifests
-        fnodes = {} # needed file nodes
-        changedfiles = set()
-
-        # Callback for the changelog, used to collect changed files and manifest
-        # nodes.
-        # Returns the linkrev node (identity in the changelog case).
-        def lookupcl(x):
-            c = cl.read(x)
-            clrevorder[x] = len(clrevorder)
-            n = c[0]
-            # record the first changeset introducing this manifest version
-            mfs.setdefault(n, x)
-            # Record a complete list of potentially-changed files in
-            # this manifest.
-            changedfiles.update(c[3])
-            return x
-
         self._verbosenote(_('uncompressed size of bundle content:\n'))
         size = 0
-        for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
-            size += len(chunk)
-            yield chunk
+
+        clstate, deltas = self._generatechangelog(cl, clnodes)
+        for delta in deltas:
+            if changelog:
+                for chunk in _revisiondeltatochunks(delta,
+                                                    self._builddeltaheader):
+                    size += len(chunk)
+                    yield chunk
+
+        close = closechunk()
+        size += len(close)
+        yield closechunk()
+
         self._verbosenote(_('%8.i (changelog)\n') % size)
 
+        clrevorder = clstate['clrevorder']
+        manifests = clstate['manifests']
+        changedfiles = clstate['changedfiles']
+
         # We need to make sure that the linkrev in the changegroup refers to
         # the first changeset that introduced the manifest or file revision.
         # The fastpath is usually safer than the slowpath, because the filelogs
@@ -648,34 +879,142 @@
         fastpathlinkrev = fastpathlinkrev and (
             'treemanifest' not in repo.requirements)
 
-        for chunk in self.generatemanifests(commonrevs, clrevorder,
-                fastpathlinkrev, mfs, fnodes, source):
-            yield chunk
-        mfs.clear()
+        fnodes = {}  # needed file nodes
+
+        size = 0
+        it = self.generatemanifests(
+            commonrevs, clrevorder, fastpathlinkrev, manifests, fnodes, source,
+            clstate['clrevtomanifestrev'])
+
+        for tree, deltas in it:
+            if tree:
+                assert self.version == b'03'
+                chunk = _fileheader(tree)
+                size += len(chunk)
+                yield chunk
+
+            for delta in deltas:
+                chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
+                for chunk in chunks:
+                    size += len(chunk)
+                    yield chunk
+
+            close = closechunk()
+            size += len(close)
+            yield close
+
+        self._verbosenote(_('%8.i (manifests)\n') % size)
+        yield self._manifestsend
+
+        mfdicts = None
+        if self._ellipses and self._isshallow:
+            mfdicts = [(self._repo.manifestlog[n].read(), lr)
+                       for (n, lr) in manifests.iteritems()]
+
+        manifests.clear()
         clrevs = set(cl.rev(x) for x in clnodes)
 
-        if not fastpathlinkrev:
-            def linknodes(unused, fname):
-                return fnodes.get(fname, {})
-        else:
-            cln = cl.node
-            def linknodes(filerevlog, fname):
-                llr = filerevlog.linkrev
-                fln = filerevlog.node
-                revs = ((r, llr(r)) for r in filerevlog)
-                return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
+        it = self.generatefiles(changedfiles, commonrevs,
+                                source, mfdicts, fastpathlinkrev,
+                                fnodes, clrevs)
+
+        for path, deltas in it:
+            h = _fileheader(path)
+            size = len(h)
+            yield h
 
-        for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
-                                        source):
-            yield chunk
+            for delta in deltas:
+                chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
+                for chunk in chunks:
+                    size += len(chunk)
+                    yield chunk
 
-        yield self.close()
+            close = closechunk()
+            size += len(close)
+            yield close
+
+            self._verbosenote(_('%8.i  %s\n') % (size, path))
+
+        yield closechunk()
 
         if clnodes:
             repo.hook('outgoing', node=hex(clnodes[0]), source=source)
 
-    def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
-                          fnodes, source):
+    def _generatechangelog(self, cl, nodes):
+        """Generate data for changelog chunks.
+
+        Returns a 2-tuple of a dict containing state and an iterable of
+        byte chunks. The state will not be fully populated until the
+        chunk stream has been fully consumed.
+        """
+        clrevorder = {}
+        manifests = {}
+        mfl = self._repo.manifestlog
+        changedfiles = set()
+        clrevtomanifestrev = {}
+
+        # Callback for the changelog, used to collect changed files and
+        # manifest nodes.
+        # Returns the linkrev node (identity in the changelog case).
+        def lookupcl(x):
+            c = cl.changelogrevision(x)
+            clrevorder[x] = len(clrevorder)
+
+            if self._ellipses:
+                # Only update manifests if x is going to be sent. Otherwise we
+                # end up with bogus linkrevs specified for manifests and
+                # we skip some manifest nodes that we should otherwise
+                # have sent.
+                if (x in self._fullclnodes
+                    or cl.rev(x) in self._precomputedellipsis):
+
+                    manifestnode = c.manifest
+                    # Record the first changeset introducing this manifest
+                    # version.
+                    manifests.setdefault(manifestnode, x)
+                    # Set this narrow-specific dict so we have the lowest
+                    # manifest revnum to look up for this cl revnum. (Part of
+                    # mapping changelog ellipsis parents to manifest ellipsis
+                    # parents)
+                    clrevtomanifestrev.setdefault(
+                        cl.rev(x), mfl.rev(manifestnode))
+                # We can't trust the changed files list in the changeset if the
+                # client requested a shallow clone.
+                if self._isshallow:
+                    changedfiles.update(mfl[c.manifest].read().keys())
+                else:
+                    changedfiles.update(c.files)
+            else:
+                # record the first changeset introducing this manifest version
+                manifests.setdefault(c.manifest, x)
+                # Record a complete list of potentially-changed files in
+                # this manifest.
+                changedfiles.update(c.files)
+
+            return x
+
+        state = {
+            'clrevorder': clrevorder,
+            'manifests': manifests,
+            'changedfiles': changedfiles,
+            'clrevtomanifestrev': clrevtomanifestrev,
+        }
+
+        gen = deltagroup(
+            self._repo, cl, nodes, True, lookupcl,
+            self._forcedeltaparentprev,
+            # Reorder settings are currently ignored for changelog.
+            True,
+            ellipses=self._ellipses,
+            topic=_('changesets'),
+            clrevtolocalrev={},
+            fullclnodes=self._fullclnodes,
+            precomputedellipsis=self._precomputedellipsis)
+
+        return state, gen
+
+    def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev,
+                          manifests, fnodes, source, clrevtolocalrev):
         """Returns an iterator of changegroup chunks containing manifests.
 
         `source` is unused here, but is used by extensions like remotefilelog to
@@ -683,16 +1022,15 @@
         """
         repo = self._repo
         mfl = repo.manifestlog
-        dirlog = mfl._revlog.dirlog
-        tmfnodes = {'': mfs}
+        tmfnodes = {'': manifests}
 
         # Callback for the manifest, used to collect linkrevs for filelog
         # revisions.
         # Returns the linkrev node (collected in lookupcl).
-        def makelookupmflinknode(dir, nodes):
+        def makelookupmflinknode(tree, nodes):
             if fastpathlinkrev:
-                assert not dir
-                return mfs.__getitem__
+                assert not tree
+                return manifests.__getitem__
 
             def lookupmflinknode(x):
                 """Callback for looking up the linknode for manifests.
@@ -711,16 +1049,16 @@
                 treemanifests to send.
                 """
                 clnode = nodes[x]
-                mdata = mfl.get(dir, x).readfast(shallow=True)
+                mdata = mfl.get(tree, x).readfast(shallow=True)
                 for p, n, fl in mdata.iterentries():
                     if fl == 't': # subdirectory manifest
-                        subdir = dir + p + '/'
-                        tmfclnodes = tmfnodes.setdefault(subdir, {})
+                        subtree = tree + p + '/'
+                        tmfclnodes = tmfnodes.setdefault(subtree, {})
                         tmfclnode = tmfclnodes.setdefault(n, clnode)
                         if clrevorder[clnode] < clrevorder[tmfclnode]:
                             tmfclnodes[n] = clnode
                     else:
-                        f = dir + p
+                        f = tree + p
                         fclnodes = fnodes.setdefault(f, {})
                         fclnode = fclnodes.setdefault(n, clnode)
                         if clrevorder[clnode] < clrevorder[fclnode]:
@@ -728,22 +1066,94 @@
                 return clnode
             return lookupmflinknode
 
-        size = 0
         while tmfnodes:
-            dir, nodes = tmfnodes.popitem()
-            prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
-            if not dir or prunednodes:
-                for x in self._packmanifests(dir, prunednodes,
-                                             makelookupmflinknode(dir, nodes)):
-                    size += len(x)
-                    yield x
-        self._verbosenote(_('%8.i (manifests)\n') % size)
-        yield self._manifestsdone()
+            tree, nodes = tmfnodes.popitem()
+            store = mfl.getstorage(tree)
+
+            if not self._filematcher.visitdir(store.tree[:-1] or '.'):
+                # No nodes to send because this directory is out of
+                # the client's view of the repository (probably
+                # because of narrow clones).
+                prunednodes = []
+            else:
+                # Avoid sending any manifest nodes we can prove the
+                # client already has by checking linkrevs. See the
+                # related comment in generatefiles().
+                prunednodes = self._prunemanifests(store, nodes, commonrevs)
+            if tree and not prunednodes:
+                continue
+
+            lookupfn = makelookupmflinknode(tree, nodes)
+
+            deltas = deltagroup(
+                self._repo, store, prunednodes, False, lookupfn,
+                self._forcedeltaparentprev, self._reorder,
+                ellipses=self._ellipses,
+                topic=_('manifests'),
+                clrevtolocalrev=clrevtolocalrev,
+                fullclnodes=self._fullclnodes,
+                precomputedellipsis=self._precomputedellipsis)
+
+            yield tree, deltas
+
+    def _prunemanifests(self, store, nodes, commonrevs):
+        # This is split out as a separate method to allow filtering
+        # commonrevs in extension code.
+        #
+        # TODO(augie): this shouldn't be required, instead we should
+        # make filtering of revisions to send delegated to the store
+        # layer.
+        frev, flr = store.rev, store.linkrev
+        return [n for n in nodes if flr(frev(n)) not in commonrevs]
 
     # The 'source' parameter is useful for extensions
-    def generatefiles(self, changedfiles, linknodes, commonrevs, source):
+    def generatefiles(self, changedfiles, commonrevs, source,
+                      mfdicts, fastpathlinkrev, fnodes, clrevs):
+        changedfiles = list(filter(self._filematcher, changedfiles))
+
+        if not fastpathlinkrev:
+            def normallinknodes(unused, fname):
+                return fnodes.get(fname, {})
+        else:
+            cln = self._repo.changelog.node
+
+            def normallinknodes(store, fname):
+                flinkrev = store.linkrev
+                fnode = store.node
+                revs = ((r, flinkrev(r)) for r in store)
+                return dict((fnode(r), cln(lr))
+                            for r, lr in revs if lr in clrevs)
+
+        clrevtolocalrev = {}
+
+        if self._isshallow:
+            # In a shallow clone, the linknodes callback needs to also include
+            # those file nodes that are in the manifests we sent but weren't
+            # introduced by those manifests.
+            commonctxs = [self._repo[c] for c in commonrevs]
+            clrev = self._repo.changelog.rev
+
+            def linknodes(flog, fname):
+                for c in commonctxs:
+                    try:
+                        fnode = c.filenode(fname)
+                        clrevtolocalrev[c.rev()] = flog.rev(fnode)
+                    except error.ManifestLookupError:
+                        pass
+                links = normallinknodes(flog, fname)
+                if len(links) != len(mfdicts):
+                    for mf, lr in mfdicts:
+                        fnode = mf.get(fname, None)
+                        if fnode in links:
+                            links[fnode] = min(links[fnode], lr, key=clrev)
+                        elif fnode:
+                            links[fnode] = lr
+                return links
+        else:
+            linknodes = normallinknodes
+
         repo = self._repo
-        progress = repo.ui.makeprogress(_('bundling'), unit=_('files'),
+        progress = repo.ui.makeprogress(_('files'), unit=_('files'),
                                         total=len(changedfiles))
         for i, fname in enumerate(sorted(changedfiles)):
             filerevlog = repo.file(fname)
@@ -751,129 +1161,93 @@
                 raise error.Abort(_("empty or missing file data for %s") %
                                   fname)
 
+            clrevtolocalrev.clear()
+
             linkrevnodes = linknodes(filerevlog, fname)
             # Lookup for filenodes, we collected the linkrev nodes above in the
             # fastpath case and with lookupmf in the slowpath case.
             def lookupfilelog(x):
                 return linkrevnodes[x]
 
-            filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
-            if filenodes:
-                progress.update(i + 1, item=fname)
-                h = self.fileheader(fname)
-                size = len(h)
-                yield h
-                for chunk in self.group(filenodes, filerevlog, lookupfilelog):
-                    size += len(chunk)
-                    yield chunk
-                self._verbosenote(_('%8.i  %s\n') % (size, fname))
+            frev, flr = filerevlog.rev, filerevlog.linkrev
+            # Skip sending any filenode we know the client already
+            # has. This avoids over-sending files relatively
+            # inexpensively, so it's not a problem if we under-filter
+            # here.
+            filenodes = [n for n in linkrevnodes
+                         if flr(frev(n)) not in commonrevs]
+
+            if not filenodes:
+                continue
+
+            progress.update(i + 1, item=fname)
+
+            deltas = deltagroup(
+                self._repo, filerevlog, filenodes, False, lookupfilelog,
+                self._forcedeltaparentprev, self._reorder,
+                ellipses=self._ellipses,
+                clrevtolocalrev=clrevtolocalrev,
+                fullclnodes=self._fullclnodes,
+                precomputedellipsis=self._precomputedellipsis)
+
+            yield fname, deltas
+
         progress.complete()
 
-    def deltaparent(self, revlog, rev, p1, p2, prev):
-        if not revlog.candelta(prev, rev):
-            raise error.ProgrammingError('cg1 should not be used in this case')
-        return prev
-
-    def revchunk(self, revlog, rev, prev, linknode):
-        node = revlog.node(rev)
-        p1, p2 = revlog.parentrevs(rev)
-        base = self.deltaparent(revlog, rev, p1, p2, prev)
+def _makecg1packer(repo, filematcher, bundlecaps, ellipses=False,
+                   shallow=False, ellipsisroots=None, fullnodes=None):
+    builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
+        d.node, d.p1node, d.p2node, d.linknode)
 
-        prefix = ''
-        if revlog.iscensored(base) or revlog.iscensored(rev):
-            try:
-                delta = revlog.revision(node, raw=True)
-            except error.CensoredNodeError as e:
-                delta = e.tombstone
-            if base == nullrev:
-                prefix = mdiff.trivialdiffheader(len(delta))
-            else:
-                baselen = revlog.rawsize(base)
-                prefix = mdiff.replacediffheader(baselen, len(delta))
-        elif base == nullrev:
-            delta = revlog.revision(node, raw=True)
-            prefix = mdiff.trivialdiffheader(len(delta))
-        else:
-            delta = revlog.revdiff(base, rev)
-        p1n, p2n = revlog.parents(node)
-        basenode = revlog.node(base)
-        flags = revlog.flags(rev)
-        meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
-        meta += prefix
-        l = len(meta) + len(delta)
-        yield chunkheader(l)
-        yield meta
-        yield delta
-    def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
-        # do nothing with basenode, it is implicitly the previous one in HG10
-        # do nothing with flags, it is implicitly 0 for cg1 and cg2
-        return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
+    return cgpacker(repo, filematcher, b'01',
+                    allowreorder=None,
+                    builddeltaheader=builddeltaheader,
+                    manifestsend=b'',
+                    forcedeltaparentprev=True,
+                    bundlecaps=bundlecaps,
+                    ellipses=ellipses,
+                    shallow=shallow,
+                    ellipsisroots=ellipsisroots,
+                    fullnodes=fullnodes)
 
-class cg2packer(cg1packer):
-    version = '02'
-    deltaheader = _CHANGEGROUPV2_DELTA_HEADER
-
-    def __init__(self, repo, bundlecaps=None):
-        super(cg2packer, self).__init__(repo, bundlecaps)
-        if self._reorder is None:
-            # Since generaldelta is directly supported by cg2, reordering
-            # generally doesn't help, so we disable it by default (treating
-            # bundle.reorder=auto just like bundle.reorder=False).
-            self._reorder = False
+def _makecg2packer(repo, filematcher, bundlecaps, ellipses=False,
+                   shallow=False, ellipsisroots=None, fullnodes=None):
+    builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
+        d.node, d.p1node, d.p2node, d.basenode, d.linknode)
 
-    def deltaparent(self, revlog, rev, p1, p2, prev):
-        dp = revlog.deltaparent(rev)
-        if dp == nullrev and revlog.storedeltachains:
-            # Avoid sending full revisions when delta parent is null. Pick prev
-            # in that case. It's tempting to pick p1 in this case, as p1 will
-            # be smaller in the common case. However, computing a delta against
-            # p1 may require resolving the raw text of p1, which could be
-            # expensive. The revlog caches should have prev cached, meaning
-            # less CPU for changegroup generation. There is likely room to add
-            # a flag and/or config option to control this behavior.
-            base = prev
-        elif dp == nullrev:
-            # revlog is configured to use full snapshot for a reason,
-            # stick to full snapshot.
-            base = nullrev
-        elif dp not in (p1, p2, prev):
-            # Pick prev when we can't be sure remote has the base revision.
-            return prev
-        else:
-            base = dp
-        if base != nullrev and not revlog.candelta(base, rev):
-            base = nullrev
-        return base
+    # Since generaldelta is directly supported by cg2, reordering
+    # generally doesn't help, so we disable it by default (treating
+    # bundle.reorder=auto just like bundle.reorder=False).
+    return cgpacker(repo, filematcher, b'02',
+                    allowreorder=False,
+                    builddeltaheader=builddeltaheader,
+                    manifestsend=b'',
+                    bundlecaps=bundlecaps,
+                    ellipses=ellipses,
+                    shallow=shallow,
+                    ellipsisroots=ellipsisroots,
+                    fullnodes=fullnodes)
 
-    def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
-        # Do nothing with flags, it is implicitly 0 in cg1 and cg2
-        return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
-
-class cg3packer(cg2packer):
-    version = '03'
-    deltaheader = _CHANGEGROUPV3_DELTA_HEADER
-
-    def _packmanifests(self, dir, mfnodes, lookuplinknode):
-        if dir:
-            yield self.fileheader(dir)
+def _makecg3packer(repo, filematcher, bundlecaps, ellipses=False,
+                   shallow=False, ellipsisroots=None, fullnodes=None):
+    builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
+        d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags)
 
-        dirlog = self._repo.manifestlog._revlog.dirlog(dir)
-        for chunk in self.group(mfnodes, dirlog, lookuplinknode,
-                                units=_('manifests')):
-            yield chunk
-
-    def _manifestsdone(self):
-        return self.close()
+    return cgpacker(repo, filematcher, b'03',
+                    allowreorder=False,
+                    builddeltaheader=builddeltaheader,
+                    manifestsend=closechunk(),
+                    bundlecaps=bundlecaps,
+                    ellipses=ellipses,
+                    shallow=shallow,
+                    ellipsisroots=ellipsisroots,
+                    fullnodes=fullnodes)
 
-    def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
-        return struct.pack(
-            self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
-
-_packermap = {'01': (cg1packer, cg1unpacker),
+_packermap = {'01': (_makecg1packer, cg1unpacker),
              # cg2 adds support for exchanging generaldelta
-             '02': (cg2packer, cg2unpacker),
+             '02': (_makecg2packer, cg2unpacker),
              # cg3 adds support for exchanging revlog flags and treemanifests
-             '03': (cg3packer, cg3unpacker),
+             '03': (_makecg3packer, cg3unpacker),
 }
 
 def allsupportedversions(repo):
@@ -899,7 +1273,7 @@
         # support versions 01 and 02.
         versions.discard('01')
         versions.discard('02')
-    if NARROW_REQUIREMENT in repo.requirements:
+    if repository.NARROW_REQUIREMENT in repo.requirements:
         # Versions 01 and 02 don't support revlog flags, and we need to
         # support that for stripping and unbundling to work.
         versions.discard('01')
@@ -927,9 +1301,32 @@
     assert versions
     return min(versions)
 
-def getbundler(version, repo, bundlecaps=None):
+def getbundler(version, repo, bundlecaps=None, filematcher=None,
+               ellipses=False, shallow=False, ellipsisroots=None,
+               fullnodes=None):
     assert version in supportedoutgoingversions(repo)
-    return _packermap[version][0](repo, bundlecaps)
+
+    if filematcher is None:
+        filematcher = matchmod.alwaysmatcher(repo.root, '')
+
+    if version == '01' and not filematcher.always():
+        raise error.ProgrammingError('version 01 changegroups do not support '
+                                     'sparse file matchers')
+
+    if ellipses and version in (b'01', b'02'):
+        raise error.Abort(
+            _('ellipsis nodes require at least cg3 on client and server, '
+              'but negotiated version %s') % version)
+
+    # Requested files could include files not in the local store. So
+    # filter those out.
+    filematcher = matchmod.intersectmatchers(repo.narrowmatch(),
+                                             filematcher)
+
+    fn = _packermap[version][0]
+    return fn(repo, filematcher, bundlecaps, ellipses=ellipses,
+              shallow=shallow, ellipsisroots=ellipsisroots,
+              fullnodes=fullnodes)
 
 def getunbundler(version, fh, alg, extras=None):
     return _packermap[version][1](fh, alg, extras=extras)
@@ -950,8 +1347,9 @@
                         {'clcount': len(outgoing.missing) })
 
 def makestream(repo, outgoing, version, source, fastpath=False,
-               bundlecaps=None):
-    bundler = getbundler(version, repo, bundlecaps=bundlecaps)
+               bundlecaps=None, filematcher=None):
+    bundler = getbundler(version, repo, bundlecaps=bundlecaps,
+                         filematcher=filematcher)
 
     repo = repo.unfiltered()
     commonrevs = outgoing.common
@@ -989,7 +1387,7 @@
         revisions += len(fl) - o
         if f in needfiles:
             needs = needfiles[f]
-            for new in xrange(o, len(fl)):
+            for new in pycompat.xrange(o, len(fl)):
                 n = fl.node(new)
                 if n in needs:
                     needs.remove(n)
--- a/mercurial/changelog.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/changelog.py	Wed Sep 26 20:33:09 2018 +0900
@@ -22,7 +22,6 @@
     error,
     pycompat,
     revlog,
-    util,
 )
 from .utils import (
     dateutil,
@@ -304,7 +303,7 @@
         # Delta chains for changelogs tend to be very small because entries
         # tend to be small and don't delta well with each. So disable delta
         # chains.
-        self.storedeltachains = False
+        self._storedeltachains = False
 
         self._realopener = opener
         self._delayed = False
@@ -313,7 +312,7 @@
         self.filteredrevs = frozenset()
 
     def tiprev(self):
-        for i in xrange(len(self) -1, -2, -1):
+        for i in pycompat.xrange(len(self) -1, -2, -1):
             if i not in self.filteredrevs:
                 return i
 
@@ -332,7 +331,7 @@
             return revlog.revlog.__iter__(self)
 
         def filterediter():
-            for i in xrange(len(self)):
+            for i in pycompat.xrange(len(self)):
                 if i not in self.filteredrevs:
                     yield i
 
@@ -344,12 +343,6 @@
             if i not in self.filteredrevs:
                 yield i
 
-    @util.propertycache
-    def nodemap(self):
-        # XXX need filtering too
-        self.rev(self.node(0))
-        return self._nodecache
-
     def reachableroots(self, minroot, heads, roots, includepath=False):
         return self.index.reachableroots2(minroot, heads, roots, includepath)
 
@@ -520,10 +513,10 @@
         # revision text contain two "\n\n" sequences -> corrupt
         # repository since read cannot unpack the revision.
         if not user:
-            raise error.RevlogError(_("empty username"))
+            raise error.StorageError(_("empty username"))
         if "\n" in user:
-            raise error.RevlogError(_("username %r contains a newline")
-                                    % pycompat.bytestr(user))
+            raise error.StorageError(_("username %r contains a newline")
+                                     % pycompat.bytestr(user))
 
         desc = stripdesc(desc)
 
@@ -536,8 +529,8 @@
             if branch in ("default", ""):
                 del extra["branch"]
             elif branch in (".", "null", "tip"):
-                raise error.RevlogError(_('the name \'%s\' is reserved')
-                                        % branch)
+                raise error.StorageError(_('the name \'%s\' is reserved')
+                                         % branch)
         if extra:
             extra = encodeextra(extra)
             parseddate = "%s %s" % (parseddate, extra)
@@ -552,19 +545,3 @@
         just to access this is costly."""
         extra = self.read(rev)[5]
         return encoding.tolocal(extra.get("branch")), 'close' in extra
-
-    def _addrevision(self, node, rawtext, transaction, *args, **kwargs):
-        # overlay over the standard revlog._addrevision to track the new
-        # revision on the transaction.
-        rev = len(self)
-        node = super(changelog, self)._addrevision(node, rawtext, transaction,
-                                                   *args, **kwargs)
-        revs = transaction.changes.get('revs')
-        if revs is not None:
-            if revs:
-                assert revs[-1] + 1 == rev
-                revs = xrange(revs[0], rev + 1)
-            else:
-                revs = xrange(rev, rev + 1)
-            transaction.changes['revs'] = revs
-        return node
--- a/mercurial/chgserver.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/chgserver.py	Wed Sep 26 20:33:09 2018 +0900
@@ -311,6 +311,7 @@
             _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
             repo, fin, fout)
         self.clientsock = sock
+        self._ioattached = False
         self._oldios = []  # original (self.ch, ui.fp, fd) before "attachio"
         self.hashstate = hashstate
         self.baseaddress = baseaddress
@@ -324,6 +325,7 @@
         # handled by dispatch._dispatch()
         self.ui.flush()
         self._restoreio()
+        self._ioattached = False
 
     def attachio(self):
         """Attach to client's stdio passed via unix domain socket; all
@@ -337,13 +339,13 @@
 
         ui = self.ui
         ui.flush()
-        first = self._saveio()
+        self._saveio()
         for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
             assert fd > 0
             fp = getattr(ui, fn)
             os.dup2(fd, fp.fileno())
             os.close(fd)
-            if not first:
+            if self._ioattached:
                 continue
             # reset buffering mode when client is first attached. as we want
             # to see output immediately on pager, the mode stays unchanged
@@ -362,18 +364,18 @@
                 setattr(ui, fn, newfp)
             setattr(self, cn, newfp)
 
+        self._ioattached = True
         self.cresult.write(struct.pack('>i', len(clientfds)))
 
     def _saveio(self):
         if self._oldios:
-            return False
+            return
         ui = self.ui
         for cn, fn, _mode in _iochannels:
             ch = getattr(self, cn)
             fp = getattr(ui, fn)
             fd = os.dup(fp.fileno())
             self._oldios.append((ch, fp, fd))
-        return True
 
     def _restoreio(self):
         ui = self.ui
@@ -454,7 +456,16 @@
         os.umask(mask)
 
     def runcommand(self):
-        return super(chgcmdserver, self).runcommand()
+        # pager may be attached within the runcommand session, which should
+        # be detached at the end of the session. otherwise the pager wouldn't
+        # receive EOF.
+        globaloldios = self._oldios
+        self._oldios = []
+        try:
+            return super(chgcmdserver, self).runcommand()
+        finally:
+            self._restoreio()
+            self._oldios = globaloldios
 
     def setenv(self):
         """Clear and update os.environ
--- a/mercurial/cmdutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/cmdutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -581,7 +581,7 @@
     unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
     if unresolvedlist:
         mergeliststr = '\n'.join(
-            ['    %s' % util.pathto(repo.root, pycompat.getcwd(), path)
+            ['    %s' % util.pathto(repo.root, encoding.getcwd(), path)
              for path in unresolvedlist])
         msg = _('''Unresolved merge conflicts:
 
@@ -607,17 +607,13 @@
 def _unshelvemsg():
     return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
 
-def _updatecleanmsg(dest=None):
-    warning = _('warning: this will discard uncommitted changes')
-    return 'hg update --clean %s (%s)' % (dest or '.', warning)
-
 def _graftmsg():
     # tweakdefaults requires `update` to have a rev hence the `.`
-    return _helpmessage('hg graft --continue', _updatecleanmsg())
+    return _helpmessage('hg graft --continue', 'hg graft --abort')
 
 def _mergemsg():
     # tweakdefaults requires `update` to have a rev hence the `.`
-     return _helpmessage('hg commit', _updatecleanmsg())
+    return _helpmessage('hg commit', 'hg merge --abort')
 
 def _bisectmsg():
     msg = _('To mark the changeset good:    hg bisect --good\n'
@@ -658,16 +654,15 @@
     statetuple = _getrepostate(repo)
     label = 'status.morestatus'
     if statetuple:
-        fm.startitem()
         state, statedetectionpredicate, helpfulmsg = statetuple
         statemsg = _('The repository is in an unfinished *%s* state.') % state
-        fm.write('statemsg', '%s\n',  _commentlines(statemsg), label=label)
+        fm.plain('%s\n' % _commentlines(statemsg), label=label)
         conmsg = _conflictsmsg(repo)
         if conmsg:
-            fm.write('conflictsmsg', '%s\n', conmsg, label=label)
+            fm.plain('%s\n' % conmsg, label=label)
         if helpfulmsg:
             helpmsg = helpfulmsg()
-            fm.write('helpmsg', '%s\n', helpmsg, label=label)
+            fm.plain('%s\n' % helpmsg, label=label)
 
 def findpossible(cmd, table, strict=False):
     """
@@ -1058,7 +1053,7 @@
     fn = makefilename(ctx, pat, **props)
     return open(fn, mode)
 
-def openrevlog(repo, cmd, file_, opts):
+def openstorage(repo, cmd, file_, opts, returnrevlog=False):
     """opens the changelog, manifest, a filelog or a given revlog"""
     cl = opts['changelog']
     mf = opts['manifest']
@@ -1087,24 +1082,50 @@
                                    "treemanifest enabled"))
             if not dir.endswith('/'):
                 dir = dir + '/'
-            dirlog = repo.manifestlog._revlog.dirlog(dir)
+            dirlog = repo.manifestlog.getstorage(dir)
             if len(dirlog):
                 r = dirlog
         elif mf:
-            r = repo.manifestlog._revlog
+            r = repo.manifestlog.getstorage(b'')
         elif file_:
             filelog = repo.file(file_)
             if len(filelog):
                 r = filelog
+
+        # Not all storage may be revlogs. If requested, try to return an actual
+        # revlog instance.
+        if returnrevlog:
+            if isinstance(r, revlog.revlog):
+                pass
+            elif util.safehasattr(r, '_revlog'):
+                r = r._revlog
+            elif r is not None:
+                raise error.Abort(_('%r does not appear to be a revlog') % r)
+
     if not r:
+        if not returnrevlog:
+            raise error.Abort(_('cannot give path to non-revlog'))
+
         if not file_:
             raise error.CommandError(cmd, _('invalid arguments'))
         if not os.path.isfile(file_):
             raise error.Abort(_("revlog '%s' not found") % file_)
-        r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
+        r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
                           file_[:-2] + ".i")
     return r
 
+def openrevlog(repo, cmd, file_, opts):
+    """Obtain a revlog backing storage of an item.
+
+    This is similar to ``openstorage()`` except it always returns a revlog.
+
+    In most cases, a caller cares about the main storage object - not the
+    revlog backing it. Therefore, this function should only be used by code
+    that needs to examine low-level revlog implementation details. e.g. debug
+    commands.
+    """
+    return openstorage(repo, cmd, file_, opts, returnrevlog=True)
+
 def copy(ui, repo, pats, opts, rename=False):
     # called with the repo lock held
     #
@@ -1162,7 +1183,7 @@
             ui.warn(_('%s: not overwriting - %s collides with %s\n') %
                     (reltarget, repo.pathto(abssrc, cwd),
                      repo.pathto(prevsrc, cwd)))
-            return
+            return True # report a failure
 
         # check for overwrites
         exists = os.path.lexists(target)
@@ -1172,7 +1193,7 @@
                 repo.dirstate.normalize(abstarget)):
                 if not rename:
                     ui.warn(_("%s: can't copy - same file\n") % reltarget)
-                    return
+                    return True # report a failure
                 exists = False
                 samefile = True
 
@@ -1185,20 +1206,20 @@
                     else:
                         flags = '--force'
                     if rename:
-                        hint = _('(hg rename %s to replace the file by '
+                        hint = _("('hg rename %s' to replace the file by "
                                  'recording a rename)\n') % flags
                     else:
-                        hint = _('(hg copy %s to replace the file by '
+                        hint = _("('hg copy %s' to replace the file by "
                                  'recording a copy)\n') % flags
                 else:
                     msg = _('%s: not overwriting - file exists\n')
                     if rename:
-                        hint = _('(hg rename --after to record the rename)\n')
+                        hint = _("('hg rename --after' to record the rename)\n")
                     else:
-                        hint = _('(hg copy --after to record the copy)\n')
+                        hint = _("('hg copy --after' to record the copy)\n")
                 ui.warn(msg % reltarget)
                 ui.warn(hint)
-                return
+                return True # report a failure
 
         if after:
             if not exists:
@@ -1208,7 +1229,7 @@
                 else:
                     ui.warn(_('%s: not recording copy - %s does not exist\n') %
                             (relsrc, reltarget))
-                return
+                return True # report a failure
         elif not dryrun:
             try:
                 if exists:
@@ -1232,6 +1253,10 @@
                 else:
                     ui.warn(_('%s: cannot copy - %s\n') %
                             (relsrc, encoding.strtolocal(inst.strerror)))
+                    if rename:
+                        hint = _("('hg rename --after' to record the rename)\n")
+                    else:
+                        hint = _("('hg copy --after' to record the copy)\n")
                     return True # report a failure
 
         if ui.verbose or not exact:
@@ -1349,9 +1374,6 @@
             if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
                 errors += 1
 
-    if errors:
-        ui.warn(_('(consider using --after)\n'))
-
     return errors != 0
 
 ## facility to let extension process additional data into an import patch
@@ -1755,7 +1777,7 @@
         """
         cl_count = len(repo)
         revs = []
-        for j in xrange(0, last + 1):
+        for j in pycompat.xrange(0, last + 1):
             linkrev = filelog.linkrev(j)
             if linkrev < minrev:
                 continue
@@ -1889,9 +1911,6 @@
     revs = _walkrevs(repo, opts)
     if not revs:
         return []
-    if allfiles and len(revs) > 1:
-        raise error.Abort(_("multiple revisions not supported with "
-                            "--all-files"))
     wanted = set()
     slowpath = match.anypats() or (not match.always() and opts.get('removed'))
     fncache = {}
@@ -1902,7 +1921,7 @@
     # wanted: a cache of filenames that were changed (ctx.files()) and that
     # match the file filtering conditions.
 
-    if match.always():
+    if match.always() or allfiles:
         # No files, no patterns.  Display all revs.
         wanted = revs
     elif not slowpath:
@@ -1966,7 +1985,7 @@
         rev = repo[rev].rev()
         ff = _followfilter(repo)
         stop = min(revs[0], revs[-1])
-        for x in xrange(rev, stop - 1, -1):
+        for x in pycompat.xrange(rev, stop - 1, -1):
             if ff.match(x):
                 wanted = wanted - [x]
 
@@ -1985,7 +2004,7 @@
         stopiteration = False
         for windowsize in increasingwindows():
             nrevs = []
-            for i in xrange(windowsize):
+            for i in pycompat.xrange(windowsize):
                 rev = next(it, None)
                 if rev is None:
                     stopiteration = True
@@ -2038,7 +2057,8 @@
                 cca(f)
             names.append(f)
             if ui.verbose or not exact:
-                ui.status(_('adding %s\n') % match.rel(f))
+                ui.status(_('adding %s\n') % match.rel(f),
+                          label='addremove.added')
 
     for subpath in sorted(wctx.substate):
         sub = wctx.sub(subpath)
@@ -2136,7 +2156,8 @@
 
     for f in forget:
         if ui.verbose or not match.exact(f) or interactive:
-            ui.status(_('removing %s\n') % match.rel(f))
+            ui.status(_('removing %s\n') % match.rel(f),
+                      label='addremove.removed')
 
     if not dryrun:
         rejected = wctx.forget(forget, prefix)
@@ -2154,8 +2175,8 @@
         if needsfctx:
             fc = ctx[f]
             fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
-        fm.data(abspath=f)
-        fm.write('path', fmt, m.rel(f))
+        fm.data(path=f)
+        fm.plain(fmt % m.rel(f))
         ret = 0
 
     for subpath in sorted(ctx.substate):
@@ -2269,7 +2290,8 @@
     for f in list:
         if ui.verbose or not m.exact(f):
             progress.increment()
-            ui.status(_('removing %s\n') % m.rel(f))
+            ui.status(_('removing %s\n') % m.rel(f),
+                      label='addremove.removed')
     progress.complete()
 
     if not dryrun:
@@ -2300,7 +2322,7 @@
     fm.startitem()
     fm.context(ctx=ctx)
     fm.write('data', '%s', data)
-    fm.data(abspath=path, path=matcher.rel(path))
+    fm.data(path=path)
 
 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
     err = 1
@@ -2428,7 +2450,7 @@
         if len(old.parents()) > 1:
             # ctx.files() isn't reliable for merges, so fall back to the
             # slower repo.status() method
-            files = set([fn for st in repo.status(base, old)[:3]
+            files = set([fn for st in base.status(old)[:3]
                          for fn in st])
         else:
             files = set(old.files())
@@ -2556,8 +2578,10 @@
         obsmetadata = None
         if opts.get('note'):
             obsmetadata = {'note': encoding.fromlocal(opts['note'])}
+        backup = ui.configbool('ui', 'history-editing-backup')
         scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
-                             fixphase=True, targetphase=commitphase)
+                             fixphase=True, targetphase=commitphase,
+                             backup=backup)
 
         # Fixing the dirstate because localrepo.commitctx does not update
         # it. This is rather convenient because we did not need to update
@@ -2605,7 +2629,7 @@
         committext = buildcommittext(repo, ctx, subs, extramsg)
 
     # run editor in the repository root
-    olddir = pycompat.getcwd()
+    olddir = encoding.getcwd()
     os.chdir(repo.root)
 
     # make in-memory changes visible to external process
@@ -2749,7 +2773,7 @@
 
     # `names` is a mapping for all elements in working copy and target revision
     # The mapping is in the form:
-    #   <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
+    #   <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
     names = {}
 
     with repo.wlock():
@@ -2994,10 +3018,9 @@
                                     util.copyfile(target, bakname)
                                 else:
                                     util.rename(target, bakname)
-                    if ui.verbose or not exact:
-                        if not isinstance(msg, bytes):
-                            msg = msg(abs)
-                        ui.status(msg % rel)
+                    if opts.get('dry_run'):
+                        if ui.verbose or not exact:
+                            ui.status(msg % rel)
                 elif exact:
                     ui.warn(msg % rel)
                 break
@@ -3010,7 +3033,8 @@
             prefetch(repo, [ctx.rev()],
                      matchfiles(repo,
                                 [f for sublist in oplist for f in sublist]))
-            _performrevert(repo, parents, ctx, actions, interactive, tobackup)
+            _performrevert(repo, parents, ctx, names, actions, interactive,
+                           tobackup)
 
         if targetsubs:
             # Revert the subrepos on the revert list
@@ -3022,7 +3046,7 @@
                     raise error.Abort("subrepository '%s' does not exist in %s!"
                                       % (sub, short(ctx.node())))
 
-def _performrevert(repo, parents, ctx, actions, interactive=False,
+def _performrevert(repo, parents, ctx, names, actions, interactive=False,
                    tobackup=None):
     """function that actually perform all the actions computed for revert
 
@@ -3047,16 +3071,23 @@
             pass
         repo.dirstate.remove(f)
 
+    def prntstatusmsg(action, f):
+        rel, exact = names[f]
+        if repo.ui.verbose or not exact:
+            repo.ui.status(actions[action][1] % rel)
+
     audit_path = pathutil.pathauditor(repo.root, cached=True)
     for f in actions['forget'][0]:
         if interactive:
             choice = repo.ui.promptchoice(
                 _("forget added file %s (Yn)?$$ &Yes $$ &No") % f)
             if choice == 0:
+                prntstatusmsg('forget', f)
                 repo.dirstate.drop(f)
             else:
                 excluded_files.append(f)
         else:
+            prntstatusmsg('forget', f)
             repo.dirstate.drop(f)
     for f in actions['remove'][0]:
         audit_path(f)
@@ -3064,13 +3095,16 @@
             choice = repo.ui.promptchoice(
                 _("remove added file %s (Yn)?$$ &Yes $$ &No") % f)
             if choice == 0:
+                prntstatusmsg('remove', f)
                 doremove(f)
             else:
                 excluded_files.append(f)
         else:
+            prntstatusmsg('remove', f)
             doremove(f)
     for f in actions['drop'][0]:
         audit_path(f)
+        prntstatusmsg('drop', f)
         repo.dirstate.remove(f)
 
     normal = None
@@ -3117,14 +3151,18 @@
             tobackup = set()
         # Apply changes
         fp = stringio()
+        # chunks are serialized per file, but files aren't sorted
+        for f in sorted(set(c.header.filename() for c in chunks if ishunk(c))):
+            prntstatusmsg('revert', f)
         for c in chunks:
-            # Create a backup file only if this hunk should be backed up
-            if ishunk(c) and c.header.filename() in tobackup:
+            if ishunk(c):
                 abs = c.header.filename()
-                target = repo.wjoin(abs)
-                bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
-                util.copyfile(target, bakname)
-                tobackup.remove(abs)
+                # Create a backup file only if this hunk should be backed up
+                if c.header.filename() in tobackup:
+                    target = repo.wjoin(abs)
+                    bakname = scmutil.origpath(repo.ui, repo, m.rel(abs))
+                    util.copyfile(target, bakname)
+                    tobackup.remove(abs)
             c.write(fp)
         dopatch = fp.tell()
         fp.seek(0)
@@ -3136,6 +3174,7 @@
         del fp
     else:
         for f in actions['revert'][0]:
+            prntstatusmsg('revert', f)
             checkout(f)
             if normal:
                 normal(f)
@@ -3143,6 +3182,7 @@
     for f in actions['add'][0]:
         # Don't checkout modified files, they are already created by the diff
         if f not in newlyaddedandmodifiedfiles:
+            prntstatusmsg('add', f)
             checkout(f)
             repo.dirstate.add(f)
 
@@ -3150,6 +3190,7 @@
     if node == parent and p2 == nullid:
         normal = repo.dirstate.normal
     for f in actions['undelete'][0]:
+        prntstatusmsg('undelete', f)
         checkout(f)
         normal(f)
 
--- a/mercurial/color.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/color.py	Wed Sep 26 20:33:09 2018 +0900
@@ -83,6 +83,8 @@
     'grep.filename': 'magenta',
     'grep.user': 'magenta',
     'grep.date': 'magenta',
+    'addremove.added': 'green',
+    'addremove.removed': 'red',
     'bookmarks.active': 'green',
     'branches.active': 'none',
     'branches.closed': 'black bold',
@@ -117,6 +119,7 @@
     'formatvariant.config.default': 'green',
     'formatvariant.default': '',
     'histedit.remaining': 'red bold',
+    'ui.error': 'red',
     'ui.prompt': 'yellow',
     'log.changeset': 'yellow',
     'patchbomb.finalsummary': '',
@@ -405,21 +408,21 @@
     _INVALID_HANDLE_VALUE = -1
 
     class _COORD(ctypes.Structure):
-        _fields_ = [('X', ctypes.c_short),
-                    ('Y', ctypes.c_short)]
+        _fields_ = [(r'X', ctypes.c_short),
+                    (r'Y', ctypes.c_short)]
 
     class _SMALL_RECT(ctypes.Structure):
-        _fields_ = [('Left', ctypes.c_short),
-                    ('Top', ctypes.c_short),
-                    ('Right', ctypes.c_short),
-                    ('Bottom', ctypes.c_short)]
+        _fields_ = [(r'Left', ctypes.c_short),
+                    (r'Top', ctypes.c_short),
+                    (r'Right', ctypes.c_short),
+                    (r'Bottom', ctypes.c_short)]
 
     class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
-        _fields_ = [('dwSize', _COORD),
-                    ('dwCursorPosition', _COORD),
-                    ('wAttributes', _WORD),
-                    ('srWindow', _SMALL_RECT),
-                    ('dwMaximumWindowSize', _COORD)]
+        _fields_ = [(r'dwSize', _COORD),
+                    (r'dwCursorPosition', _COORD),
+                    (r'wAttributes', _WORD),
+                    (r'srWindow', _SMALL_RECT),
+                    (r'dwMaximumWindowSize', _COORD)]
 
     _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
     _STD_ERROR_HANDLE = 0xfffffff4  # (DWORD)-12
@@ -481,7 +484,7 @@
             w32effects = None
         else:
             origattr = csbi.wAttributes
-            ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
+            ansire = re.compile(b'\033\[([^m]*)m([^\033]*)(.*)',
                                 re.MULTILINE | re.DOTALL)
 
     def win32print(ui, writefunc, *msgs, **opts):
@@ -513,15 +516,15 @@
                     # them if not found
                     pass
         # hack to ensure regexp finds data
-        if not text.startswith('\033['):
-            text = '\033[m' + text
+        if not text.startswith(b'\033['):
+            text = b'\033[m' + text
 
         # Look for ANSI-like codes embedded in text
         m = re.match(ansire, text)
 
         try:
             while m:
-                for sattr in m.group(1).split(';'):
+                for sattr in m.group(1).split(b';'):
                     if sattr:
                         attr = mapcolor(int(sattr), attr)
                 ui.flush()
--- a/mercurial/commands.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/commands.py	Wed Sep 26 20:33:09 2018 +0900
@@ -19,6 +19,8 @@
     nullid,
     nullrev,
     short,
+    wdirhex,
+    wdirrev,
 )
 from . import (
     archival,
@@ -35,13 +37,16 @@
     error,
     exchange,
     extensions,
+    filemerge,
     formatter,
     graphmod,
     hbisect,
     help,
     hg,
     logcmdutil,
+    match as matchmod,
     merge as mergemod,
+    narrowspec,
     obsolete,
     obsutil,
     patch,
@@ -300,46 +305,46 @@
     ctx = scmutil.revsingle(repo, rev)
 
     rootfm = ui.formatter('annotate', opts)
+    if ui.debugflag:
+        shorthex = pycompat.identity
+    else:
+        def shorthex(h):
+            return h[:12]
     if ui.quiet:
         datefunc = dateutil.shortdate
     else:
         datefunc = dateutil.datestr
     if ctx.rev() is None:
-        def hexfn(node):
-            if node is None:
-                return None
-            else:
-                return rootfm.hexfunc(node)
         if opts.get('changeset'):
             # omit "+" suffix which is appended to node hex
             def formatrev(rev):
-                if rev is None:
+                if rev == wdirrev:
                     return '%d' % ctx.p1().rev()
                 else:
                     return '%d' % rev
         else:
             def formatrev(rev):
-                if rev is None:
+                if rev == wdirrev:
                     return '%d+' % ctx.p1().rev()
                 else:
                     return '%d ' % rev
-        def formathex(hex):
-            if hex is None:
-                return '%s+' % rootfm.hexfunc(ctx.p1().node())
+        def formathex(h):
+            if h == wdirhex:
+                return '%s+' % shorthex(hex(ctx.p1().node()))
             else:
-                return '%s ' % hex
+                return '%s ' % shorthex(h)
     else:
-        hexfn = rootfm.hexfunc
-        formatrev = formathex = pycompat.bytestr
+        formatrev = b'%d'.__mod__
+        formathex = shorthex
 
     opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
-             ('rev', ' ', lambda x: x.fctx.rev(), formatrev),
-             ('node', ' ', lambda x: hexfn(x.fctx.node()), formathex),
+             ('rev', ' ', lambda x: scmutil.intrev(x.fctx), formatrev),
+             ('node', ' ', lambda x: hex(scmutil.binnode(x.fctx)), formathex),
              ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
-             ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
+             ('path', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
              ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
             ]
-    opnamemap = {'rev': 'number', 'node': 'changeset'}
+    opnamemap = {'rev': 'number', 'node': 'changeset', 'path': 'file'}
 
     if (not opts.get('user') and not opts.get('changeset')
         and not opts.get('date') and not opts.get('file')):
@@ -379,7 +384,7 @@
     for abs in ctx.walk(m):
         fctx = ctx[abs]
         rootfm.startitem()
-        rootfm.data(abspath=abs, path=m.rel(abs))
+        rootfm.data(path=abs)
         if not opts.get('text') and fctx.isbinary():
             rootfm.plain(_("%s: binary file\n")
                          % ((pats and m.rel(abs)) or abs))
@@ -900,6 +905,7 @@
     ('d', 'delete', False, _('delete a given bookmark')),
     ('m', 'rename', '', _('rename a given bookmark'), _('OLD')),
     ('i', 'inactive', False, _('mark a bookmark inactive')),
+    ('l', 'list', False, _('list existing bookmarks')),
     ] + formatteropts,
     _('hg bookmarks [OPTIONS]... [NAME]...'))
 def bookmark(ui, repo, *names, **opts):
@@ -920,7 +926,7 @@
     diverged, a new 'divergent bookmark' of the form 'name@path' will
     be created. Using :hg:`merge` will resolve the divergence.
 
-    Specifying bookmark as '.' to -m or -d options is equivalent to specifying
+    Specifying bookmark as '.' to -m/-d/-l options is equivalent to specifying
     the active bookmark's name.
 
     A bookmark named '@' has the special property that :hg:`clone` will
@@ -949,45 +955,63 @@
       - move the '@' bookmark from another branch::
 
           hg book -f @
+
+      - print only the active bookmark name::
+
+          hg book -ql .
     '''
-    force = opts.get(r'force')
-    rev = opts.get(r'rev')
-    delete = opts.get(r'delete')
-    rename = opts.get(r'rename')
-    inactive = opts.get(r'inactive')
-
-    if delete and rename:
-        raise error.Abort(_("--delete and --rename are incompatible"))
-    if delete and rev:
-        raise error.Abort(_("--rev is incompatible with --delete"))
-    if rename and rev:
-        raise error.Abort(_("--rev is incompatible with --rename"))
-    if not names and (delete or rev):
+    opts = pycompat.byteskwargs(opts)
+    force = opts.get('force')
+    rev = opts.get('rev')
+    inactive = opts.get('inactive')  # meaning add/rename to inactive bookmark
+
+    selactions = [k for k in ['delete', 'rename', 'list'] if opts.get(k)]
+    if len(selactions) > 1:
+        raise error.Abort(_('--%s and --%s are incompatible')
+                          % tuple(selactions[:2]))
+    if selactions:
+        action = selactions[0]
+    elif names or rev:
+        action = 'add'
+    elif inactive:
+        action = 'inactive'  # meaning deactivate
+    else:
+        action = 'list'
+
+    if rev and action in {'delete', 'rename', 'list'}:
+        raise error.Abort(_("--rev is incompatible with --%s") % action)
+    if inactive and action in {'delete', 'list'}:
+        raise error.Abort(_("--inactive is incompatible with --%s") % action)
+    if not names and action in {'add', 'delete'}:
         raise error.Abort(_("bookmark name required"))
 
-    if delete or rename or names or inactive:
+    if action in {'add', 'delete', 'rename', 'inactive'}:
         with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
-            if delete:
+            if action == 'delete':
                 names = pycompat.maplist(repo._bookmarks.expandname, names)
                 bookmarks.delete(repo, tr, names)
-            elif rename:
+            elif action == 'rename':
                 if not names:
                     raise error.Abort(_("new bookmark name required"))
                 elif len(names) > 1:
                     raise error.Abort(_("only one new bookmark name allowed"))
-                rename = repo._bookmarks.expandname(rename)
-                bookmarks.rename(repo, tr, rename, names[0], force, inactive)
-            elif names:
+                oldname = repo._bookmarks.expandname(opts['rename'])
+                bookmarks.rename(repo, tr, oldname, names[0], force, inactive)
+            elif action == 'add':
                 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
-            elif inactive:
+            elif action == 'inactive':
                 if len(repo._bookmarks) == 0:
                     ui.status(_("no bookmarks set\n"))
                 elif not repo._activebookmark:
                     ui.status(_("no active bookmark\n"))
                 else:
                     bookmarks.deactivate(repo)
-    else: # show bookmarks
-        bookmarks.printbookmarks(ui, repo, **opts)
+    elif action == 'list':
+        names = pycompat.maplist(repo._bookmarks.expandname, names)
+        with ui.formatter('bookmarks', opts) as fm:
+            bookmarks.printbookmarks(ui, repo, fm, names)
+    else:
+        raise error.ProgrammingError('invalid action: %s' % action)
 
 @command('branch',
     [('f', 'force', None,
@@ -1444,13 +1468,29 @@
     if opts.get('noupdate') and opts.get('updaterev'):
         raise error.Abort(_("cannot specify both --noupdate and --updaterev"))
 
+    # --include/--exclude can come from narrow or sparse.
+    includepats, excludepats = None, None
+
+    # hg.clone() differentiates between None and an empty set. So make sure
+    # patterns are sets if narrow is requested without patterns.
+    if opts.get('narrow'):
+        includepats = set()
+        excludepats = set()
+
+        if opts.get('include'):
+            includepats = narrowspec.parsepatterns(opts.get('include'))
+        if opts.get('exclude'):
+            excludepats = narrowspec.parsepatterns(opts.get('exclude'))
+
     r = hg.clone(ui, opts, source, dest,
                  pull=opts.get('pull'),
                  stream=opts.get('stream') or opts.get('uncompressed'),
                  revs=opts.get('rev'),
                  update=opts.get('updaterev') or not opts.get('noupdate'),
                  branch=opts.get('branch'),
-                 shareopts=opts.get('shareopts'))
+                 shareopts=opts.get('shareopts'),
+                 storeincludepats=includepats,
+                 storeexcludepats=excludepats)
 
     return r is None
 
@@ -1870,6 +1910,7 @@
 
     diffopts = patch.diffallopts(ui, opts)
     m = scmutil.match(ctx2, pats, opts)
+    m = matchmod.intersectmatchers(m, repo.narrowmatch())
     ui.pager('diff')
     logcmdutil.diffordiffstat(ui, repo, diffopts, node1, node2, m, stat=stat,
                               listsubrepos=opts.get('subrepos'),
@@ -2532,6 +2573,7 @@
     """
     opts = pycompat.byteskwargs(opts)
     diff = opts.get('all') or opts.get('diff')
+    all_files = opts.get('all_files')
     if diff and opts.get('all_files'):
         raise error.Abort(_('--diff and --all-files are mutually exclusive'))
     # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
@@ -2606,16 +2648,16 @@
     def difflinestates(a, b):
         sm = difflib.SequenceMatcher(None, a, b)
         for tag, alo, ahi, blo, bhi in sm.get_opcodes():
-            if tag == 'insert':
-                for i in xrange(blo, bhi):
+            if tag == r'insert':
+                for i in pycompat.xrange(blo, bhi):
                     yield ('+', b[i])
-            elif tag == 'delete':
-                for i in xrange(alo, ahi):
+            elif tag == r'delete':
+                for i in pycompat.xrange(alo, ahi):
                     yield ('-', a[i])
-            elif tag == 'replace':
-                for i in xrange(alo, ahi):
+            elif tag == r'replace':
+                for i in pycompat.xrange(alo, ahi):
                     yield ('-', a[i])
-                for i in xrange(blo, bhi):
+                for i in pycompat.xrange(blo, bhi):
                     yield ('+', b[i])
 
     def display(fm, fn, ctx, pstates, states):
@@ -2623,7 +2665,7 @@
         if fm.isplain():
             formatuser = ui.shortuser
         else:
-            formatuser = str
+            formatuser = pycompat.bytestr
         if ui.quiet:
             datefmt = '%Y-%m-%d'
         else:
@@ -2637,7 +2679,7 @@
             except error.WdirUnsupported:
                 return ctx[fn].isbinary()
 
-        fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
+        fieldnamemap = {'filename': 'path', 'linenumber': 'line_number'}
         if diff:
             iter = difflinestates(pstates, states)
         else:
@@ -2648,20 +2690,22 @@
             fm.data(node=fm.hexfunc(scmutil.binnode(ctx)))
 
             cols = [
-                ('filename', fn, True),
-                ('rev', rev, not plaingrep),
-                ('linenumber', l.linenum, opts.get('line_number')),
+                ('filename', '%s', fn, True),
+                ('rev', '%d', rev, not plaingrep),
+                ('linenumber', '%d', l.linenum, opts.get('line_number')),
             ]
             if diff:
-                cols.append(('change', change, True))
+                cols.append(('change', '%s', change, True))
             cols.extend([
-                ('user', formatuser(ctx.user()), opts.get('user')),
-                ('date', fm.formatdate(ctx.date(), datefmt), opts.get('date')),
+                ('user', '%s', formatuser(ctx.user()), opts.get('user')),
+                ('date', '%s', fm.formatdate(ctx.date(), datefmt),
+                 opts.get('date')),
             ])
-            lastcol = next(name for name, data, cond in reversed(cols) if cond)
-            for name, data, cond in cols:
+            lastcol = next(
+                name for name, fmt, data, cond in reversed(cols) if cond)
+            for name, fmt, data, cond in cols:
                 field = fieldnamemap.get(name, name)
-                fm.condwrite(cond, field, '%s', data, label='grep.%s' % name)
+                fm.condwrite(cond, field, fmt, data, label='grep.%s' % name)
                 if cond and name != lastcol:
                     fm.plain(sep, label='grep.sep')
             if not opts.get('files_with_matches'):
@@ -2756,7 +2800,7 @@
             if pstates or states:
                 r = display(fm, fn, ctx, pstates, states)
                 found = found or r
-                if r and not diff:
+                if r and not diff and not all_files:
                     skip[fn] = True
                     if copy:
                         skip[copy] = True
@@ -2941,10 +2985,6 @@
         raise error.Abort(_("there is no Mercurial repository here "
                            "(.hg not found)"))
 
-    if ui.debugflag:
-        hexfunc = hex
-    else:
-        hexfunc = short
     default = not (num or id or branch or tags or bookmarks)
     output = []
     revs = []
@@ -2968,7 +3008,7 @@
             rev = "tip"
 
         remoterev = peer.lookup(rev)
-        hexrev = hexfunc(remoterev)
+        hexrev = fm.hexfunc(remoterev)
         if default or id:
             output = [hexrev]
         fm.data(id=hexrev)
@@ -3011,7 +3051,7 @@
                 dirty = '+'
             fm.data(dirty=dirty)
 
-            hexoutput = [hexfunc(p.node()) for p in parents]
+            hexoutput = [fm.hexfunc(p.node()) for p in parents]
             if default or id:
                 output = ["%s%s" % ('+'.join(hexoutput), dirty)]
             fm.data(id="%s%s" % ('+'.join(hexoutput), dirty))
@@ -3020,15 +3060,10 @@
                 numoutput = ["%d" % p.rev() for p in parents]
                 output.append("%s%s" % ('+'.join(numoutput), dirty))
 
-            fn = fm.nested('parents', tmpl='{rev}:{node|formatnode}', sep=' ')
-            for p in parents:
-                fn.startitem()
-                fn.data(rev=p.rev())
-                fn.data(node=p.hex())
-                fn.context(ctx=p)
-            fn.end()
+            fm.data(parents=fm.formatlist([fm.hexfunc(p.node())
+                                           for p in parents], name='node'))
         else:
-            hexoutput = hexfunc(ctx.node())
+            hexoutput = fm.hexfunc(ctx.node())
             if default or id:
                 output = [hexoutput]
             fm.data(id=hexoutput)
@@ -4026,7 +4061,7 @@
     # search for a unique phase argument
     targetphase = None
     for idx, name in enumerate(phases.phasenames):
-        if opts[name]:
+        if opts.get(name, False):
             if targetphase is not None:
                 raise error.Abort(_('only one phase can be specified'))
             targetphase = idx
@@ -4481,7 +4516,8 @@
     ('l', 'list', None, _('list state of files needing merge')),
     ('m', 'mark', None, _('mark files as resolved')),
     ('u', 'unmark', None, _('mark files as unresolved')),
-    ('n', 'no-status', None, _('hide status prefix'))]
+    ('n', 'no-status', None, _('hide status prefix')),
+    ('', 're-merge', None, _('re-merge files'))]
     + mergetoolopts + walkopts + formatteropts,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
@@ -4498,9 +4534,9 @@
 
     The resolve command can be used in the following ways:
 
-    - :hg:`resolve [--tool TOOL] FILE...`: attempt to re-merge the specified
-      files, discarding any previous merge attempts. Re-merging is not
-      performed for files already marked as resolved. Use ``--all/-a``
+    - :hg:`resolve [--re-merge] [--tool TOOL] FILE...`: attempt to re-merge
+      the specified files, discarding any previous merge attempts. Re-merging
+      is not performed for files already marked as resolved. Use ``--all/-a``
       to select all unresolved files. ``--tool`` can be used to specify
       the merge tool used for the given files. It overrides the HGMERGE
       environment variable and your configuration files.  Previous file
@@ -4528,18 +4564,38 @@
     """
 
     opts = pycompat.byteskwargs(opts)
-    flaglist = 'all mark unmark list no_status'.split()
-    all, mark, unmark, show, nostatus = \
+    confirm = ui.configbool('commands', 'resolve.confirm')
+    flaglist = 'all mark unmark list no_status re_merge'.split()
+    all, mark, unmark, show, nostatus, remerge = \
         [opts.get(o) for o in flaglist]
 
-    if (show and (mark or unmark)) or (mark and unmark):
-        raise error.Abort(_("too many options specified"))
+    actioncount = len(list(filter(None, [show, mark, unmark, remerge])))
+    if actioncount > 1:
+        raise error.Abort(_("too many actions specified"))
+    elif (actioncount == 0
+          and ui.configbool('commands', 'resolve.explicit-re-merge')):
+        hint = _('use --mark, --unmark, --list or --re-merge')
+        raise error.Abort(_('no action specified'), hint=hint)
     if pats and all:
         raise error.Abort(_("can't specify --all and patterns"))
     if not (all or pats or show or mark or unmark):
         raise error.Abort(_('no files or directories specified'),
                          hint=('use --all to re-merge all unresolved files'))
 
+    if confirm:
+        if all:
+            if ui.promptchoice(_(b're-merge all unresolved files (yn)?'
+                                 b'$$ &Yes $$ &No')):
+                raise error.Abort(_('user quit'))
+        if mark and not pats:
+            if ui.promptchoice(_(b'mark all unresolved files as resolved (yn)?'
+                                 b'$$ &Yes $$ &No')):
+                raise error.Abort(_('user quit'))
+        if unmark and not pats:
+            if ui.promptchoice(_(b'mark all resolved files as unresolved (yn)?'
+                                 b'$$ &Yes $$ &No')):
+                raise error.Abort(_('user quit'))
+
     if show:
         ui.pager('resolve')
         fm = ui.formatter('resolve', opts)
@@ -4594,6 +4650,12 @@
         runconclude = False
 
         tocomplete = []
+        hasconflictmarkers = []
+        if mark:
+            markcheck = ui.config('commands', 'resolve.mark-check')
+            if markcheck not in ['warn', 'abort']:
+                # Treat all invalid / unrecognized values as 'none'.
+                markcheck = False
         for f in ms:
             if not m(f):
                 continue
@@ -4629,6 +4691,12 @@
                 continue
 
             if mark:
+                if markcheck:
+                    with repo.wvfs(f) as fobj:
+                        fdata = fobj.read()
+                    if filemerge.hasconflictmarkers(fdata) and \
+                        ms[f] != mergemod.MERGE_RECORD_RESOLVED:
+                        hasconflictmarkers.append(f)
                 ms.mark(f, mergemod.MERGE_RECORD_RESOLVED)
             elif unmark:
                 ms.mark(f, mergemod.MERGE_RECORD_UNRESOLVED)
@@ -4663,6 +4731,13 @@
                         if inst.errno != errno.ENOENT:
                             raise
 
+        if hasconflictmarkers:
+            ui.warn(_('warning: the following files still have conflict '
+                      'markers:\n  ') + '\n  '.join(hasconflictmarkers) + '\n')
+            if markcheck == 'abort' and not all:
+                raise error.Abort(_('conflict markers detected'),
+                                  hint=_('use --all to mark anyway'))
+
         for f in tocomplete:
             try:
                 # resolve file
@@ -4693,8 +4768,11 @@
                 for f in ms:
                     if not m(f):
                         continue
-                    flags = ''.join(['-%s ' % o[0:1] for o in flaglist
-                                                   if opts.get(o)])
+                    def flag(o):
+                        if o == 're_merge':
+                            return '--re-merge '
+                        return '-%s ' % o[0:1]
+                    flags = ''.join([flag(o) for o in flaglist if opts.get(o)])
                     hint = _("(try: hg resolve %s%s)\n") % (
                              flags,
                              ' '.join(pats))
@@ -4800,10 +4878,10 @@
         if node != parent:
             if dirty:
                 hint = _("uncommitted changes, use --all to discard all"
-                         " changes, or 'hg update %s' to update") % ctx.rev()
+                         " changes, or 'hg update %d' to update") % ctx.rev()
             else:
                 hint = _("use --all to revert all files,"
-                         " or 'hg update %s' to update") % ctx.rev()
+                         " or 'hg update %d' to update") % ctx.rev()
         elif dirty:
             hint = _("uncommitted changes, use --all to discard all changes")
         else:
@@ -5128,10 +5206,12 @@
             for f in files:
                 fm.startitem()
                 fm.context(ctx=ctx2)
+                fm.data(path=f)
                 fm.condwrite(showchar, 'status', '%s ', char, label=label)
-                fm.write('path', fmt, repo.pathto(f, cwd), label=label)
+                fm.plain(fmt % repo.pathto(f, cwd), label=label)
                 if f in copy:
-                    fm.write("copy", '  %s' + end, repo.pathto(copy[f], cwd),
+                    fm.data(source=copy[f])
+                    fm.plain(('  %s' + end) % repo.pathto(copy[f], cwd),
                              label='status.copied')
 
     if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
@@ -5552,7 +5632,6 @@
     opts = pycompat.byteskwargs(opts)
     ui.pager('tags')
     fm = ui.formatter('tags', opts)
-    contexthint = fm.contexthint('tag rev node type')
     hexfunc = fm.hexfunc
     tagtype = ""
 
@@ -5565,8 +5644,7 @@
             tagtype = 'local'
 
         fm.startitem()
-        if 'ctx' in contexthint:
-            fm.context(ctx=repo[n])
+        fm.context(repo=repo)
         fm.write('tag', '%s', t, label=label)
         fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
         fm.condwrite(not ui.quiet, 'rev node', fmt,
--- a/mercurial/commandserver.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/commandserver.py	Wed Sep 26 20:33:09 2018 +0900
@@ -26,7 +26,6 @@
 from . import (
     encoding,
     error,
-    pycompat,
     util,
 )
 from .utils import (
@@ -161,7 +160,7 @@
     based stream to fout.
     """
     def __init__(self, ui, repo, fin, fout):
-        self.cwd = pycompat.getcwd()
+        self.cwd = encoding.getcwd()
 
         # developer config: cmdserver.log
         logpath = ui.config("cmdserver", "log")
@@ -353,7 +352,7 @@
         # handle exceptions that may be raised by command server. most of
         # known exceptions are caught by dispatch.
         except error.Abort as inst:
-            ui.warn(_('abort: %s\n') % inst)
+            ui.error(_('abort: %s\n') % inst)
         except IOError as inst:
             if inst.errno != errno.EPIPE:
                 raise
--- a/mercurial/compat.h	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/compat.h	Wed Sep 26 20:33:09 2018 +0900
@@ -3,6 +3,7 @@
 
 #ifdef _WIN32
 #ifdef _MSC_VER
+#if _MSC_VER < 1900
 /* msvc 6.0 has problems */
 #define inline __inline
 #if defined(_WIN64)
@@ -21,6 +22,18 @@
 typedef unsigned long uint32_t;
 typedef unsigned __int64 uint64_t;
 #else
+/* VC++ 14 */
+#include <stdint.h>
+
+#if defined(_WIN64)
+typedef __int64 ssize_t;
+#else
+typedef int ssize_t;
+#endif
+#endif /* _MSC_VER < 1900 */
+
+#else
+/* not msvc */
 #include <stdint.h>
 #endif
 #else
--- a/mercurial/configitems.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/configitems.py	Wed Sep 26 20:33:09 2018 +0900
@@ -190,6 +190,15 @@
 coreconfigitem('commands', 'grep.all-files',
     default=False,
 )
+coreconfigitem('commands', 'resolve.confirm',
+    default=False,
+)
+coreconfigitem('commands', 'resolve.explicit-re-merge',
+    default=False,
+)
+coreconfigitem('commands', 'resolve.mark-check',
+    default='none',
+)
 coreconfigitem('commands', 'show.aliasprefix',
     default=list,
 )
@@ -447,9 +456,6 @@
 coreconfigitem('experimental', 'bundle2.pushback',
     default=False,
 )
-coreconfigitem('experimental', 'bundle2.stream',
-    default=False,
-)
 coreconfigitem('experimental', 'bundle2lazylocking',
     default=False,
 )
@@ -584,9 +590,15 @@
 coreconfigitem('experimental', 'removeemptydirs',
     default=True,
 )
+coreconfigitem('experimental', 'revisions.prefixhexnode',
+    default=False,
+)
 coreconfigitem('experimental', 'revlogv2',
     default=None,
 )
+coreconfigitem('experimental', 'revisions.disambiguatewithin',
+    default=None,
+)
 coreconfigitem('experimental', 'single-head-per-branch',
     default=False,
 )
@@ -650,7 +662,7 @@
     default=None,
 )
 coreconfigitem('format', 'maxchainlen',
-    default=None,
+    default=dynamicdefault,
 )
 coreconfigitem('format', 'obsstore-version',
     default=None,
@@ -667,6 +679,9 @@
 coreconfigitem('format', 'usestore',
     default=True,
 )
+coreconfigitem('format', 'internal-phase',
+    default=False,
+)
 coreconfigitem('fsmonitor', 'warn_when_unused',
     default=True,
 )
@@ -759,6 +774,9 @@
 coreconfigitem('merge', 'preferancestor',
         default=lambda: ['*'],
 )
+coreconfigitem('merge', 'strict-capability-check',
+    default=False,
+)
 coreconfigitem('merge-tools', '.*',
     default=None,
     generic=True,
@@ -952,6 +970,10 @@
 coreconfigitem('server', 'bundle1gd.push',
     default=None,
 )
+coreconfigitem('server', 'bundle2.stream',
+    default=True,
+    alias=[('experimental', 'bundle2.stream')]
+)
 coreconfigitem('server', 'compressionengines',
     default=list,
 )
@@ -1330,6 +1352,9 @@
 coreconfigitem('web', 'server-header',
     default=None,
 )
+coreconfigitem('web', 'static',
+    default=None,
+)
 coreconfigitem('web', 'staticurl',
     default=None,
 )
--- a/mercurial/context.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/context.py	Wed Sep 26 20:33:09 2018 +0900
@@ -36,7 +36,6 @@
     phases,
     pycompat,
     repoview,
-    revlog,
     scmutil,
     sparse,
     subrepo,
@@ -193,25 +192,26 @@
         return self.rev() in obsmod.getrevs(self._repo, 'extinct')
 
     def orphan(self):
-        """True if the changeset is not obsolete but it's ancestor are"""
+        """True if the changeset is not obsolete, but its ancestor is"""
         return self.rev() in obsmod.getrevs(self._repo, 'orphan')
 
     def phasedivergent(self):
-        """True if the changeset try to be a successor of a public changeset
+        """True if the changeset tries to be a successor of a public changeset
 
-        Only non-public and non-obsolete changesets may be bumped.
+        Only non-public and non-obsolete changesets may be phase-divergent.
         """
         return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
 
     def contentdivergent(self):
-        """Is a successors of a changeset with multiple possible successors set
+        """Is a successor of a changeset with multiple possible successor sets
 
-        Only non-public and non-obsolete changesets may be divergent.
+        Only non-public and non-obsolete changesets may be content-divergent.
         """
         return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
 
     def isunstable(self):
-        """True if the changeset is either unstable, bumped or divergent"""
+        """True if the changeset is either orphan, phase-divergent or
+        content-divergent"""
         return self.orphan() or self.phasedivergent() or self.contentdivergent()
 
     def instabilities(self):
@@ -372,6 +372,10 @@
                 for rfiles, sfiles in zip(r, s):
                     rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
 
+        narrowmatch = self._repo.narrowmatch()
+        if not narrowmatch.always():
+            for l in r:
+                l[:] = list(filter(narrowmatch, l))
         for l in r:
             l.sort()
 
@@ -438,7 +442,6 @@
                         "unsupported changeid '%s' of type %s" %
                         (changeid, type(changeid)))
 
-            # lookup failed
         except (error.FilteredIndexError, error.FilteredLookupError):
             raise error.FilteredRepoLookupError(_("filtered revision '%s'")
                                                 % pycompat.bytestr(changeid))
@@ -590,12 +593,6 @@
                             short(n) for n in sorted(cahs) if n != anc))
         return changectx(self._repo, anc)
 
-    def descendant(self, other):
-        msg = (b'ctx.descendant(other) is deprecated, '
-               b'use ctx.isancestorof(other)')
-        self._repo.ui.deprecwarn(msg, b'4.7')
-        return self.isancestorof(other)
-
     def isancestorof(self, other):
         """True if this changeset is an ancestor of other"""
         return self._repo.changelog.isancestorrev(self._rev, other._rev)
@@ -625,7 +622,6 @@
     workingfilectx: a filecontext that represents files from the working
                     directory,
     memfilectx: a filecontext that represents files in-memory,
-    overlayfilectx: duplicate another filecontext with some fields overridden.
     """
     @propertycache
     def _filelog(self):
@@ -1054,7 +1050,7 @@
 
         renamed = self._filelog.renamed(self._filenode)
         if not renamed:
-            return renamed
+            return None
 
         if self.rev() == self.linkrev():
             return renamed
@@ -1903,23 +1899,28 @@
         # Test that each new directory to be created to write this path from p2
         # is not a file in p1.
         components = path.split('/')
-        for i in xrange(len(components)):
+        for i in pycompat.xrange(len(components)):
             component = "/".join(components[0:i])
-            if component in self.p1():
+            if component in self.p1() and self._cache[component]['exists']:
                 fail(path, component)
 
         # Test the other direction -- that this path from p2 isn't a directory
         # in p1 (test that p1 doesn't any paths matching `path/*`).
         match = matchmod.match('/', '', [path + '/'], default=b'relpath')
         matches = self.p1().manifest().matches(match)
-        if len(matches) > 0:
-            if len(matches) == 1 and matches.keys()[0] == path:
+        mfiles = matches.keys()
+        if len(mfiles) > 0:
+            if len(mfiles) == 1 and mfiles[0] == path:
+                return
+            # omit the files which are deleted in current IMM wctx
+            mfiles = [m for m in mfiles if self._cache[m]['exists']]
+            if not mfiles:
                 return
             raise error.Abort("error: file '%s' cannot be written because "
                               " '%s/' is a folder in %s (containing %d "
                               "entries: %s)"
-                              % (path, path, self.p1(), len(matches),
-                                 ', '.join(matches.keys())))
+                              % (path, path, self.p1(), len(mfiles),
+                                 ', '.join(mfiles)))
 
     def write(self, path, data, flags='', **kwargs):
         if data is None:
@@ -1929,8 +1930,13 @@
                         flags=flags)
 
     def setflags(self, path, l, x):
+        flag = ''
+        if l:
+            flag = 'l'
+        elif x:
+            flag = 'x'
         self._markdirty(path, exists=True, date=dateutil.makedate(),
-                        flags=(l and 'l' or '') + (x and 'x' or ''))
+                        flags=flag)
 
     def remove(self, path):
         self._markdirty(path, exists=False)
@@ -2037,6 +2043,13 @@
         return keys
 
     def _markdirty(self, path, exists, data=None, date=None, flags=''):
+        # data not provided, let's see if we already have some; if not, let's
+        # grab it from our underlying context, so that we always have data if
+        # the file is marked as existing.
+        if exists and data is None:
+            oldentry = self._cache.get(path) or {}
+            data = oldentry.get('data') or self._wrappedctx[path].data()
+
         self._cache[path] = {
             'exists': exists,
             'data': data,
@@ -2117,8 +2130,8 @@
     """
     def __init__(self, repo, changes,
                  text="", user=None, date=None, extra=None):
-        super(workingctx, self).__init__(repo, text, user, date, extra,
-                                         changes)
+        super(workingcommitctx, self).__init__(repo, text, user, date, extra,
+                                               changes)
 
     def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
         """Return matched files only in ``self._status``
@@ -2273,17 +2286,10 @@
         man = pctx.manifest().copy()
 
         for f in self._status.modified:
-            p1node = nullid
-            p2node = nullid
-            p = pctx[f].parents() # if file isn't in pctx, check p2?
-            if len(p) > 0:
-                p1node = p[0].filenode()
-                if len(p) > 1:
-                    p2node = p[1].filenode()
-            man[f] = revlog.hash(self[f].data(), p1node, p2node)
+            man[f] = modifiednodeid
 
         for f in self._status.added:
-            man[f] = revlog.hash(self[f].data(), nullid, nullid)
+            man[f] = addednodeid
 
         for f in self._status.removed:
             if f in man:
@@ -2355,76 +2361,6 @@
         """wraps repo.wwrite"""
         self._data = data
 
-class overlayfilectx(committablefilectx):
-    """Like memfilectx but take an original filectx and optional parameters to
-    override parts of it. This is useful when fctx.data() is expensive (i.e.
-    flag processor is expensive) and raw data, flags, and filenode could be
-    reused (ex. rebase or mode-only amend a REVIDX_EXTSTORED file).
-    """
-
-    def __init__(self, originalfctx, datafunc=None, path=None, flags=None,
-                 copied=None, ctx=None):
-        """originalfctx: filecontext to duplicate
-
-        datafunc: None or a function to override data (file content). It is a
-        function to be lazy. path, flags, copied, ctx: None or overridden value
-
-        copied could be (path, rev), or False. copied could also be just path,
-        and will be converted to (path, nullid). This simplifies some callers.
-        """
-
-        if path is None:
-            path = originalfctx.path()
-        if ctx is None:
-            ctx = originalfctx.changectx()
-            ctxmatch = lambda: True
-        else:
-            ctxmatch = lambda: ctx == originalfctx.changectx()
-
-        repo = originalfctx.repo()
-        flog = originalfctx.filelog()
-        super(overlayfilectx, self).__init__(repo, path, flog, ctx)
-
-        if copied is None:
-            copied = originalfctx.renamed()
-            copiedmatch = lambda: True
-        else:
-            if copied and not isinstance(copied, tuple):
-                # repo._filecommit will recalculate copyrev so nullid is okay
-                copied = (copied, nullid)
-            copiedmatch = lambda: copied == originalfctx.renamed()
-
-        # When data, copied (could affect data), ctx (could affect filelog
-        # parents) are not overridden, rawdata, rawflags, and filenode may be
-        # reused (repo._filecommit should double check filelog parents).
-        #
-        # path, flags are not hashed in filelog (but in manifestlog) so they do
-        # not affect reusable here.
-        #
-        # If ctx or copied is overridden to a same value with originalfctx,
-        # still consider it's reusable. originalfctx.renamed() may be a bit
-        # expensive so it's not called unless necessary. Assuming datafunc is
-        # always expensive, do not call it for this "reusable" test.
-        reusable = datafunc is None and ctxmatch() and copiedmatch()
-
-        if datafunc is None:
-            datafunc = originalfctx.data
-        if flags is None:
-            flags = originalfctx.flags()
-
-        self._datafunc = datafunc
-        self._flags = flags
-        self._copied = copied
-
-        if reusable:
-            # copy extra fields from originalfctx
-            attrs = ['rawdata', 'rawflags', '_filenode', '_filerev']
-            for attr_ in attrs:
-                if util.safehasattr(originalfctx, attr_):
-                    setattr(self, attr_, getattr(originalfctx, attr_))
-
-    def data(self):
-        return self._datafunc()
 
 class metadataonlyctx(committablectx):
     """Like memctx but it's reusing the manifest of different commit.
--- a/mercurial/copies.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/copies.py	Wed Sep 26 20:33:09 2018 +0900
@@ -20,6 +20,9 @@
     scmutil,
     util,
 )
+from .utils import (
+    stringutil,
+)
 
 def _findlimit(repo, a, b):
     """
@@ -366,19 +369,22 @@
         return repo.dirstate.copies(), {}, {}, {}, {}
 
     copytracing = repo.ui.config('experimental', 'copytrace')
+    boolctrace = stringutil.parsebool(copytracing)
 
     # Copy trace disabling is explicitly below the node == p1 logic above
     # because the logic above is required for a simple copy to be kept across a
     # rebase.
-    if copytracing == 'off':
-        return {}, {}, {}, {}, {}
-    elif copytracing == 'heuristics':
+    if copytracing == 'heuristics':
         # Do full copytracing if only non-public revisions are involved as
         # that will be fast enough and will also cover the copies which could
         # be missed by heuristics
         if _isfullcopytraceable(repo, c1, base):
             return _fullcopytracing(repo, c1, c2, base)
         return _heuristicscopytracing(repo, c1, c2, base)
+    elif boolctrace is False:
+        # stringutil.parsebool() returns None when it is unable to parse the
+        # value, so we should rely on making sure copytracing is on such cases
+        return {}, {}, {}, {}, {}
     else:
         return _fullcopytracing(repo, c1, c2, base)
 
@@ -593,16 +599,16 @@
             continue
         elif dsrc in d1 and ddst in d1:
             # directory wasn't entirely moved locally
-            invalid.add(dsrc + "/")
+            invalid.add(dsrc)
         elif dsrc in d2 and ddst in d2:
             # directory wasn't entirely moved remotely
-            invalid.add(dsrc + "/")
-        elif dsrc + "/" in dirmove and dirmove[dsrc + "/"] != ddst + "/":
+            invalid.add(dsrc)
+        elif dsrc in dirmove and dirmove[dsrc] != ddst:
             # files from the same directory moved to two different places
-            invalid.add(dsrc + "/")
+            invalid.add(dsrc)
         else:
             # looks good so far
-            dirmove[dsrc + "/"] = ddst + "/"
+            dirmove[dsrc] = ddst
 
     for i in invalid:
         if i in dirmove:
@@ -612,6 +618,8 @@
     if not dirmove:
         return copy, {}, diverge, renamedelete, {}
 
+    dirmove = {k + "/": v + "/" for k, v in dirmove.iteritems()}
+
     for d in dirmove:
         repo.ui.debug("   discovered dir src: '%s' -> dst: '%s'\n" %
                       (d, dirmove[d]))
@@ -868,8 +876,10 @@
     copies between fromrev and rev.
     """
     exclude = {}
+    ctraceconfig = repo.ui.config('experimental', 'copytrace')
+    bctrace = stringutil.parsebool(ctraceconfig)
     if (skiprev is not None and
-        repo.ui.config('experimental', 'copytrace') != 'off'):
+        (ctraceconfig == 'heuristics' or bctrace or bctrace is None)):
         # copytrace='off' skips this line, but not the entire function because
         # the line below is O(size of the repo) during a rebase, while the rest
         # of the function is much faster (and is required for carrying copy
--- a/mercurial/dagop.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/dagop.py	Wed Sep 26 20:33:09 2018 +0900
@@ -195,7 +195,7 @@
     """Build map of 'rev -> child revs', offset from startrev"""
     cl = repo.changelog
     nullrev = node.nullrev
-    descmap = [[] for _rev in xrange(startrev, len(cl))]
+    descmap = [[] for _rev in pycompat.xrange(startrev, len(cl))]
     for currev in cl.revs(startrev + 1):
         p1rev, p2rev = cl.parentrevs(currev)
         if p1rev >= startrev:
@@ -435,7 +435,7 @@
         for idx, (parent, blocks) in enumerate(pblocks):
             for (a1, a2, b1, b2), _t in blocks:
                 if a2 - a1 >= b2 - b1:
-                    for bk in xrange(b1, b2):
+                    for bk in pycompat.xrange(b1, b2):
                         if child.fctxs[bk] == childfctx:
                             ak = min(a1 + (bk - b1), a2 - 1)
                             child.fctxs[bk] = parent.fctxs[ak]
@@ -448,7 +448,7 @@
         # line.
         for parent, blocks in remaining:
             for a1, a2, b1, b2 in blocks:
-                for bk in xrange(b1, b2):
+                for bk in pycompat.xrange(b1, b2):
                     if child.fctxs[bk] == childfctx:
                         ak = min(a1 + (bk - b1), a2 - 1)
                         child.fctxs[bk] = parent.fctxs[ak]
@@ -715,3 +715,63 @@
     for g in groups:
         for r in g[0]:
             yield r
+
+def headrevs(revs, parentsfn):
+    """Resolve the set of heads from a set of revisions.
+
+    Receives an iterable of revision numbers and a callbable that receives a
+    revision number and returns an iterable of parent revision numbers, possibly
+    including nullrev.
+
+    Returns a set of revision numbers that are DAG heads within the passed
+    subset.
+
+    ``nullrev`` is never included in the returned set, even if it is provided in
+    the input set.
+    """
+    headrevs = set(revs)
+
+    for rev in revs:
+        for prev in parentsfn(rev):
+            headrevs.discard(prev)
+
+    headrevs.discard(node.nullrev)
+
+    return headrevs
+
+def linearize(revs, parentsfn):
+    """Linearize and topologically sort a list of revisions.
+
+    The linearization process tries to create long runs of revs where a child
+    rev comes immediately after its first parent. This is done by visiting the
+    heads of the revs in inverse topological order, and for each visited rev,
+    visiting its second parent, then its first parent, then adding the rev
+    itself to the output list.
+
+    Returns a list of revision numbers.
+    """
+    visit = list(sorted(headrevs(revs, parentsfn), reverse=True))
+    finished = set()
+    result = []
+
+    while visit:
+        rev = visit.pop()
+        if rev < 0:
+            rev = -rev - 1
+
+            if rev not in finished:
+                result.append(rev)
+                finished.add(rev)
+
+        else:
+            visit.append(-rev - 1)
+
+            for prev in parentsfn(rev):
+                if prev == node.nullrev or prev not in revs or prev in finished:
+                    continue
+
+                visit.append(prev)
+
+    assert len(result) == len(revs)
+
+    return result
--- a/mercurial/dagparser.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/dagparser.py	Wed Sep 26 20:33:09 2018 +0900
@@ -222,7 +222,7 @@
         elif c == '+':
             c, digs = nextrun(nextch(), pycompat.bytestr(string.digits))
             n = int(digs)
-            for i in xrange(0, n):
+            for i in pycompat.xrange(0, n):
                 yield 'n', (r, [p1])
                 p1 = r
                 r += 1
--- a/mercurial/dagutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,287 +0,0 @@
-# dagutil.py - dag utilities for mercurial
-#
-# Copyright 2010 Benoit Boissinot <bboissin@gmail.com>
-# and Peter Arrenbrecht <peter@arrenbrecht.ch>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from .i18n import _
-from .node import nullrev
-
-class basedag(object):
-    '''generic interface for DAGs
-
-    terms:
-    "ix" (short for index) identifies a nodes internally,
-    "id" identifies one externally.
-
-    All params are ixs unless explicitly suffixed otherwise.
-    Pluralized params are lists or sets.
-    '''
-
-    def __init__(self):
-        self._inverse = None
-
-    def nodeset(self):
-        '''set of all node ixs'''
-        raise NotImplementedError
-
-    def heads(self):
-        '''list of head ixs'''
-        raise NotImplementedError
-
-    def parents(self, ix):
-        '''list of parents ixs of ix'''
-        raise NotImplementedError
-
-    def inverse(self):
-        '''inverse DAG, where parents becomes children, etc.'''
-        raise NotImplementedError
-
-    def ancestorset(self, starts, stops=None):
-        '''
-        set of all ancestors of starts (incl), but stop walk at stops (excl)
-        '''
-        raise NotImplementedError
-
-    def descendantset(self, starts, stops=None):
-        '''
-        set of all descendants of starts (incl), but stop walk at stops (excl)
-        '''
-        return self.inverse().ancestorset(starts, stops)
-
-    def headsetofconnecteds(self, ixs):
-        '''
-        subset of connected list of ixs so that no node has a descendant in it
-
-        By "connected list" we mean that if an ancestor and a descendant are in
-        the list, then so is at least one path connecting them.
-        '''
-        raise NotImplementedError
-
-    def externalize(self, ix):
-        '''return a node id'''
-        return self._externalize(ix)
-
-    def externalizeall(self, ixs):
-        '''return a list of (or set if given a set) of node ids'''
-        ids = self._externalizeall(ixs)
-        if isinstance(ixs, set):
-            return set(ids)
-        return list(ids)
-
-    def internalize(self, id):
-        '''return a node ix'''
-        return self._internalize(id)
-
-    def internalizeall(self, ids, filterunknown=False):
-        '''return a list of (or set if given a set) of node ixs'''
-        ixs = self._internalizeall(ids, filterunknown)
-        if isinstance(ids, set):
-            return set(ixs)
-        return list(ixs)
-
-
-class genericdag(basedag):
-    '''generic implementations for DAGs'''
-
-    def ancestorset(self, starts, stops=None):
-        if stops:
-            stops = set(stops)
-        else:
-            stops = set()
-        seen = set()
-        pending = list(starts)
-        while pending:
-            n = pending.pop()
-            if n not in seen and n not in stops:
-                seen.add(n)
-                pending.extend(self.parents(n))
-        return seen
-
-    def headsetofconnecteds(self, ixs):
-        hds = set(ixs)
-        if not hds:
-            return hds
-        for n in ixs:
-            for p in self.parents(n):
-                hds.discard(p)
-        assert hds
-        return hds
-
-
-class revlogbaseddag(basedag):
-    '''generic dag interface to a revlog'''
-
-    def __init__(self, revlog, nodeset):
-        basedag.__init__(self)
-        self._revlog = revlog
-        self._heads = None
-        self._nodeset = nodeset
-
-    def nodeset(self):
-        return self._nodeset
-
-    def heads(self):
-        if self._heads is None:
-            self._heads = self._getheads()
-        return self._heads
-
-    def _externalize(self, ix):
-        return self._revlog.index[ix][7]
-    def _externalizeall(self, ixs):
-        idx = self._revlog.index
-        return [idx[i][7] for i in ixs]
-
-    def _internalize(self, id):
-        ix = self._revlog.rev(id)
-        if ix == nullrev:
-            raise LookupError(id, self._revlog.indexfile, _('nullid'))
-        return ix
-    def _internalizeall(self, ids, filterunknown):
-        rl = self._revlog
-        if filterunknown:
-            return [r for r in map(rl.nodemap.get, ids)
-                    if (r is not None
-                        and r != nullrev
-                        and r not in rl.filteredrevs)]
-        return [self._internalize(i) for i in ids]
-
-
-class revlogdag(revlogbaseddag):
-    '''dag interface to a revlog'''
-
-    def __init__(self, revlog, localsubset=None):
-        revlogbaseddag.__init__(self, revlog, set(revlog))
-        self._heads = localsubset
-
-    def _getheads(self):
-        return [r for r in self._revlog.headrevs() if r != nullrev]
-
-    def parents(self, ix):
-        rlog = self._revlog
-        idx = rlog.index
-        revdata = idx[ix]
-        prev = revdata[5]
-        if prev != nullrev:
-            prev2 = revdata[6]
-            if prev2 == nullrev:
-                return [prev]
-            return [prev, prev2]
-        prev2 = revdata[6]
-        if prev2 != nullrev:
-            return [prev2]
-        return []
-
-    def inverse(self):
-        if self._inverse is None:
-            self._inverse = inverserevlogdag(self)
-        return self._inverse
-
-    def ancestorset(self, starts, stops=None):
-        rlog = self._revlog
-        idx = rlog.index
-        if stops:
-            stops = set(stops)
-        else:
-            stops = set()
-        seen = set()
-        pending = list(starts)
-        while pending:
-            rev = pending.pop()
-            if rev not in seen and rev not in stops:
-                seen.add(rev)
-                revdata = idx[rev]
-                for i in [5, 6]:
-                    prev = revdata[i]
-                    if prev != nullrev:
-                        pending.append(prev)
-        return seen
-
-    def headsetofconnecteds(self, ixs):
-        if not ixs:
-            return set()
-        rlog = self._revlog
-        idx = rlog.index
-        headrevs = set(ixs)
-        for rev in ixs:
-            revdata = idx[rev]
-            for i in [5, 6]:
-                prev = revdata[i]
-                if prev != nullrev:
-                    headrevs.discard(prev)
-        assert headrevs
-        return headrevs
-
-    def linearize(self, ixs):
-        '''linearize and topologically sort a list of revisions
-
-        The linearization process tries to create long runs of revs where
-        a child rev comes immediately after its first parent. This is done by
-        visiting the heads of the given revs in inverse topological order,
-        and for each visited rev, visiting its second parent, then its first
-        parent, then adding the rev itself to the output list.
-        '''
-        sorted = []
-        visit = list(self.headsetofconnecteds(ixs))
-        visit.sort(reverse=True)
-        finished = set()
-
-        while visit:
-            cur = visit.pop()
-            if cur < 0:
-                cur = -cur - 1
-                if cur not in finished:
-                    sorted.append(cur)
-                    finished.add(cur)
-            else:
-                visit.append(-cur - 1)
-                visit += [p for p in self.parents(cur)
-                          if p in ixs and p not in finished]
-        assert len(sorted) == len(ixs)
-        return sorted
-
-
-class inverserevlogdag(revlogbaseddag, genericdag):
-    '''inverse of an existing revlog dag; see revlogdag.inverse()'''
-
-    def __init__(self, orig):
-        revlogbaseddag.__init__(self, orig._revlog, orig._nodeset)
-        self._orig = orig
-        self._children = {}
-        self._roots = []
-        self._walkfrom = len(self._revlog) - 1
-
-    def _walkto(self, walkto):
-        rev = self._walkfrom
-        cs = self._children
-        roots = self._roots
-        idx = self._revlog.index
-        while rev >= walkto:
-            data = idx[rev]
-            isroot = True
-            for prev in [data[5], data[6]]: # parent revs
-                if prev != nullrev:
-                    cs.setdefault(prev, []).append(rev)
-                    isroot = False
-            if isroot:
-                roots.append(rev)
-            rev -= 1
-        self._walkfrom = rev
-
-    def _getheads(self):
-        self._walkto(nullrev)
-        return self._roots
-
-    def parents(self, ix):
-        if ix is None:
-            return []
-        if ix <= self._walkfrom:
-            self._walkto(ix)
-        return self._children.get(ix, [])
-
-    def inverse(self):
-        return self._orig
--- a/mercurial/debugcommands.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/debugcommands.py	Wed Sep 26 20:33:09 2018 +0900
@@ -32,9 +32,6 @@
     nullrev,
     short,
 )
-from .thirdparty import (
-    cbor,
-)
 from . import (
     bundle2,
     changegroup,
@@ -42,13 +39,12 @@
     color,
     context,
     dagparser,
-    dagutil,
     encoding,
     error,
     exchange,
     extensions,
     filemerge,
-    fileset,
+    filesetlang,
     formatter,
     hg,
     httppeer,
@@ -84,11 +80,16 @@
     wireprotov2peer,
 )
 from .utils import (
+    cborutil,
     dateutil,
     procutil,
     stringutil,
 )
 
+from .revlogutils import (
+    deltas as deltautil
+)
+
 release = lockmod.release
 
 command = registrar.command()
@@ -98,7 +99,7 @@
     """find the ancestor revision of two revisions in a given index"""
     if len(args) == 3:
         index, rev1, rev2 = args
-        r = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False), index)
+        r = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False), index)
         lookup = r.lookup
     elif len(args) == 2:
         if not repo:
@@ -177,7 +178,8 @@
     if mergeable_file:
         linesperrev = 2
         # make a file with k lines per rev
-        initialmergedlines = ['%d' % i for i in xrange(0, total * linesperrev)]
+        initialmergedlines = ['%d' % i
+                              for i in pycompat.xrange(0, total * linesperrev)]
         initialmergedlines.append("")
 
     tags = []
@@ -501,7 +503,7 @@
     spaces = opts.get(r'spaces')
     dots = opts.get(r'dots')
     if file_:
-        rlog = revlog.revlog(vfsmod.vfs(pycompat.getcwd(), audit=False),
+        rlog = revlog.revlog(vfsmod.vfs(encoding.getcwd(), audit=False),
                              file_)
         revs = set((int(r) for r in revs))
         def events():
@@ -556,7 +558,7 @@
         file_, rev = None, file_
     elif rev is None:
         raise error.CommandError('debugdata', _('invalid arguments'))
-    r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
+    r = cmdutil.openstorage(repo, 'debugdata', file_, opts)
     try:
         ui.write(r.revision(r.lookup(rev), raw=True))
     except KeyError:
@@ -706,7 +708,7 @@
             largestblock = 0
             srchunks = 0
 
-            for revschunk in revlog._slicechunk(r, chain):
+            for revschunk in deltautil.slicechunk(r, chain):
                 srchunks += 1
                 blkend = start(revschunk[-1]) + length(revschunk[-1])
                 blksize = blkend - start(revschunk[0])
@@ -731,13 +733,16 @@
     fm.end()
 
 @command('debugdirstate|debugstate',
-    [('', 'nodates', None, _('do not display the saved mtime')),
-    ('', 'datesort', None, _('sort by saved mtime'))],
+    [('', 'nodates', None, _('do not display the saved mtime (DEPRECATED)')),
+     ('', 'dates', True, _('display the saved mtime')),
+     ('', 'datesort', None, _('sort by saved mtime'))],
     _('[OPTION]...'))
 def debugstate(ui, repo, **opts):
     """show the contents of the current dirstate"""
 
-    nodates = opts.get(r'nodates')
+    nodates = not opts[r'dates']
+    if opts.get(r'nodates') is not None:
+        nodates = True
     datesort = opts.get(r'datesort')
 
     timestr = ""
@@ -790,9 +795,10 @@
             if not opts.get('nonheads'):
                 ui.write(("unpruned common: %s\n") %
                          " ".join(sorted(short(n) for n in common)))
-                dag = dagutil.revlogdag(repo.changelog)
-                all = dag.ancestorset(dag.internalizeall(common))
-                common = dag.externalizeall(dag.headsetofconnecteds(all))
+
+                clnode = repo.changelog.node
+                common = repo.revs('heads(::%ln)', common)
+                common = {clnode(r) for r in common}
         else:
             nodes = None
             if pushedrevs:
@@ -887,15 +893,45 @@
 @command('debugfileset',
     [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
      ('', 'all-files', False,
-      _('test files from all revisions and working directory'))],
-    _('[-r REV] [--all-files] FILESPEC'))
+      _('test files from all revisions and working directory')),
+     ('s', 'show-matcher', None,
+      _('print internal representation of matcher')),
+     ('p', 'show-stage', [],
+      _('print parsed tree at the given stage'), _('NAME'))],
+    _('[-r REV] [--all-files] [OPTION]... FILESPEC'))
 def debugfileset(ui, repo, expr, **opts):
     '''parse and apply a fileset specification'''
+    from . import fileset
+    fileset.symbols # force import of fileset so we have predicates to optimize
     opts = pycompat.byteskwargs(opts)
     ctx = scmutil.revsingle(repo, opts.get('rev'), None)
-    if ui.verbose:
-        tree = fileset.parse(expr)
-        ui.note(fileset.prettyformat(tree), "\n")
+
+    stages = [
+        ('parsed', pycompat.identity),
+        ('analyzed', filesetlang.analyze),
+        ('optimized', filesetlang.optimize),
+    ]
+    stagenames = set(n for n, f in stages)
+
+    showalways = set()
+    if ui.verbose and not opts['show_stage']:
+        # show parsed tree by --verbose (deprecated)
+        showalways.add('parsed')
+    if opts['show_stage'] == ['all']:
+        showalways.update(stagenames)
+    else:
+        for n in opts['show_stage']:
+            if n not in stagenames:
+                raise error.Abort(_('invalid stage name: %s') % n)
+        showalways.update(opts['show_stage'])
+
+    tree = filesetlang.parse(expr)
+    for n, f in stages:
+        tree = f(tree)
+        if n in showalways:
+            if opts['show_stage'] or n != 'parsed':
+                ui.write(("* %s:\n") % n)
+            ui.write(filesetlang.prettyformat(tree), "\n")
 
     files = set()
     if opts['all_files']:
@@ -914,14 +950,15 @@
         files.update(ctx.substate)
 
     m = ctx.matchfileset(expr)
+    if opts['show_matcher'] or (opts['show_matcher'] is None and ui.verbose):
+        ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
     for f in sorted(files):
         if not m(f):
             continue
         ui.write("%s\n" % f)
 
 @command('debugformat',
-         [] + cmdutil.formatteropts,
-        _(''))
+         [] + cmdutil.formatteropts)
 def debugformat(ui, repo, **opts):
     """display format information about the current repository
 
@@ -1076,77 +1113,48 @@
             else:
                 ui.write(_("%s is not ignored\n") % m.uipath(f))
 
-@command('debugindex', cmdutil.debugrevlogopts +
-    [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
-    _('[-f FORMAT] -c|-m|FILE'),
-    optionalrepo=True)
+@command('debugindex', cmdutil.debugrevlogopts + cmdutil.formatteropts,
+         _('-c|-m|FILE'))
 def debugindex(ui, repo, file_=None, **opts):
-    """dump the contents of an index file"""
+    """dump index data for a storage primitive"""
     opts = pycompat.byteskwargs(opts)
-    r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
-    format = opts.get('format', 0)
-    if format not in (0, 1):
-        raise error.Abort(_("unknown format %d") % format)
+    store = cmdutil.openstorage(repo, 'debugindex', file_, opts)
 
     if ui.debugflag:
         shortfn = hex
     else:
         shortfn = short
 
-    # There might not be anything in r, so have a sane default
     idlen = 12
-    for i in r:
-        idlen = len(shortfn(r.node(i)))
+    for i in store:
+        idlen = len(shortfn(store.node(i)))
         break
 
-    if format == 0:
-        if ui.verbose:
-            ui.write(("   rev    offset  length linkrev"
-                     " %s %s p2\n") % ("nodeid".ljust(idlen),
-                                       "p1".ljust(idlen)))
-        else:
-            ui.write(("   rev linkrev %s %s p2\n") % (
-                "nodeid".ljust(idlen), "p1".ljust(idlen)))
-    elif format == 1:
-        if ui.verbose:
-            ui.write(("   rev flag   offset   length     size   link     p1"
-                      "     p2 %s\n") % "nodeid".rjust(idlen))
-        else:
-            ui.write(("   rev flag     size   link     p1     p2 %s\n") %
-                     "nodeid".rjust(idlen))
-
-    for i in r:
-        node = r.node(i)
-        if format == 0:
-            try:
-                pp = r.parents(node)
-            except Exception:
-                pp = [nullid, nullid]
-            if ui.verbose:
-                ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
-                        i, r.start(i), r.length(i), r.linkrev(i),
-                        shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
-            else:
-                ui.write("% 6d % 7d %s %s %s\n" % (
-                    i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
-                    shortfn(pp[1])))
-        elif format == 1:
-            pr = r.parentrevs(i)
-            if ui.verbose:
-                ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
-                        i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
-                        r.linkrev(i), pr[0], pr[1], shortfn(node)))
-            else:
-                ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
-                    i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
-                    shortfn(node)))
+    fm = ui.formatter('debugindex', opts)
+    fm.plain(b'   rev linkrev %s %s p2\n' % (
+        b'nodeid'.ljust(idlen),
+        b'p1'.ljust(idlen)))
+
+    for rev in store:
+        node = store.node(rev)
+        parents = store.parents(node)
+
+        fm.startitem()
+        fm.write(b'rev', b'%6d ', rev)
+        fm.write(b'linkrev', '%7d ', store.linkrev(rev))
+        fm.write(b'node', '%s ', shortfn(node))
+        fm.write(b'p1', '%s ', shortfn(parents[0]))
+        fm.write(b'p2', '%s', shortfn(parents[1]))
+        fm.plain(b'\n')
+
+    fm.end()
 
 @command('debugindexdot', cmdutil.debugrevlogopts,
     _('-c|-m|FILE'), optionalrepo=True)
 def debugindexdot(ui, repo, file_=None, **opts):
     """dump an index DAG as a graphviz dot file"""
     opts = pycompat.byteskwargs(opts)
-    r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
+    r = cmdutil.openstorage(repo, 'debugindexdot', file_, opts)
     ui.write(("digraph G {\n"))
     for i in r:
         node = r.node(i)
@@ -1446,6 +1454,53 @@
 
     return held
 
+@command('debugmanifestfulltextcache', [
+        ('', 'clear', False, _('clear the cache')),
+        ('a', 'add', '', _('add the given manifest node to the cache'),
+         _('NODE'))
+    ], '')
+def debugmanifestfulltextcache(ui, repo, add=None, **opts):
+    """show, clear or amend the contents of the manifest fulltext cache"""
+    with repo.lock():
+        r = repo.manifestlog.getstorage(b'')
+        try:
+            cache = r._fulltextcache
+        except AttributeError:
+            ui.warn(_(
+                "Current revlog implementation doesn't appear to have a "
+                'manifest fulltext cache\n'))
+            return
+
+        if opts.get(r'clear'):
+            cache.clear()
+
+        if add:
+            try:
+                manifest = repo.manifestlog[r.lookup(add)]
+            except error.LookupError as e:
+                raise error.Abort(e, hint="Check your manifest node id")
+            manifest.read()  # stores revisision in cache too
+
+        if not len(cache):
+            ui.write(_('Cache empty'))
+        else:
+            ui.write(
+                _('Cache contains %d manifest entries, in order of most to '
+                  'least recent:\n') % (len(cache),))
+            totalsize = 0
+            for nodeid in cache:
+                # Use cache.get to not update the LRU order
+                data = cache.get(nodeid)
+                size = len(data)
+                totalsize += size + 24   # 20 bytes nodeid, 4 bytes size
+                ui.write(_('id: %s, size %s\n') % (
+                    hex(nodeid), util.bytecount(size)))
+            ondisk = cache._opener.stat('manifestfulltextcache').st_size
+            ui.write(
+                _('Total cache data size %s, on-disk %s\n') % (
+                    util.bytecount(totalsize), util.bytecount(ondisk))
+            )
+
 @command('debugmergestate', [], '')
 def debugmergestate(ui, repo, *args):
     """print merge state
@@ -1699,7 +1754,7 @@
 
     def complete(path, acceptable):
         dirstate = repo.dirstate
-        spec = os.path.normpath(os.path.join(pycompat.getcwd(), path))
+        spec = os.path.normpath(os.path.join(encoding.getcwd(), path))
         rootdir = repo.root + pycompat.ossep
         if spec != repo.root and not spec.startswith(rootdir):
             return [], []
@@ -1971,7 +2026,7 @@
         ts = 0
         heads = set()
 
-        for rev in xrange(numrevs):
+        for rev in pycompat.xrange(numrevs):
             dbase = r.deltaparent(rev)
             if dbase == -1:
                 dbase = rev
@@ -2006,20 +2061,43 @@
     if not flags:
         flags = ['(none)']
 
+    ### tracks merge vs single parent
     nummerges = 0
+
+    ### tracks ways the "delta" are build
+    # nodelta
+    numempty = 0
+    numemptytext = 0
+    numemptydelta = 0
+    # full file content
     numfull = 0
+    # intermediate snapshot against a prior snapshot
+    numsemi = 0
+    # snapshot count per depth
+    numsnapdepth = collections.defaultdict(lambda: 0)
+    # delta against previous revision
     numprev = 0
+    # delta against first or second parent (not prev)
     nump1 = 0
     nump2 = 0
+    # delta against neither prev nor parents
     numother = 0
+    # delta against prev that are also first or second parent
+    # (details of `numprev`)
     nump1prev = 0
     nump2prev = 0
+
+    # data about delta chain of each revs
     chainlengths = []
     chainbases = []
     chainspans = []
 
+    # data about each revision
     datasize = [None, 0, 0]
     fullsize = [None, 0, 0]
+    semisize = [None, 0, 0]
+    # snapshot count per depth
+    snapsizedepth = collections.defaultdict(lambda: [None, 0, 0])
     deltasize = [None, 0, 0]
     chunktypecounts = {}
     chunktypesizes = {}
@@ -2032,7 +2110,7 @@
         l[2] += size
 
     numrevs = len(r)
-    for rev in xrange(numrevs):
+    for rev in pycompat.xrange(numrevs):
         p1, p2 = r.parentrevs(rev)
         delta = r.deltaparent(rev)
         if format > 0:
@@ -2044,30 +2122,49 @@
             chainlengths.append(0)
             chainbases.append(r.start(rev))
             chainspans.append(size)
-            numfull += 1
-            addsize(size, fullsize)
+            if size == 0:
+                numempty += 1
+                numemptytext += 1
+            else:
+                numfull += 1
+                numsnapdepth[0] += 1
+                addsize(size, fullsize)
+                addsize(size, snapsizedepth[0])
         else:
             chainlengths.append(chainlengths[delta] + 1)
             baseaddr = chainbases[delta]
             revaddr = r.start(rev)
             chainbases.append(baseaddr)
             chainspans.append((revaddr - baseaddr) + size)
-            addsize(size, deltasize)
-            if delta == rev - 1:
-                numprev += 1
-                if delta == p1:
-                    nump1prev += 1
+            if size == 0:
+                numempty += 1
+                numemptydelta += 1
+            elif r.issnapshot(rev):
+                addsize(size, semisize)
+                numsemi += 1
+                depth = r.snapshotdepth(rev)
+                numsnapdepth[depth] += 1
+                addsize(size, snapsizedepth[depth])
+            else:
+                addsize(size, deltasize)
+                if delta == rev - 1:
+                    numprev += 1
+                    if delta == p1:
+                        nump1prev += 1
+                    elif delta == p2:
+                        nump2prev += 1
+                elif delta == p1:
+                    nump1 += 1
                 elif delta == p2:
-                    nump2prev += 1
-            elif delta == p1:
-                nump1 += 1
-            elif delta == p2:
-                nump2 += 1
-            elif delta != nullrev:
-                numother += 1
+                    nump2 += 1
+                elif delta != nullrev:
+                    numother += 1
 
         # Obtain data on the raw chunks in the revlog.
-        segment = r._getsegmentforrevs(rev, rev)[1]
+        if util.safehasattr(r, '_getsegmentforrevs'):
+            segment = r._getsegmentforrevs(rev, rev)[1]
+        else:
+            segment = r._revlog._getsegmentforrevs(rev, rev)[1]
         if segment:
             chunktype = bytes(segment[0:1])
         else:
@@ -2081,20 +2178,28 @@
         chunktypesizes[chunktype] += size
 
     # Adjust size min value for empty cases
-    for size in (datasize, fullsize, deltasize):
+    for size in (datasize, fullsize, semisize, deltasize):
         if size[0] is None:
             size[0] = 0
 
-    numdeltas = numrevs - numfull
+    numdeltas = numrevs - numfull - numempty - numsemi
     numoprev = numprev - nump1prev - nump2prev
     totalrawsize = datasize[2]
     datasize[2] /= numrevs
     fulltotal = fullsize[2]
     fullsize[2] /= numfull
+    semitotal = semisize[2]
+    snaptotal = {}
+    if 0 < numsemi:
+        semisize[2] /= numsemi
+    for depth in snapsizedepth:
+        snaptotal[depth] = snapsizedepth[depth][2]
+        snapsizedepth[depth][2] /= numsnapdepth[depth]
+
     deltatotal = deltasize[2]
-    if numrevs - numfull > 0:
-        deltasize[2] /= numrevs - numfull
-    totalsize = fulltotal + deltatotal
+    if numdeltas > 0:
+        deltasize[2] /= numdeltas
+    totalsize = fulltotal + semitotal + deltatotal
     avgchainlen = sum(chainlengths) / numrevs
     maxchainlen = max(chainlengths)
     maxchainspan = max(chainspans)
@@ -2126,10 +2231,22 @@
     ui.write(('    merges    : ') + fmt % pcfmt(nummerges, numrevs))
     ui.write(('    normal    : ') + fmt % pcfmt(numrevs - nummerges, numrevs))
     ui.write(('revisions     : ') + fmt2 % numrevs)
-    ui.write(('    full      : ') + fmt % pcfmt(numfull, numrevs))
+    ui.write(('    empty     : ') + fmt % pcfmt(numempty, numrevs))
+    ui.write(('                   text  : ')
+             + fmt % pcfmt(numemptytext, numemptytext + numemptydelta))
+    ui.write(('                   delta : ')
+             + fmt % pcfmt(numemptydelta, numemptytext + numemptydelta))
+    ui.write(('    snapshot  : ') + fmt % pcfmt(numfull + numsemi, numrevs))
+    for depth in sorted(numsnapdepth):
+        ui.write(('      lvl-%-3d :       ' % depth)
+                 + fmt % pcfmt(numsnapdepth[depth], numrevs))
     ui.write(('    deltas    : ') + fmt % pcfmt(numdeltas, numrevs))
     ui.write(('revision size : ') + fmt2 % totalsize)
-    ui.write(('    full      : ') + fmt % pcfmt(fulltotal, totalsize))
+    ui.write(('    snapshot  : ')
+             + fmt % pcfmt(fulltotal + semitotal, totalsize))
+    for depth in sorted(numsnapdepth):
+        ui.write(('      lvl-%-3d :       ' % depth)
+                 + fmt % pcfmt(snaptotal[depth], totalsize))
     ui.write(('    deltas    : ') + fmt % pcfmt(deltatotal, totalsize))
 
     def fmtchunktype(chunktype):
@@ -2163,6 +2280,13 @@
                  % tuple(datasize))
     ui.write(('full revision size (min/max/avg)     : %d / %d / %d\n')
              % tuple(fullsize))
+    ui.write(('inter-snapshot size (min/max/avg)    : %d / %d / %d\n')
+             % tuple(semisize))
+    for depth in sorted(snapsizedepth):
+        if depth == 0:
+            continue
+        ui.write(('    level-%-3d (min/max/avg)          : %d / %d / %d\n')
+                 % ((depth,) + tuple(snapsizedepth[depth])))
     ui.write(('delta size (min/max/avg)             : %d / %d / %d\n')
              % tuple(deltasize))
 
@@ -2186,6 +2310,71 @@
             ui.write(('deltas against other : ') + fmt % pcfmt(numother,
                                                              numdeltas))
 
+@command('debugrevlogindex', cmdutil.debugrevlogopts +
+    [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
+    _('[-f FORMAT] -c|-m|FILE'),
+    optionalrepo=True)
+def debugrevlogindex(ui, repo, file_=None, **opts):
+    """dump the contents of a revlog index"""
+    opts = pycompat.byteskwargs(opts)
+    r = cmdutil.openrevlog(repo, 'debugrevlogindex', file_, opts)
+    format = opts.get('format', 0)
+    if format not in (0, 1):
+        raise error.Abort(_("unknown format %d") % format)
+
+    if ui.debugflag:
+        shortfn = hex
+    else:
+        shortfn = short
+
+    # There might not be anything in r, so have a sane default
+    idlen = 12
+    for i in r:
+        idlen = len(shortfn(r.node(i)))
+        break
+
+    if format == 0:
+        if ui.verbose:
+            ui.write(("   rev    offset  length linkrev"
+                     " %s %s p2\n") % ("nodeid".ljust(idlen),
+                                       "p1".ljust(idlen)))
+        else:
+            ui.write(("   rev linkrev %s %s p2\n") % (
+                "nodeid".ljust(idlen), "p1".ljust(idlen)))
+    elif format == 1:
+        if ui.verbose:
+            ui.write(("   rev flag   offset   length     size   link     p1"
+                      "     p2 %s\n") % "nodeid".rjust(idlen))
+        else:
+            ui.write(("   rev flag     size   link     p1     p2 %s\n") %
+                     "nodeid".rjust(idlen))
+
+    for i in r:
+        node = r.node(i)
+        if format == 0:
+            try:
+                pp = r.parents(node)
+            except Exception:
+                pp = [nullid, nullid]
+            if ui.verbose:
+                ui.write("% 6d % 9d % 7d % 7d %s %s %s\n" % (
+                        i, r.start(i), r.length(i), r.linkrev(i),
+                        shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
+            else:
+                ui.write("% 6d % 7d %s %s %s\n" % (
+                    i, r.linkrev(i), shortfn(node), shortfn(pp[0]),
+                    shortfn(pp[1])))
+        elif format == 1:
+            pr = r.parentrevs(i)
+            if ui.verbose:
+                ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d %s\n" % (
+                        i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
+                        r.linkrev(i), pr[0], pr[1], shortfn(node)))
+            else:
+                ui.write("% 6d %04x % 8d % 6d % 6d % 6d %s\n" % (
+                    i, r.flags(i), r.rawsize(i), r.linkrev(i), pr[0], pr[1],
+                    shortfn(node)))
+
 @command('debugrevspec',
     [('', 'optimize', None,
       _('print parsed tree after optimizing (DEPRECATED)')),
@@ -2642,7 +2831,7 @@
         if line.startswith(b'#'):
             continue
 
-        if not line.startswith(' '):
+        if not line.startswith(b' '):
             # New block. Flush previous one.
             if activeaction:
                 yield activeaction, blocklines
@@ -2885,7 +3074,8 @@
             '-R', repo.root,
             'debugserve', '--sshstdio',
         ]
-        proc = subprocess.Popen(args, stdin=subprocess.PIPE,
+        proc = subprocess.Popen(pycompat.rapply(procutil.tonativestr, args),
+                                stdin=subprocess.PIPE,
                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                                 bufsize=0)
 
@@ -3054,13 +3244,12 @@
                     res = e.callcommand(command, args).result()
 
                 if isinstance(res, wireprotov2peer.commandresponse):
-                    val = list(res.cborobjects())
+                    val = res.objects()
                     ui.status(_('response: %s\n') %
-                              stringutil.pprint(val, bprefix=True))
-
+                              stringutil.pprint(val, bprefix=True, indent=2))
                 else:
                     ui.status(_('response: %s\n') %
-                              stringutil.pprint(res, bprefix=True))
+                              stringutil.pprint(res, bprefix=True, indent=2))
 
         elif action == 'batchbegin':
             if batchedcommands is not None:
@@ -3122,18 +3311,21 @@
             # urllib.Request insists on using has_data() as a proxy for
             # determining the request method. Override that to use our
             # explicitly requested method.
-            req.get_method = lambda: method
+            req.get_method = lambda: pycompat.sysstr(method)
 
             try:
                 res = opener.open(req)
                 body = res.read()
             except util.urlerr.urlerror as e:
-                e.read()
+                # read() method must be called, but only exists in Python 2
+                getattr(e, 'read', lambda: None)()
                 continue
 
             if res.headers.get('Content-Type') == 'application/mercurial-cbor':
                 ui.write(_('cbor> %s\n') %
-                         stringutil.pprint(cbor.loads(body), bprefix=True))
+                         stringutil.pprint(cborutil.decodeall(body)[0],
+                                           bprefix=True,
+                                           indent=2))
 
         elif action == 'close':
             peer.close()
--- a/mercurial/default.d/mergetools.rc	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/default.d/mergetools.rc	Wed Sep 26 20:33:09 2018 +0900
@@ -26,7 +26,7 @@
 gpyfm.gui=True
 
 meld.gui=True
-meld.args=--label=$labellocal $local --label='merged' $base --label=$labelother $other -o $output
+meld.args=--label=$labellocal $local --label='merged' $base --label=$labelother $other -o $output --auto-merge
 meld.check=changed
 meld.diffargs=-a --label=$plabel1 $parent --label=$clabel $child
 
--- a/mercurial/diffhelper.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/diffhelper.py	Wed Sep 26 20:33:09 2018 +0900
@@ -11,6 +11,7 @@
 
 from . import (
     error,
+    pycompat,
 )
 
 def addlines(fp, hunk, lena, lenb, a, b):
@@ -26,7 +27,7 @@
         num = max(todoa, todob)
         if num == 0:
             break
-        for i in xrange(num):
+        for i in pycompat.xrange(num):
             s = fp.readline()
             if not s:
                 raise error.ParseError(_('incomplete hunk'))
@@ -71,7 +72,7 @@
     blen = len(b)
     if alen > blen - bstart or bstart < 0:
         return False
-    for i in xrange(alen):
+    for i in pycompat.xrange(alen):
         if a[i][1:] != b[i + bstart]:
             return False
     return True
--- a/mercurial/dirstate.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/dirstate.py	Wed Sep 26 20:33:09 2018 +0900
@@ -210,7 +210,7 @@
         forcecwd = self._ui.config('ui', 'forcecwd')
         if forcecwd:
             return forcecwd
-        return pycompat.getcwd()
+        return encoding.getcwd()
 
     def getcwd(self):
         '''Return the path from which a canonical path is calculated.
@@ -893,8 +893,11 @@
             wadd = work.append
             while work:
                 nd = work.pop()
-                if not match.visitdir(nd):
+                visitentries = match.visitchildrenset(nd)
+                if not visitentries:
                     continue
+                if visitentries == 'this' or visitentries == 'all':
+                    visitentries = None
                 skip = None
                 if nd == '.':
                     nd = ''
@@ -909,6 +912,16 @@
                         continue
                     raise
                 for f, kind, st in entries:
+                    # Some matchers may return files in the visitentries set,
+                    # instead of 'this', if the matcher explicitly mentions them
+                    # and is not an exactmatcher. This is acceptable; we do not
+                    # make any hard assumptions about file-or-directory below
+                    # based on the presence of `f` in visitentries. If
+                    # visitchildrenset returned a set, we can always skip the
+                    # entries *not* in the set it provided regardless of whether
+                    # they're actually a file or a directory.
+                    if visitentries and f not in visitentries:
+                        continue
                     if normalizefile:
                         # even though f might be a directory, we're only
                         # interested in comparing it to files currently in the
--- a/mercurial/dirstateguard.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/dirstateguard.py	Wed Sep 26 20:33:09 2018 +0900
@@ -11,6 +11,7 @@
 
 from . import (
     error,
+    narrowspec,
     util,
 )
 
@@ -33,7 +34,10 @@
         self._active = False
         self._closed = False
         self._backupname = 'dirstate.backup.%s.%d' % (name, id(self))
+        self._narrowspecbackupname = ('narrowspec.backup.%s.%d' %
+                                      (name, id(self)))
         repo.dirstate.savebackup(repo.currenttransaction(), self._backupname)
+        narrowspec.savebackup(repo, self._narrowspecbackupname)
         self._active = True
 
     def __del__(self):
@@ -52,10 +56,12 @@
 
         self._repo.dirstate.clearbackup(self._repo.currenttransaction(),
                                          self._backupname)
+        narrowspec.clearbackup(self._repo, self._narrowspecbackupname)
         self._active = False
         self._closed = True
 
     def _abort(self):
+        narrowspec.restorebackup(self._repo, self._narrowspecbackupname)
         self._repo.dirstate.restorebackup(self._repo.currenttransaction(),
                                            self._backupname)
         self._active = False
--- a/mercurial/dispatch.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/dispatch.py	Wed Sep 26 20:33:09 2018 +0900
@@ -21,6 +21,8 @@
 
 from .i18n import _
 
+from hgdemandimport import tracing
+
 from . import (
     cmdutil,
     color,
@@ -84,7 +86,8 @@
 def run():
     "run the command in sys.argv"
     initstdio()
-    req = request(pycompat.sysargv[1:])
+    with tracing.log('parse args into request'):
+        req = request(pycompat.sysargv[1:])
     err = None
     try:
         status = dispatch(req)
@@ -176,182 +179,184 @@
 
 def dispatch(req):
     """run the command specified in req.args; returns an integer status code"""
-    if req.ferr:
-        ferr = req.ferr
-    elif req.ui:
-        ferr = req.ui.ferr
-    else:
-        ferr = procutil.stderr
+    with tracing.log('dispatch.dispatch'):
+        if req.ferr:
+            ferr = req.ferr
+        elif req.ui:
+            ferr = req.ui.ferr
+        else:
+            ferr = procutil.stderr
 
-    try:
-        if not req.ui:
-            req.ui = uimod.ui.load()
-        req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
-        if req.earlyoptions['traceback']:
-            req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
+        try:
+            if not req.ui:
+                req.ui = uimod.ui.load()
+            req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
+            if req.earlyoptions['traceback']:
+                req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
 
-        # set ui streams from the request
-        if req.fin:
-            req.ui.fin = req.fin
-        if req.fout:
-            req.ui.fout = req.fout
-        if req.ferr:
-            req.ui.ferr = req.ferr
-    except error.Abort as inst:
-        ferr.write(_("abort: %s\n") % inst)
-        if inst.hint:
-            ferr.write(_("(%s)\n") % inst.hint)
-        return -1
-    except error.ParseError as inst:
-        _formatparse(ferr.write, inst)
-        return -1
+            # set ui streams from the request
+            if req.fin:
+                req.ui.fin = req.fin
+            if req.fout:
+                req.ui.fout = req.fout
+            if req.ferr:
+                req.ui.ferr = req.ferr
+        except error.Abort as inst:
+            ferr.write(_("abort: %s\n") % inst)
+            if inst.hint:
+                ferr.write(_("(%s)\n") % inst.hint)
+            return -1
+        except error.ParseError as inst:
+            _formatparse(ferr.write, inst)
+            return -1
 
-    msg = _formatargs(req.args)
-    starttime = util.timer()
-    ret = 1  # default of Python exit code on unhandled exception
-    try:
-        ret = _runcatch(req) or 0
-    except error.ProgrammingError as inst:
-        req.ui.warn(_('** ProgrammingError: %s\n') % inst)
-        if inst.hint:
-            req.ui.warn(_('** (%s)\n') % inst.hint)
-        raise
-    except KeyboardInterrupt as inst:
+        msg = _formatargs(req.args)
+        starttime = util.timer()
+        ret = 1  # default of Python exit code on unhandled exception
         try:
-            if isinstance(inst, error.SignalInterrupt):
-                msg = _("killed!\n")
-            else:
-                msg = _("interrupted!\n")
-            req.ui.warn(msg)
-        except error.SignalInterrupt:
-            # maybe pager would quit without consuming all the output, and
-            # SIGPIPE was raised. we cannot print anything in this case.
-            pass
-        except IOError as inst:
-            if inst.errno != errno.EPIPE:
-                raise
-        ret = -1
-    finally:
-        duration = util.timer() - starttime
-        req.ui.flush()
-        if req.ui.logblockedtimes:
-            req.ui._blockedtimes['command_duration'] = duration * 1000
-            req.ui.log('uiblocked', 'ui blocked ms',
-                       **pycompat.strkwargs(req.ui._blockedtimes))
-        req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
-                   msg, ret & 255, duration)
-        try:
-            req._runexithandlers()
-        except: # exiting, so no re-raises
-            ret = ret or -1
-    return ret
+            ret = _runcatch(req) or 0
+        except error.ProgrammingError as inst:
+            req.ui.error(_('** ProgrammingError: %s\n') % inst)
+            if inst.hint:
+                req.ui.error(_('** (%s)\n') % inst.hint)
+            raise
+        except KeyboardInterrupt as inst:
+            try:
+                if isinstance(inst, error.SignalInterrupt):
+                    msg = _("killed!\n")
+                else:
+                    msg = _("interrupted!\n")
+                req.ui.error(msg)
+            except error.SignalInterrupt:
+                # maybe pager would quit without consuming all the output, and
+                # SIGPIPE was raised. we cannot print anything in this case.
+                pass
+            except IOError as inst:
+                if inst.errno != errno.EPIPE:
+                    raise
+            ret = -1
+        finally:
+            duration = util.timer() - starttime
+            req.ui.flush()
+            if req.ui.logblockedtimes:
+                req.ui._blockedtimes['command_duration'] = duration * 1000
+                req.ui.log('uiblocked', 'ui blocked ms',
+                           **pycompat.strkwargs(req.ui._blockedtimes))
+            req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
+                       msg, ret & 255, duration)
+            try:
+                req._runexithandlers()
+            except: # exiting, so no re-raises
+                ret = ret or -1
+        return ret
 
 def _runcatch(req):
-    def catchterm(*args):
-        raise error.SignalInterrupt
+    with tracing.log('dispatch._runcatch'):
+        def catchterm(*args):
+            raise error.SignalInterrupt
 
-    ui = req.ui
-    try:
-        for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
-            num = getattr(signal, name, None)
-            if num:
-                signal.signal(num, catchterm)
-    except ValueError:
-        pass # happens if called in a thread
-
-    def _runcatchfunc():
-        realcmd = None
+        ui = req.ui
         try:
-            cmdargs = fancyopts.fancyopts(req.args[:], commands.globalopts, {})
-            cmd = cmdargs[0]
-            aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
-            realcmd = aliases[0]
-        except (error.UnknownCommand, error.AmbiguousCommand,
-                IndexError, getopt.GetoptError):
-            # Don't handle this here. We know the command is
-            # invalid, but all we're worried about for now is that
-            # it's not a command that server operators expect to
-            # be safe to offer to users in a sandbox.
-            pass
-        if realcmd == 'serve' and '--stdio' in cmdargs:
-            # We want to constrain 'hg serve --stdio' instances pretty
-            # closely, as many shared-ssh access tools want to grant
-            # access to run *only* 'hg -R $repo serve --stdio'. We
-            # restrict to exactly that set of arguments, and prohibit
-            # any repo name that starts with '--' to prevent
-            # shenanigans wherein a user does something like pass
-            # --debugger or --config=ui.debugger=1 as a repo
-            # name. This used to actually run the debugger.
-            if (len(req.args) != 4 or
-                req.args[0] != '-R' or
-                req.args[1].startswith('--') or
-                req.args[2] != 'serve' or
-                req.args[3] != '--stdio'):
-                raise error.Abort(
-                    _('potentially unsafe serve --stdio invocation: %s') %
-                    (stringutil.pprint(req.args),))
+            for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
+                num = getattr(signal, name, None)
+                if num:
+                    signal.signal(num, catchterm)
+        except ValueError:
+            pass # happens if called in a thread
 
-        try:
-            debugger = 'pdb'
-            debugtrace = {
-                'pdb': pdb.set_trace
-            }
-            debugmortem = {
-                'pdb': pdb.post_mortem
-            }
+        def _runcatchfunc():
+            realcmd = None
+            try:
+                cmdargs = fancyopts.fancyopts(
+                    req.args[:], commands.globalopts, {})
+                cmd = cmdargs[0]
+                aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
+                realcmd = aliases[0]
+            except (error.UnknownCommand, error.AmbiguousCommand,
+                    IndexError, getopt.GetoptError):
+                # Don't handle this here. We know the command is
+                # invalid, but all we're worried about for now is that
+                # it's not a command that server operators expect to
+                # be safe to offer to users in a sandbox.
+                pass
+            if realcmd == 'serve' and '--stdio' in cmdargs:
+                # We want to constrain 'hg serve --stdio' instances pretty
+                # closely, as many shared-ssh access tools want to grant
+                # access to run *only* 'hg -R $repo serve --stdio'. We
+                # restrict to exactly that set of arguments, and prohibit
+                # any repo name that starts with '--' to prevent
+                # shenanigans wherein a user does something like pass
+                # --debugger or --config=ui.debugger=1 as a repo
+                # name. This used to actually run the debugger.
+                if (len(req.args) != 4 or
+                    req.args[0] != '-R' or
+                    req.args[1].startswith('--') or
+                    req.args[2] != 'serve' or
+                    req.args[3] != '--stdio'):
+                    raise error.Abort(
+                        _('potentially unsafe serve --stdio invocation: %s') %
+                        (stringutil.pprint(req.args),))
 
-            # read --config before doing anything else
-            # (e.g. to change trust settings for reading .hg/hgrc)
-            cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
-
-            if req.repo:
-                # copy configs that were passed on the cmdline (--config) to
-                # the repo ui
-                for sec, name, val in cfgs:
-                    req.repo.ui.setconfig(sec, name, val, source='--config')
+            try:
+                debugger = 'pdb'
+                debugtrace = {
+                    'pdb': pdb.set_trace
+                }
+                debugmortem = {
+                    'pdb': pdb.post_mortem
+                }
 
-            # developer config: ui.debugger
-            debugger = ui.config("ui", "debugger")
-            debugmod = pdb
-            if not debugger or ui.plain():
-                # if we are in HGPLAIN mode, then disable custom debugging
-                debugger = 'pdb'
-            elif req.earlyoptions['debugger']:
-                # This import can be slow for fancy debuggers, so only
-                # do it when absolutely necessary, i.e. when actual
-                # debugging has been requested
-                with demandimport.deactivated():
-                    try:
-                        debugmod = __import__(debugger)
-                    except ImportError:
-                        pass # Leave debugmod = pdb
+                # read --config before doing anything else
+                # (e.g. to change trust settings for reading .hg/hgrc)
+                cfgs = _parseconfig(req.ui, req.earlyoptions['config'])
+
+                if req.repo:
+                    # copy configs that were passed on the cmdline (--config) to
+                    # the repo ui
+                    for sec, name, val in cfgs:
+                        req.repo.ui.setconfig(sec, name, val, source='--config')
 
-            debugtrace[debugger] = debugmod.set_trace
-            debugmortem[debugger] = debugmod.post_mortem
+                # developer config: ui.debugger
+                debugger = ui.config("ui", "debugger")
+                debugmod = pdb
+                if not debugger or ui.plain():
+                    # if we are in HGPLAIN mode, then disable custom debugging
+                    debugger = 'pdb'
+                elif req.earlyoptions['debugger']:
+                    # This import can be slow for fancy debuggers, so only
+                    # do it when absolutely necessary, i.e. when actual
+                    # debugging has been requested
+                    with demandimport.deactivated():
+                        try:
+                            debugmod = __import__(debugger)
+                        except ImportError:
+                            pass # Leave debugmod = pdb
 
-            # enter the debugger before command execution
-            if req.earlyoptions['debugger']:
-                ui.warn(_("entering debugger - "
-                        "type c to continue starting hg or h for help\n"))
+                debugtrace[debugger] = debugmod.set_trace
+                debugmortem[debugger] = debugmod.post_mortem
 
-                if (debugger != 'pdb' and
-                    debugtrace[debugger] == debugtrace['pdb']):
-                    ui.warn(_("%s debugger specified "
-                              "but its module was not found\n") % debugger)
-                with demandimport.deactivated():
-                    debugtrace[debugger]()
-            try:
-                return _dispatch(req)
-            finally:
-                ui.flush()
-        except: # re-raises
-            # enter the debugger when we hit an exception
-            if req.earlyoptions['debugger']:
-                traceback.print_exc()
-                debugmortem[debugger](sys.exc_info()[2])
-            raise
+                # enter the debugger before command execution
+                if req.earlyoptions['debugger']:
+                    ui.warn(_("entering debugger - "
+                            "type c to continue starting hg or h for help\n"))
 
-    return _callcatch(ui, _runcatchfunc)
+                    if (debugger != 'pdb' and
+                        debugtrace[debugger] == debugtrace['pdb']):
+                        ui.warn(_("%s debugger specified "
+                                  "but its module was not found\n") % debugger)
+                    with demandimport.deactivated():
+                        debugtrace[debugger]()
+                try:
+                    return _dispatch(req)
+                finally:
+                    ui.flush()
+            except: # re-raises
+                # enter the debugger when we hit an exception
+                if req.earlyoptions['debugger']:
+                    traceback.print_exc()
+                    debugmortem[debugger](sys.exc_info()[2])
+                raise
+        return _callcatch(ui, _runcatchfunc)
 
 def _callcatch(ui, func):
     """like scmutil.callcatch but handles more high-level exceptions about
@@ -370,9 +375,8 @@
             ui.warn(_("hg %s: %s\n") % (inst.args[0], msgbytes))
             commands.help_(ui, inst.args[0], full=False, command=True)
         else:
-            ui.pager('help')
             ui.warn(_("hg: %s\n") % inst.args[1])
-            commands.help_(ui, 'shortlist')
+            ui.warn(_("(use 'hg help -v' for a list of global options)\n"))
     except error.ParseError as inst:
         _formatparse(ui.warn, inst)
         return -1
@@ -394,9 +398,8 @@
                     _reportsimilar(ui.warn, sim)
                     suggested = True
             if not suggested:
-                ui.pager('help')
                 ui.warn(nocmdmsg)
-                commands.help_(ui, 'shortlist')
+                ui.warn(_("(use 'hg help' for a list of commands)\n"))
     except IOError:
         raise
     except KeyboardInterrupt:
@@ -745,7 +748,7 @@
     """
     if wd is None:
         try:
-            wd = pycompat.getcwd()
+            wd = encoding.getcwd()
         except OSError as e:
             raise error.Abort(_("error getting current working directory: %s") %
                               encoding.strtolocal(e.strerror))
@@ -965,7 +968,7 @@
                         if not path:
                             raise error.RepoError(_("no repository found in"
                                                     " '%s' (.hg not found)")
-                                                  % pycompat.getcwd())
+                                                  % encoding.getcwd())
                         raise
             if repo:
                 ui = repo.ui
@@ -989,7 +992,8 @@
 def _runcommand(ui, options, cmd, cmdfunc):
     """Run a command function, possibly with profiling enabled."""
     try:
-        return cmdfunc()
+        with tracing.log("Running %s command" % cmd):
+            return cmdfunc()
     except error.SignatureError:
         raise error.CommandError(cmd, _('invalid arguments'))
 
--- a/mercurial/encoding.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/encoding.py	Wed Sep 26 20:33:09 2018 +0900
@@ -233,6 +233,18 @@
     environ = dict((tolocal(k.encode(u'utf-8')), tolocal(v.encode(u'utf-8')))
                    for k, v in os.environ.items())  # re-exports
 
+if pycompat.ispy3:
+    # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
+    # returns bytes.
+    if pycompat.iswindows:
+        # Python 3 on Windows issues a DeprecationWarning about using the bytes
+        # API when os.getcwdb() is called.
+        getcwd = lambda: strtolocal(os.getcwd())  # re-exports
+    else:
+        getcwd = os.getcwdb  # re-exports
+else:
+    getcwd = os.getcwd  # re-exports
+
 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
 _wide = _sysstr(environ.get("HGENCODINGAMBIGUOUS", "narrow") == "wide"
                 and "WFA" or "WF")
@@ -251,7 +263,7 @@
 def getcols(s, start, c):
     '''Use colwidth to find a c-column substring of s starting at byte
     index start'''
-    for x in xrange(start + c, len(s)):
+    for x in pycompat.xrange(start + c, len(s)):
         t = s[start:x]
         if colwidth(t) == c:
             return t
@@ -346,7 +358,7 @@
     else:
         uslice = lambda i: u[:-i]
         concat = lambda s: s + ellipsis
-    for i in xrange(1, len(u)):
+    for i in pycompat.xrange(1, len(u)):
         usub = uslice(i)
         if ucolwidth(usub) <= width:
             return concat(usub.encode(_sysstr(encoding)))
--- a/mercurial/error.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/error.py	Wed Sep 26 20:33:09 2018 +0900
@@ -34,7 +34,14 @@
         self.hint = kw.pop(r'hint', None)
         super(Hint, self).__init__(*args, **kw)
 
-class RevlogError(Hint, Exception):
+class StorageError(Hint, Exception):
+    """Raised when an error occurs in a storage layer.
+
+    Usually subclassed by a storage-specific exception.
+    """
+    __bytes__ = _tobytes
+
+class RevlogError(StorageError):
     __bytes__ = _tobytes
 
 class FilteredIndexError(IndexError):
@@ -58,6 +65,9 @@
     def __str__(self):
         return RevlogError.__str__(self)
 
+class AmbiguousPrefixLookupError(LookupError):
+    pass
+
 class FilteredLookupError(LookupError):
     pass
 
@@ -212,6 +222,14 @@
 
 class ProgrammingError(Hint, RuntimeError):
     """Raised if a mercurial (core or extension) developer made a mistake"""
+
+    def __init__(self, msg, *args, **kwargs):
+        # On Python 3, turn the message back into a string since this is
+        # an internal-only error that won't be printed except in a
+        # stack traces.
+        msg = pycompat.sysstr(msg)
+        super(ProgrammingError, self).__init__(msg, *args, **kwargs)
+
     __bytes__ = _tobytes
 
 class WdirUnsupported(Exception):
@@ -265,7 +283,7 @@
         Abort.__init__(self, 'failed to update value for "%s/%s"'
                        % (namespace, key))
 
-class CensoredNodeError(RevlogError):
+class CensoredNodeError(StorageError):
     """error raised when content verification fails on a censored node
 
     Also contains the tombstone data substituted for the uncensored data.
@@ -273,10 +291,10 @@
 
     def __init__(self, filename, node, tombstone):
         from .node import short
-        RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
+        StorageError.__init__(self, '%s:%s' % (filename, short(node)))
         self.tombstone = tombstone
 
-class CensoredBaseError(RevlogError):
+class CensoredBaseError(StorageError):
     """error raised when a delta is rejected because its base is censored
 
     A delta based on a censored revision must be formed as single patch
@@ -305,3 +323,14 @@
 class InMemoryMergeConflictsError(Exception):
     """Exception raised when merge conflicts arose during an in-memory merge."""
     __bytes__ = _tobytes
+
+class WireprotoCommandError(Exception):
+    """Represents an error during execution of a wire protocol command.
+
+    Should only be thrown by wire protocol version 2 commands.
+
+    The error is a formatter string and an optional iterable of arguments.
+    """
+    def __init__(self, message, args=None):
+        self.message = message
+        self.messageargs = args
--- a/mercurial/exchange.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/exchange.py	Wed Sep 26 20:33:09 2018 +0900
@@ -15,6 +15,7 @@
     bin,
     hex,
     nullid,
+    nullrev,
 )
 from .thirdparty import (
     attr,
@@ -25,12 +26,15 @@
     changegroup,
     discovery,
     error,
+    exchangev2,
     lock as lockmod,
     logexchange,
+    narrowspec,
     obsolete,
     phases,
     pushkey,
     pycompat,
+    repository,
     scmutil,
     sslutil,
     streamclone,
@@ -44,6 +48,8 @@
 urlerr = util.urlerr
 urlreq = util.urlreq
 
+_NARROWACL_SECTION = 'narrowhgacl'
+
 # Maps bundle version human names to changegroup versions.
 _bundlespeccgversions = {'v1': '01',
                          'v2': '02',
@@ -1308,7 +1314,8 @@
     """
 
     def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
-                 remotebookmarks=None, streamclonerequested=None):
+                 remotebookmarks=None, streamclonerequested=None,
+                 includepats=None, excludepats=None):
         # repo we pull into
         self.repo = repo
         # repo we pull from
@@ -1338,6 +1345,10 @@
         self.stepsdone = set()
         # Whether we attempted a clone from pre-generated bundles.
         self.clonebundleattempted = False
+        # Set of file patterns to include.
+        self.includepats = includepats
+        # Set of file patterns to exclude.
+        self.excludepats = excludepats
 
     @util.propertycache
     def pulledsubset(self):
@@ -1427,7 +1438,7 @@
         old_heads = unficl.heads()
         clstart = len(unficl)
         _pullbundle2(pullop)
-        if changegroup.NARROW_REQUIREMENT in repo.requirements:
+        if repository.NARROW_REQUIREMENT in repo.requirements:
             # XXX narrow clones filter the heads on the server side during
             # XXX getbundle and result in partial replies as well.
             # XXX Disable pull bundles in this case as band aid to avoid
@@ -1442,7 +1453,7 @@
         pullop.rheads = set(pullop.rheads) - pullop.common
 
 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
-         streamclonerequested=None):
+         streamclonerequested=None, includepats=None, excludepats=None):
     """Fetch repository data from a remote.
 
     This is the main function used to retrieve data from a remote repository.
@@ -1460,13 +1471,29 @@
     of revlogs from the server. This only works when the local repository is
     empty. The default value of ``None`` means to respect the server
     configuration for preferring stream clones.
+    ``includepats`` and ``excludepats`` define explicit file patterns to
+    include and exclude in storage, respectively. If not defined, narrow
+    patterns from the repo instance are used, if available.
 
     Returns the ``pulloperation`` created for this pull.
     """
     if opargs is None:
         opargs = {}
+
+    # We allow the narrow patterns to be passed in explicitly to provide more
+    # flexibility for API consumers.
+    if includepats or excludepats:
+        includepats = includepats or set()
+        excludepats = excludepats or set()
+    else:
+        includepats, excludepats = repo.narrowpats
+
+    narrowspec.validatepatterns(includepats)
+    narrowspec.validatepatterns(excludepats)
+
     pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
                            streamclonerequested=streamclonerequested,
+                           includepats=includepats, excludepats=excludepats,
                            **pycompat.strkwargs(opargs))
 
     peerlocal = pullop.remote.local()
@@ -1480,17 +1507,21 @@
 
     pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
     with repo.wlock(), repo.lock(), pullop.trmanager:
-        # This should ideally be in _pullbundle2(). However, it needs to run
-        # before discovery to avoid extra work.
-        _maybeapplyclonebundle(pullop)
-        streamclone.maybeperformlegacystreamclone(pullop)
-        _pulldiscovery(pullop)
-        if pullop.canusebundle2:
-            _fullpullbundle2(repo, pullop)
-        _pullchangeset(pullop)
-        _pullphase(pullop)
-        _pullbookmarks(pullop)
-        _pullobsolete(pullop)
+        # Use the modern wire protocol, if available.
+        if remote.capable('command-changesetdata'):
+            exchangev2.pull(pullop)
+        else:
+            # This should ideally be in _pullbundle2(). However, it needs to run
+            # before discovery to avoid extra work.
+            _maybeapplyclonebundle(pullop)
+            streamclone.maybeperformlegacystreamclone(pullop)
+            _pulldiscovery(pullop)
+            if pullop.canusebundle2:
+                _fullpullbundle2(repo, pullop)
+            _pullchangeset(pullop)
+            _pullphase(pullop)
+            _pullbookmarks(pullop)
+            _pullobsolete(pullop)
 
     # storing remotenames
     if repo.ui.configbool('experimental', 'remotenames'):
@@ -1830,6 +1861,176 @@
             pullop.repo.invalidatevolatilesets()
     return tr
 
+def applynarrowacl(repo, kwargs):
+    """Apply narrow fetch access control.
+
+    This massages the named arguments for getbundle wire protocol commands
+    so requested data is filtered through access control rules.
+    """
+    ui = repo.ui
+    # TODO this assumes existence of HTTP and is a layering violation.
+    username = ui.shortuser(ui.environ.get('REMOTE_USER') or ui.username())
+    user_includes = ui.configlist(
+        _NARROWACL_SECTION, username + '.includes',
+        ui.configlist(_NARROWACL_SECTION, 'default.includes'))
+    user_excludes = ui.configlist(
+        _NARROWACL_SECTION, username + '.excludes',
+        ui.configlist(_NARROWACL_SECTION, 'default.excludes'))
+    if not user_includes:
+        raise error.Abort(_("{} configuration for user {} is empty")
+                          .format(_NARROWACL_SECTION, username))
+
+    user_includes = [
+        'path:.' if p == '*' else 'path:' + p for p in user_includes]
+    user_excludes = [
+        'path:.' if p == '*' else 'path:' + p for p in user_excludes]
+
+    req_includes = set(kwargs.get(r'includepats', []))
+    req_excludes = set(kwargs.get(r'excludepats', []))
+
+    req_includes, req_excludes, invalid_includes = narrowspec.restrictpatterns(
+        req_includes, req_excludes, user_includes, user_excludes)
+
+    if invalid_includes:
+        raise error.Abort(
+            _("The following includes are not accessible for {}: {}")
+            .format(username, invalid_includes))
+
+    new_args = {}
+    new_args.update(kwargs)
+    new_args[r'narrow'] = True
+    new_args[r'includepats'] = req_includes
+    if req_excludes:
+        new_args[r'excludepats'] = req_excludes
+
+    return new_args
+
+def _computeellipsis(repo, common, heads, known, match, depth=None):
+    """Compute the shape of a narrowed DAG.
+
+    Args:
+      repo: The repository we're transferring.
+      common: The roots of the DAG range we're transferring.
+              May be just [nullid], which means all ancestors of heads.
+      heads: The heads of the DAG range we're transferring.
+      match: The narrowmatcher that allows us to identify relevant changes.
+      depth: If not None, only consider nodes to be full nodes if they are at
+             most depth changesets away from one of heads.
+
+    Returns:
+      A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:
+
+        visitnodes: The list of nodes (either full or ellipsis) which
+                    need to be sent to the client.
+        relevant_nodes: The set of changelog nodes which change a file inside
+                 the narrowspec. The client needs these as non-ellipsis nodes.
+        ellipsisroots: A dict of {rev: parents} that is used in
+                       narrowchangegroup to produce ellipsis nodes with the
+                       correct parents.
+    """
+    cl = repo.changelog
+    mfl = repo.manifestlog
+
+    clrev = cl.rev
+
+    commonrevs = {clrev(n) for n in common} | {nullrev}
+    headsrevs = {clrev(n) for n in heads}
+
+    if depth:
+        revdepth = {h: 0 for h in headsrevs}
+
+    ellipsisheads = collections.defaultdict(set)
+    ellipsisroots = collections.defaultdict(set)
+
+    def addroot(head, curchange):
+        """Add a root to an ellipsis head, splitting heads with 3 roots."""
+        ellipsisroots[head].add(curchange)
+        # Recursively split ellipsis heads with 3 roots by finding the
+        # roots' youngest common descendant which is an elided merge commit.
+        # That descendant takes 2 of the 3 roots as its own, and becomes a
+        # root of the head.
+        while len(ellipsisroots[head]) > 2:
+            child, roots = splithead(head)
+            splitroots(head, child, roots)
+            head = child  # Recurse in case we just added a 3rd root
+
+    def splitroots(head, child, roots):
+        ellipsisroots[head].difference_update(roots)
+        ellipsisroots[head].add(child)
+        ellipsisroots[child].update(roots)
+        ellipsisroots[child].discard(child)
+
+    def splithead(head):
+        r1, r2, r3 = sorted(ellipsisroots[head])
+        for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):
+            mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',
+                            nr1, head, nr2, head)
+            for j in mid:
+                if j == nr2:
+                    return nr2, (nr1, nr2)
+                if j not in ellipsisroots or len(ellipsisroots[j]) < 2:
+                    return j, (nr1, nr2)
+        raise error.Abort(_('Failed to split up ellipsis node! head: %d, '
+                            'roots: %d %d %d') % (head, r1, r2, r3))
+
+    missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))
+    visit = reversed(missing)
+    relevant_nodes = set()
+    visitnodes = [cl.node(m) for m in missing]
+    required = set(headsrevs) | known
+    for rev in visit:
+        clrev = cl.changelogrevision(rev)
+        ps = [prev for prev in cl.parentrevs(rev) if prev != nullrev]
+        if depth is not None:
+            curdepth = revdepth[rev]
+            for p in ps:
+                revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))
+        needed = False
+        shallow_enough = depth is None or revdepth[rev] <= depth
+        if shallow_enough:
+            curmf = mfl[clrev.manifest].read()
+            if ps:
+                # We choose to not trust the changed files list in
+                # changesets because it's not always correct. TODO: could
+                # we trust it for the non-merge case?
+                p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()
+                needed = bool(curmf.diff(p1mf, match))
+                if not needed and len(ps) > 1:
+                    # For merge changes, the list of changed files is not
+                    # helpful, since we need to emit the merge if a file
+                    # in the narrow spec has changed on either side of the
+                    # merge. As a result, we do a manifest diff to check.
+                    p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()
+                    needed = bool(curmf.diff(p2mf, match))
+            else:
+                # For a root node, we need to include the node if any
+                # files in the node match the narrowspec.
+                needed = any(curmf.walk(match))
+
+        if needed:
+            for head in ellipsisheads[rev]:
+                addroot(head, rev)
+            for p in ps:
+                required.add(p)
+            relevant_nodes.add(cl.node(rev))
+        else:
+            if not ps:
+                ps = [nullrev]
+            if rev in required:
+                for head in ellipsisheads[rev]:
+                    addroot(head, rev)
+                for p in ps:
+                    ellipsisheads[p].add(rev)
+            else:
+                for p in ps:
+                    ellipsisheads[p] |= ellipsisheads[rev]
+
+    # add common changesets as roots of their reachable ellipsis heads
+    for c in commonrevs:
+        for head in ellipsisheads[c]:
+            addroot(head, c)
+    return visitnodes, relevant_nodes, ellipsisroots
+
 def caps20to10(repo, role):
     """return a set with appropriate options to use bundle20 during getbundle"""
     caps = {'HG20'}
@@ -1924,30 +2125,52 @@
 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
                               b2caps=None, heads=None, common=None, **kwargs):
     """add a changegroup part to the requested bundle"""
-    cgstream = None
-    if kwargs.get(r'cg', True):
-        # build changegroup bundle here.
-        version = '01'
-        cgversions = b2caps.get('changegroup')
-        if cgversions:  # 3.1 and 3.2 ship with an empty value
-            cgversions = [v for v in cgversions
-                          if v in changegroup.supportedoutgoingversions(repo)]
-            if not cgversions:
-                raise ValueError(_('no common changegroup version'))
-            version = max(cgversions)
-        outgoing = _computeoutgoing(repo, heads, common)
-        if outgoing.missing:
-            cgstream = changegroup.makestream(repo, outgoing, version, source,
-                                              bundlecaps=bundlecaps)
+    if not kwargs.get(r'cg', True):
+        return
+
+    version = '01'
+    cgversions = b2caps.get('changegroup')
+    if cgversions:  # 3.1 and 3.2 ship with an empty value
+        cgversions = [v for v in cgversions
+                      if v in changegroup.supportedoutgoingversions(repo)]
+        if not cgversions:
+            raise ValueError(_('no common changegroup version'))
+        version = max(cgversions)
+
+    outgoing = _computeoutgoing(repo, heads, common)
+    if not outgoing.missing:
+        return
 
-    if cgstream:
-        part = bundler.newpart('changegroup', data=cgstream)
-        if cgversions:
-            part.addparam('version', version)
-        part.addparam('nbchanges', '%d' % len(outgoing.missing),
-                      mandatory=False)
-        if 'treemanifest' in repo.requirements:
-            part.addparam('treemanifest', '1')
+    if kwargs.get(r'narrow', False):
+        include = sorted(filter(bool, kwargs.get(r'includepats', [])))
+        exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))
+        filematcher = narrowspec.match(repo.root, include=include,
+                                       exclude=exclude)
+    else:
+        filematcher = None
+
+    cgstream = changegroup.makestream(repo, outgoing, version, source,
+                                      bundlecaps=bundlecaps,
+                                      filematcher=filematcher)
+
+    part = bundler.newpart('changegroup', data=cgstream)
+    if cgversions:
+        part.addparam('version', version)
+
+    part.addparam('nbchanges', '%d' % len(outgoing.missing),
+                  mandatory=False)
+
+    if 'treemanifest' in repo.requirements:
+        part.addparam('treemanifest', '1')
+
+    if kwargs.get(r'narrow', False) and (include or exclude):
+        narrowspecpart = bundler.newpart('narrow:spec')
+        if include:
+            narrowspecpart.addparam(
+                'include', '\n'.join(include), mandatory=True)
+        if exclude:
+            narrowspecpart.addparam(
+                'exclude', '\n'.join(exclude), mandatory=True)
 
 @getbundle2partsgenerator('bookmarks')
 def _getbundlebookmarkpart(bundler, repo, source, bundlecaps=None,
@@ -2069,8 +2292,13 @@
     # Don't send unless:
     # - changeset are being exchanged,
     # - the client supports it.
-    if not (kwargs.get(r'cg', True)) or 'rev-branch-cache' not in b2caps:
+    # - narrow bundle isn't in play (not currently compatible).
+    if (not kwargs.get(r'cg', True)
+        or 'rev-branch-cache' not in b2caps
+        or kwargs.get(r'narrow', False)
+        or repo.ui.has_section(_NARROWACL_SECTION)):
         return
+
     outgoing = _computeoutgoing(repo, heads, common)
     bundle2.addpartrevbranchcache(repo, bundler, outgoing)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/exchangev2.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,411 @@
+# exchangev2.py - repository exchange for wire protocol version 2
+#
+# Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import collections
+import weakref
+
+from .i18n import _
+from .node import (
+    nullid,
+    short,
+)
+from . import (
+    bookmarks,
+    error,
+    mdiff,
+    phases,
+    pycompat,
+    setdiscovery,
+)
+
+def pull(pullop):
+    """Pull using wire protocol version 2."""
+    repo = pullop.repo
+    remote = pullop.remote
+    tr = pullop.trmanager.transaction()
+
+    # Figure out what needs to be fetched.
+    common, fetch, remoteheads = _pullchangesetdiscovery(
+        repo, remote, pullop.heads, abortwhenunrelated=pullop.force)
+
+    # And fetch the data.
+    pullheads = pullop.heads or remoteheads
+    csetres = _fetchchangesets(repo, tr, remote, common, fetch, pullheads)
+
+    # New revisions are written to the changelog. But all other updates
+    # are deferred. Do those now.
+
+    # Ensure all new changesets are draft by default. If the repo is
+    # publishing, the phase will be adjusted by the loop below.
+    if csetres['added']:
+        phases.registernew(repo, tr, phases.draft, csetres['added'])
+
+    # And adjust the phase of all changesets accordingly.
+    for phase in phases.phasenames:
+        if phase == b'secret' or not csetres['nodesbyphase'][phase]:
+            continue
+
+        phases.advanceboundary(repo, tr, phases.phasenames.index(phase),
+                               csetres['nodesbyphase'][phase])
+
+    # Write bookmark updates.
+    bookmarks.updatefromremote(repo.ui, repo, csetres['bookmarks'],
+                               remote.url(), pullop.gettransaction,
+                               explicit=pullop.explicitbookmarks)
+
+    manres = _fetchmanifests(repo, tr, remote, csetres['manifestnodes'])
+
+    # Find all file nodes referenced by added manifests and fetch those
+    # revisions.
+    fnodes = _derivefilesfrommanifests(repo, manres['added'])
+    _fetchfiles(repo, tr, remote, fnodes, manres['linkrevs'])
+
+def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True):
+    """Determine which changesets need to be pulled."""
+
+    if heads:
+        knownnode = repo.changelog.hasnode
+        if all(knownnode(head) for head in heads):
+            return heads, False, heads
+
+    # TODO wire protocol version 2 is capable of more efficient discovery
+    # than setdiscovery. Consider implementing something better.
+    common, fetch, remoteheads = setdiscovery.findcommonheads(
+        repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated)
+
+    common = set(common)
+    remoteheads = set(remoteheads)
+
+    # If a remote head is filtered locally, put it back in the common set.
+    # See the comment in exchange._pulldiscoverychangegroup() for more.
+
+    if fetch and remoteheads:
+        nodemap = repo.unfiltered().changelog.nodemap
+
+        common |= {head for head in remoteheads if head in nodemap}
+
+        if set(remoteheads).issubset(common):
+            fetch = []
+
+    common.discard(nullid)
+
+    return common, fetch, remoteheads
+
+def _fetchchangesets(repo, tr, remote, common, fetch, remoteheads):
+    # TODO consider adding a step here where we obtain the DAG shape first
+    # (or ask the server to slice changesets into chunks for us) so that
+    # we can perform multiple fetches in batches. This will facilitate
+    # resuming interrupted clones, higher server-side cache hit rates due
+    # to smaller segments, etc.
+    with remote.commandexecutor() as e:
+        objs = e.callcommand(b'changesetdata', {
+            b'noderange': [sorted(common), sorted(remoteheads)],
+            b'fields': {b'bookmarks', b'parents', b'phase', b'revision'},
+        }).result()
+
+        # The context manager waits on all response data when exiting. So
+        # we need to remain in the context manager in order to stream data.
+        return _processchangesetdata(repo, tr, objs)
+
+def _processchangesetdata(repo, tr, objs):
+    repo.hook('prechangegroup', throw=True,
+              **pycompat.strkwargs(tr.hookargs))
+
+    urepo = repo.unfiltered()
+    cl = urepo.changelog
+
+    cl.delayupdate(tr)
+
+    # The first emitted object is a header describing the data that
+    # follows.
+    meta = next(objs)
+
+    progress = repo.ui.makeprogress(_('changesets'),
+                                    unit=_('chunks'),
+                                    total=meta.get(b'totalitems'))
+
+    manifestnodes = {}
+
+    def linkrev(node):
+        repo.ui.debug('add changeset %s\n' % short(node))
+        # Linkrev for changelog is always self.
+        return len(cl)
+
+    def onchangeset(cl, node):
+        progress.increment()
+
+        revision = cl.changelogrevision(node)
+
+        # We need to preserve the mapping of changelog revision to node
+        # so we can set the linkrev accordingly when manifests are added.
+        manifestnodes[cl.rev(node)] = revision.manifest
+
+    nodesbyphase = {phase: set() for phase in phases.phasenames}
+    remotebookmarks = {}
+
+    # addgroup() expects a 7-tuple describing revisions. This normalizes
+    # the wire data to that format.
+    #
+    # This loop also aggregates non-revision metadata, such as phase
+    # data.
+    def iterrevisions():
+        for cset in objs:
+            node = cset[b'node']
+
+            if b'phase' in cset:
+                nodesbyphase[cset[b'phase']].add(node)
+
+            for mark in cset.get(b'bookmarks', []):
+                remotebookmarks[mark] = node
+
+            # TODO add mechanism for extensions to examine records so they
+            # can siphon off custom data fields.
+
+            extrafields = {}
+
+            for field, size in cset.get(b'fieldsfollowing', []):
+                extrafields[field] = next(objs)
+
+            # Some entries might only be metadata only updates.
+            if b'revision' not in extrafields:
+                continue
+
+            data = extrafields[b'revision']
+
+            yield (
+                node,
+                cset[b'parents'][0],
+                cset[b'parents'][1],
+                # Linknode is always itself for changesets.
+                cset[b'node'],
+                # We always send full revisions. So delta base is not set.
+                nullid,
+                mdiff.trivialdiffheader(len(data)) + data,
+                # Flags not yet supported.
+                0,
+            )
+
+    added = cl.addgroup(iterrevisions(), linkrev, weakref.proxy(tr),
+                        addrevisioncb=onchangeset)
+
+    progress.complete()
+
+    return {
+        'added': added,
+        'nodesbyphase': nodesbyphase,
+        'bookmarks': remotebookmarks,
+        'manifestnodes': manifestnodes,
+    }
+
+def _fetchmanifests(repo, tr, remote, manifestnodes):
+    rootmanifest = repo.manifestlog.getstorage(b'')
+
+    # Some manifests can be shared between changesets. Filter out revisions
+    # we already know about.
+    fetchnodes = []
+    linkrevs = {}
+    seen = set()
+
+    for clrev, node in sorted(manifestnodes.iteritems()):
+        if node in seen:
+            continue
+
+        try:
+            rootmanifest.rev(node)
+        except error.LookupError:
+            fetchnodes.append(node)
+            linkrevs[node] = clrev
+
+        seen.add(node)
+
+    # TODO handle tree manifests
+
+    # addgroup() expects 7-tuple describing revisions. This normalizes
+    # the wire data to that format.
+    def iterrevisions(objs, progress):
+        for manifest in objs:
+            node = manifest[b'node']
+
+            extrafields = {}
+
+            for field, size in manifest.get(b'fieldsfollowing', []):
+                extrafields[field] = next(objs)
+
+            if b'delta' in extrafields:
+                basenode = manifest[b'deltabasenode']
+                delta = extrafields[b'delta']
+            elif b'revision' in extrafields:
+                basenode = nullid
+                revision = extrafields[b'revision']
+                delta = mdiff.trivialdiffheader(len(revision)) + revision
+            else:
+                continue
+
+            yield (
+                node,
+                manifest[b'parents'][0],
+                manifest[b'parents'][1],
+                # The value passed in is passed to the lookup function passed
+                # to addgroup(). We already have a map of manifest node to
+                # changelog revision number. So we just pass in the
+                # manifest node here and use linkrevs.__getitem__ as the
+                # resolution function.
+                node,
+                basenode,
+                delta,
+                # Flags not yet supported.
+                0
+            )
+
+            progress.increment()
+
+    progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
+                                    total=len(fetchnodes))
+
+    # Fetch manifests 10,000 per command.
+    # TODO have server advertise preferences?
+    # TODO make size configurable on client?
+    batchsize = 10000
+
+    # We send commands 1 at a time to the remote. This is not the most
+    # efficient because we incur a round trip at the end of each batch.
+    # However, the existing frame-based reactor keeps consuming server
+    # data in the background. And this results in response data buffering
+    # in memory. This can consume gigabytes of memory.
+    # TODO send multiple commands in a request once background buffering
+    # issues are resolved.
+
+    added = []
+
+    for i in pycompat.xrange(0, len(fetchnodes), batchsize):
+        batch = [node for node in fetchnodes[i:i + batchsize]]
+        if not batch:
+            continue
+
+        with remote.commandexecutor() as e:
+            objs = e.callcommand(b'manifestdata', {
+                b'tree': b'',
+                b'nodes': batch,
+                b'fields': {b'parents', b'revision'},
+                b'haveparents': True,
+            }).result()
+
+            # Chomp off header object.
+            next(objs)
+
+            added.extend(rootmanifest.addgroup(
+                iterrevisions(objs, progress),
+                linkrevs.__getitem__,
+                weakref.proxy(tr)))
+
+    progress.complete()
+
+    return {
+        'added': added,
+        'linkrevs': linkrevs,
+    }
+
+def _derivefilesfrommanifests(repo, manifestnodes):
+    """Determine what file nodes are relevant given a set of manifest nodes.
+
+    Returns a dict mapping file paths to dicts of file node to first manifest
+    node.
+    """
+    ml = repo.manifestlog
+    fnodes = collections.defaultdict(dict)
+
+    for manifestnode in manifestnodes:
+        m = ml.get(b'', manifestnode)
+
+        # TODO this will pull in unwanted nodes because it takes the storage
+        # delta into consideration. What we really want is something that takes
+        # the delta between the manifest's parents. And ideally we would
+        # ignore file nodes that are known locally. For now, ignore both
+        # these limitations. This will result in incremental fetches requesting
+        # data we already have. So this is far from ideal.
+        md = m.readfast()
+
+        for path, fnode in md.items():
+            fnodes[path].setdefault(fnode, manifestnode)
+
+    return fnodes
+
+def _fetchfiles(repo, tr, remote, fnodes, linkrevs):
+    def iterrevisions(objs, progress):
+        for filerevision in objs:
+            node = filerevision[b'node']
+
+            extrafields = {}
+
+            for field, size in filerevision.get(b'fieldsfollowing', []):
+                extrafields[field] = next(objs)
+
+            if b'delta' in extrafields:
+                basenode = filerevision[b'deltabasenode']
+                delta = extrafields[b'delta']
+            elif b'revision' in extrafields:
+                basenode = nullid
+                revision = extrafields[b'revision']
+                delta = mdiff.trivialdiffheader(len(revision)) + revision
+            else:
+                continue
+
+            yield (
+                node,
+                filerevision[b'parents'][0],
+                filerevision[b'parents'][1],
+                node,
+                basenode,
+                delta,
+                # Flags not yet supported.
+                0,
+            )
+
+            progress.increment()
+
+    progress = repo.ui.makeprogress(
+        _('files'), unit=_('chunks'),
+         total=sum(len(v) for v in fnodes.itervalues()))
+
+    # TODO make batch size configurable
+    batchsize = 10000
+    fnodeslist = [x for x in sorted(fnodes.items())]
+
+    for i in pycompat.xrange(0, len(fnodeslist), batchsize):
+        batch = [x for x in fnodeslist[i:i + batchsize]]
+        if not batch:
+            continue
+
+        with remote.commandexecutor() as e:
+            fs = []
+            locallinkrevs = {}
+
+            for path, nodes in batch:
+                fs.append((path, e.callcommand(b'filedata', {
+                    b'path': path,
+                    b'nodes': sorted(nodes),
+                    b'fields': {b'parents', b'revision'},
+                    b'haveparents': True,
+                })))
+
+                locallinkrevs[path] = {
+                    node: linkrevs[manifestnode]
+                    for node, manifestnode in nodes.iteritems()}
+
+            for path, f in fs:
+                objs = f.result()
+
+                # Chomp off header objects.
+                next(objs)
+
+                store = repo.file(path)
+                store.addgroup(
+                    iterrevisions(objs, progress),
+                    locallinkrevs[path].__getitem__,
+                    weakref.proxy(tr))
--- a/mercurial/extensions.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/extensions.py	Wed Sep 26 20:33:09 2018 +0900
@@ -124,7 +124,7 @@
     # note: this ui.debug happens before --debug is processed,
     #       Use --config ui.debug=1 to see them.
     if ui.configbool('devel', 'debug.extensions'):
-        ui.debug('could not import %s (%s): trying %s\n'
+        ui.debug('debug.extensions:     - could not import %s (%s): trying %s\n'
                  % (failed, stringutil.forcebytestr(err), next))
         if ui.debugflag:
             ui.traceback()
@@ -166,7 +166,7 @@
             _rejectunicode(t, o._table)
     _validatecmdtable(ui, getattr(mod, 'cmdtable', {}))
 
-def load(ui, name, path):
+def load(ui, name, path, log=lambda *a: None, loadingtime=None):
     if name.startswith('hgext.') or name.startswith('hgext/'):
         shortname = name[6:]
     else:
@@ -175,8 +175,13 @@
         return None
     if shortname in _extensions:
         return _extensions[shortname]
+    log('  - loading extension: %r\n', shortname)
     _extensions[shortname] = None
-    mod = _importext(name, path, bind(_reportimporterror, ui))
+    with util.timedcm('load extension %r', shortname) as stats:
+        mod = _importext(name, path, bind(_reportimporterror, ui))
+    log('  > %r extension loaded in %s\n', shortname, stats)
+    if loadingtime is not None:
+        loadingtime[shortname] += stats.elapsed
 
     # Before we do anything with the extension, check against minimum stated
     # compatibility. This gives extension authors a mechanism to have their
@@ -187,12 +192,16 @@
         ui.warn(_('(third party extension %s requires version %s or newer '
                   'of Mercurial; disabling)\n') % (shortname, minver))
         return
+    log('    - validating extension tables: %r\n', shortname)
     _validatetables(ui, mod)
 
     _extensions[shortname] = mod
     _order.append(shortname)
-    for fn in _aftercallbacks.get(shortname, []):
-        fn(loaded=True)
+    log('    - invoking registered callbacks: %r\n', shortname)
+    with util.timedcm('callbacks extension %r', shortname) as stats:
+        for fn in _aftercallbacks.get(shortname, []):
+            fn(loaded=True)
+    log('    > callbacks completed in %s\n', stats)
     return mod
 
 def _runuisetup(name, ui):
@@ -225,28 +234,42 @@
     return True
 
 def loadall(ui, whitelist=None):
+    if ui.configbool('devel', 'debug.extensions'):
+        log = lambda msg, *values: ui.debug('debug.extensions: ',
+            msg % values, label='debug.extensions')
+    else:
+        log = lambda *a, **kw: None
+    loadingtime = collections.defaultdict(int)
     result = ui.configitems("extensions")
     if whitelist is not None:
         result = [(k, v) for (k, v) in result if k in whitelist]
     newindex = len(_order)
-    for (name, path) in result:
-        if path:
-            if path[0:1] == '!':
-                _disabledextensions[name] = path[1:]
-                continue
-        try:
-            load(ui, name, path)
-        except Exception as inst:
-            msg = stringutil.forcebytestr(inst)
+    log('loading %sextensions\n', 'additional ' if newindex else '')
+    log('- processing %d entries\n', len(result))
+    with util.timedcm('load all extensions') as stats:
+        for (name, path) in result:
             if path:
-                ui.warn(_("*** failed to import extension %s from %s: %s\n")
-                        % (name, path, msg))
-            else:
-                ui.warn(_("*** failed to import extension %s: %s\n")
-                        % (name, msg))
-            if isinstance(inst, error.Hint) and inst.hint:
-                ui.warn(_("*** (%s)\n") % inst.hint)
-            ui.traceback()
+                if path[0:1] == '!':
+                    if name not in _disabledextensions:
+                        log('  - skipping disabled extension: %r\n', name)
+                    _disabledextensions[name] = path[1:]
+                    continue
+            try:
+                load(ui, name, path, log, loadingtime)
+            except Exception as inst:
+                msg = stringutil.forcebytestr(inst)
+                if path:
+                    ui.warn(_("*** failed to import extension %s from %s: %s\n")
+                            % (name, path, msg))
+                else:
+                    ui.warn(_("*** failed to import extension %s: %s\n")
+                            % (name, msg))
+                if isinstance(inst, error.Hint) and inst.hint:
+                    ui.warn(_("*** (%s)\n") % inst.hint)
+                ui.traceback()
+
+    log('> loaded %d extensions, total time %s\n',
+        len(_order) - newindex, stats)
     # list of (objname, loadermod, loadername) tuple:
     # - objname is the name of an object in extension module,
     #   from which extra information is loaded
@@ -258,29 +281,53 @@
     earlyextraloaders = [
         ('configtable', configitems, 'loadconfigtable'),
     ]
+
+    log('- loading configtable attributes\n')
     _loadextra(ui, newindex, earlyextraloaders)
 
     broken = set()
-    for name in _order[newindex:]:
-        if not _runuisetup(name, ui):
-            broken.add(name)
+    log('- executing uisetup hooks\n')
+    with util.timedcm('all uisetup') as alluisetupstats:
+        for name in _order[newindex:]:
+            log('  - running uisetup for %r\n', name)
+            with util.timedcm('uisetup %r', name) as stats:
+                if not _runuisetup(name, ui):
+                    log('    - the %r extension uisetup failed\n', name)
+                    broken.add(name)
+            log('  > uisetup for %r took %s\n', name, stats)
+            loadingtime[name] += stats.elapsed
+    log('> all uisetup took %s\n', alluisetupstats)
 
-    for name in _order[newindex:]:
-        if name in broken:
-            continue
-        if not _runextsetup(name, ui):
-            broken.add(name)
+    log('- executing extsetup hooks\n')
+    with util.timedcm('all extsetup') as allextetupstats:
+        for name in _order[newindex:]:
+            if name in broken:
+                continue
+            log('  - running extsetup for %r\n', name)
+            with util.timedcm('extsetup %r', name) as stats:
+                if not _runextsetup(name, ui):
+                    log('    - the %r extension extsetup failed\n', name)
+                    broken.add(name)
+            log('  > extsetup for %r took %s\n', name, stats)
+            loadingtime[name] += stats.elapsed
+    log('> all extsetup took %s\n', allextetupstats)
 
     for name in broken:
+        log('    - disabling broken %r extension\n', name)
         _extensions[name] = None
 
     # Call aftercallbacks that were never met.
-    for shortname in _aftercallbacks:
-        if shortname in _extensions:
-            continue
+    log('- executing remaining aftercallbacks\n')
+    with util.timedcm('aftercallbacks') as stats:
+        for shortname in _aftercallbacks:
+            if shortname in _extensions:
+                continue
 
-        for fn in _aftercallbacks[shortname]:
-            fn(loaded=False)
+            for fn in _aftercallbacks[shortname]:
+                log('  - extension %r not loaded, notify callbacks\n',
+                    shortname)
+                fn(loaded=False)
+    log('> remaining aftercallbacks completed in %s\n', stats)
 
     # loadall() is called multiple times and lingering _aftercallbacks
     # entries could result in double execution. See issue4646.
@@ -304,6 +351,7 @@
     # - loadermod is the module where loader is placed
     # - loadername is the name of the function,
     #   which takes (ui, extensionname, extraobj) arguments
+    log('- loading extension registration objects\n')
     extraloaders = [
         ('cmdtable', commands, 'loadcmdtable'),
         ('colortable', color, 'loadcolortable'),
@@ -314,7 +362,16 @@
         ('templatefunc', templatefuncs, 'loadfunction'),
         ('templatekeyword', templatekw, 'loadkeyword'),
     ]
-    _loadextra(ui, newindex, extraloaders)
+    with util.timedcm('load registration objects') as stats:
+        _loadextra(ui, newindex, extraloaders)
+    log('> extension registration object loading took %s\n', stats)
+
+    # Report per extension loading time (except reposetup)
+    for name in sorted(loadingtime):
+        extension_msg = '> extension %s take a total of %s to load\n'
+        log(extension_msg, name, util.timecount(loadingtime[name]))
+
+    log('extension loading complete\n')
 
 def _loadextra(ui, newindex, extraloaders):
     for name in _order[newindex:]:
--- a/mercurial/filelog.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/filelog.py	Wed Sep 26 20:33:09 2018 +0900
@@ -22,11 +22,14 @@
         self._revlog = revlog.revlog(opener,
                                      '/'.join(('data', path + '.i')),
                                      censorable=True)
-        # full name of the user visible file, relative to the repository root
+        # Full name of the user visible file, relative to the repository root.
+        # Used by LFS.
         self.filename = path
+        # Used by repo upgrade.
         self.index = self._revlog.index
+        # Used by verify.
         self.version = self._revlog.version
-        self.storedeltachains = self._revlog.storedeltachains
+        # Used by changegroup generation.
         self._generaldelta = self._revlog._generaldelta
 
     def __len__(self):
@@ -56,36 +59,35 @@
     def linkrev(self, rev):
         return self._revlog.linkrev(rev)
 
+    # Used by LFS, verify.
     def flags(self, rev):
         return self._revlog.flags(rev)
 
     def commonancestorsheads(self, node1, node2):
         return self._revlog.commonancestorsheads(node1, node2)
 
+    # Used by dagop.blockdescendants().
     def descendants(self, revs):
         return self._revlog.descendants(revs)
 
-    def headrevs(self):
-        return self._revlog.headrevs()
-
     def heads(self, start=None, stop=None):
         return self._revlog.heads(start, stop)
 
+    # Used by hgweb, children extension.
     def children(self, node):
         return self._revlog.children(node)
 
     def deltaparent(self, rev):
         return self._revlog.deltaparent(rev)
 
-    def candelta(self, baserev, rev):
-        return self._revlog.candelta(baserev, rev)
-
     def iscensored(self, rev):
         return self._revlog.iscensored(rev)
 
+    # Used by verify.
     def rawsize(self, rev):
         return self._revlog.rawsize(rev)
 
+    # Might be unused.
     def checkhash(self, text, node, p1=None, p2=None, rev=None):
         return self._revlog.checkhash(text, node, p1=p1, p2=p2, rev=rev)
 
@@ -95,6 +97,9 @@
     def revdiff(self, rev1, rev2):
         return self._revlog.revdiff(rev1, rev2)
 
+    def emitrevisiondeltas(self, requests):
+        return self._revlog.emitrevisiondeltas(requests)
+
     def addrevision(self, revisiondata, transaction, linkrev, p1, p2,
                     node=None, flags=revlog.REVIDX_DEFAULT_FLAGS,
                     cachedelta=None):
@@ -112,9 +117,13 @@
     def strip(self, minlink, transaction):
         return self._revlog.strip(minlink, transaction)
 
+    def censorrevision(self, tr, node, tombstone=b''):
+        return self._revlog.censorrevision(node, tombstone=tombstone)
+
     def files(self):
         return self._revlog.files()
 
+    # Used by verify.
     def checksize(self):
         return self._revlog.checksize()
 
@@ -180,6 +189,10 @@
 
         return True
 
+    # TODO these aren't part of the interface and aren't internal methods.
+    # Callers should be fixed to not use them.
+
+    # Used by LFS.
     @property
     def filename(self):
         return self._revlog.filename
@@ -188,8 +201,7 @@
     def filename(self, value):
         self._revlog.filename = value
 
-    # TODO these aren't part of the interface and aren't internal methods.
-    # Callers should be fixed to not use them.
+    # Used by bundlefilelog, unionfilelog.
     @property
     def indexfile(self):
         return self._revlog.indexfile
@@ -198,72 +210,65 @@
     def indexfile(self, value):
         self._revlog.indexfile = value
 
-    @property
-    def datafile(self):
-        return self._revlog.datafile
-
+    # Used by LFS, repo upgrade.
     @property
     def opener(self):
         return self._revlog.opener
 
-    @property
-    def _lazydeltabase(self):
-        return self._revlog._lazydeltabase
-
-    @_lazydeltabase.setter
-    def _lazydeltabase(self, value):
-        self._revlog._lazydeltabase = value
-
-    @property
-    def _deltabothparents(self):
-        return self._revlog._deltabothparents
-
-    @_deltabothparents.setter
-    def _deltabothparents(self, value):
-        self._revlog._deltabothparents = value
-
-    @property
-    def _inline(self):
-        return self._revlog._inline
-
-    @property
-    def _withsparseread(self):
-        return getattr(self._revlog, '_withsparseread', False)
-
-    @property
-    def _srmingapsize(self):
-        return self._revlog._srmingapsize
-
-    @property
-    def _srdensitythreshold(self):
-        return self._revlog._srdensitythreshold
-
-    def _deltachain(self, rev, stoprev=None):
-        return self._revlog._deltachain(rev, stoprev)
-
-    def chainbase(self, rev):
-        return self._revlog.chainbase(rev)
-
-    def chainlen(self, rev):
-        return self._revlog.chainlen(rev)
-
+    # Used by repo upgrade.
     def clone(self, tr, destrevlog, **kwargs):
         if not isinstance(destrevlog, filelog):
             raise error.ProgrammingError('expected filelog to clone()')
 
         return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
 
-    def start(self, rev):
-        return self._revlog.start(rev)
+class narrowfilelog(filelog):
+    """Filelog variation to be used with narrow stores."""
 
-    def end(self, rev):
-        return self._revlog.end(rev)
+    def __init__(self, opener, path, narrowmatch):
+        super(narrowfilelog, self).__init__(opener, path)
+        self._narrowmatch = narrowmatch
+
+    def renamed(self, node):
+        res = super(narrowfilelog, self).renamed(node)
 
-    def length(self, rev):
-        return self._revlog.length(rev)
+        # Renames that come from outside the narrowspec are problematic
+        # because we may lack the base text for the rename. This can result
+        # in code attempting to walk the ancestry or compute a diff
+        # encountering a missing revision. We address this by silently
+        # removing rename metadata if the source file is outside the
+        # narrow spec.
+        #
+        # A better solution would be to see if the base revision is available,
+        # rather than assuming it isn't.
+        #
+        # An even better solution would be to teach all consumers of rename
+        # metadata that the base revision may not be available.
+        #
+        # TODO consider better ways of doing this.
+        if res and not self._narrowmatch(res[0]):
+            return None
+
+        return res
 
-    def compress(self, data):
-        return self._revlog.compress(data)
+    def size(self, rev):
+        # Because we have a custom renamed() that may lie, we need to call
+        # the base renamed() to report accurate results.
+        node = self.node(rev)
+        if super(narrowfilelog, self).renamed(node):
+            return len(self.read(node))
+        else:
+            return super(narrowfilelog, self).size(rev)
 
-    def _addrevision(self, *args, **kwargs):
-        return self._revlog._addrevision(*args, **kwargs)
+    def cmp(self, node, text):
+        different = super(narrowfilelog, self).cmp(node, text)
+
+        # Because renamed() may lie, we may get false positives for
+        # different content. Check for this by comparing against the original
+        # renamed() implementation.
+        if different:
+            if super(narrowfilelog, self).renamed(node):
+                t2 = self.read(node)
+                return t2 != text
+
+        return different
--- a/mercurial/filemerge.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/filemerge.py	Wed Sep 26 20:33:09 2018 +0900
@@ -56,12 +56,14 @@
 fullmerge = internaltool.fullmerge # both premerge and merge
 
 _localchangedotherdeletedmsg = _(
-    "local%(l)s changed %(fd)s which other%(o)s deleted\n"
+    "file '%(fd)s' was deleted in other%(o)s but was modified in local%(l)s.\n"
+    "What do you want to do?\n"
     "use (c)hanged version, (d)elete, or leave (u)nresolved?"
     "$$ &Changed $$ &Delete $$ &Unresolved")
 
 _otherchangedlocaldeletedmsg = _(
-    "other%(o)s changed %(fd)s which local%(l)s deleted\n"
+    "file '%(fd)s' was deleted in local%(l)s but was modified in other%(o)s.\n"
+    "What do you want to do?\n"
     "use (c)hanged version, leave (d)eleted, or "
     "leave (u)nresolved?"
     "$$ &Changed $$ &Deleted $$ &Unresolved")
@@ -137,6 +139,13 @@
     return procutil.findexe(util.expandpath(exe))
 
 def _picktool(repo, ui, path, binary, symlink, changedelete):
+    strictcheck = ui.configbool('merge', 'strict-capability-check')
+
+    def hascapability(tool, capability, strict=False):
+        if tool in internals:
+            return strict and internals[tool].capabilities.get(capability)
+        return _toolbool(ui, tool, capability)
+
     def supportscd(tool):
         return tool in internals and internals[tool].mergetype == nomerge
 
@@ -149,9 +158,9 @@
                 ui.warn(_("couldn't find merge tool %s\n") % tmsg)
             else: # configured but non-existing tools are more silent
                 ui.note(_("couldn't find merge tool %s\n") % tmsg)
-        elif symlink and not _toolbool(ui, tool, "symlink"):
+        elif symlink and not hascapability(tool, "symlink", strictcheck):
             ui.warn(_("tool %s can't handle symlinks\n") % tmsg)
-        elif binary and not _toolbool(ui, tool, "binary"):
+        elif binary and not hascapability(tool, "binary", strictcheck):
             ui.warn(_("tool %s can't handle binary\n") % tmsg)
         elif changedelete and not supportscd(tool):
             # the nomerge tools are the only tools that support change/delete
@@ -186,9 +195,19 @@
             return (hgmerge, hgmerge)
 
     # then patterns
+
+    # whether binary capability should be checked strictly
+    binarycap = binary and strictcheck
+
     for pat, tool in ui.configitems("merge-patterns"):
         mf = match.match(repo.root, '', [pat])
-        if mf(path) and check(tool, pat, symlink, False, changedelete):
+        if mf(path) and check(tool, pat, symlink, binarycap, changedelete):
+            if binary and not hascapability(tool, "binary", strict=True):
+                ui.warn(_("warning: check merge-patterns configurations,"
+                          " if %r for binary file %r is unintentional\n"
+                          "(see 'hg help merge-tools'"
+                          " for binary files capability)\n")
+                        % (pycompat.bytestr(tool), pycompat.bytestr(path)))
             toolpath = _findtool(ui, tool)
             return (tool, _quotetoolpath(toolpath))
 
@@ -208,9 +227,10 @@
     if uimerge:
         # external tools defined in uimerge won't be able to handle
         # change/delete conflicts
-        if uimerge not in names and not changedelete:
-            return (uimerge, uimerge)
-        tools.insert(0, (None, uimerge)) # highest priority
+        if check(uimerge, path, symlink, binary, changedelete):
+            if uimerge not in names and not changedelete:
+                return (uimerge, uimerge)
+            tools.insert(0, (None, uimerge)) # highest priority
     tools.append((None, "hgmerge")) # the old default, if found
     for p, t in tools:
         if check(t, None, symlink, binary, changedelete):
@@ -469,7 +489,7 @@
     success, status = tagmerge.merge(repo, fcd, fco, fca)
     return success, status, False
 
-@internaltool('dump', fullmerge)
+@internaltool('dump', fullmerge, binary=True, symlink=True)
 def _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
     """
     Creates three versions of the files to merge, containing the
@@ -495,7 +515,7 @@
     repo.wwrite(fd + ".base", fca.data(), fca.flags())
     return False, 1, False
 
-@internaltool('forcedump', mergeonly)
+@internaltool('forcedump', mergeonly, binary=True, symlink=True)
 def _forcedump(repo, mynode, orig, fcd, fco, fca, toolconf, files,
                 labels=None):
     """
@@ -916,14 +936,17 @@
         _haltmerge()
     # default action is 'continue', in which case we neither prompt nor halt
 
+def hasconflictmarkers(data):
+    return bool(re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", data,
+                          re.MULTILINE))
+
 def _check(repo, r, ui, tool, fcd, files):
     fd = fcd.path()
     unused, unused, unused, back = files
 
     if not r and (_toolbool(ui, tool, "checkconflicts") or
                   'conflicts' in _toollist(ui, tool, "check")):
-        if re.search("^(<<<<<<< .*|=======|>>>>>>> .*)$", fcd.data(),
-                     re.MULTILINE):
+        if hasconflictmarkers(fcd.data()):
             r = 1
 
     checked = False
@@ -967,6 +990,24 @@
         internals['internal:' + name] = func
         internalsdoc[fullname] = func
 
+        capabilities = sorted([k for k, v in func.capabilities.items() if v])
+        if capabilities:
+            capdesc = "    (actual capabilities: %s)" % ', '.join(capabilities)
+            func.__doc__ = (func.__doc__ +
+                            pycompat.sysstr("\n\n%s" % capdesc))
+
+    # to put i18n comments into hg.pot for automatically generated texts
+
+    # i18n: "binary" and "symlink" are keywords
+    # i18n: this text is added automatically
+    _("    (actual capabilities: binary, symlink)")
+    # i18n: "binary" is keyword
+    # i18n: this text is added automatically
+    _("    (actual capabilities: binary)")
+    # i18n: "symlink" is keyword
+    # i18n: this text is added automatically
+    _("    (actual capabilities: symlink)")
+
 # load built-in merge tools explicitly to setup internalsdoc
 loadinternalmerge(None, None, internaltool)
 
--- a/mercurial/fileset.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/fileset.py	Wed Sep 26 20:33:09 2018 +0900
@@ -13,9 +13,9 @@
 from .i18n import _
 from . import (
     error,
+    filesetlang,
     match as matchmod,
     merge,
-    parser,
     pycompat,
     registrar,
     scmutil,
@@ -25,126 +25,28 @@
     stringutil,
 )
 
-elements = {
-    # token-type: binding-strength, primary, prefix, infix, suffix
-    "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
-    ":": (15, None, None, ("kindpat", 15), None),
-    "-": (5, None, ("negate", 19), ("minus", 5), None),
-    "not": (10, None, ("not", 10), None, None),
-    "!": (10, None, ("not", 10), None, None),
-    "and": (5, None, None, ("and", 5), None),
-    "&": (5, None, None, ("and", 5), None),
-    "or": (4, None, None, ("or", 4), None),
-    "|": (4, None, None, ("or", 4), None),
-    "+": (4, None, None, ("or", 4), None),
-    ",": (2, None, None, ("list", 2), None),
-    ")": (0, None, None, None, None),
-    "symbol": (0, "symbol", None, None, None),
-    "string": (0, "string", None, None, None),
-    "end": (0, None, None, None, None),
-}
-
-keywords = {'and', 'or', 'not'}
-
-globchars = ".*{}[]?/\\_"
+# common weight constants
+_WEIGHT_CHECK_FILENAME = filesetlang.WEIGHT_CHECK_FILENAME
+_WEIGHT_READ_CONTENTS = filesetlang.WEIGHT_READ_CONTENTS
+_WEIGHT_STATUS = filesetlang.WEIGHT_STATUS
+_WEIGHT_STATUS_THOROUGH = filesetlang.WEIGHT_STATUS_THOROUGH
 
-def tokenize(program):
-    pos, l = 0, len(program)
-    program = pycompat.bytestr(program)
-    while pos < l:
-        c = program[pos]
-        if c.isspace(): # skip inter-token whitespace
-            pass
-        elif c in "(),-:|&+!": # handle simple operators
-            yield (c, None, pos)
-        elif (c in '"\'' or c == 'r' and
-              program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
-            if c == 'r':
-                pos += 1
-                c = program[pos]
-                decode = lambda x: x
-            else:
-                decode = parser.unescapestr
-            pos += 1
-            s = pos
-            while pos < l: # find closing quote
-                d = program[pos]
-                if d == '\\': # skip over escaped characters
-                    pos += 2
-                    continue
-                if d == c:
-                    yield ('string', decode(program[s:pos]), s)
-                    break
-                pos += 1
-            else:
-                raise error.ParseError(_("unterminated string"), s)
-        elif c.isalnum() or c in globchars or ord(c) > 127:
-            # gather up a symbol/keyword
-            s = pos
-            pos += 1
-            while pos < l: # find end of symbol
-                d = program[pos]
-                if not (d.isalnum() or d in globchars or ord(d) > 127):
-                    break
-                pos += 1
-            sym = program[s:pos]
-            if sym in keywords: # operator keywords
-                yield (sym, None, s)
-            else:
-                yield ('symbol', sym, s)
-            pos -= 1
-        else:
-            raise error.ParseError(_("syntax error"), pos)
-        pos += 1
-    yield ('end', None, pos)
-
-def parse(expr):
-    p = parser.parser(elements)
-    tree, pos = p.parse(tokenize(expr))
-    if pos != len(expr):
-        raise error.ParseError(_("invalid token"), pos)
-    return tree
-
-def getsymbol(x):
-    if x and x[0] == 'symbol':
-        return x[1]
-    raise error.ParseError(_('not a symbol'))
-
-def getstring(x, err):
-    if x and (x[0] == 'string' or x[0] == 'symbol'):
-        return x[1]
-    raise error.ParseError(err)
-
-def _getkindpat(x, y, allkinds, err):
-    kind = getsymbol(x)
-    pat = getstring(y, err)
-    if kind not in allkinds:
-        raise error.ParseError(_("invalid pattern kind: %s") % kind)
-    return '%s:%s' % (kind, pat)
-
-def getpattern(x, allkinds, err):
-    if x and x[0] == 'kindpat':
-        return _getkindpat(x[1], x[2], allkinds, err)
-    return getstring(x, err)
-
-def getlist(x):
-    if not x:
-        return []
-    if x[0] == 'list':
-        return getlist(x[1]) + [x[2]]
-    return [x]
-
-def getargs(x, min, max, err):
-    l = getlist(x)
-    if len(l) < min or len(l) > max:
-        raise error.ParseError(err)
-    return l
+# helpers for processing parsed tree
+getsymbol = filesetlang.getsymbol
+getstring = filesetlang.getstring
+_getkindpat = filesetlang.getkindpat
+getpattern = filesetlang.getpattern
+getargs = filesetlang.getargs
 
 def getmatch(mctx, x):
     if not x:
         raise error.ParseError(_("missing argument"))
     return methods[x[0]](mctx, *x[1:])
 
+def getmatchwithstatus(mctx, x, hint):
+    keys = set(getstring(hint, 'status hint must be a string').split())
+    return getmatch(mctx.withstatus(keys), x)
+
 def stringmatch(mctx, x):
     return mctx.matcher([x])
 
@@ -152,15 +54,20 @@
     return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
                                          _("pattern must be a string")))
 
+def patternsmatch(mctx, *xs):
+    allkinds = matchmod.allpatternkinds
+    patterns = [getpattern(x, allkinds, _("pattern must be a string"))
+                for x in xs]
+    return mctx.matcher(patterns)
+
 def andmatch(mctx, x, y):
     xm = getmatch(mctx, x)
-    ym = getmatch(mctx, y)
+    ym = getmatch(mctx.narrowed(xm), y)
     return matchmod.intersectmatchers(xm, ym)
 
-def ormatch(mctx, x, y):
-    xm = getmatch(mctx, x)
-    ym = getmatch(mctx, y)
-    return matchmod.unionmatcher([xm, ym])
+def ormatch(mctx, *xs):
+    ms = [getmatch(mctx, x) for x in xs]
+    return matchmod.unionmatcher(ms)
 
 def notmatch(mctx, x):
     m = getmatch(mctx, x)
@@ -168,15 +75,12 @@
 
 def minusmatch(mctx, x, y):
     xm = getmatch(mctx, x)
-    ym = getmatch(mctx, y)
+    ym = getmatch(mctx.narrowed(xm), y)
     return matchmod.differencematcher(xm, ym)
 
-def negatematch(mctx, x):
-    raise error.ParseError(_("can't use negate operator in this context"))
-
-def listmatch(mctx, x, y):
+def listmatch(mctx, *xs):
     raise error.ParseError(_("can't use a list in this context"),
-                           hint=_('see hg help "filesets.x or y"'))
+                           hint=_('see \'hg help "filesets.x or y"\''))
 
 def func(mctx, a, b):
     funcname = getsymbol(a)
@@ -193,14 +97,11 @@
 # with:
 #  mctx - current matchctx instance
 #  x - argument in tree form
-symbols = {}
+symbols = filesetlang.symbols
 
-# filesets using matchctx.status()
-_statuscallers = set()
+predicate = registrar.filesetpredicate(symbols)
 
-predicate = registrar.filesetpredicate()
-
-@predicate('modified()', callstatus=True)
+@predicate('modified()', callstatus=True, weight=_WEIGHT_STATUS)
 def modified(mctx, x):
     """File that is modified according to :hg:`status`.
     """
@@ -209,7 +110,7 @@
     s = set(mctx.status().modified)
     return mctx.predicate(s.__contains__, predrepr='modified')
 
-@predicate('added()', callstatus=True)
+@predicate('added()', callstatus=True, weight=_WEIGHT_STATUS)
 def added(mctx, x):
     """File that is added according to :hg:`status`.
     """
@@ -218,7 +119,7 @@
     s = set(mctx.status().added)
     return mctx.predicate(s.__contains__, predrepr='added')
 
-@predicate('removed()', callstatus=True)
+@predicate('removed()', callstatus=True, weight=_WEIGHT_STATUS)
 def removed(mctx, x):
     """File that is removed according to :hg:`status`.
     """
@@ -227,7 +128,7 @@
     s = set(mctx.status().removed)
     return mctx.predicate(s.__contains__, predrepr='removed')
 
-@predicate('deleted()', callstatus=True)
+@predicate('deleted()', callstatus=True, weight=_WEIGHT_STATUS)
 def deleted(mctx, x):
     """Alias for ``missing()``.
     """
@@ -236,7 +137,7 @@
     s = set(mctx.status().deleted)
     return mctx.predicate(s.__contains__, predrepr='deleted')
 
-@predicate('missing()', callstatus=True)
+@predicate('missing()', callstatus=True, weight=_WEIGHT_STATUS)
 def missing(mctx, x):
     """File that is missing according to :hg:`status`.
     """
@@ -245,7 +146,7 @@
     s = set(mctx.status().deleted)
     return mctx.predicate(s.__contains__, predrepr='deleted')
 
-@predicate('unknown()', callstatus=True)
+@predicate('unknown()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
 def unknown(mctx, x):
     """File that is unknown according to :hg:`status`."""
     # i18n: "unknown" is a keyword
@@ -253,7 +154,7 @@
     s = set(mctx.status().unknown)
     return mctx.predicate(s.__contains__, predrepr='unknown')
 
-@predicate('ignored()', callstatus=True)
+@predicate('ignored()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
 def ignored(mctx, x):
     """File that is ignored according to :hg:`status`."""
     # i18n: "ignored" is a keyword
@@ -261,7 +162,7 @@
     s = set(mctx.status().ignored)
     return mctx.predicate(s.__contains__, predrepr='ignored')
 
-@predicate('clean()', callstatus=True)
+@predicate('clean()', callstatus=True, weight=_WEIGHT_STATUS)
 def clean(mctx, x):
     """File that is clean according to :hg:`status`.
     """
@@ -277,7 +178,7 @@
     getargs(x, 0, 0, _("tracked takes no arguments"))
     return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
 
-@predicate('binary()')
+@predicate('binary()', weight=_WEIGHT_READ_CONTENTS)
 def binary(mctx, x):
     """File that appears to be binary (contains NUL bytes).
     """
@@ -304,7 +205,7 @@
     ctx = mctx.ctx
     return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
 
-@predicate('resolved()')
+@predicate('resolved()', weight=_WEIGHT_STATUS)
 def resolved(mctx, x):
     """File that is marked resolved according to :hg:`resolve -l`.
     """
@@ -316,7 +217,7 @@
     return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
                           predrepr='resolved')
 
-@predicate('unresolved()')
+@predicate('unresolved()', weight=_WEIGHT_STATUS)
 def unresolved(mctx, x):
     """File that is marked unresolved according to :hg:`resolve -l`.
     """
@@ -328,7 +229,7 @@
     return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
                           predrepr='unresolved')
 
-@predicate('hgignore()')
+@predicate('hgignore()', weight=_WEIGHT_STATUS)
 def hgignore(mctx, x):
     """File that matches the active .hgignore pattern.
     """
@@ -336,7 +237,7 @@
     getargs(x, 0, 0, _("hgignore takes no arguments"))
     return mctx.ctx.repo().dirstate._ignore
 
-@predicate('portable()')
+@predicate('portable()', weight=_WEIGHT_CHECK_FILENAME)
 def portable(mctx, x):
     """File that has a portable name. (This doesn't include filenames with case
     collisions.)
@@ -346,7 +247,7 @@
     return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
                           predrepr='portable')
 
-@predicate('grep(regex)')
+@predicate('grep(regex)', weight=_WEIGHT_READ_CONTENTS)
 def grep(mctx, x):
     """File contains the given regular expression.
     """
@@ -400,7 +301,7 @@
         b = _sizetomax(expr)
         return lambda x: x >= a and x <= b
 
-@predicate('size(expression)')
+@predicate('size(expression)', weight=_WEIGHT_STATUS)
 def size(mctx, x):
     """File size matches the given expression. Examples:
 
@@ -415,7 +316,7 @@
     return mctx.fpredicate(lambda fctx: m(fctx.size()),
                            predrepr=('size(%r)', expr), cache=True)
 
-@predicate('encoding(name)')
+@predicate('encoding(name)', weight=_WEIGHT_READ_CONTENTS)
 def encoding(mctx, x):
     """File can be successfully decoded with the given character
     encoding. May not be useful for encodings other than ASCII and
@@ -437,7 +338,7 @@
 
     return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
 
-@predicate('eol(style)')
+@predicate('eol(style)', weight=_WEIGHT_READ_CONTENTS)
 def eol(mctx, x):
     """File contains newlines of the given style (dos, unix, mac). Binary
     files are excluded, files with mixed line endings match multiple
@@ -471,7 +372,7 @@
         return p and p[0].path() != fctx.path()
     return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
 
-@predicate('revs(revs, pattern)')
+@predicate('revs(revs, pattern)', weight=_WEIGHT_STATUS)
 def revs(mctx, x):
     """Evaluate set in the specified revisions. If the revset match multiple
     revs, this will return file matching pattern in any of the revision.
@@ -486,14 +387,15 @@
     matchers = []
     for r in revs:
         ctx = repo[r]
-        matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
+        mc = mctx.switch(ctx.p1(), ctx)
+        matchers.append(getmatch(mc, x))
     if not matchers:
         return mctx.never()
     if len(matchers) == 1:
         return matchers[0]
     return matchmod.unionmatcher(matchers)
 
-@predicate('status(base, rev, pattern)')
+@predicate('status(base, rev, pattern)', weight=_WEIGHT_STATUS)
 def status(mctx, x):
     """Evaluate predicate using status change between ``base`` and
     ``rev``. Examples:
@@ -513,7 +415,8 @@
     if not revspec:
         raise error.ParseError(reverr)
     basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
-    return getmatch(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
+    mc = mctx.switch(basectx, ctx)
+    return getmatch(mc, x)
 
 @predicate('subrepo([pattern])')
 def subrepo(mctx, x):
@@ -539,24 +442,52 @@
         return mctx.predicate(sstate.__contains__, predrepr='subrepo')
 
 methods = {
+    'withstatus': getmatchwithstatus,
     'string': stringmatch,
     'symbol': stringmatch,
     'kindpat': kindpatmatch,
+    'patterns': patternsmatch,
     'and': andmatch,
     'or': ormatch,
     'minus': minusmatch,
-    'negate': negatematch,
     'list': listmatch,
-    'group': getmatch,
     'not': notmatch,
     'func': func,
 }
 
 class matchctx(object):
-    def __init__(self, ctx, status=None, badfn=None):
+    def __init__(self, basectx, ctx, badfn=None):
+        self._basectx = basectx
         self.ctx = ctx
-        self._status = status
         self._badfn = badfn
+        self._match = None
+        self._status = None
+
+    def narrowed(self, match):
+        """Create matchctx for a sub-tree narrowed by the given matcher"""
+        mctx = matchctx(self._basectx, self.ctx, self._badfn)
+        mctx._match = match
+        # leave wider status which we don't have to care
+        mctx._status = self._status
+        return mctx
+
+    def switch(self, basectx, ctx):
+        mctx = matchctx(basectx, ctx, self._badfn)
+        mctx._match = self._match
+        return mctx
+
+    def withstatus(self, keys):
+        """Create matchctx which has precomputed status specified by the keys"""
+        mctx = matchctx(self._basectx, self.ctx, self._badfn)
+        mctx._match = self._match
+        mctx._buildstatus(keys)
+        return mctx
+
+    def _buildstatus(self, keys):
+        self._status = self._basectx.status(self.ctx, self._match,
+                                            listignored='ignored' in keys,
+                                            listclean='clean' in keys,
+                                            listunknown='unknown' in keys)
 
     def status(self):
         return self._status
@@ -612,62 +543,20 @@
         return matchmod.nevermatcher(repo.root, repo.getcwd(),
                                      badfn=self._badfn)
 
-    def switch(self, ctx, status=None):
-        return matchctx(ctx, status, self._badfn)
-
-# filesets using matchctx.switch()
-_switchcallers = [
-    'revs',
-    'status',
-]
-
-def _intree(funcs, tree):
-    if isinstance(tree, tuple):
-        if tree[0] == 'func' and tree[1][0] == 'symbol':
-            if tree[1][1] in funcs:
-                return True
-            if tree[1][1] in _switchcallers:
-                # arguments won't be evaluated in the current context
-                return False
-        for s in tree[1:]:
-            if _intree(funcs, s):
-                return True
-    return False
-
 def match(ctx, expr, badfn=None):
     """Create a matcher for a single fileset expression"""
-    tree = parse(expr)
-    mctx = matchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
+    tree = filesetlang.parse(expr)
+    tree = filesetlang.analyze(tree)
+    tree = filesetlang.optimize(tree)
+    mctx = matchctx(ctx.p1(), ctx, badfn=badfn)
     return getmatch(mctx, tree)
 
-def _buildstatus(ctx, tree, basectx=None):
-    # do we need status info?
-
-    if _intree(_statuscallers, tree):
-        unknown = _intree(['unknown'], tree)
-        ignored = _intree(['ignored'], tree)
-
-        r = ctx.repo()
-        if basectx is None:
-            basectx = ctx.p1()
-        return r.status(basectx, ctx,
-                        unknown=unknown, ignored=ignored, clean=True)
-    else:
-        return None
-
-def prettyformat(tree):
-    return parser.prettyformat(tree, ('string', 'symbol'))
 
 def loadpredicate(ui, extname, registrarobj):
     """Load fileset predicates from specified registrarobj
     """
     for name, func in registrarobj._table.iteritems():
         symbols[name] = func
-        if func._callstatus:
-            _statuscallers.add(name)
-
-# load built-in predicates explicitly to setup _statuscallers
-loadpredicate(None, None, predicate)
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = symbols.values()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/filesetlang.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,330 @@
+# filesetlang.py - parser, tokenizer and utility for file set language
+#
+# Copyright 2010 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from .i18n import _
+from . import (
+    error,
+    parser,
+    pycompat,
+)
+
+# common weight constants for static optimization
+# (see registrar.filesetpredicate for details)
+WEIGHT_CHECK_FILENAME = 0.5
+WEIGHT_READ_CONTENTS = 30
+WEIGHT_STATUS = 10
+WEIGHT_STATUS_THOROUGH = 50
+
+elements = {
+    # token-type: binding-strength, primary, prefix, infix, suffix
+    "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
+    ":": (15, None, None, ("kindpat", 15), None),
+    "-": (5, None, ("negate", 19), ("minus", 5), None),
+    "not": (10, None, ("not", 10), None, None),
+    "!": (10, None, ("not", 10), None, None),
+    "and": (5, None, None, ("and", 5), None),
+    "&": (5, None, None, ("and", 5), None),
+    "or": (4, None, None, ("or", 4), None),
+    "|": (4, None, None, ("or", 4), None),
+    "+": (4, None, None, ("or", 4), None),
+    ",": (2, None, None, ("list", 2), None),
+    ")": (0, None, None, None, None),
+    "symbol": (0, "symbol", None, None, None),
+    "string": (0, "string", None, None, None),
+    "end": (0, None, None, None, None),
+}
+
+keywords = {'and', 'or', 'not'}
+
+symbols = {}
+
+globchars = ".*{}[]?/\\_"
+
+def tokenize(program):
+    pos, l = 0, len(program)
+    program = pycompat.bytestr(program)
+    while pos < l:
+        c = program[pos]
+        if c.isspace(): # skip inter-token whitespace
+            pass
+        elif c in "(),-:|&+!": # handle simple operators
+            yield (c, None, pos)
+        elif (c in '"\'' or c == 'r' and
+              program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
+            if c == 'r':
+                pos += 1
+                c = program[pos]
+                decode = lambda x: x
+            else:
+                decode = parser.unescapestr
+            pos += 1
+            s = pos
+            while pos < l: # find closing quote
+                d = program[pos]
+                if d == '\\': # skip over escaped characters
+                    pos += 2
+                    continue
+                if d == c:
+                    yield ('string', decode(program[s:pos]), s)
+                    break
+                pos += 1
+            else:
+                raise error.ParseError(_("unterminated string"), s)
+        elif c.isalnum() or c in globchars or ord(c) > 127:
+            # gather up a symbol/keyword
+            s = pos
+            pos += 1
+            while pos < l: # find end of symbol
+                d = program[pos]
+                if not (d.isalnum() or d in globchars or ord(d) > 127):
+                    break
+                pos += 1
+            sym = program[s:pos]
+            if sym in keywords: # operator keywords
+                yield (sym, None, s)
+            else:
+                yield ('symbol', sym, s)
+            pos -= 1
+        else:
+            raise error.ParseError(_("syntax error"), pos)
+        pos += 1
+    yield ('end', None, pos)
+
+def parse(expr):
+    p = parser.parser(elements)
+    tree, pos = p.parse(tokenize(expr))
+    if pos != len(expr):
+        raise error.ParseError(_("invalid token"), pos)
+    return parser.simplifyinfixops(tree, {'list', 'or'})
+
+def getsymbol(x):
+    if x and x[0] == 'symbol':
+        return x[1]
+    raise error.ParseError(_('not a symbol'))
+
+def getstring(x, err):
+    if x and (x[0] == 'string' or x[0] == 'symbol'):
+        return x[1]
+    raise error.ParseError(err)
+
+def getkindpat(x, y, allkinds, err):
+    kind = getsymbol(x)
+    pat = getstring(y, err)
+    if kind not in allkinds:
+        raise error.ParseError(_("invalid pattern kind: %s") % kind)
+    return '%s:%s' % (kind, pat)
+
+def getpattern(x, allkinds, err):
+    if x and x[0] == 'kindpat':
+        return getkindpat(x[1], x[2], allkinds, err)
+    return getstring(x, err)
+
+def getlist(x):
+    if not x:
+        return []
+    if x[0] == 'list':
+        return list(x[1:])
+    return [x]
+
+def getargs(x, min, max, err):
+    l = getlist(x)
+    if len(l) < min or len(l) > max:
+        raise error.ParseError(err)
+    return l
+
+def _analyze(x):
+    if x is None:
+        return x
+
+    op = x[0]
+    if op in {'string', 'symbol'}:
+        return x
+    if op == 'kindpat':
+        getsymbol(x[1])  # kind must be a symbol
+        t = _analyze(x[2])
+        return (op, x[1], t)
+    if op == 'group':
+        return _analyze(x[1])
+    if op == 'negate':
+        raise error.ParseError(_("can't use negate operator in this context"))
+    if op == 'not':
+        t = _analyze(x[1])
+        return (op, t)
+    if op == 'and':
+        ta = _analyze(x[1])
+        tb = _analyze(x[2])
+        return (op, ta, tb)
+    if op == 'minus':
+        return _analyze(('and', x[1], ('not', x[2])))
+    if op in {'list', 'or'}:
+        ts = tuple(_analyze(y) for y in x[1:])
+        return (op,) + ts
+    if op == 'func':
+        getsymbol(x[1])  # function name must be a symbol
+        ta = _analyze(x[2])
+        return (op, x[1], ta)
+    raise error.ProgrammingError('invalid operator %r' % op)
+
+def _insertstatushints(x):
+    """Insert hint nodes where status should be calculated (first path)
+
+    This works in bottom-up way, summing up status names and inserting hint
+    nodes at 'and' and 'or' as needed. Thus redundant hint nodes may be left.
+
+    Returns (status-names, new-tree) at the given subtree, where status-names
+    is a sum of status names referenced in the given subtree.
+    """
+    if x is None:
+        return (), x
+
+    op = x[0]
+    if op in {'string', 'symbol', 'kindpat'}:
+        return (), x
+    if op == 'not':
+        h, t = _insertstatushints(x[1])
+        return h, (op, t)
+    if op == 'and':
+        ha, ta = _insertstatushints(x[1])
+        hb, tb = _insertstatushints(x[2])
+        hr = ha + hb
+        if ha and hb:
+            return hr, ('withstatus', (op, ta, tb), ('string', ' '.join(hr)))
+        return hr, (op, ta, tb)
+    if op == 'or':
+        hs, ts = zip(*(_insertstatushints(y) for y in x[1:]))
+        hr = sum(hs, ())
+        if sum(bool(h) for h in hs) > 1:
+            return hr, ('withstatus', (op,) + ts, ('string', ' '.join(hr)))
+        return hr, (op,) + ts
+    if op == 'list':
+        hs, ts = zip(*(_insertstatushints(y) for y in x[1:]))
+        return sum(hs, ()), (op,) + ts
+    if op == 'func':
+        f = getsymbol(x[1])
+        # don't propagate 'ha' crossing a function boundary
+        ha, ta = _insertstatushints(x[2])
+        if getattr(symbols.get(f), '_callstatus', False):
+            return (f,), ('withstatus', (op, x[1], ta), ('string', f))
+        return (), (op, x[1], ta)
+    raise error.ProgrammingError('invalid operator %r' % op)
+
+def _mergestatushints(x, instatus):
+    """Remove redundant status hint nodes (second path)
+
+    This is the top-down path to eliminate inner hint nodes.
+    """
+    if x is None:
+        return x
+
+    op = x[0]
+    if op == 'withstatus':
+        if instatus:
+            # drop redundant hint node
+            return _mergestatushints(x[1], instatus)
+        t = _mergestatushints(x[1], instatus=True)
+        return (op, t, x[2])
+    if op in {'string', 'symbol', 'kindpat'}:
+        return x
+    if op == 'not':
+        t = _mergestatushints(x[1], instatus)
+        return (op, t)
+    if op == 'and':
+        ta = _mergestatushints(x[1], instatus)
+        tb = _mergestatushints(x[2], instatus)
+        return (op, ta, tb)
+    if op in {'list', 'or'}:
+        ts = tuple(_mergestatushints(y, instatus) for y in x[1:])
+        return (op,) + ts
+    if op == 'func':
+        # don't propagate 'instatus' crossing a function boundary
+        ta = _mergestatushints(x[2], instatus=False)
+        return (op, x[1], ta)
+    raise error.ProgrammingError('invalid operator %r' % op)
+
+def analyze(x):
+    """Transform raw parsed tree to evaluatable tree which can be fed to
+    optimize() or getmatch()
+
+    All pseudo operations should be mapped to real operations or functions
+    defined in methods or symbols table respectively.
+    """
+    t = _analyze(x)
+    _h, t = _insertstatushints(t)
+    return _mergestatushints(t, instatus=False)
+
+def _optimizeandops(op, ta, tb):
+    if tb is not None and tb[0] == 'not':
+        return ('minus', ta, tb[1])
+    return (op, ta, tb)
+
+def _optimizeunion(xs):
+    # collect string patterns so they can be compiled into a single regexp
+    ws, ts, ss = [], [], []
+    for x in xs:
+        w, t = _optimize(x)
+        if t is not None and t[0] in {'string', 'symbol', 'kindpat'}:
+            ss.append(t)
+            continue
+        ws.append(w)
+        ts.append(t)
+    if ss:
+        ws.append(WEIGHT_CHECK_FILENAME)
+        ts.append(('patterns',) + tuple(ss))
+    return ws, ts
+
+def _optimize(x):
+    if x is None:
+        return 0, x
+
+    op = x[0]
+    if op == 'withstatus':
+        w, t = _optimize(x[1])
+        return w, (op, t, x[2])
+    if op in {'string', 'symbol'}:
+        return WEIGHT_CHECK_FILENAME, x
+    if op == 'kindpat':
+        w, t = _optimize(x[2])
+        return w, (op, x[1], t)
+    if op == 'not':
+        w, t = _optimize(x[1])
+        return w, (op, t)
+    if op == 'and':
+        wa, ta = _optimize(x[1])
+        wb, tb = _optimize(x[2])
+        if wa <= wb:
+            return wa, _optimizeandops(op, ta, tb)
+        else:
+            return wb, _optimizeandops(op, tb, ta)
+    if op == 'or':
+        ws, ts = _optimizeunion(x[1:])
+        if len(ts) == 1:
+            return ws[0], ts[0] # 'or' operation is fully optimized out
+        ts = tuple(it[1] for it in sorted(enumerate(ts),
+                                          key=lambda it: ws[it[0]]))
+        return max(ws), (op,) + ts
+    if op == 'list':
+        ws, ts = zip(*(_optimize(y) for y in x[1:]))
+        return sum(ws), (op,) + ts
+    if op == 'func':
+        f = getsymbol(x[1])
+        w = getattr(symbols.get(f), '_weight', 1)
+        wa, ta = _optimize(x[2])
+        return w + wa, (op, x[1], ta)
+    raise error.ProgrammingError('invalid operator %r' % op)
+
+def optimize(x):
+    """Reorder/rewrite evaluatable tree for optimization
+
+    All pseudo operations should be transformed beforehand.
+    """
+    _w, t = _optimize(x)
+    return t
+
+def prettyformat(tree):
+    return parser.prettyformat(tree, ('string', 'symbol'))
--- a/mercurial/formatter.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/formatter.py	Wed Sep 26 20:33:09 2018 +0900
@@ -124,7 +124,6 @@
     error,
     pycompat,
     templatefilters,
-    templatefuncs,
     templatekw,
     templater,
     templateutil,
@@ -193,14 +192,16 @@
         # name is mandatory argument for now, but it could be optional if
         # we have default template keyword, e.g. {item}
         return self._converter.formatlist(data, name, fmt, sep)
-    def contexthint(self, datafields):
-        '''set of context object keys to be required given datafields set'''
-        return set()
     def context(self, **ctxs):
         '''insert context objects to be used to render template keywords'''
         ctxs = pycompat.byteskwargs(ctxs)
-        assert all(k in {'ctx', 'fctx'} for k in ctxs)
+        assert all(k in {'repo', 'ctx', 'fctx'} for k in ctxs)
         if self._converter.storecontext:
+            # populate missing resources in fctx -> ctx -> repo order
+            if 'fctx' in ctxs and 'ctx' not in ctxs:
+                ctxs['ctx'] = ctxs['fctx'].changectx()
+            if 'ctx' in ctxs and 'repo' not in ctxs:
+                ctxs['repo'] = ctxs['ctx'].repo()
             self._item.update(ctxs)
     def datahint(self):
         '''set of field names to be referenced'''
@@ -422,24 +423,6 @@
     def _symbolsused(self):
         return self._t.symbolsused(self._tref)
 
-    def contexthint(self, datafields):
-        '''set of context object keys to be required by the template, given
-        datafields overridden by immediate values'''
-        requires = set()
-        ksyms, fsyms = self._symbolsused
-        ksyms = ksyms - set(datafields.split())  # exclude immediate fields
-        symtables = [(ksyms, templatekw.keywords),
-                     (fsyms, templatefuncs.funcs)]
-        for syms, table in symtables:
-            for k in syms:
-                f = table.get(k)
-                if not f:
-                    continue
-                requires.update(getattr(f, '_requires', ()))
-        if 'repo' in requires:
-            requires.add('ctx')  # there's no API to pass repo to formatter
-        return requires & {'ctx', 'fctx'}
-
     def datahint(self):
         '''set of field names to be referenced from the template'''
         return self._symbolsused[0]
@@ -538,6 +521,10 @@
         t.cache[''] = tmpl
     return t
 
+# marker to denote a resource to be loaded on demand based on mapping values
+# (e.g. (ctx, path) -> fctx)
+_placeholder = object()
+
 class templateresources(templater.resourcemapper):
     """Resource mapper designed for the default templatekw and function"""
 
@@ -548,61 +535,81 @@
             'ui': ui,
         }
 
-    def availablekeys(self, context, mapping):
-        return {k for k, g in self._gettermap.iteritems()
-                if g(self, context, mapping, k) is not None}
+    def availablekeys(self, mapping):
+        return {k for k in self.knownkeys()
+                if self._getsome(mapping, k) is not None}
 
     def knownkeys(self):
-        return self._knownkeys
+        return {'cache', 'ctx', 'fctx', 'repo', 'revcache', 'ui'}
 
-    def lookup(self, context, mapping, key):
-        get = self._gettermap.get(key)
-        if not get:
+    def lookup(self, mapping, key):
+        if key not in self.knownkeys():
             return None
-        return get(self, context, mapping, key)
+        v = self._getsome(mapping, key)
+        if v is _placeholder:
+            v = mapping[key] = self._loadermap[key](self, mapping)
+        return v
 
     def populatemap(self, context, origmapping, newmapping):
         mapping = {}
-        if self._hasctx(newmapping):
+        if self._hasnodespec(newmapping):
             mapping['revcache'] = {}  # per-ctx cache
-        if (('node' in origmapping or self._hasctx(origmapping))
-            and ('node' in newmapping or self._hasctx(newmapping))):
+        if self._hasnodespec(origmapping) and self._hasnodespec(newmapping):
             orignode = templateutil.runsymbol(context, origmapping, 'node')
             mapping['originalnode'] = orignode
+        # put marker to override 'ctx'/'fctx' in mapping if any, and flag
+        # its existence to be reported by availablekeys()
+        if 'ctx' not in newmapping and self._hasliteral(newmapping, 'node'):
+            mapping['ctx'] = _placeholder
+        if 'fctx' not in newmapping and self._hasliteral(newmapping, 'path'):
+            mapping['fctx'] = _placeholder
         return mapping
 
-    def _getsome(self, context, mapping, key):
+    def _getsome(self, mapping, key):
         v = mapping.get(key)
         if v is not None:
             return v
         return self._resmap.get(key)
 
-    def _hasctx(self, mapping):
-        return 'ctx' in mapping or 'fctx' in mapping
+    def _hasliteral(self, mapping, key):
+        """Test if a literal value is set or unset in the given mapping"""
+        return key in mapping and not callable(mapping[key])
 
-    def _getctx(self, context, mapping, key):
-        ctx = mapping.get('ctx')
-        if ctx is not None:
-            return ctx
-        fctx = mapping.get('fctx')
-        if fctx is not None:
-            return fctx.changectx()
+    def _getliteral(self, mapping, key):
+        """Return value of the given name if it is a literal"""
+        v = mapping.get(key)
+        if callable(v):
+            return None
+        return v
+
+    def _hasnodespec(self, mapping):
+        """Test if context revision is set or unset in the given mapping"""
+        return 'node' in mapping or 'ctx' in mapping
 
-    def _getrepo(self, context, mapping, key):
-        ctx = self._getctx(context, mapping, 'ctx')
-        if ctx is not None:
-            return ctx.repo()
-        return self._getsome(context, mapping, key)
+    def _loadctx(self, mapping):
+        repo = self._getsome(mapping, 'repo')
+        node = self._getliteral(mapping, 'node')
+        if repo is None or node is None:
+            return
+        try:
+            return repo[node]
+        except error.RepoLookupError:
+            return None # maybe hidden/non-existent node
 
-    _gettermap = {
-        'cache': _getsome,
-        'ctx': _getctx,
-        'fctx': _getsome,
-        'repo': _getrepo,
-        'revcache': _getsome,
-        'ui': _getsome,
+    def _loadfctx(self, mapping):
+        ctx = self._getsome(mapping, 'ctx')
+        path = self._getliteral(mapping, 'path')
+        if ctx is None or path is None:
+            return None
+        try:
+            return ctx[path]
+        except error.LookupError:
+            return None # maybe removed file?
+
+    _loadermap = {
+        'ctx': _loadctx,
+        'fctx': _loadfctx,
     }
-    _knownkeys = set(_gettermap.keys())
 
 def formatter(ui, out, topic, opts):
     template = opts.get("template", "")
--- a/mercurial/graphmod.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/graphmod.py	Wed Sep 26 20:33:09 2018 +0900
@@ -22,6 +22,7 @@
 from .node import nullrev
 from . import (
     dagop,
+    pycompat,
     smartset,
     util,
 )
@@ -280,7 +281,7 @@
         line.extend(echars[-(remainder * 2):])
     return line
 
-def _drawendinglines(lines, extra, edgemap, seen):
+def _drawendinglines(lines, extra, edgemap, seen, state):
     """Draw ending lines for missing parent edges
 
     None indicates an edge that ends at between this node and the next
@@ -297,7 +298,8 @@
     while edgechars and edgechars[-1] is None:
         edgechars.pop()
     shift_size = max((edgechars.count(None) * 2) - 1, 0)
-    while len(lines) < 3 + shift_size:
+    minlines = 3 if not state['graphshorten'] else 2
+    while len(lines) < minlines + shift_size:
         lines.append(extra[:])
 
     if shift_size:
@@ -318,7 +320,7 @@
                 positions[i] = max(pos, targets[i])
                 line[pos] = '/' if pos > targets[i] else extra[toshift[i]]
 
-    map = {1: '|', 2: '~'}
+    map = {1: '|', 2: '~'} if not state['graphshorten'] else {1: '~'}
     for i, line in enumerate(lines):
         if None not in line:
             continue
@@ -426,16 +428,16 @@
     # shift_interline is the line containing the non-vertical
     # edges between this entry and the next
     shift_interline = echars[:idx * 2]
-    for i in xrange(2 + coldiff):
+    for i in pycompat.xrange(2 + coldiff):
         shift_interline.append(' ')
     count = ncols - idx - 1
     if coldiff == -1:
-        for i in xrange(count):
+        for i in pycompat.xrange(count):
             shift_interline.extend(['/', ' '])
     elif coldiff == 0:
         shift_interline.extend(echars[(idx + 1) * 2:ncols * 2])
     else:
-        for i in xrange(count):
+        for i in pycompat.xrange(count):
             shift_interline.extend(['\\', ' '])
 
     # draw edges from the current node to its parents
@@ -462,7 +464,7 @@
         while len(lines) < len(text):
             lines.append(extra_interline[:])
 
-    _drawendinglines(lines, extra_interline, edgemap, seen)
+    _drawendinglines(lines, extra_interline, edgemap, seen, state)
 
     while len(text) < len(lines):
         text.append("")
--- a/mercurial/help.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/help.py	Wed Sep 26 20:33:09 2018 +0900
@@ -205,6 +205,8 @@
      loaddoc('bundle2', subdir='internals')),
     (['bundles'], _('Bundles'),
      loaddoc('bundles', subdir='internals')),
+    (['cbor'], _('CBOR'),
+     loaddoc('cbor', subdir='internals')),
     (['censor'], _('Censor'),
      loaddoc('censor', subdir='internals')),
     (['changegroups'], _('Changegroups'),
@@ -217,6 +219,10 @@
      loaddoc('revlogs', subdir='internals')),
     (['wireprotocol'], _('Wire Protocol'),
      loaddoc('wireprotocol', subdir='internals')),
+    (['wireprotocolrpc'], _('Wire Protocol RPC'),
+     loaddoc('wireprotocolrpc', subdir='internals')),
+    (['wireprotocolv2'], _('Wire Protocol Version 2'),
+     loaddoc('wireprotocolv2', subdir='internals')),
 ])
 
 def internalshelp(ui):
@@ -642,8 +648,8 @@
 
     return ''.join(rst)
 
-def formattedhelp(ui, commands, name, keep=None, unknowncmd=False, full=True,
-                  **opts):
+def formattedhelp(ui, commands, fullname, keep=None, unknowncmd=False,
+                  full=True, **opts):
     """get help for a given topic (as a dotted name) as rendered rst
 
     Either returns the rendered help text or raises an exception.
@@ -652,19 +658,17 @@
         keep = []
     else:
         keep = list(keep) # make a copy so we can mutate this later
-    fullname = name
-    section = None
-    subtopic = None
-    if name and '.' in name:
-        name, remaining = name.split('.', 1)
-        remaining = encoding.lower(remaining)
-        if '.' in remaining:
-            subtopic, section = remaining.split('.', 1)
-        else:
-            if name in subtopics:
-                subtopic = remaining
-            else:
-                section = remaining
+
+    # <fullname> := <name>[.<subtopic][.<section>]
+    name = subtopic = section = None
+    if fullname is not None:
+        nameparts = fullname.split('.')
+        name = nameparts.pop(0)
+        if nameparts and name in subtopics:
+            subtopic = nameparts.pop(0)
+        if nameparts:
+            section = encoding.lower('.'.join(nameparts))
+
     textwidth = ui.configint('ui', 'textwidth')
     termwidth = ui.termwidth() - 2
     if textwidth <= 0 or termwidth < textwidth:
@@ -672,19 +676,19 @@
     text = help_(ui, commands, name,
                  subtopic=subtopic, unknowncmd=unknowncmd, full=full, **opts)
 
-    formatted, pruned = minirst.format(text, textwidth, keep=keep,
-                                       section=section)
+    blocks, pruned = minirst.parse(text, keep=keep)
+    if 'verbose' in pruned:
+        keep.append('omitted')
+    else:
+        keep.append('notomitted')
+    blocks, pruned = minirst.parse(text, keep=keep)
+    if section:
+        blocks = minirst.filtersections(blocks, section)
 
     # We could have been given a weird ".foo" section without a name
     # to look for, or we could have simply failed to found "foo.bar"
     # because bar isn't a section of foo
-    if section and not (formatted and name):
+    if section and not (blocks and name):
         raise error.Abort(_("help section not found: %s") % fullname)
 
-    if 'verbose' in pruned:
-        keep.append('omitted')
-    else:
-        keep.append('notomitted')
-    formatted, pruned = minirst.format(text, textwidth, keep=keep,
-                                       section=section)
-    return formatted
+    return minirst.formatplain(blocks, textwidth)
--- a/mercurial/help/config.txt	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/help/config.txt	Wed Sep 26 20:33:09 2018 +0900
@@ -438,6 +438,25 @@
 ``commands``
 ------------
 
+``resolve.confirm``
+    Confirm before performing action if no filename is passed.
+    (default: False)
+
+``resolve.explicit-re-merge``
+    Require uses of ``hg resolve`` to specify which action it should perform,
+    instead of re-merging files by default.
+    (default: False)
+
+``resolve.mark-check``
+    Determines what level of checking :hg:`resolve --mark` will perform before
+    marking files as resolved. Valid values are ``none`, ``warn``, and
+    ``abort``. ``warn`` will output a warning listing the file(s) that still
+    have conflict markers in them, but will still mark everything resolved.
+    ``abort`` will output the same warning but will not mark things as resolved.
+    If --all is passed and this is set to ``abort``, only a warning will be
+    shown (an error will not be raised).
+    (default: ``none``)
+
 ``status.relative``
     Make paths in :hg:`status` output relative to the current directory.
     (default: False)
@@ -1333,6 +1352,11 @@
    halted, the repository is left in a normal ``unresolved`` merge state.
    (default: ``continue``)
 
+``strict-capability-check``
+   Whether capabilities of internal merge tools are checked strictly
+   or not, while examining rules to decide merge tool to be used.
+   (default: False)
+
 ``merge-patterns``
 ------------------
 
@@ -1903,6 +1927,10 @@
     repositories to the exchange format required by the bundle1 data
     format can consume a lot of CPU.
 
+``bundle2.stream``
+    Whether to allow clients to pull using the bundle2 streaming protocol.
+    (default: True)
+
 ``zliblevel``
     Integer between ``-1`` and ``9`` that controls the zlib compression level
     for wire protocol commands that send zlib compressed output (notably the
@@ -2330,7 +2358,7 @@
     to release, but over time the recommended config settings
     shift. Enable this config to opt in to get automatic tweaks to
     Mercurial's behavior over time. This config setting will have no
-    effet if ``HGPLAIN` is set or ``HGPLAINEXCEPT`` is set and does
+    effect if ``HGPLAIN` is set or ``HGPLAINEXCEPT`` is set and does
     not include ``tweakdefaults``. (default: False)
 
 ``username``
@@ -2598,6 +2626,9 @@
 ``server-header``
     Value for HTTP ``Server`` response header.
 
+``static``
+    Directory where static files are served from.
+
 ``staticurl``
     Base URL to use for static files. If unset, static files (e.g. the
     hgicon.png favicon) will be served by the CGI script itself. Use
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/help/internals/cbor.txt	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,130 @@
+Mercurial uses Concise Binary Object Representation (CBOR)
+(RFC 7049) for various data formats.
+
+This document describes the subset of CBOR that Mercurial uses and
+gives recommendations for appropriate use of CBOR within Mercurial.
+
+Type Limitations
+================
+
+Major types 0 and 1 (unsigned integers and negative integers) MUST be
+fully supported.
+
+Major type 2 (byte strings) MUST be fully supported. However, there
+are limitations around the use of indefinite-length byte strings.
+(See below.)
+
+Major type 3 (text strings) are NOT supported.
+
+Major type 4 (arrays) MUST be supported. However, values are limited
+to the set of types described in the "Container Types" section below.
+And indefinite-length arrays are NOT supported.
+
+Major type 5 (maps) MUST be supported. However, key values are limited
+to the set of types described in the "Container Types" section below.
+And indefinite-length maps are NOT supported.
+
+Major type 6 (semantic tagging of major types) can be used with the
+following semantic tag values:
+
+258
+   Mathematical finite set. Suitable for representing Python's
+   ``set`` type.
+
+All other semantic tag values are not allowed.
+
+Major type 7 (simple data types) can be used with the following
+type values:
+
+20
+   False
+21
+   True
+22
+   Null
+31
+   Break stop code (for indefinite-length items).
+
+All other simple data type values (including every value requiring the
+1 byte extension) are disallowed.
+
+Indefinite-Length Byte Strings
+==============================
+
+Indefinite-length byte strings (major type 2) are allowed. However,
+they MUST NOT occur inside a container type (such as an array or map).
+i.e. they can only occur as the "top-most" element in a stream of
+values.
+
+Encoders and decoders SHOULD *stream* indefinite-length byte strings.
+i.e. an encoder or decoder SHOULD NOT buffer the entirety of a long
+byte string value when indefinite-length byte strings are being used
+if it can be avoided. Mercurial MAY use extremely long indefinite-length
+byte strings and buffering the source or destination value COULD lead to
+memory exhaustion.
+
+Chunks in an indefinite-length byte string SHOULD NOT exceed 2^20
+bytes.
+
+Container Types
+===============
+
+Mercurial may use the array (major type 4), map (major type 5), and
+set (semantic tag 258 plus major type 4 array) container types.
+
+An array may contain any supported type as values.
+
+A map MUST only use the following types as keys:
+
+* unsigned integers (major type 0)
+* negative integers (major type 1)
+* byte strings (major type 2) (but not indefinite-length byte strings)
+* false (simple type 20)
+* true (simple type 21)
+* null (simple type 22)
+
+A map MUST only use the following types as values:
+
+* all types supported as map keys
+* arrays
+* maps
+* sets
+
+A set may only use the following types as values:
+
+* all types supported as map keys
+
+It is recommended that keys in maps and values in sets and arrays all
+be of a uniform type.
+
+Avoiding Large Byte Strings
+===========================
+
+The use of large byte strings is discouraged, especially in scenarios where
+the total size of the byte string may by unbound for some inputs (e.g. when
+representing the content of a tracked file). It is highly recommended to use
+indefinite-length byte strings for these purposes.
+
+Since indefinite-length byte strings cannot be nested within an outer
+container (such as an array or map), to associate a large byte string
+with another data structure, it is recommended to use an array or
+map followed immediately by an indefinite-length byte string. For example,
+instead of the following map::
+
+   {
+      "key1": "value1",
+      "key2": "value2",
+      "long_value": "some very large value...",
+   }
+
+Use a map followed by a byte string:
+
+   {
+      "key1": "value1",
+      "key2": "value2",
+      "value_follows": True,
+   }
+   <BEGIN INDEFINITE-LENGTH BYTE STRING>
+   "some very large value"
+   "..."
+   <END INDEFINITE-LENGTH BYTE STRING>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/help/internals/linelog.txt	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,302 @@
+linelog is a storage format inspired by the "Interleaved deltas" idea. See
+https://en.wikipedia.org/wiki/Interleaved_deltas for its introduction.
+
+0. SCCS Weave
+
+  To understand what linelog is, first we have a quick look at a simplified
+  (with header removed) SCCS weave format, which is an implementation of the
+  "Interleaved deltas" idea.
+
+0.1 Basic SCCS Weave File Format
+
+  A SCCS weave file consists of plain text lines. Each line is either a
+  special instruction starting with "^A" or part of the content of the real
+  file the weave tracks. There are 3 important operations, where REV denotes
+  the revision number:
+
+    ^AI REV, marking the beginning of an insertion block introduced by REV
+    ^AD REV, marking the beginning of a deletion block introduced by REV
+    ^AE REV, marking the end of the block started by "^AI REV" or "^AD REV"
+
+  Note on revision numbers: For any two different revision numbers, one must
+  be an ancestor of the other to make them comparable. This enforces linear
+  history. Besides, the comparison functions (">=", "<") should be efficient.
+  This means, if revisions are strings like git or hg, an external map is
+  required to convert them into integers.
+
+  For example, to represent the following changes:
+
+    REV 1 | REV 2 | REV 3
+    ------+-------+-------
+    a     | a     | a
+    b     | b     | 2
+    c     | 1     | c
+          | 2     |
+          | c     |
+
+  A possible weave file looks like:
+
+    ^AI 1
+    a
+    ^AD 3
+    b
+    ^AI 2
+    1
+    ^AE 3
+    2
+    ^AE 2
+    c
+    ^AE 1
+
+  An "^AE" does not always match its nearest operation ("^AI" or "^AD"). In
+  the above example, "^AE 3" does not match the nearest "^AI 2" but "^AD 3".
+  Therefore we need some extra information for "^AE". The SCCS weave uses a
+  revision number. It could also be a boolean value about whether it is an
+  insertion or a deletion (see section 0.4).
+
+0.2 Checkout
+
+  The "checkout" operation is to retrieve file content at a given revision,
+  say X. It's doable by going through the file line by line and:
+
+    - If meet ^AI rev, and rev > X, find the corresponding ^AE and jump there
+    - If meet ^AD rev, and rev <= X, find the corresponding ^AE and jump there
+    - Ignore ^AE
+    - For normal lines, just output them
+
+0.3 Annotate
+
+  The "annotate" operation is to show extra metadata like the revision number
+  and the original line number a line comes from.
+
+  It's basically just a "Checkout". For the extra metadata, they can be stored
+  side by side with the line contents. Alternatively, we can infer the
+  revision number from "^AI"s.
+
+  Some SCM tools have to calculate diffs on the fly and thus are much slower
+  on this operation.
+
+0.4 Tree Structure
+
+  The word "interleaved" is used because "^AI" .. "^AE" and "^AD" .. "^AE"
+  blocks can be interleaved.
+
+  If we consider insertions and deletions separately, they can form tree
+  structures, respectively.
+
+    +--- ^AI 1        +--- ^AD 3
+    | +- ^AI 2        | +- ^AD 2
+    | |               | |
+    | +- ^AE 2        | +- ^AE 2
+    |                 |
+    +--- ^AE 1        +--- ^AE 3
+
+  More specifically, it's possible to build a tree for all insertions, where
+  the tree node has the structure "(rev, startline, endline)". "startline" is
+  the line number of "^AI" and "endline" is the line number of the matched
+  "^AE".  The tree will have these properties:
+
+    1. child.rev > parent.rev
+    2. child.startline > parent.startline
+    3. child.endline < parent.endline
+
+  A similar tree for all deletions can also be built with the first property
+  changed to:
+
+    1. child.rev < parent.rev
+
+0.5 Malformed Cases
+
+  The following cases are considered malformed in our implementation:
+
+    1. Interleaved insertions, or interleaved deletions.
+       It can be rewritten to a non-interleaved tree structure.
+
+       Take insertions as example, deletions are similar:
+
+       ^AI x         ^AI x
+       a             a
+       ^AI x + 1  -> ^AI x + 1
+       b             b
+       ^AE x         ^AE x + 1
+       c             ^AE x
+       ^AE x + 1     ^AI x + 1
+                     c
+                     ^AE x + 1
+
+    2. Nested insertions, where the inner one has a smaller revision number.
+       Or nested deletions, where the inner one has a larger revision number.
+       It can be rewritten to a non-nested form.
+
+       Take insertions as example, deletions are similar:
+
+       ^AI x + 1     ^AI x + 1
+       a             a
+       ^AI x      -> ^AE x + 1
+       b             ^AI x
+       ^AE x         b
+       c             ^AE x
+       ^AE x + 1     ^AI x + 1
+                     c
+                     ^AE x + 1
+
+    3. Insertion inside deletion with a smaller revision number.
+
+       Rewrite by duplicating the content inserted:
+
+       ^AD x          ^AD x
+       a              a
+       ^AI x + 1  ->  b
+       b              c
+       ^AE x + 1      ^AE x
+       c              ^AI x + 1
+       ^AE x          b
+                      ^AE x + 1
+
+       Note: If "annotate" purely depends on "^AI" information, then the
+       duplication content will lose track of where "b" is originally from.
+
+  Some of them may be valid in other implementations for special purposes. For
+  example, to "revive" a previously deleted block in a newer revision.
+
+0.6 Cases Can Be Optimized
+
+  It's always better to get things nested. For example, the left is more
+  efficient than the right while they represent the same content:
+
+    +--- ^AD 2          +- ^AD 1
+    | +- ^AD 1          |   LINE A
+    | |   LINE A        +- ^AE 1
+    | +- ^AE 1          +- ^AD 2
+    |     LINE B        |   LINE B
+    +--- ^AE 2          +- ^AE 2
+
+  Our implementation sometimes generates the less efficient data. To always
+  get the optimal form, it requires extra code complexity that seems unworthy.
+
+0.7 Inefficiency
+
+  The file format can be slow because:
+
+  - Inserting a new line at position P requires rewriting all data after P.
+  - Finding "^AE" requires walking through the content (O(N), where N is the
+    number of lines between "^AI/D" and "^AE").
+
+1. Linelog
+
+  The linelog is a binary format that dedicates to speed up mercurial (or
+  git)'s "annotate" operation. It's designed to avoid issues mentioned in
+  section 0.7.
+
+1.1 Content Stored
+
+  Linelog is not another storage for file contents. It only stores line
+  numbers and corresponding revision numbers, instead of actual line content.
+  This is okay for the "annotate" operation because usually the external
+  source is fast to checkout the content of a file at a specific revision.
+
+  A typical SCCS weave is also fast on the "grep" operation, which needs
+  random accesses to line contents from different revisions of a file. This
+  can be slow with linelog's no-line-content design. However we could use
+  an extra map ((rev, line num) -> line content) to speed it up.
+
+  Note the revision numbers in linelog should be independent from mercurial
+  integer revision numbers. There should be some mapping between linelog rev
+  and hg hash stored side by side, to make the files reusable after being
+  copied to another machine.
+
+1.2 Basic Format
+
+  A linelog file consists of "instruction"s. An "instruction" can be either:
+
+    - JGE  REV ADDR     # jump to ADDR if rev >= REV
+    - JL   REV ADDR     # jump to ADDR if rev < REV
+    - LINE REV LINENUM  # append the (LINENUM+1)-th line in revision REV
+
+  For example, here is the example linelog representing the same file with
+  3 revisions mentioned in section 0.1:
+
+    SCCS  |    Linelog
+    Weave | Addr : Instruction
+    ------+------+-------------
+    ^AI 1 |    0 : JL   1 8
+    a     |    1 : LINE 1 0
+    ^AD 3 |    2 : JGE  3 6
+    b     |    3 : LINE 1 1
+    ^AI 2 |    4 : JL   2 7
+    1     |    5 : LINE 2 2
+    ^AE 3 |
+    2     |    6 : LINE 2 3
+    ^AE 2 |
+    c     |    7 : LINE 1 2
+    ^AE 1 |
+          |    8 : END
+
+  This way, "find ^AE" is O(1) because we just jump there. And we can insert
+  new lines without rewriting most part of the file by appending new lines and
+  changing a single instruction to jump to them.
+
+  The current implementation uses 64 bits for an instruction: The opcode (JGE,
+  JL or LINE) takes 2 bits, REV takes 30 bits and ADDR or LINENUM takes 32
+  bits. It also stores the max revision number and buffer size at the first
+  64 bits for quick access to these values.
+
+1.3 Comparing with Mercurial's revlog format
+
+  Apparently, linelog is very different from revlog: linelog stores rev and
+  line numbers, while revlog has line contents and other metadata (like
+  parents, flags). However, the revlog format could also be used to store rev
+  and line numbers. For example, to speed up the annotate operation, we could
+  also pre-calculate annotate results and just store them using the revlog
+  format.
+
+  Therefore, linelog is actually somehow similar to revlog, with the important
+  trade-off that it only supports linear history (mentioned in section 0.1).
+  Essentially, the differences are:
+
+    a) Linelog is full of deltas, while revlog could contain full file
+       contents sometimes. So linelog is smaller. Revlog could trade
+       reconstruction speed for file size - best case, revlog is as small as
+       linelog.
+    b) The interleaved delta structure allows skipping large portion of
+       uninteresting deltas so linelog's content reconstruction is faster than
+       the delta-only version of revlog (however it's possible to construct
+       a case where interleaved deltas degrade to plain deltas, so linelog
+       worst case would be delta-only revlog). Revlog could trade file size
+       for reconstruction speed.
+    c) Linelog implicitly maintains the order of all lines it stores. So it
+       could dump all the lines from all revisions, with a reasonable order.
+       While revlog could also dump all line additions, it requires extra
+       computation to figure out the order putting those lines - that's some
+       kind of "merge".
+
+  "c" makes "hg absorb" easier to implement and makes it possible to do
+  "annotate --deleted".
+
+1.4 Malformed Cases Handling
+
+  The following "case 1", "case 2", and "case 3" refer to cases mentioned
+  in section 0.5.
+
+  Using the exposed API (replacelines), case 1 is impossible to generate,
+  although it's possible to generate it by constructing rawdata and load that
+  via linelog.fromdata.
+
+  Doing annotate(maxrev) before replacelines (aka. a1, a2 passed to
+  replacelines are related to the latest revision) eliminates the possibility
+  of case 3. That makes sense since usually you'd like to make edits on top of
+  the latest revision. Practically, both absorb and fastannotate do this.
+
+  Doing annotate(maxrev), plus replacelines(rev, ...) where rev >= maxrev
+  eliminates the possibility of case 2. That makes sense since usually the
+  edits belong to "new revisions", not "old revisions". Practically,
+  fastannotate does this. Absorb calls replacelines with rev < maxrev to edit
+  past revisions. So it needs some extra care to not generate case 2.
+
+  If case 1 occurs, that probably means linelog file corruption (assuming
+  linelog is edited via public APIs) the checkout or annotate result could
+  be less meaningful or even error out, but linelog wouldn't enter an infinite
+  loop.
+
+  If either case 2 or 3 occurs, linelog works as if the inner "^AI/D" and "^AE"
+  operations on the left side are silently ignored.
--- a/mercurial/help/internals/wireprotocol.txt	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/help/internals/wireprotocol.txt	Wed Sep 26 20:33:09 2018 +0900
@@ -220,9 +220,10 @@
 Requests to unknown commands or URLS result in an HTTP 404.
 TODO formally define response type, how error is communicated, etc.
 
-HTTP request and response bodies use the *Unified Frame-Based Protocol*
-(defined below) for media exchange. The entirety of the HTTP message
-body is 0 or more frames as defined by this protocol.
+HTTP request and response bodies use the ``hgrpc`` protocol for media
+exchange.` (See :hg:`help internals.wireprotocolrpc` for details of
+the protocol.) The entirety of the HTTP message body is 0 or more frames
+as defined by this protocol.
 
 Clients and servers MUST advertise the ``TBD`` media type via the
 ``Content-Type`` request and response headers. In addition, clients MUST
@@ -236,11 +237,10 @@
 Servers receiving requests with an invalid ``Content-Type`` header SHOULD
 respond with an HTTP 415.
 
-The command to run is specified in the POST payload as defined by the
-*Unified Frame-Based Protocol*. This is redundant with data already
-encoded in the URL. This is by design, so server operators can have
-better understanding about server activity from looking merely at
-HTTP access logs.
+The command to run is specified in the POST payload as defined by ``hgrpc``.
+This is redundant with data already encoded in the URL. This is by design,
+so server operators can have better understanding about server activity from
+looking merely at HTTP access logs.
 
 In most circumstances, the command specified in the URL MUST match
 the command specified in the frame-based payload or the server will
@@ -254,9 +254,9 @@
 *any* command and allow the execution of multiple commands. If the
 HTTP request issues multiple commands across multiple frames, all
 issued commands will be processed by the server. Per the defined
-behavior of the *Unified Frame-Based Protocol*, commands may be
-issued interleaved and responses may come back in a different order
-than they were issued. Clients MUST be able to deal with this.
+behavior of ``hgrpc```, commands may be issued interleaved and responses
+may come back in a different order than they were issued. Clients MUST
+be able to deal with this.
 
 SSH Protocol
 ============
@@ -513,503 +513,6 @@
 Following capabilities advertisement, the peers communicate using version
 1 of the SSH transport.
 
-Unified Frame-Based Protocol
-============================
-
-**Experimental and under development**
-
-The *Unified Frame-Based Protocol* is a communications protocol between
-Mercurial peers. The protocol aims to be mostly transport agnostic
-(works similarly on HTTP, SSH, etc).
-
-To operate the protocol, a bi-directional, half-duplex pipe supporting
-ordered sends and receives is required. That is, each peer has one pipe
-for sending data and another for receiving.
-
-All data is read and written in atomic units called *frames*. These
-are conceptually similar to TCP packets. Higher-level functionality
-is built on the exchange and processing of frames.
-
-All frames are associated with a *stream*. A *stream* provides a
-unidirectional grouping of frames. Streams facilitate two goals:
-content encoding and parallelism. There is a dedicated section on
-streams below.
-
-The protocol is request-response based: the client issues requests to
-the server, which issues replies to those requests. Server-initiated
-messaging is not currently supported, but this specification carves
-out room to implement it.
-
-All frames are associated with a numbered request. Frames can thus
-be logically grouped by their request ID.
-
-Frames begin with an 8 octet header followed by a variable length
-payload::
-
-    +------------------------------------------------+
-    |                 Length (24)                    |
-    +--------------------------------+---------------+
-    |         Request ID (16)        | Stream ID (8) |
-    +------------------+-------------+---------------+
-    | Stream Flags (8) |
-    +-----------+------+
-    | Type (4)  |
-    +-----------+
-    | Flags (4) |
-    +===========+===================================================|
-    |                     Frame Payload (0...)                    ...
-    +---------------------------------------------------------------+
-
-The length of the frame payload is expressed as an unsigned 24 bit
-little endian integer. Values larger than 65535 MUST NOT be used unless
-given permission by the server as part of the negotiated capabilities
-during the handshake. The frame header is not part of the advertised
-frame length. The payload length is the over-the-wire length. If there
-is content encoding applied to the payload as part of the frame's stream,
-the length is the output of that content encoding, not the input.
-
-The 16-bit ``Request ID`` field denotes the integer request identifier,
-stored as an unsigned little endian integer. Odd numbered requests are
-client-initiated. Even numbered requests are server-initiated. This
-refers to where the *request* was initiated - not where the *frame* was
-initiated, so servers will send frames with odd ``Request ID`` in
-response to client-initiated requests. Implementations are advised to
-start ordering request identifiers at ``1`` and ``0``, increment by
-``2``, and wrap around if all available numbers have been exhausted.
-
-The 8-bit ``Stream ID`` field denotes the stream that the frame is
-associated with. Frames belonging to a stream may have content
-encoding applied and the receiver may need to decode the raw frame
-payload to obtain the original data. Odd numbered IDs are
-client-initiated. Even numbered IDs are server-initiated.
-
-The 8-bit ``Stream Flags`` field defines stream processing semantics.
-See the section on streams below.
-
-The 4-bit ``Type`` field denotes the type of frame being sent.
-
-The 4-bit ``Flags`` field defines special, per-type attributes for
-the frame.
-
-The sections below define the frame types and their behavior.
-
-Command Request (``0x01``)
---------------------------
-
-This frame contains a request to run a command.
-
-The payload consists of a CBOR map defining the command request. The
-bytestring keys of that map are:
-
-name
-   Name of the command that should be executed (bytestring).
-args
-   Map of bytestring keys to various value types containing the named
-   arguments to this command.
-
-   Each command defines its own set of argument names and their expected
-   types.
-
-This frame type MUST ONLY be sent from clients to servers: it is illegal
-for a server to send this frame to a client.
-
-The following flag values are defined for this type:
-
-0x01
-   New command request. When set, this frame represents the beginning
-   of a new request to run a command. The ``Request ID`` attached to this
-   frame MUST NOT be active.
-0x02
-   Command request continuation. When set, this frame is a continuation
-   from a previous command request frame for its ``Request ID``. This
-   flag is set when the CBOR data for a command request does not fit
-   in a single frame.
-0x04
-   Additional frames expected. When set, the command request didn't fit
-   into a single frame and additional CBOR data follows in a subsequent
-   frame.
-0x08
-   Command data frames expected. When set, command data frames are
-   expected to follow the final command request frame for this request.
-
-``0x01`` MUST be set on the initial command request frame for a
-``Request ID``.
-
-``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
-a series of command request frames.
-
-If command data frames are to be sent, ``0x08`` MUST be set on ALL
-command request frames.
-
-Command Data (``0x02``)
------------------------
-
-This frame contains raw data for a command.
-
-Most commands can be executed by specifying arguments. However,
-arguments have an upper bound to their length. For commands that
-accept data that is beyond this length or whose length isn't known
-when the command is initially sent, they will need to stream
-arbitrary data to the server. This frame type facilitates the sending
-of this data.
-
-The payload of this frame type consists of a stream of raw data to be
-consumed by the command handler on the server. The format of the data
-is command specific.
-
-The following flag values are defined for this type:
-
-0x01
-   Command data continuation. When set, the data for this command
-   continues into a subsequent frame.
-
-0x02
-   End of data. When set, command data has been fully sent to the
-   server. The command has been fully issued and no new data for this
-   command will be sent. The next frame will belong to a new command.
-
-Command Response Data (``0x03``)
---------------------------------
-
-This frame contains response data to an issued command.
-
-Response data ALWAYS consists of a series of 1 or more CBOR encoded
-values. A CBOR value may be using indefinite length encoding. And the
-bytes constituting the value may span several frames.
-
-The following flag values are defined for this type:
-
-0x01
-   Data continuation. When set, an additional frame containing response data
-   will follow.
-0x02
-   End of data. When set, the response data has been fully sent and
-   no additional frames for this response will be sent.
-
-The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
-
-Error Occurred (``0x05``)
--------------------------
-
-Some kind of error occurred.
-
-There are 3 general kinds of failures that can occur:
-
-* Command error encountered before any response issued
-* Command error encountered after a response was issued
-* Protocol or stream level error
-
-This frame type is used to capture the latter cases. (The general
-command error case is handled by the leading CBOR map in
-``Command Response`` frames.)
-
-The payload of this frame contains a CBOR map detailing the error. That
-map has the following bytestring keys:
-
-type
-   (bytestring) The overall type of error encountered. Can be one of the
-   following values:
-
-   protocol
-      A protocol-level error occurred. This typically means someone
-      is violating the framing protocol semantics and the server is
-      refusing to proceed.
-
-   server
-      A server-level error occurred. This typically indicates some kind of
-      logic error on the server, likely the fault of the server.
-
-   command
-      A command-level error, likely the fault of the client.
-
-message
-   (array of maps) A richly formatted message that is intended for
-   human consumption. See the ``Human Output Side-Channel`` frame
-   section for a description of the format of this data structure.
-
-Human Output Side-Channel (``0x06``)
-------------------------------------
-
-This frame contains a message that is intended to be displayed to
-people. Whereas most frames communicate machine readable data, this
-frame communicates textual data that is intended to be shown to
-humans.
-
-The frame consists of a series of *formatting requests*. Each formatting
-request consists of a formatting string, arguments for that formatting
-string, and labels to apply to that formatting string.
-
-A formatting string is a printf()-like string that allows variable
-substitution within the string. Labels allow the rendered text to be
-*decorated*. Assuming use of the canonical Mercurial code base, a
-formatting string can be the input to the ``i18n._`` function. This
-allows messages emitted from the server to be localized. So even if
-the server has different i18n settings, people could see messages in
-their *native* settings. Similarly, the use of labels allows
-decorations like coloring and underlining to be applied using the
-client's configured rendering settings.
-
-Formatting strings are similar to ``printf()`` strings or how
-Python's ``%`` operator works. The only supported formatting sequences
-are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
-at that position resolves to. ``%%`` will be replaced by ``%``. All
-other 2-byte sequences beginning with ``%`` represent a literal
-``%`` followed by that character. However, future versions of the
-wire protocol reserve the right to allow clients to opt in to receiving
-formatting strings with additional formatters, hence why ``%%`` is
-required to represent the literal ``%``.
-
-The frame payload consists of a CBOR array of CBOR maps. Each map
-defines an *atom* of text data to print. Each *atom* has the following
-bytestring keys:
-
-msg
-   (bytestring) The formatting string. Content MUST be ASCII.
-args (optional)
-   Array of bytestrings defining arguments to the formatting string.
-labels (optional)
-   Array of bytestrings defining labels to apply to this atom.
-
-All data to be printed MUST be encoded into a single frame: this frame
-does not support spanning data across multiple frames.
-
-All textual data encoded in these frames is assumed to be line delimited.
-The last atom in the frame SHOULD end with a newline (``\n``). If it
-doesn't, clients MAY add a newline to facilitate immediate printing.
-
-Progress Update (``0x07``)
---------------------------
-
-This frame holds the progress of an operation on the peer. Consumption
-of these frames allows clients to display progress bars, estimated
-completion times, etc.
-
-Each frame defines the progress of a single operation on the peer. The
-payload consists of a CBOR map with the following bytestring keys:
-
-topic
-   Topic name (string)
-pos
-   Current numeric position within the topic (integer)
-total
-   Total/end numeric position of this topic (unsigned integer)
-label (optional)
-   Unit label (string)
-item (optional)
-   Item name (string)
-
-Progress state is created when a frame is received referencing a
-*topic* that isn't currently tracked. Progress tracking for that
-*topic* is finished when a frame is received reporting the current
-position of that topic as ``-1``.
-
-Multiple *topics* may be active at any given time.
-
-Rendering of progress information is not mandated or governed by this
-specification: implementations MAY render progress information however
-they see fit, including not at all.
-
-The string data describing the topic SHOULD be static strings to
-facilitate receivers localizing that string data. The emitter
-MUST normalize all string data to valid UTF-8 and receivers SHOULD
-validate that received data conforms to UTF-8. The topic name
-SHOULD be ASCII.
-
-Stream Encoding Settings (``0x08``)
------------------------------------
-
-This frame type holds information defining the content encoding
-settings for a *stream*.
-
-This frame type is likely consumed by the protocol layer and is not
-passed on to applications.
-
-This frame type MUST ONLY occur on frames having the *Beginning of Stream*
-``Stream Flag`` set.
-
-The payload of this frame defines what content encoding has (possibly)
-been applied to the payloads of subsequent frames in this stream.
-
-The payload begins with an 8-bit integer defining the length of the
-encoding *profile*, followed by the string name of that profile, which
-must be an ASCII string. All bytes that follow can be used by that
-profile for supplemental settings definitions. See the section below
-on defined encoding profiles.
-
-Stream States and Flags
------------------------
-
-Streams can be in two states: *open* and *closed*. An *open* stream
-is active and frames attached to that stream could arrive at any time.
-A *closed* stream is not active. If a frame attached to a *closed*
-stream arrives, that frame MUST have an appropriate stream flag
-set indicating beginning of stream. All streams are in the *closed*
-state by default.
-
-The ``Stream Flags`` field denotes a set of bit flags for defining
-the relationship of this frame within a stream. The following flags
-are defined:
-
-0x01
-   Beginning of stream. The first frame in the stream MUST set this
-   flag. When received, the ``Stream ID`` this frame is attached to
-   becomes ``open``.
-
-0x02
-   End of stream. The last frame in a stream MUST set this flag. When
-   received, the ``Stream ID`` this frame is attached to becomes
-   ``closed``. Any content encoding context associated with this stream
-   can be destroyed after processing the payload of this frame.
-
-0x04
-   Apply content encoding. When set, any content encoding settings
-   defined by the stream should be applied when attempting to read
-   the frame. When not set, the frame payload isn't encoded.
-
-Streams
--------
-
-Streams - along with ``Request IDs`` - facilitate grouping of frames.
-But the purpose of each is quite different and the groupings they
-constitute are independent.
-
-A ``Request ID`` is essentially a tag. It tells you which logical
-request a frame is associated with.
-
-A *stream* is a sequence of frames grouped for the express purpose
-of applying a stateful encoding or for denoting sub-groups of frames.
-
-Unlike ``Request ID``s which span the request and response, a stream
-is unidirectional and stream IDs are independent from client to
-server.
-
-There is no strict hierarchical relationship between ``Request IDs``
-and *streams*. A stream can contain frames having multiple
-``Request IDs``. Frames belonging to the same ``Request ID`` can
-span multiple streams.
-
-One goal of streams is to facilitate content encoding. A stream can
-define an encoding to be applied to frame payloads. For example, the
-payload transmitted over the wire may contain output from a
-zstandard compression operation and the receiving end may decompress
-that payload to obtain the original data.
-
-The other goal of streams is to facilitate concurrent execution. For
-example, a server could spawn 4 threads to service a request that can
-be easily parallelized. Each of those 4 threads could write into its
-own stream. Those streams could then in turn be delivered to 4 threads
-on the receiving end, with each thread consuming its stream in near
-isolation. The *main* thread on both ends merely does I/O and
-encodes/decodes frame headers: the bulk of the work is done by worker
-threads.
-
-In addition, since content encoding is defined per stream, each
-*worker thread* could perform potentially CPU bound work concurrently
-with other threads. This approach of applying encoding at the
-sub-protocol / stream level eliminates a potential resource constraint
-on the protocol stream as a whole (it is common for the throughput of
-a compression engine to be smaller than the throughput of a network).
-
-Having multiple streams - each with their own encoding settings - also
-facilitates the use of advanced data compression techniques. For
-example, a transmitter could see that it is generating data faster
-and slower than the receiving end is consuming it and adjust its
-compression settings to trade CPU for compression ratio accordingly.
-
-While streams can define a content encoding, not all frames within
-that stream must use that content encoding. This can be useful when
-data is being served from caches and being derived dynamically. A
-cache could pre-compressed data so the server doesn't have to
-recompress it. The ability to pick and choose which frames are
-compressed allows servers to easily send data to the wire without
-involving potentially expensive encoding overhead.
-
-Content Encoding Profiles
--------------------------
-
-Streams can have named content encoding *profiles* associated with
-them. A profile defines a shared understanding of content encoding
-settings and behavior.
-
-The following profiles are defined:
-
-TBD
-
-Command Protocol
-----------------
-
-A client can request that a remote run a command by sending it
-frames defining that command. This logical stream is composed of
-1 or more ``Command Request`` frames and and 0 or more ``Command Data``
-frames.
-
-All frames composing a single command request MUST be associated with
-the same ``Request ID``.
-
-Clients MAY send additional command requests without waiting on the
-response to a previous command request. If they do so, they MUST ensure
-that the ``Request ID`` field of outbound frames does not conflict
-with that of an active ``Request ID`` whose response has not yet been
-fully received.
-
-Servers MAY respond to commands in a different order than they were
-sent over the wire. Clients MUST be prepared to deal with this. Servers
-also MAY start executing commands in a different order than they were
-received, or MAY execute multiple commands concurrently.
-
-If there is a dependency between commands or a race condition between
-commands executing (e.g. a read-only command that depends on the results
-of a command that mutates the repository), then clients MUST NOT send
-frames issuing a command until a response to all dependent commands has
-been received.
-TODO think about whether we should express dependencies between commands
-to avoid roundtrip latency.
-
-A command is defined by a command name, 0 or more command arguments,
-and optional command data.
-
-Arguments are the recommended mechanism for transferring fixed sets of
-parameters to a command. Data is appropriate for transferring variable
-data. Thinking in terms of HTTP, arguments would be headers and data
-would be the message body.
-
-It is recommended for servers to delay the dispatch of a command
-until all argument have been received. Servers MAY impose limits on the
-maximum argument size.
-TODO define failure mechanism.
-
-Servers MAY dispatch to commands immediately once argument data
-is available or delay until command data is received in full.
-
-Once a ``Command Request`` frame is sent, a client must be prepared to
-receive any of the following frames associated with that request:
-``Command Response``, ``Error Response``, ``Human Output Side-Channel``,
-``Progress Update``.
-
-The *main* response for a command will be in ``Command Response`` frames.
-The payloads of these frames consist of 1 or more CBOR encoded values.
-The first CBOR value on the first ``Command Response`` frame is special
-and denotes the overall status of the command. This CBOR map contains
-the following bytestring keys:
-
-status
-   (bytestring) A well-defined message containing the overall status of
-   this command request. The following values are defined:
-
-   ok
-      The command was received successfully and its response follows.
-   error
-      There was an error processing the command. More details about the
-      error are encoded in the ``error`` key.
-
-error (optional)
-   A map containing information about an encountered error. The map has the
-   following keys:
-
-   message
-      (array of maps) A message describing the error. The message uses the
-      same format as those in the ``Human Output Side-Channel`` frame.
-
 Capabilities
 ============
 
@@ -1379,6 +882,9 @@
 This section contains a list of all wire protocol commands implemented by
 the canonical Mercurial server.
 
+See :hg:`help internals.wireprotocolv2` for information on commands exposed
+to the frame-based protocol.
+
 batch
 -----
 
@@ -1750,164 +1256,3 @@
 
 The server may also respond with a generic error type, which contains a string
 indicating the failure.
-
-Frame-Based Protocol Commands
-=============================
-
-**Experimental and under active development**
-
-This section documents the wire protocol commands exposed to transports
-using the frame-based protocol. The set of commands exposed through
-these transports is distinct from the set of commands exposed to legacy
-transports.
-
-The frame-based protocol uses CBOR to encode command execution requests.
-All command arguments must be mapped to a specific or set of CBOR data
-types.
-
-The response to many commands is also CBOR. There is no common response
-format: each command defines its own response format.
-
-TODO require node type be specified, as N bytes of binary node value
-could be ambiguous once SHA-1 is replaced.
-
-branchmap
----------
-
-Obtain heads in named branches.
-
-Receives no arguments.
-
-The response is a map with bytestring keys defining the branch name.
-Values are arrays of bytestring defining raw changeset nodes.
-
-capabilities
-------------
-
-Obtain the server's capabilities.
-
-Receives no arguments.
-
-This command is typically called only as part of the handshake during
-initial connection establishment.
-
-The response is a map with bytestring keys defining server information.
-
-The defined keys are:
-
-commands
-   A map defining available wire protocol commands on this server.
-
-   Keys in the map are the names of commands that can be invoked. Values
-   are maps defining information about that command. The bytestring keys
-   are:
-
-      args
-         A map of argument names and their expected types.
-
-         Types are defined as a representative value for the expected type.
-         e.g. an argument expecting a boolean type will have its value
-         set to true. An integer type will have its value set to 42. The
-         actual values are arbitrary and may not have meaning.
-      permissions
-         An array of permissions required to execute this command.
-
-compression
-   An array of maps defining available compression format support.
-
-   The array is sorted from most preferred to least preferred.
-
-   Each entry has the following bytestring keys:
-
-      name
-         Name of the compression engine. e.g. ``zstd`` or ``zlib``.
-
-framingmediatypes
-   An array of bytestrings defining the supported framing protocol
-   media types. Servers will not accept media types not in this list.
-
-rawrepoformats
-   An array of storage formats the repository is using. This set of
-   requirements can be used to determine whether a client can read a
-   *raw* copy of file data available.
-
-heads
------
-
-Obtain DAG heads in the repository.
-
-The command accepts the following arguments:
-
-publiconly (optional)
-   (boolean) If set, operate on the DAG for public phase changesets only.
-   Non-public (i.e. draft) phase DAG heads will not be returned.
-
-The response is a CBOR array of bytestrings defining changeset nodes
-of DAG heads. The array can be empty if the repository is empty or no
-changesets satisfied the request.
-
-TODO consider exposing phase of heads in response
-
-known
------
-
-Determine whether a series of changeset nodes is known to the server.
-
-The command accepts the following arguments:
-
-nodes
-   (array of bytestrings) List of changeset nodes whose presence to
-   query.
-
-The response is a bytestring where each byte contains a 0 or 1 for the
-corresponding requested node at the same index.
-
-TODO use a bit array for even more compact response
-
-listkeys
---------
-
-List values in a specified ``pushkey`` namespace.
-
-The command receives the following arguments:
-
-namespace
-   (bytestring) Pushkey namespace to query.
-
-The response is a map with bytestring keys and values.
-
-TODO consider using binary to represent nodes in certain pushkey namespaces.
-
-lookup
-------
-
-Try to resolve a value to a changeset revision.
-
-Unlike ``known`` which operates on changeset nodes, lookup operates on
-node fragments and other names that a user may use.
-
-The command receives the following arguments:
-
-key
-   (bytestring) Value to try to resolve.
-
-On success, returns a bytestring containing the resolved node.
-
-pushkey
--------
-
-Set a value using the ``pushkey`` protocol.
-
-The command receives the following arguments:
-
-namespace
-   (bytestring) Pushkey namespace to operate on.
-key
-   (bytestring) The pushkey key to set.
-old
-   (bytestring) Old value for this key.
-new
-   (bytestring) New value for this key.
-
-TODO consider using binary to represent nodes is certain pushkey namespaces.
-TODO better define response type and meaning.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/help/internals/wireprotocolrpc.txt	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,519 @@
+**Experimental and under development**
+
+This document describe's Mercurial's transport-agnostic remote procedure
+call (RPC) protocol which is used to perform interactions with remote
+servers. This protocol is also referred to as ``hgrpc``.
+
+The protocol has the following high-level features:
+
+* Concurrent request and response support (multiple commands can be issued
+  simultaneously and responses can be streamed simultaneously).
+* Supports half-duplex and full-duplex connections.
+* All data is transmitted within *frames*, which have a well-defined
+  header and encode their length.
+* Side-channels for sending progress updates and printing output. Text
+  output from the remote can be localized locally.
+* Support for simultaneous and long-lived compression streams, even across
+  requests.
+* Uses CBOR for data exchange.
+
+The protocol is not specific to Mercurial and could be used by other
+applications.
+
+High-level Overview
+===================
+
+To operate the protocol, a bi-directional, half-duplex pipe supporting
+ordered sends and receives is required. That is, each peer has one pipe
+for sending data and another for receiving. Full-duplex pipes are also
+supported.
+
+All data is read and written in atomic units called *frames*. These
+are conceptually similar to TCP packets. Higher-level functionality
+is built on the exchange and processing of frames.
+
+All frames are associated with a *stream*. A *stream* provides a
+unidirectional grouping of frames. Streams facilitate two goals:
+content encoding and parallelism. There is a dedicated section on
+streams below.
+
+The protocol is request-response based: the client issues requests to
+the server, which issues replies to those requests. Server-initiated
+messaging is not currently supported, but this specification carves
+out room to implement it.
+
+All frames are associated with a numbered request. Frames can thus
+be logically grouped by their request ID.
+
+Frames
+======
+
+Frames begin with an 8 octet header followed by a variable length
+payload::
+
+    +------------------------------------------------+
+    |                 Length (24)                    |
+    +--------------------------------+---------------+
+    |         Request ID (16)        | Stream ID (8) |
+    +------------------+-------------+---------------+
+    | Stream Flags (8) |
+    +-----------+------+
+    | Type (4)  |
+    +-----------+
+    | Flags (4) |
+    +===========+===================================================|
+    |                     Frame Payload (0...)                    ...
+    +---------------------------------------------------------------+
+
+The length of the frame payload is expressed as an unsigned 24 bit
+little endian integer. Values larger than 65535 MUST NOT be used unless
+given permission by the server as part of the negotiated capabilities
+during the handshake. The frame header is not part of the advertised
+frame length. The payload length is the over-the-wire length. If there
+is content encoding applied to the payload as part of the frame's stream,
+the length is the output of that content encoding, not the input.
+
+The 16-bit ``Request ID`` field denotes the integer request identifier,
+stored as an unsigned little endian integer. Odd numbered requests are
+client-initiated. Even numbered requests are server-initiated. This
+refers to where the *request* was initiated - not where the *frame* was
+initiated, so servers will send frames with odd ``Request ID`` in
+response to client-initiated requests. Implementations are advised to
+start ordering request identifiers at ``1`` and ``0``, increment by
+``2``, and wrap around if all available numbers have been exhausted.
+
+The 8-bit ``Stream ID`` field denotes the stream that the frame is
+associated with. Frames belonging to a stream may have content
+encoding applied and the receiver may need to decode the raw frame
+payload to obtain the original data. Odd numbered IDs are
+client-initiated. Even numbered IDs are server-initiated.
+
+The 8-bit ``Stream Flags`` field defines stream processing semantics.
+See the section on streams below.
+
+The 4-bit ``Type`` field denotes the type of frame being sent.
+
+The 4-bit ``Flags`` field defines special, per-type attributes for
+the frame.
+
+The sections below define the frame types and their behavior.
+
+Command Request (``0x01``)
+--------------------------
+
+This frame contains a request to run a command.
+
+The payload consists of a CBOR map defining the command request. The
+bytestring keys of that map are:
+
+name
+   Name of the command that should be executed (bytestring).
+args
+   Map of bytestring keys to various value types containing the named
+   arguments to this command.
+
+   Each command defines its own set of argument names and their expected
+   types.
+
+This frame type MUST ONLY be sent from clients to servers: it is illegal
+for a server to send this frame to a client.
+
+The following flag values are defined for this type:
+
+0x01
+   New command request. When set, this frame represents the beginning
+   of a new request to run a command. The ``Request ID`` attached to this
+   frame MUST NOT be active.
+0x02
+   Command request continuation. When set, this frame is a continuation
+   from a previous command request frame for its ``Request ID``. This
+   flag is set when the CBOR data for a command request does not fit
+   in a single frame.
+0x04
+   Additional frames expected. When set, the command request didn't fit
+   into a single frame and additional CBOR data follows in a subsequent
+   frame.
+0x08
+   Command data frames expected. When set, command data frames are
+   expected to follow the final command request frame for this request.
+
+``0x01`` MUST be set on the initial command request frame for a
+``Request ID``.
+
+``0x01`` or ``0x02`` MUST be set to indicate this frame's role in
+a series of command request frames.
+
+If command data frames are to be sent, ``0x08`` MUST be set on ALL
+command request frames.
+
+Command Data (``0x02``)
+-----------------------
+
+This frame contains raw data for a command.
+
+Most commands can be executed by specifying arguments. However,
+arguments have an upper bound to their length. For commands that
+accept data that is beyond this length or whose length isn't known
+when the command is initially sent, they will need to stream
+arbitrary data to the server. This frame type facilitates the sending
+of this data.
+
+The payload of this frame type consists of a stream of raw data to be
+consumed by the command handler on the server. The format of the data
+is command specific.
+
+The following flag values are defined for this type:
+
+0x01
+   Command data continuation. When set, the data for this command
+   continues into a subsequent frame.
+
+0x02
+   End of data. When set, command data has been fully sent to the
+   server. The command has been fully issued and no new data for this
+   command will be sent. The next frame will belong to a new command.
+
+Command Response Data (``0x03``)
+--------------------------------
+
+This frame contains response data to an issued command.
+
+Response data ALWAYS consists of a series of 1 or more CBOR encoded
+values. A CBOR value may be using indefinite length encoding. And the
+bytes constituting the value may span several frames.
+
+The following flag values are defined for this type:
+
+0x01
+   Data continuation. When set, an additional frame containing response data
+   will follow.
+0x02
+   End of data. When set, the response data has been fully sent and
+   no additional frames for this response will be sent.
+
+The ``0x01`` flag is mutually exclusive with the ``0x02`` flag.
+
+Error Occurred (``0x05``)
+-------------------------
+
+Some kind of error occurred.
+
+There are 3 general kinds of failures that can occur:
+
+* Command error encountered before any response issued
+* Command error encountered after a response was issued
+* Protocol or stream level error
+
+This frame type is used to capture the latter cases. (The general
+command error case is handled by the leading CBOR map in
+``Command Response`` frames.)
+
+The payload of this frame contains a CBOR map detailing the error. That
+map has the following bytestring keys:
+
+type
+   (bytestring) The overall type of error encountered. Can be one of the
+   following values:
+
+   protocol
+      A protocol-level error occurred. This typically means someone
+      is violating the framing protocol semantics and the server is
+      refusing to proceed.
+
+   server
+      A server-level error occurred. This typically indicates some kind of
+      logic error on the server, likely the fault of the server.
+
+   command
+      A command-level error, likely the fault of the client.
+
+message
+   (array of maps) A richly formatted message that is intended for
+   human consumption. See the ``Human Output Side-Channel`` frame
+   section for a description of the format of this data structure.
+
+Human Output Side-Channel (``0x06``)
+------------------------------------
+
+This frame contains a message that is intended to be displayed to
+people. Whereas most frames communicate machine readable data, this
+frame communicates textual data that is intended to be shown to
+humans.
+
+The frame consists of a series of *formatting requests*. Each formatting
+request consists of a formatting string, arguments for that formatting
+string, and labels to apply to that formatting string.
+
+A formatting string is a printf()-like string that allows variable
+substitution within the string. Labels allow the rendered text to be
+*decorated*. Assuming use of the canonical Mercurial code base, a
+formatting string can be the input to the ``i18n._`` function. This
+allows messages emitted from the server to be localized. So even if
+the server has different i18n settings, people could see messages in
+their *native* settings. Similarly, the use of labels allows
+decorations like coloring and underlining to be applied using the
+client's configured rendering settings.
+
+Formatting strings are similar to ``printf()`` strings or how
+Python's ``%`` operator works. The only supported formatting sequences
+are ``%s`` and ``%%``. ``%s`` will be replaced by whatever the string
+at that position resolves to. ``%%`` will be replaced by ``%``. All
+other 2-byte sequences beginning with ``%`` represent a literal
+``%`` followed by that character. However, future versions of the
+wire protocol reserve the right to allow clients to opt in to receiving
+formatting strings with additional formatters, hence why ``%%`` is
+required to represent the literal ``%``.
+
+The frame payload consists of a CBOR array of CBOR maps. Each map
+defines an *atom* of text data to print. Each *atom* has the following
+bytestring keys:
+
+msg
+   (bytestring) The formatting string. Content MUST be ASCII.
+args (optional)
+   Array of bytestrings defining arguments to the formatting string.
+labels (optional)
+   Array of bytestrings defining labels to apply to this atom.
+
+All data to be printed MUST be encoded into a single frame: this frame
+does not support spanning data across multiple frames.
+
+All textual data encoded in these frames is assumed to be line delimited.
+The last atom in the frame SHOULD end with a newline (``\n``). If it
+doesn't, clients MAY add a newline to facilitate immediate printing.
+
+Progress Update (``0x07``)
+--------------------------
+
+This frame holds the progress of an operation on the peer. Consumption
+of these frames allows clients to display progress bars, estimated
+completion times, etc.
+
+Each frame defines the progress of a single operation on the peer. The
+payload consists of a CBOR map with the following bytestring keys:
+
+topic
+   Topic name (string)
+pos
+   Current numeric position within the topic (integer)
+total
+   Total/end numeric position of this topic (unsigned integer)
+label (optional)
+   Unit label (string)
+item (optional)
+   Item name (string)
+
+Progress state is created when a frame is received referencing a
+*topic* that isn't currently tracked. Progress tracking for that
+*topic* is finished when a frame is received reporting the current
+position of that topic as ``-1``.
+
+Multiple *topics* may be active at any given time.
+
+Rendering of progress information is not mandated or governed by this
+specification: implementations MAY render progress information however
+they see fit, including not at all.
+
+The string data describing the topic SHOULD be static strings to
+facilitate receivers localizing that string data. The emitter
+MUST normalize all string data to valid UTF-8 and receivers SHOULD
+validate that received data conforms to UTF-8. The topic name
+SHOULD be ASCII.
+
+Stream Encoding Settings (``0x08``)
+-----------------------------------
+
+This frame type holds information defining the content encoding
+settings for a *stream*.
+
+This frame type is likely consumed by the protocol layer and is not
+passed on to applications.
+
+This frame type MUST ONLY occur on frames having the *Beginning of Stream*
+``Stream Flag`` set.
+
+The payload of this frame defines what content encoding has (possibly)
+been applied to the payloads of subsequent frames in this stream.
+
+The payload begins with an 8-bit integer defining the length of the
+encoding *profile*, followed by the string name of that profile, which
+must be an ASCII string. All bytes that follow can be used by that
+profile for supplemental settings definitions. See the section below
+on defined encoding profiles.
+
+Stream States and Flags
+=======================
+
+Streams can be in two states: *open* and *closed*. An *open* stream
+is active and frames attached to that stream could arrive at any time.
+A *closed* stream is not active. If a frame attached to a *closed*
+stream arrives, that frame MUST have an appropriate stream flag
+set indicating beginning of stream. All streams are in the *closed*
+state by default.
+
+The ``Stream Flags`` field denotes a set of bit flags for defining
+the relationship of this frame within a stream. The following flags
+are defined:
+
+0x01
+   Beginning of stream. The first frame in the stream MUST set this
+   flag. When received, the ``Stream ID`` this frame is attached to
+   becomes ``open``.
+
+0x02
+   End of stream. The last frame in a stream MUST set this flag. When
+   received, the ``Stream ID`` this frame is attached to becomes
+   ``closed``. Any content encoding context associated with this stream
+   can be destroyed after processing the payload of this frame.
+
+0x04
+   Apply content encoding. When set, any content encoding settings
+   defined by the stream should be applied when attempting to read
+   the frame. When not set, the frame payload isn't encoded.
+
+Streams
+=======
+
+Streams - along with ``Request IDs`` - facilitate grouping of frames.
+But the purpose of each is quite different and the groupings they
+constitute are independent.
+
+A ``Request ID`` is essentially a tag. It tells you which logical
+request a frame is associated with.
+
+A *stream* is a sequence of frames grouped for the express purpose
+of applying a stateful encoding or for denoting sub-groups of frames.
+
+Unlike ``Request ID``s which span the request and response, a stream
+is unidirectional and stream IDs are independent from client to
+server.
+
+There is no strict hierarchical relationship between ``Request IDs``
+and *streams*. A stream can contain frames having multiple
+``Request IDs``. Frames belonging to the same ``Request ID`` can
+span multiple streams.
+
+One goal of streams is to facilitate content encoding. A stream can
+define an encoding to be applied to frame payloads. For example, the
+payload transmitted over the wire may contain output from a
+zstandard compression operation and the receiving end may decompress
+that payload to obtain the original data.
+
+The other goal of streams is to facilitate concurrent execution. For
+example, a server could spawn 4 threads to service a request that can
+be easily parallelized. Each of those 4 threads could write into its
+own stream. Those streams could then in turn be delivered to 4 threads
+on the receiving end, with each thread consuming its stream in near
+isolation. The *main* thread on both ends merely does I/O and
+encodes/decodes frame headers: the bulk of the work is done by worker
+threads.
+
+In addition, since content encoding is defined per stream, each
+*worker thread* could perform potentially CPU bound work concurrently
+with other threads. This approach of applying encoding at the
+sub-protocol / stream level eliminates a potential resource constraint
+on the protocol stream as a whole (it is common for the throughput of
+a compression engine to be smaller than the throughput of a network).
+
+Having multiple streams - each with their own encoding settings - also
+facilitates the use of advanced data compression techniques. For
+example, a transmitter could see that it is generating data faster
+and slower than the receiving end is consuming it and adjust its
+compression settings to trade CPU for compression ratio accordingly.
+
+While streams can define a content encoding, not all frames within
+that stream must use that content encoding. This can be useful when
+data is being served from caches and being derived dynamically. A
+cache could pre-compressed data so the server doesn't have to
+recompress it. The ability to pick and choose which frames are
+compressed allows servers to easily send data to the wire without
+involving potentially expensive encoding overhead.
+
+Content Encoding Profiles
+=========================
+
+Streams can have named content encoding *profiles* associated with
+them. A profile defines a shared understanding of content encoding
+settings and behavior.
+
+The following profiles are defined:
+
+TBD
+
+Command Protocol
+================
+
+A client can request that a remote run a command by sending it
+frames defining that command. This logical stream is composed of
+1 or more ``Command Request`` frames and and 0 or more ``Command Data``
+frames.
+
+All frames composing a single command request MUST be associated with
+the same ``Request ID``.
+
+Clients MAY send additional command requests without waiting on the
+response to a previous command request. If they do so, they MUST ensure
+that the ``Request ID`` field of outbound frames does not conflict
+with that of an active ``Request ID`` whose response has not yet been
+fully received.
+
+Servers MAY respond to commands in a different order than they were
+sent over the wire. Clients MUST be prepared to deal with this. Servers
+also MAY start executing commands in a different order than they were
+received, or MAY execute multiple commands concurrently.
+
+If there is a dependency between commands or a race condition between
+commands executing (e.g. a read-only command that depends on the results
+of a command that mutates the repository), then clients MUST NOT send
+frames issuing a command until a response to all dependent commands has
+been received.
+TODO think about whether we should express dependencies between commands
+to avoid roundtrip latency.
+
+A command is defined by a command name, 0 or more command arguments,
+and optional command data.
+
+Arguments are the recommended mechanism for transferring fixed sets of
+parameters to a command. Data is appropriate for transferring variable
+data. Thinking in terms of HTTP, arguments would be headers and data
+would be the message body.
+
+It is recommended for servers to delay the dispatch of a command
+until all argument have been received. Servers MAY impose limits on the
+maximum argument size.
+TODO define failure mechanism.
+
+Servers MAY dispatch to commands immediately once argument data
+is available or delay until command data is received in full.
+
+Once a ``Command Request`` frame is sent, a client must be prepared to
+receive any of the following frames associated with that request:
+``Command Response``, ``Error Response``, ``Human Output Side-Channel``,
+``Progress Update``.
+
+The *main* response for a command will be in ``Command Response`` frames.
+The payloads of these frames consist of 1 or more CBOR encoded values.
+The first CBOR value on the first ``Command Response`` frame is special
+and denotes the overall status of the command. This CBOR map contains
+the following bytestring keys:
+
+status
+   (bytestring) A well-defined message containing the overall status of
+   this command request. The following values are defined:
+
+   ok
+      The command was received successfully and its response follows.
+   error
+      There was an error processing the command. More details about the
+      error are encoded in the ``error`` key.
+
+error (optional)
+   A map containing information about an encountered error. The map has the
+   following keys:
+
+   message
+      (array of maps) A message describing the error. The message uses the
+      same format as those in the ``Human Output Side-Channel`` frame.
+
+TODO formalize when error frames can be seen and how errors can be
+recognized midway through a command response.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/help/internals/wireprotocolv2.txt	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,507 @@
+**Experimental and under active development**
+
+This section documents the wire protocol commands exposed to transports
+using the frame-based protocol. The set of commands exposed through
+these transports is distinct from the set of commands exposed to legacy
+transports.
+
+The frame-based protocol uses CBOR to encode command execution requests.
+All command arguments must be mapped to a specific or set of CBOR data
+types.
+
+The response to many commands is also CBOR. There is no common response
+format: each command defines its own response format.
+
+TODOs
+=====
+
+* Add "node namespace" support to each command. In order to support
+  SHA-1 hash transition, we want servers to be able to expose different
+  "node namespaces" for the same data. Every command operating on nodes
+  should specify which "node namespace" it is operating on and responses
+  should encode the "node namespace" accordingly.
+
+Commands
+========
+
+The sections below detail all commands available to wire protocol version
+2.
+
+branchmap
+---------
+
+Obtain heads in named branches.
+
+Receives no arguments.
+
+The response is a map with bytestring keys defining the branch name.
+Values are arrays of bytestring defining raw changeset nodes.
+
+capabilities
+------------
+
+Obtain the server's capabilities.
+
+Receives no arguments.
+
+This command is typically called only as part of the handshake during
+initial connection establishment.
+
+The response is a map with bytestring keys defining server information.
+
+The defined keys are:
+
+commands
+   A map defining available wire protocol commands on this server.
+
+   Keys in the map are the names of commands that can be invoked. Values
+   are maps defining information about that command. The bytestring keys
+   are:
+
+      args
+         (map) Describes arguments accepted by the command.
+
+         Keys are bytestrings denoting the argument name.
+
+         Values are maps describing the argument. The map has the following
+         bytestring keys:
+
+         default
+            (varied) The default value for this argument if not specified. Only
+            present if ``required`` is not true.
+
+         required
+            (boolean) Whether the argument must be specified. Failure to send
+            required arguments will result in an error executing the command.
+
+         type
+            (bytestring) The type of the argument. e.g. ``bytes`` or ``bool``.
+
+         validvalues
+            (set) Values that are recognized for this argument. Some arguments
+            only allow a fixed set of values to be specified. These arguments
+            may advertise that set in this key. If this set is advertised and
+            a value not in this set is specified, the command should result
+            in error.
+
+      permissions
+         An array of permissions required to execute this command.
+
+compression
+   An array of maps defining available compression format support.
+
+   The array is sorted from most preferred to least preferred.
+
+   Each entry has the following bytestring keys:
+
+      name
+         Name of the compression engine. e.g. ``zstd`` or ``zlib``.
+
+framingmediatypes
+   An array of bytestrings defining the supported framing protocol
+   media types. Servers will not accept media types not in this list.
+
+pathfilterprefixes
+   (set of bytestring) Matcher prefixes that are recognized when performing
+   path filtering. Specifying a path filter whose type/prefix does not
+   match one in this set will likely be rejected by the server.
+
+rawrepoformats
+   An array of storage formats the repository is using. This set of
+   requirements can be used to determine whether a client can read a
+   *raw* copy of file data available.
+
+changesetdata
+-------------
+
+Obtain various data related to changesets.
+
+The command accepts the following arguments:
+
+noderange
+   (array of arrays of bytestrings) An array of 2 elements, each being an
+   array of node bytestrings. The first array denotes the changelog revisions
+   that are already known to the client. The second array denotes the changelog
+   revision DAG heads to fetch. The argument essentially defines a DAG range
+   bounded by root and head nodes to fetch.
+
+   The roots array may be empty. The heads array must be defined.
+
+nodes
+   (array of bytestrings) Changelog revisions to request explicitly.
+
+nodesdepth
+   (unsigned integer) Number of ancestor revisions of elements in ``nodes``
+   to also fetch. When defined, for each element in ``nodes``, DAG ancestors
+   will be walked until at most N total revisions are emitted.
+
+fields
+   (set of bytestring) Which data associated with changelog revisions to
+   fetch. The following values are recognized:
+
+   bookmarks
+      Bookmarks associated with a revision.
+
+   parents
+      Parent revisions.
+
+   phase
+      The phase state of a revision.
+
+   revision
+      The raw, revision data for the changelog entry. The hash of this data
+      will match the revision's node value.
+
+The server resolves the set of revisions relevant to the request by taking
+the union of the ``noderange`` and ``nodes`` arguments. At least one of these
+arguments must be defined.
+
+The response bytestream starts with a CBOR map describing the data that follows.
+This map has the following bytestring keys:
+
+totalitems
+   (unsigned integer) Total number of changelog revisions whose data is being
+   transferred. This maps to the set of revisions in the requested node
+   range, not the total number of records that follow (see below for why).
+
+Following the map header is a series of 0 or more CBOR values. If values
+are present, the first value will always be a map describing a single changeset
+revision.
+
+If the ``fieldsfollowing`` key is present, the map will immediately be followed
+by N CBOR bytestring values, where N is the number of elements in
+``fieldsfollowing``. Each bytestring value corresponds to a field denoted
+by ``fieldsfollowing``.
+
+Following the optional bytestring field values is the next revision descriptor
+map, or end of stream.
+
+Each revision descriptor map has the following bytestring keys:
+
+node
+   (bytestring) The node value for this revision. This is the SHA-1 hash of
+   the raw revision data.
+
+bookmarks (optional)
+   (array of bytestrings) Bookmarks attached to this revision. Only present
+   if ``bookmarks`` data is being requested and the revision has bookmarks
+   attached.
+
+fieldsfollowing (optional)
+   (array of 2-array) Denotes what fields immediately follow this map. Each
+   value is an array with 2 elements: the bytestring field name and an unsigned
+   integer describing the length of the data, in bytes.
+
+   If this key isn't present, no special fields will follow this map.
+
+   The following fields may be present:
+
+   revision
+      Raw, revision data for the changelog entry. Contains a serialized form
+      of the changeset data, including the author, date, commit message, set
+      of changed files, manifest node, and other metadata.
+
+      Only present if the ``revision`` field was requested.
+
+parents (optional)
+   (array of bytestrings) The nodes representing the parent revisions of this
+   revision. Only present if ``parents`` data is being requested.
+
+phase (optional)
+   (bytestring) The phase that a revision is in. Recognized values are
+   ``secret``, ``draft``, and ``public``. Only present if ``phase`` data
+   is being requested.
+
+If nodes are requested via ``noderange``, they will be emitted in DAG order,
+parents always before children.
+
+If nodes are requested via ``nodes``, they will be emitted in requested order.
+
+Nodes from ``nodes`` are emitted before nodes from ``noderange``.
+
+The set of changeset revisions emitted may not match the exact set of
+changesets requested. Furthermore, the set of keys present on each
+map may vary. This is to facilitate emitting changeset updates as well
+as new revisions.
+
+For example, if the request wants ``phase`` and ``revision`` data,
+the response may contain entries for each changeset in the common nodes
+set with the ``phase`` key and without the ``revision`` key in order
+to reflect a phase-only update.
+
+TODO support different revision selection mechanisms (e.g. non-public, specific
+revisions)
+TODO support different hash "namespaces" for revisions (e.g. sha-1 versus other)
+TODO support emitting obsolescence data
+TODO support filtering based on relevant paths (narrow clone)
+TODO support hgtagsfnodes cache / tags data
+TODO support branch heads cache
+TODO consider unify query mechanism. e.g. as an array of "query descriptors"
+rather than a set of top-level arguments that have semantics when combined.
+
+filedata
+--------
+
+Obtain various data related to an individual tracked file.
+
+The command accepts the following arguments:
+
+fields
+   (set of bytestring) Which data associated with a file to fetch.
+   The following values are recognized:
+
+   parents
+      Parent nodes for the revision.
+
+   revision
+      The raw revision data for a file.
+
+haveparents
+   (bool) Whether the client has the parent revisions of all requested
+   nodes. If set, the server may emit revision data as deltas against
+   any parent revision. If not set, the server MUST only emit deltas for
+   revisions previously emitted by this command.
+
+   False is assumed in the absence of any value.
+
+nodes
+   (array of bytestrings) File nodes whose data to retrieve.
+
+path
+   (bytestring) Path of the tracked file whose data to retrieve.
+
+TODO allow specifying revisions via alternate means (such as from
+changeset revisions or ranges)
+
+The response bytestream starts with a CBOR map describing the data that
+follows. It has the following bytestream keys:
+
+totalitems
+   (unsigned integer) Total number of file revisions whose data is
+   being returned.
+
+Following the map header is a series of 0 or more CBOR values. If values
+are present, the first value will always be a map describing a single changeset
+revision.
+
+If the ``fieldsfollowing`` key is present, the map will immediately be followed
+by N CBOR bytestring values, where N is the number of elements in
+``fieldsfollowing``. Each bytestring value corresponds to a field denoted
+by ``fieldsfollowing``.
+
+Following the optional bytestring field values is the next revision descriptor
+map, or end of stream.
+
+Each revision descriptor map has the following bytestring keys:
+
+Each map has the following bytestring keys:
+
+node
+   (bytestring) The node of the file revision whose data is represented.
+
+deltabasenode
+   (bytestring) Node of the file revision the following delta is against.
+
+   Only present if the ``revision`` field is requested and delta data
+   follows this map.
+
+fieldsfollowing
+   (array of 2-array) Denotes extra bytestring fields that following this map.
+   See the documentation for ``changesetdata`` for semantics.
+
+   The following named fields may be present:
+
+   ``delta``
+      The delta data to use to construct the fulltext revision.
+
+      Only present if the ``revision`` field is requested and a delta is
+      being emitted. The ``deltabasenode`` top-level key will also be
+      present if this field is being emitted.
+
+   ``revision``
+      The fulltext revision data for this manifest. Only present if the
+      ``revision`` field is requested and a fulltext revision is being emitted.
+
+parents
+   (array of bytestring) The nodes of the parents of this file revision.
+
+   Only present if the ``parents`` field is requested.
+
+When ``revision`` data is requested, the server chooses to emit either fulltext
+revision data or a delta. What the server decides can be inferred by looking
+for the presence of the ``delta`` or ``revision`` keys in the
+``fieldsfollowing`` array.
+
+heads
+-----
+
+Obtain DAG heads in the repository.
+
+The command accepts the following arguments:
+
+publiconly (optional)
+   (boolean) If set, operate on the DAG for public phase changesets only.
+   Non-public (i.e. draft) phase DAG heads will not be returned.
+
+The response is a CBOR array of bytestrings defining changeset nodes
+of DAG heads. The array can be empty if the repository is empty or no
+changesets satisfied the request.
+
+TODO consider exposing phase of heads in response
+
+known
+-----
+
+Determine whether a series of changeset nodes is known to the server.
+
+The command accepts the following arguments:
+
+nodes
+   (array of bytestrings) List of changeset nodes whose presence to
+   query.
+
+The response is a bytestring where each byte contains a 0 or 1 for the
+corresponding requested node at the same index.
+
+TODO use a bit array for even more compact response
+
+listkeys
+--------
+
+List values in a specified ``pushkey`` namespace.
+
+The command receives the following arguments:
+
+namespace
+   (bytestring) Pushkey namespace to query.
+
+The response is a map with bytestring keys and values.
+
+TODO consider using binary to represent nodes in certain pushkey namespaces.
+
+lookup
+------
+
+Try to resolve a value to a changeset revision.
+
+Unlike ``known`` which operates on changeset nodes, lookup operates on
+node fragments and other names that a user may use.
+
+The command receives the following arguments:
+
+key
+   (bytestring) Value to try to resolve.
+
+On success, returns a bytestring containing the resolved node.
+
+manifestdata
+------------
+
+Obtain various data related to manifests (which are lists of files in
+a revision).
+
+The command accepts the following arguments:
+
+fields
+   (set of bytestring) Which data associated with manifests to fetch.
+   The following values are recognized:
+
+   parents
+      Parent nodes for the manifest.
+
+   revision
+      The raw revision data for the manifest.
+
+haveparents
+   (bool) Whether the client has the parent revisions of all requested
+   nodes. If set, the server may emit revision data as deltas against
+   any parent revision. If not set, the server MUST only emit deltas for
+   revisions previously emitted by this command.
+
+   False is assumed in the absence of any value.
+
+nodes
+   (array of bytestring) Manifest nodes whose data to retrieve.
+
+tree
+   (bytestring) Path to manifest to retrieve. The empty bytestring represents
+   the root manifest. All other values represent directories/trees within
+   the repository.
+
+TODO allow specifying revisions via alternate means (such as from changeset
+revisions or ranges)
+TODO consider recursive expansion of manifests (with path filtering for
+narrow use cases)
+
+The response bytestream starts with a CBOR map describing the data that
+follows. It has the following bytestring keys:
+
+totalitems
+   (unsigned integer) Total number of manifest revisions whose data is
+   being returned.
+
+Following the map header is a series of 0 or more CBOR values. If values
+are present, the first value will always be a map describing a single manifest
+revision.
+
+If the ``fieldsfollowing`` key is present, the map will immediately be followed
+by N CBOR bytestring values, where N is the number of elements in
+``fieldsfollowing``. Each bytestring value corresponds to a field denoted
+by ``fieldsfollowing``.
+
+Following the optional bytestring field values is the next revision descriptor
+map, or end of stream.
+
+Each revision descriptor map has the following bytestring keys:
+
+node
+   (bytestring) The node of the manifest revision whose data is represented.
+
+deltabasenode
+   (bytestring) The node that the delta representation of this revision is
+   computed against. Only present if the ``revision`` field is requested and
+   a delta is being emitted.
+
+fieldsfollowing
+   (array of 2-array) Denotes extra bytestring fields that following this map.
+   See the documentation for ``changesetdata`` for semantics.
+
+   The following named fields may be present:
+
+   ``delta``
+      The delta data to use to construct the fulltext revision.
+
+      Only present if the ``revision`` field is requested and a delta is
+      being emitted. The ``deltabasenode`` top-level key will also be
+      present if this field is being emitted.
+
+   ``revision``
+      The fulltext revision data for this manifest. Only present if the
+      ``revision`` field is requested and a fulltext revision is being emitted.
+
+parents
+   (array of bytestring) The nodes of the parents of this manifest revision.
+   Only present if the ``parents`` field is requested.
+
+When ``revision`` data is requested, the server chooses to emit either fulltext
+revision data or a delta. What the server decides can be inferred by looking
+for the presence of ``delta`` or ``revision`` in the ``fieldsfollowing`` array.
+
+pushkey
+-------
+
+Set a value using the ``pushkey`` protocol.
+
+The command receives the following arguments:
+
+namespace
+   (bytestring) Pushkey namespace to operate on.
+key
+   (bytestring) The pushkey key to set.
+old
+   (bytestring) Old value for this key.
+new
+   (bytestring) New value for this key.
+
+TODO consider using binary to represent nodes is certain pushkey namespaces.
+TODO better define response type and meaning.
--- a/mercurial/help/merge-tools.txt	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/help/merge-tools.txt	Wed Sep 26 20:33:09 2018 +0900
@@ -36,8 +36,9 @@
 
 .. internaltoolsmarker
 
-Internal tools are always available and do not require a GUI but will by default
-not handle symlinks or binary files.
+Internal tools are always available and do not require a GUI but will
+by default not handle symlinks or binary files. See next section for
+detail about "actual capabilities" described above.
 
 Choosing a merge tool
 =====================
@@ -54,8 +55,7 @@
 
 3. If the filename of the file to be merged matches any of the patterns in the
    merge-patterns configuration section, the first usable merge tool
-   corresponding to a matching pattern is used. Here, binary capabilities of the
-   merge tool are not considered.
+   corresponding to a matching pattern is used.
 
 4. If ui.merge is set it will be considered next. If the value is not the name
    of a configured tool, the specified value is used and must be executable by
@@ -72,6 +72,30 @@
 
 8. Otherwise, ``:prompt`` is used.
 
+For historical reason, Mercurial treats merge tools as below while
+examining rules above.
+
+==== =============== ====== =======
+step specified via   binary symlink
+==== =============== ====== =======
+1.   --tool          o/o    o/o
+2.   HGMERGE         o/o    o/o
+3.   merge-patterns  o/o(*) x/?(*)
+4.   ui.merge        x/?(*) x/?(*)
+==== =============== ====== =======
+
+Each capability column indicates Mercurial behavior for
+internal/external merge tools at examining each rule.
+
+- "o": "assume that a tool has capability"
+- "x": "assume that a tool does not have capability"
+- "?": "check actual capability of a tool"
+
+If ``merge.strict-capability-check`` configuration is true, Mercurial
+checks capabilities of merge tools strictly in (*) cases above (= each
+capability column becomes "?/?"). It is false by default for backward
+compatibility.
+
 .. note::
 
    After selecting a merge program, Mercurial will by default attempt
--- a/mercurial/hg.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/hg.py	Wed Sep 26 20:33:09 2018 +0900
@@ -35,6 +35,7 @@
     logcmdutil,
     logexchange,
     merge as mergemod,
+    narrowspec,
     node,
     phases,
     scmutil,
@@ -158,35 +159,49 @@
 wirepeersetupfuncs = []
 
 def _peerorrepo(ui, path, create=False, presetupfuncs=None,
-                intents=None):
+                intents=None, createopts=None):
     """return a repository object for the specified path"""
-    obj = _peerlookup(path).instance(ui, path, create, intents=intents)
+    obj = _peerlookup(path).instance(ui, path, create, intents=intents,
+                                     createopts=createopts)
     ui = getattr(obj, "ui", ui)
+    if ui.configbool('devel', 'debug.extensions'):
+        log = lambda msg, *values: ui.debug('debug.extensions: ',
+            msg % values, label='debug.extensions')
+    else:
+        log = lambda *a, **kw: None
     for f in presetupfuncs or []:
         f(ui, obj)
-    for name, module in extensions.extensions(ui):
-        hook = getattr(module, 'reposetup', None)
-        if hook:
-            hook(ui, obj)
+    log('- executing reposetup hooks\n')
+    with util.timedcm('all reposetup') as allreposetupstats:
+        for name, module in extensions.extensions(ui):
+            log('  - running reposetup for %s\n' % (name,))
+            hook = getattr(module, 'reposetup', None)
+            if hook:
+                with util.timedcm('reposetup %r', name) as stats:
+                    hook(ui, obj)
+                log('  > reposetup for %r took %s\n', name, stats)
+    log('> all reposetup took %s\n', allreposetupstats)
     if not obj.local():
         for f in wirepeersetupfuncs:
             f(ui, obj)
     return obj
 
-def repository(ui, path='', create=False, presetupfuncs=None, intents=None):
+def repository(ui, path='', create=False, presetupfuncs=None, intents=None,
+               createopts=None):
     """return a repository object for the specified path"""
     peer = _peerorrepo(ui, path, create, presetupfuncs=presetupfuncs,
-                       intents=intents)
+                       intents=intents, createopts=createopts)
     repo = peer.local()
     if not repo:
         raise error.Abort(_("repository '%s' is not local") %
                          (path or peer.url()))
     return repo.filtered('visible')
 
-def peer(uiorrepo, opts, path, create=False, intents=None):
+def peer(uiorrepo, opts, path, create=False, intents=None, createopts=None):
     '''return a repository peer for the specified path'''
     rui = remoteui(uiorrepo, opts)
-    return _peerorrepo(rui, path, create, intents=intents).peer()
+    return _peerorrepo(rui, path, create, intents=intents,
+                       createopts=createopts).peer()
 
 def defaultdest(source):
     '''return default destination of clone if none is given
@@ -258,7 +273,7 @@
         raise error.Abort(_('destination already exists'))
 
     if not destwvfs.isdir():
-        destwvfs.mkdir()
+        destwvfs.makedirs()
     destvfs.makedir()
 
     requirements = ''
@@ -292,6 +307,11 @@
     """convert a shared repository to a normal one
 
     Copy the store data to the repo and remove the sharedpath data.
+
+    Returns a new repository object representing the unshared repository.
+
+    The passed repository object is not usable after this function is
+    called.
     """
 
     destlock = lock = None
@@ -314,16 +334,22 @@
         destlock and destlock.release()
         lock and lock.release()
 
-    # update store, spath, svfs and sjoin of repo
-    repo.unfiltered().__init__(repo.baseui, repo.root)
+    # Removing share changes some fundamental properties of the repo instance.
+    # So we instantiate a new repo object and operate on it rather than
+    # try to keep the existing repo usable.
+    newrepo = repository(repo.baseui, repo.root, create=False)
 
     # TODO: figure out how to access subrepos that exist, but were previously
     #       removed from .hgsub
-    c = repo['.']
+    c = newrepo['.']
     subs = c.substate
     for s in sorted(subs):
         c.sub(s).unshare()
 
+    localrepo.poisonrepository(repo)
+
+    return newrepo
+
 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
     """Called after a new shared repo is created.
 
@@ -373,31 +399,30 @@
     try:
         hardlink = None
         topic = _('linking') if hardlink else _('copying')
-        progress = ui.makeprogress(topic)
-        num = 0
-        srcpublishing = srcrepo.publishing()
-        srcvfs = vfsmod.vfs(srcrepo.sharedpath)
-        dstvfs = vfsmod.vfs(destpath)
-        for f in srcrepo.store.copylist():
-            if srcpublishing and f.endswith('phaseroots'):
-                continue
-            dstbase = os.path.dirname(f)
-            if dstbase and not dstvfs.exists(dstbase):
-                dstvfs.mkdir(dstbase)
-            if srcvfs.exists(f):
-                if f.endswith('data'):
-                    # 'dstbase' may be empty (e.g. revlog format 0)
-                    lockfile = os.path.join(dstbase, "lock")
-                    # lock to avoid premature writing to the target
-                    destlock = lock.lock(dstvfs, lockfile)
-                hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
-                                             hardlink, progress)
-                num += n
-        if hardlink:
-            ui.debug("linked %d files\n" % num)
-        else:
-            ui.debug("copied %d files\n" % num)
-        progress.complete()
+        with ui.makeprogress(topic) as progress:
+            num = 0
+            srcpublishing = srcrepo.publishing()
+            srcvfs = vfsmod.vfs(srcrepo.sharedpath)
+            dstvfs = vfsmod.vfs(destpath)
+            for f in srcrepo.store.copylist():
+                if srcpublishing and f.endswith('phaseroots'):
+                    continue
+                dstbase = os.path.dirname(f)
+                if dstbase and not dstvfs.exists(dstbase):
+                    dstvfs.mkdir(dstbase)
+                if srcvfs.exists(f):
+                    if f.endswith('data'):
+                        # 'dstbase' may be empty (e.g. revlog format 0)
+                        lockfile = os.path.join(dstbase, "lock")
+                        # lock to avoid premature writing to the target
+                        destlock = lock.lock(dstvfs, lockfile)
+                    hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
+                                                 hardlink, progress)
+                    num += n
+            if hardlink:
+                ui.debug("linked %d files\n" % num)
+            else:
+                ui.debug("copied %d files\n" % num)
         return destlock
     except: # re-raises
         release(destlock)
@@ -487,7 +512,8 @@
         util.copyfile(srcbranchcache, dstbranchcache)
 
 def clone(ui, peeropts, source, dest=None, pull=False, revs=None,
-          update=True, stream=False, branch=None, shareopts=None):
+          update=True, stream=False, branch=None, shareopts=None,
+          storeincludepats=None, storeexcludepats=None):
     """Make a copy of an existing repository.
 
     Create a copy of an existing repository in a new directory.  The
@@ -529,6 +555,13 @@
     repository. "identity" means the name is derived from the node of the first
     changeset in the repository. "remote" means the name is derived from the
     remote's path/URL. Defaults to "identity."
+
+    storeincludepats and storeexcludepats: sets of file patterns to include and
+    exclude in the repository copy, respectively. If not defined, all files
+    will be included (a "full" clone). Otherwise a "narrow" clone containing
+    only the requested files will be performed. If ``storeincludepats`` is not
+    defined but ``storeexcludepats`` is, ``storeincludepats`` is assumed to be
+    ``path:.``. If both are empty sets, no files will be cloned.
     """
 
     if isinstance(source, bytes):
@@ -561,6 +594,24 @@
         elif destvfs.listdir():
             raise error.Abort(_("destination '%s' is not empty") % dest)
 
+    createopts = {}
+    narrow = False
+
+    if storeincludepats is not None:
+        narrowspec.validatepatterns(storeincludepats)
+        narrow = True
+
+    if storeexcludepats is not None:
+        narrowspec.validatepatterns(storeexcludepats)
+        narrow = True
+
+    if narrow:
+        # Include everything by default if only exclusion patterns defined.
+        if storeexcludepats and not storeincludepats:
+            storeincludepats = {'path:.'}
+
+        createopts['narrowfiles'] = True
+
     shareopts = shareopts or {}
     sharepool = shareopts.get('pool')
     sharenamemode = shareopts.get('mode')
@@ -592,6 +643,11 @@
             raise error.Abort(_('unknown share naming mode: %s') %
                               sharenamemode)
 
+        # TODO this is a somewhat arbitrary restriction.
+        if narrow:
+            ui.status(_('(pooled storage not supported for narrow clones)\n'))
+            sharepath = None
+
         if sharepath:
             return clonewithshare(ui, peeropts, sharepath, source, srcpeer,
                                   dest, pull=pull, rev=revs, update=update,
@@ -612,6 +668,10 @@
             and not phases.hassecret(srcrepo)):
             copy = not pull and not revs
 
+        # TODO this is a somewhat arbitrary restriction.
+        if narrow:
+            copy = False
+
         if copy:
             try:
                 # we use a lock here because if we race with commit, we
@@ -626,7 +686,7 @@
             srcrepo.hook('preoutgoing', throw=True, source='clone')
             hgdir = os.path.realpath(os.path.join(dest, ".hg"))
             if not os.path.exists(dest):
-                os.mkdir(dest)
+                util.makedirs(dest)
             else:
                 # only clean up directories we create ourselves
                 cleandir = hgdir
@@ -658,8 +718,9 @@
                           node=node.hex(node.nullid))
         else:
             try:
-                destpeer = peer(srcrepo or ui, peeropts, dest, create=True)
-                                # only pass ui when no srcrepo
+                # only pass ui when no srcrepo
+                destpeer = peer(srcrepo or ui, peeropts, dest, create=True,
+                                createopts=createopts)
             except OSError as inst:
                 if inst.errno == errno.EEXIST:
                     cleandir = None
@@ -687,6 +748,10 @@
                 revs = None
             local = destpeer.local()
             if local:
+                if narrow:
+                    with local.lock():
+                        local.setnarrowpats(storeincludepats, storeexcludepats)
+
                 u = util.url(abspath)
                 defaulturl = bytes(u)
                 local.ui.setconfig('paths', 'default', defaulturl, 'clone')
@@ -699,8 +764,16 @@
                 overrides = {('ui', 'quietbookmarkmove'): True}
                 with local.ui.configoverride(overrides, 'clone'):
                     exchange.pull(local, srcpeer, revs,
-                                  streamclonerequested=stream)
+                                  streamclonerequested=stream,
+                                  includepats=storeincludepats,
+                                  excludepats=storeexcludepats)
             elif srcrepo:
+                # TODO lift restriction once exchange.push() accepts narrow
+                # push.
+                if narrow:
+                    raise error.Abort(_('narrow clone not available for '
+                                        'remote destinations'))
+
                 exchange.push(srcrepo, destpeer, revs=revs,
                               bookmarks=srcrepo._bookmarks.keys())
             else:
--- a/mercurial/hgweb/hgweb_mod.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/hgweb/hgweb_mod.py	Wed Sep 26 20:33:09 2018 +0900
@@ -140,11 +140,6 @@
         if not staticurl.endswith('/'):
             staticurl += '/'
 
-        # some functions for the templater
-
-        def motd(**map):
-            yield self.config('web', 'motd')
-
         # figure out which style to use
 
         vars = {}
@@ -177,12 +172,16 @@
             'urlbase': req.advertisedbaseurl,
             'repo': self.reponame,
             'encoding': encoding.encoding,
-            'motd': motd,
             'sessionvars': sessionvars,
             'pathdef': makebreadcrumb(req.apppath),
             'style': style,
             'nonce': self.nonce,
         }
+        templatekeyword = registrar.templatekeyword(defaults)
+        @templatekeyword('motd', requires=())
+        def motd(context, mapping):
+            yield self.config('web', 'motd')
+
         tres = formatter.templateresources(self.repo.ui, self.repo)
         tmpl = templater.templater.frommapfile(mapfile,
                                                filters=filters,
@@ -436,10 +435,14 @@
             res.status = '404 Not Found'
             res.headers['Content-Type'] = ctype
             return rctx.sendtemplate('error', error=msg)
-        except (error.RepoError, error.RevlogError) as e:
+        except (error.RepoError, error.StorageError) as e:
             res.status = '500 Internal Server Error'
             res.headers['Content-Type'] = ctype
             return rctx.sendtemplate('error', error=pycompat.bytestr(e))
+        except error.Abort as e:
+            res.status = '403 Forbidden'
+            res.headers['Content-Type'] = ctype
+            return rctx.sendtemplate('error', error=pycompat.bytestr(e))
         except ErrorResponse as e:
             for k, v in e.headers:
                 res.headers[k] = v
--- a/mercurial/hgweb/hgwebdir_mod.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/hgweb/hgwebdir_mod.py	Wed Sep 26 20:33:09 2018 +0900
@@ -33,6 +33,7 @@
     hg,
     profiling,
     pycompat,
+    registrar,
     scmutil,
     templater,
     templateutil,
@@ -382,8 +383,7 @@
                     fname = virtual[7:]
                 else:
                     fname = req.qsparams['static']
-                static = self.ui.config("web", "static", None,
-                                        untrusted=False)
+                static = self.ui.config("web", "static", untrusted=False)
                 if not static:
                     tp = self.templatepath or templater.templatepaths()
                     if isinstance(tp, str):
@@ -495,12 +495,6 @@
 
     def templater(self, req, nonce):
 
-        def motd(**map):
-            if self.motd is not None:
-                yield self.motd
-            else:
-                yield config('web', 'motd')
-
         def config(section, name, default=uimod._unset, untrusted=True):
             return self.ui.config(section, name, default, untrusted)
 
@@ -520,7 +514,6 @@
 
         defaults = {
             "encoding": encoding.encoding,
-            "motd": motd,
             "url": req.apppath + '/',
             "logourl": logourl,
             "logoimg": logoimg,
@@ -529,5 +522,13 @@
             "style": style,
             "nonce": nonce,
         }
+        templatekeyword = registrar.templatekeyword(defaults)
+        @templatekeyword('motd', requires=())
+        def motd(context, mapping):
+            if self.motd is not None:
+                yield self.motd
+            else:
+                yield config('web', 'motd')
+
         tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
         return tmpl
--- a/mercurial/hgweb/server.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/hgweb/server.py	Wed Sep 26 20:33:09 2018 +0900
@@ -101,8 +101,8 @@
         try:
             self.do_write()
         except Exception:
-            self._start_response("500 Internal Server Error", [])
-            self._write("Internal Server Error")
+            self._start_response(r"500 Internal Server Error", [])
+            self._write(b"Internal Server Error")
             self._done()
             tb = r"".join(traceback.format_exception(*sys.exc_info()))
             # We need a native-string newline to poke in the log
--- a/mercurial/hgweb/webcommands.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/hgweb/webcommands.py	Wed Sep 26 20:33:09 2018 +0900
@@ -143,7 +143,7 @@
     f = fctx.path()
     text = fctx.data()
     parity = paritygen(web.stripecount)
-    ishead = fctx.filerev() in fctx.filelog().headrevs()
+    ishead = fctx.filenode() in fctx.filelog().heads()
 
     if stringutil.binary(text):
         mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
@@ -215,7 +215,7 @@
 
         def revgen():
             cl = web.repo.changelog
-            for i in xrange(len(web.repo) - 1, 0, -100):
+            for i in pycompat.xrange(len(web.repo) - 1, 0, -100):
                 l = []
                 for j in cl.revs(max(0, i - 99), i):
                     ctx = web.repo[j]
@@ -294,7 +294,7 @@
 
         for ctx in searchfunc[0](funcarg):
             count += 1
-            n = ctx.node()
+            n = scmutil.binnode(ctx)
             showtags = webutil.showtag(web.repo, 'changelogtag', n)
             files = webutil.listfilediffs(ctx.files(), n, web.maxfiles)
 
@@ -521,7 +521,7 @@
         symrev = 'tip'
     path = webutil.cleanpath(web.repo, web.req.qsparams.get('file', ''))
     mf = ctx.manifest()
-    node = ctx.node()
+    node = scmutil.binnode(ctx)
 
     files = {}
     dirs = {}
@@ -868,7 +868,7 @@
     leftrev = parent.rev()
     leftnode = parent.node()
     rightrev = ctx.rev()
-    rightnode = ctx.node()
+    rightnode = scmutil.binnode(ctx)
     if path in ctx:
         fctx = ctx[path]
         rightlines = filelines(fctx)
@@ -922,7 +922,7 @@
     fctx = webutil.filectx(web.repo, web.req)
     f = fctx.path()
     parity = paritygen(web.stripecount)
-    ishead = fctx.filerev() in fctx.filelog().headrevs()
+    ishead = fctx.filenode() in fctx.filelog().heads()
 
     # parents() is called once per line and several lines likely belong to
     # same revision. So it is worth caching.
@@ -1221,7 +1221,7 @@
     fname = web.req.qsparams['file']
     # a repo owner may set web.static in .hg/hgrc to get any file
     # readable by the user running the CGI script
-    static = web.config("web", "static", None, untrusted=False)
+    static = web.config("web", "static", untrusted=False)
     if not static:
         tp = web.templatepath or templater.templatepaths()
         if isinstance(tp, str):
--- a/mercurial/hgweb/webutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/hgweb/webutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -320,7 +320,8 @@
 
 def cleanpath(repo, path):
     path = path.lstrip('/')
-    return pathutil.canonpath(repo.root, '', path)
+    auditor = pathutil.pathauditor(repo.root, realfs=False)
+    return pathutil.canonpath(repo.root, '', path, auditor=auditor)
 
 def changectx(repo, req):
     changeid = "tip"
@@ -408,8 +409,14 @@
 
 whyunstable._requires = {'repo', 'ctx'}
 
+# helper to mark a function as a new-style template keyword; can be removed
+# once old-style function gets unsupported and new-style becomes the default
+def _kwfunc(f):
+    f._requires = ()
+    return f
+
 def commonentry(repo, ctx):
-    node = ctx.node()
+    node = scmutil.binnode(ctx)
     return {
         # TODO: perhaps ctx.changectx() should be assigned if ctx is a
         # filectx, but I'm not pretty sure if that would always work because
@@ -432,8 +439,8 @@
         'branches': nodebranchdict(repo, ctx),
         'tags': nodetagsdict(repo, node),
         'bookmarks': nodebookmarksdict(repo, node),
-        'parent': lambda **x: parents(ctx),
-        'child': lambda **x: children(ctx),
+        'parent': _kwfunc(lambda context, mapping: parents(ctx)),
+        'child': _kwfunc(lambda context, mapping: children(ctx)),
     }
 
 def changelistentry(web, ctx):
@@ -444,15 +451,15 @@
     '''
     repo = web.repo
     rev = ctx.rev()
-    n = ctx.node()
+    n = scmutil.binnode(ctx)
     showtags = showtag(repo, 'changelogtag', n)
     files = listfilediffs(ctx.files(), n, web.maxfiles)
 
     entry = commonentry(repo, ctx)
     entry.update(
-        allparents=lambda **x: parents(ctx),
-        parent=lambda **x: parents(ctx, rev - 1),
-        child=lambda **x: children(ctx, rev + 1),
+        allparents=_kwfunc(lambda context, mapping: parents(ctx)),
+        parent=_kwfunc(lambda context, mapping: parents(ctx, rev - 1)),
+        child=_kwfunc(lambda context, mapping: children(ctx, rev + 1)),
         changelogtag=showtags,
         files=files,
     )
@@ -478,7 +485,7 @@
     if 'node' in req.qsparams:
         return templatefilters.revescape(req.qsparams['node'])
     else:
-        return short(ctx.node())
+        return short(scmutil.binnode(ctx))
 
 def _listfilesgen(context, ctx, stripecount):
     parity = paritygen(stripecount)
@@ -494,8 +501,9 @@
 def changesetentry(web, ctx):
     '''Obtain a dictionary to be used to render the "changeset" template.'''
 
-    showtags = showtag(web.repo, 'changesettag', ctx.node())
-    showbookmarks = showbookmark(web.repo, 'changesetbookmark', ctx.node())
+    showtags = showtag(web.repo, 'changesettag', scmutil.binnode(ctx))
+    showbookmarks = showbookmark(web.repo, 'changesetbookmark',
+                                 scmutil.binnode(ctx))
     showbranch = nodebranchnodefault(ctx)
 
     basectx = basechangectx(web.repo, web.req)
@@ -521,7 +529,7 @@
         changesetbranch=showbranch,
         files=templateutil.mappedgenerator(_listfilesgen,
                                            args=(ctx, web.stripecount)),
-        diffsummary=lambda **x: diffsummary(diffstatsgen),
+        diffsummary=_kwfunc(lambda context, mapping: diffsummary(diffstatsgen)),
         diffstat=diffstats,
         archives=web.archivelist(ctx.hex()),
         **pycompat.strkwargs(commonentry(web.repo, ctx)))
@@ -613,21 +621,21 @@
         len1 = lhi - llo
         len2 = rhi - rlo
         count = min(len1, len2)
-        for i in xrange(count):
+        for i in pycompat.xrange(count):
             yield _compline(type=type,
                             leftlineno=llo + i + 1,
                             leftline=leftlines[llo + i],
                             rightlineno=rlo + i + 1,
                             rightline=rightlines[rlo + i])
         if len1 > len2:
-            for i in xrange(llo + count, lhi):
+            for i in pycompat.xrange(llo + count, lhi):
                 yield _compline(type=type,
                                 leftlineno=i + 1,
                                 leftline=leftlines[i],
                                 rightlineno=None,
                                 rightline=None)
         elif len2 > len1:
-            for i in xrange(rlo + count, rhi):
+            for i in pycompat.xrange(rlo + count, rhi):
                 yield _compline(type=type,
                                 leftlineno=None,
                                 leftline=None,
--- a/mercurial/hook.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/hook.py	Wed Sep 26 20:33:09 2018 +0900
@@ -150,7 +150,7 @@
     if repo:
         cwd = repo.root
     else:
-        cwd = pycompat.getcwd()
+        cwd = encoding.getcwd()
     r = ui.system(cmd, environ=env, cwd=cwd, blockedtag='exthook-%s' % (name,))
 
     duration = util.timer() - starttime
--- a/mercurial/httppeer.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/httppeer.py	Wed Sep 26 20:33:09 2018 +0900
@@ -16,9 +16,6 @@
 import weakref
 
 from .i18n import _
-from .thirdparty import (
-    cbor,
-)
 from . import (
     bundle2,
     error,
@@ -35,7 +32,9 @@
     wireprotov2server,
 )
 from .utils import (
+    cborutil,
     interfaceutil,
+    stringutil,
 )
 
 httplib = util.httplib
@@ -64,7 +63,7 @@
     result = []
 
     n = 0
-    for i in xrange(0, len(value), valuelen):
+    for i in pycompat.xrange(0, len(value), valuelen):
         n += 1
         result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
 
@@ -85,9 +84,10 @@
             except httplib.IncompleteRead as e:
                 # e.expected is an integer if length known or None otherwise.
                 if e.expected:
+                    got = len(e.partial)
+                    total = e.expected + got
                     msg = _('HTTP request error (incomplete response; '
-                            'expected %d bytes got %d)') % (e.expected,
-                                                           len(e.partial))
+                            'expected %d bytes got %d)') % (total, got)
                 else:
                     msg = _('HTTP request error (incomplete response)')
 
@@ -401,8 +401,8 @@
     elif version_info == (0, 2):
         # application/mercurial-0.2 always identifies the compression
         # engine in the payload header.
-        elen = struct.unpack('B', resp.read(1))[0]
-        ename = resp.read(elen)
+        elen = struct.unpack('B', util.readexactly(resp, 1))[0]
+        ename = util.readexactly(resp, elen)
         engine = util.compengines.forwiretype(ename)
 
         resp = engine.decompressorreader(resp)
@@ -557,7 +557,10 @@
     else:
         url += '/%s' % requests[0][0]
 
+    ui.debug('sending %d commands\n' % len(requests))
     for command, args, f in requests:
+        ui.debug('sending command %s: %s\n' % (
+            command, stringutil.pprint(args, indent=2)))
         assert not list(handler.callcommand(command, args, f))
 
     # TODO stream this.
@@ -724,6 +727,8 @@
         if not self._responsef:
             return
 
+        # TODO ^C here may not result in immediate program termination.
+
         try:
             self._responsef.result()
         finally:
@@ -797,9 +802,13 @@
             return True
 
         # Other concepts.
-        if name in ('bundle2',):
+        if name in ('bundle2'):
             return True
 
+        # Alias command-* to presence of command of that name.
+        if name.startswith('command-'):
+            return name[len('command-'):] in self._descriptor['commands']
+
         return False
 
     def requirecap(self, name, purpose):
@@ -907,8 +916,8 @@
     if advertisev2:
         if ct == 'application/mercurial-cbor':
             try:
-                info = cbor.loads(rawdata)
-            except cbor.CBORDecodeError:
+                info = cborutil.decodeall(rawdata)[0]
+            except cborutil.CBORDecodeError:
                 raise error.Abort(_('error decoding CBOR from remote server'),
                                   hint=_('try again and consider contacting '
                                          'the server operator'))
@@ -977,7 +986,7 @@
     return httppeer(ui, path, respurl, opener, requestbuilder,
                     info['v1capabilities'])
 
-def instance(ui, path, create, intents=None):
+def instance(ui, path, create, intents=None, createopts=None):
     if create:
         raise error.Abort(_('cannot create new http repository'))
     try:
--- a/mercurial/keepalive.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/keepalive.py	Wed Sep 26 20:33:09 2018 +0900
@@ -247,8 +247,10 @@
         except (socket.error, httplib.HTTPException) as err:
             raise urlerr.urlerror(err)
 
-        # if not a persistent connection, don't try to reuse it
-        if r.will_close:
+        # If not a persistent connection, don't try to reuse it. Look
+        # for this using getattr() since vcr doesn't define this
+        # attribute, and in that case always close the connection.
+        if getattr(r, r'will_close', True):
             self._cm.remove(h)
 
         if DEBUG:
@@ -415,9 +417,12 @@
                 s = self._rbuf[:amt]
                 self._rbuf = self._rbuf[amt:]
                 return s
-
-        s = self._rbuf + self._raw_read(amt)
+        # Careful! http.client.HTTPResponse.read() on Python 3 is
+        # implemented using readinto(), which can duplicate self._rbuf
+        # if it's not empty.
+        s = self._rbuf
         self._rbuf = ''
+        s += self._raw_read(amt)
         return s
 
     # stolen from Python SVN #68532 to fix issue1088
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/linelog.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,436 @@
+# linelog - efficient cache for annotate data
+#
+# Copyright 2018 Google LLC.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""linelog is an efficient cache for annotate data inspired by SCCS Weaves.
+
+SCCS Weaves are an implementation of
+https://en.wikipedia.org/wiki/Interleaved_deltas. See
+mercurial/help/internals/linelog.txt for an exploration of SCCS weaves
+and how linelog works in detail.
+
+Here's a hacker's summary: a linelog is a program which is executed in
+the context of a revision. Executing the program emits information
+about lines, including the revision that introduced them and the line
+number in the file at the introducing revision. When an insertion or
+deletion is performed on the file, a jump instruction is used to patch
+in a new body of annotate information.
+"""
+from __future__ import absolute_import, print_function
+
+import abc
+import struct
+
+from .thirdparty import (
+    attr,
+)
+from . import (
+    pycompat,
+)
+
+_llentry = struct.Struct('>II')
+
+class LineLogError(Exception):
+    """Error raised when something bad happens internally in linelog."""
+
+@attr.s
+class lineinfo(object):
+    # Introducing revision of this line.
+    rev = attr.ib()
+    # Line number for this line in its introducing revision.
+    linenum = attr.ib()
+    # Private. Offset in the linelog program of this line. Used internally.
+    _offset = attr.ib()
+
+@attr.s
+class annotateresult(object):
+    rev = attr.ib()
+    lines = attr.ib()
+    _eof = attr.ib()
+
+    def __iter__(self):
+        return iter(self.lines)
+
+class _llinstruction(object):
+
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def __init__(self, op1, op2):
+        pass
+
+    @abc.abstractmethod
+    def __str__(self):
+        pass
+
+    def __repr__(self):
+        return str(self)
+
+    @abc.abstractmethod
+    def __eq__(self, other):
+        pass
+
+    @abc.abstractmethod
+    def encode(self):
+        """Encode this instruction to the binary linelog format."""
+
+    @abc.abstractmethod
+    def execute(self, rev, pc, emit):
+        """Execute this instruction.
+
+        Args:
+          rev: The revision we're annotating.
+          pc: The current offset in the linelog program.
+          emit: A function that accepts a single lineinfo object.
+
+        Returns:
+          The new value of pc. Returns None if exeuction should stop
+          (that is, we've found the end of the file.)
+        """
+
+class _jge(_llinstruction):
+    """If the current rev is greater than or equal to op1, jump to op2."""
+
+    def __init__(self, op1, op2):
+        self._cmprev = op1
+        self._target = op2
+
+    def __str__(self):
+        return r'JGE %d %d' % (self._cmprev, self._target)
+
+    def __eq__(self, other):
+        return (type(self) == type(other)
+                and self._cmprev == other._cmprev
+                and self._target == other._target)
+
+    def encode(self):
+        return _llentry.pack(self._cmprev << 2, self._target)
+
+    def execute(self, rev, pc, emit):
+        if rev >= self._cmprev:
+            return self._target
+        return pc + 1
+
+class _jump(_llinstruction):
+    """Unconditional jumps are expressed as a JGE with op1 set to 0."""
+
+    def __init__(self, op1, op2):
+        if op1 != 0:
+            raise LineLogError("malformed JUMP, op1 must be 0, got %d" % op1)
+        self._target = op2
+
+    def __str__(self):
+        return r'JUMP %d' % (self._target)
+
+    def __eq__(self, other):
+        return (type(self) == type(other)
+                and self._target == other._target)
+
+    def encode(self):
+        return _llentry.pack(0, self._target)
+
+    def execute(self, rev, pc, emit):
+        return self._target
+
+class _eof(_llinstruction):
+    """EOF is expressed as a JGE that always jumps to 0."""
+
+    def __init__(self, op1, op2):
+        if op1 != 0:
+            raise LineLogError("malformed EOF, op1 must be 0, got %d" % op1)
+        if op2 != 0:
+            raise LineLogError("malformed EOF, op2 must be 0, got %d" % op2)
+
+    def __str__(self):
+        return r'EOF'
+
+    def __eq__(self, other):
+        return type(self) == type(other)
+
+    def encode(self):
+        return _llentry.pack(0, 0)
+
+    def execute(self, rev, pc, emit):
+        return None
+
+class _jl(_llinstruction):
+    """If the current rev is less than op1, jump to op2."""
+
+    def __init__(self, op1, op2):
+        self._cmprev = op1
+        self._target = op2
+
+    def __str__(self):
+        return r'JL %d %d' % (self._cmprev, self._target)
+
+    def __eq__(self, other):
+        return (type(self) == type(other)
+                and self._cmprev == other._cmprev
+                and self._target == other._target)
+
+    def encode(self):
+        return _llentry.pack(1 | (self._cmprev << 2), self._target)
+
+    def execute(self, rev, pc, emit):
+        if rev < self._cmprev:
+            return self._target
+        return pc + 1
+
+class _line(_llinstruction):
+    """Emit a line."""
+
+    def __init__(self, op1, op2):
+        # This line was introduced by this revision number.
+        self._rev = op1
+        # This line had the specified line number in the introducing revision.
+        self._origlineno = op2
+
+    def __str__(self):
+        return r'LINE %d %d' % (self._rev, self._origlineno)
+
+    def __eq__(self, other):
+        return (type(self) == type(other)
+                and self._rev == other._rev
+                and self._origlineno == other._origlineno)
+
+    def encode(self):
+        return _llentry.pack(2 | (self._rev << 2), self._origlineno)
+
+    def execute(self, rev, pc, emit):
+        emit(lineinfo(self._rev, self._origlineno, pc))
+        return pc + 1
+
+def _decodeone(data, offset):
+    """Decode a single linelog instruction from an offset in a buffer."""
+    try:
+        op1, op2 = _llentry.unpack_from(data, offset)
+    except struct.error as e:
+        raise LineLogError('reading an instruction failed: %r' % e)
+    opcode = op1 & 0b11
+    op1 = op1 >> 2
+    if opcode == 0:
+        if op1 == 0:
+            if op2 == 0:
+                return _eof(op1, op2)
+            return _jump(op1, op2)
+        return _jge(op1, op2)
+    elif opcode == 1:
+        return _jl(op1, op2)
+    elif opcode == 2:
+        return _line(op1, op2)
+    raise NotImplementedError('Unimplemented opcode %r' % opcode)
+
+class linelog(object):
+    """Efficient cache for per-line history information."""
+
+    def __init__(self, program=None, maxrev=0):
+        if program is None:
+            # We pad the program with an extra leading EOF so that our
+            # offsets will match the C code exactly. This means we can
+            # interoperate with the C code.
+            program = [_eof(0, 0), _eof(0, 0)]
+        self._program = program
+        self._lastannotate = None
+        self._maxrev = maxrev
+
+    def __eq__(self, other):
+        return (type(self) == type(other)
+                and self._program == other._program
+                and self._maxrev == other._maxrev)
+
+    def __repr__(self):
+        return '<linelog at %s: maxrev=%d size=%d>' % (
+            hex(id(self)), self._maxrev, len(self._program))
+
+    def debugstr(self):
+        fmt = r'%%%dd %%s' % len(str(len(self._program)))
+        return pycompat.sysstr('\n').join(
+            fmt % (idx, i) for idx, i in enumerate(self._program[1:], 1))
+
+    @classmethod
+    def fromdata(cls, buf):
+        if len(buf) % _llentry.size != 0:
+            raise LineLogError(
+                "invalid linelog buffer size %d (must be a multiple of %d)" % (
+                    len(buf), _llentry.size))
+        expected = len(buf) / _llentry.size
+        fakejge = _decodeone(buf, 0)
+        if isinstance(fakejge, _jump):
+            maxrev = 0
+        else:
+            maxrev = fakejge._cmprev
+        numentries = fakejge._target
+        if expected != numentries:
+            raise LineLogError("corrupt linelog data: claimed"
+                               " %d entries but given data for %d entries" % (
+                                   expected, numentries))
+        instructions = [_eof(0, 0)]
+        for offset in pycompat.xrange(1, numentries):
+            instructions.append(_decodeone(buf, offset * _llentry.size))
+        return cls(instructions, maxrev=maxrev)
+
+    def encode(self):
+        hdr = _jge(self._maxrev, len(self._program)).encode()
+        return hdr + ''.join(i.encode() for i in self._program[1:])
+
+    def clear(self):
+        self._program = []
+        self._maxrev = 0
+        self._lastannotate = None
+
+    def replacelines_vec(self, rev, a1, a2, blines):
+        return self.replacelines(rev, a1, a2, 0, len(blines),
+                                 _internal_blines=blines)
+
+    def replacelines(self, rev, a1, a2, b1, b2, _internal_blines=None):
+        """Replace lines [a1, a2) with lines [b1, b2)."""
+        if self._lastannotate:
+            # TODO(augie): make replacelines() accept a revision at
+            # which we're editing as well as a revision to mark
+            # responsible for the edits. In hg-experimental it's
+            # stateful like this, so we're doing the same thing to
+            # retain compatibility with absorb until that's imported.
+            ar = self._lastannotate
+        else:
+            ar = self.annotate(rev)
+            #        ar = self.annotate(self._maxrev)
+        if a1 > len(ar.lines):
+            raise LineLogError(
+                '%d contains %d lines, tried to access line %d' % (
+                    rev, len(ar.lines), a1))
+        elif a1 == len(ar.lines):
+            # Simulated EOF instruction since we're at EOF, which
+            # doesn't have a "real" line.
+            a1inst = _eof(0, 0)
+            a1info = lineinfo(0, 0, ar._eof)
+        else:
+            a1info = ar.lines[a1]
+            a1inst = self._program[a1info._offset]
+        programlen = self._program.__len__
+        oldproglen = programlen()
+        appendinst = self._program.append
+
+        # insert
+        blineinfos = []
+        bappend = blineinfos.append
+        if b1 < b2:
+            # Determine the jump target for the JGE at the start of
+            # the new block.
+            tgt = oldproglen + (b2 - b1 + 1)
+            # Jump to skip the insert if we're at an older revision.
+            appendinst(_jl(rev, tgt))
+            for linenum in pycompat.xrange(b1, b2):
+                if _internal_blines is None:
+                    bappend(lineinfo(rev, linenum, programlen()))
+                    appendinst(_line(rev, linenum))
+                else:
+                    newrev, newlinenum = _internal_blines[linenum]
+                    bappend(lineinfo(newrev, newlinenum, programlen()))
+                    appendinst(_line(newrev, newlinenum))
+        # delete
+        if a1 < a2:
+            if a2 > len(ar.lines):
+                raise LineLogError(
+                    '%d contains %d lines, tried to access line %d' % (
+                        rev, len(ar.lines), a2))
+            elif a2 == len(ar.lines):
+                endaddr = ar._eof
+            else:
+                endaddr = ar.lines[a2]._offset
+            if a2 > 0 and rev < self._maxrev:
+                # If we're here, we're deleting a chunk of an old
+                # commit, so we need to be careful and not touch
+                # invisible lines between a2-1 and a2 (IOW, lines that
+                # are added later).
+                endaddr = ar.lines[a2 - 1]._offset + 1
+            appendinst(_jge(rev, endaddr))
+        # copy instruction from a1
+        a1instpc = programlen()
+        appendinst(a1inst)
+        # if a1inst isn't a jump or EOF, then we need to add an unconditional
+        # jump back into the program here.
+        if not isinstance(a1inst, (_jump, _eof)):
+            appendinst(_jump(0, a1info._offset + 1))
+        # Patch instruction at a1, which makes our patch live.
+        self._program[a1info._offset] = _jump(0, oldproglen)
+
+        # Update self._lastannotate in place. This serves as a cache to avoid
+        # expensive "self.annotate" in this function, when "replacelines" is
+        # used continuously.
+        if len(self._lastannotate.lines) > a1:
+            self._lastannotate.lines[a1]._offset = a1instpc
+        else:
+            assert isinstance(a1inst, _eof)
+            self._lastannotate._eof = a1instpc
+        self._lastannotate.lines[a1:a2] = blineinfos
+        self._lastannotate.rev = max(self._lastannotate.rev, rev)
+
+        if rev > self._maxrev:
+            self._maxrev = rev
+
+    def annotate(self, rev):
+        pc = 1
+        lines = []
+        executed = 0
+        # Sanity check: if instructions executed exceeds len(program), we
+        # hit an infinite loop in the linelog program somehow and we
+        # should stop.
+        while pc is not None and executed < len(self._program):
+            inst = self._program[pc]
+            lastpc = pc
+            pc = inst.execute(rev, pc, lines.append)
+            executed += 1
+        if pc is not None:
+            raise LineLogError(
+                r'Probably hit an infinite loop in linelog. Program:\n' +
+                self.debugstr())
+        ar = annotateresult(rev, lines, lastpc)
+        self._lastannotate = ar
+        return ar
+
+    @property
+    def maxrev(self):
+        return self._maxrev
+
+    # Stateful methods which depend on the value of the last
+    # annotation run. This API is for compatiblity with the original
+    # linelog, and we should probably consider refactoring it.
+    @property
+    def annotateresult(self):
+        """Return the last annotation result. C linelog code exposed this."""
+        return [(l.rev, l.linenum) for l in self._lastannotate.lines]
+
+    def getoffset(self, line):
+        return self._lastannotate.lines[line]._offset
+
+    def getalllines(self, start=0, end=0):
+        """Get all lines that ever occurred in [start, end).
+
+        Passing start == end == 0 means "all lines ever".
+
+        This works in terms of *internal* program offsets, not line numbers.
+        """
+        pc = start or 1
+        lines = []
+        # only take as many steps as there are instructions in the
+        # program - if we don't find an EOF or our stop-line before
+        # then, something is badly broken.
+        for step in pycompat.xrange(len(self._program)):
+            inst = self._program[pc]
+            nextpc = pc + 1
+            if isinstance(inst, _jump):
+                nextpc = inst._target
+            elif isinstance(inst, _eof):
+                return lines
+            elif isinstance(inst, (_jl, _jge)):
+                pass
+            elif isinstance(inst, _line):
+                lines.append((inst._rev, inst._origlineno))
+            else:
+                raise LineLogError("Illegal instruction %r" % inst)
+            if nextpc == end:
+                return lines
+            pc = nextpc
+        raise LineLogError("Failed to perform getalllines")
--- a/mercurial/localrepo.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/localrepo.py	Wed Sep 26 20:33:09 2018 +0900
@@ -56,7 +56,7 @@
     revsetlang,
     scmutil,
     sparse,
-    store,
+    store as storemod,
     subrepoutil,
     tags as tagsmod,
     transaction,
@@ -70,6 +70,10 @@
     stringutil,
 )
 
+from .revlogutils import (
+    constants as revlogconst,
+)
+
 release = lockmod.release
 urlerr = util.urlerr
 urlreq = util.urlreq
@@ -372,8 +376,416 @@
 # set to reflect that the extension knows how to handle that requirements.
 featuresetupfuncs = set()
 
-@interfaceutil.implementer(repository.completelocalrepository)
+def makelocalrepository(baseui, path, intents=None):
+    """Create a local repository object.
+
+    Given arguments needed to construct a local repository, this function
+    performs various early repository loading functionality (such as
+    reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
+    the repository can be opened, derives a type suitable for representing
+    that repository, and returns an instance of it.
+
+    The returned object conforms to the ``repository.completelocalrepository``
+    interface.
+
+    The repository type is derived by calling a series of factory functions
+    for each aspect/interface of the final repository. These are defined by
+    ``REPO_INTERFACES``.
+
+    Each factory function is called to produce a type implementing a specific
+    interface. The cumulative list of returned types will be combined into a
+    new type and that type will be instantiated to represent the local
+    repository.
+
+    The factory functions each receive various state that may be consulted
+    as part of deriving a type.
+
+    Extensions should wrap these factory functions to customize repository type
+    creation. Note that an extension's wrapped function may be called even if
+    that extension is not loaded for the repo being constructed. Extensions
+    should check if their ``__name__`` appears in the
+    ``extensionmodulenames`` set passed to the factory function and no-op if
+    not.
+    """
+    ui = baseui.copy()
+    # Prevent copying repo configuration.
+    ui.copy = baseui.copy
+
+    # Working directory VFS rooted at repository root.
+    wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
+
+    # Main VFS for .hg/ directory.
+    hgpath = wdirvfs.join(b'.hg')
+    hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
+
+    # The .hg/ path should exist and should be a directory. All other
+    # cases are errors.
+    if not hgvfs.isdir():
+        try:
+            hgvfs.stat()
+        except OSError as e:
+            if e.errno != errno.ENOENT:
+                raise
+
+        raise error.RepoError(_(b'repository %s not found') % path)
+
+    # .hg/requires file contains a newline-delimited list of
+    # features/capabilities the opener (us) must have in order to use
+    # the repository. This file was introduced in Mercurial 0.9.2,
+    # which means very old repositories may not have one. We assume
+    # a missing file translates to no requirements.
+    try:
+        requirements = set(hgvfs.read(b'requires').splitlines())
+    except IOError as e:
+        if e.errno != errno.ENOENT:
+            raise
+        requirements = set()
+
+    # The .hg/hgrc file may load extensions or contain config options
+    # that influence repository construction. Attempt to load it and
+    # process any new extensions that it may have pulled in.
+    try:
+        ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
+    except IOError:
+        pass
+    else:
+        extensions.loadall(ui)
+
+    # Set of module names of extensions loaded for this repository.
+    extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
+
+    supportedrequirements = gathersupportedrequirements(ui)
+
+    # We first validate the requirements are known.
+    ensurerequirementsrecognized(requirements, supportedrequirements)
+
+    # Then we validate that the known set is reasonable to use together.
+    ensurerequirementscompatible(ui, requirements)
+
+    # TODO there are unhandled edge cases related to opening repositories with
+    # shared storage. If storage is shared, we should also test for requirements
+    # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
+    # that repo, as that repo may load extensions needed to open it. This is a
+    # bit complicated because we don't want the other hgrc to overwrite settings
+    # in this hgrc.
+    #
+    # This bug is somewhat mitigated by the fact that we copy the .hg/requires
+    # file when sharing repos. But if a requirement is added after the share is
+    # performed, thereby introducing a new requirement for the opener, we may
+    # will not see that and could encounter a run-time error interacting with
+    # that shared store since it has an unknown-to-us requirement.
+
+    # At this point, we know we should be capable of opening the repository.
+    # Now get on with doing that.
+
+    # The "store" part of the repository holds versioned data. How it is
+    # accessed is determined by various requirements. The ``shared`` or
+    # ``relshared`` requirements indicate the store lives in the path contained
+    # in the ``.hg/sharedpath`` file. This is an absolute path for
+    # ``shared`` and relative to ``.hg/`` for ``relshared``.
+    if b'shared' in requirements or b'relshared' in requirements:
+        sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
+        if b'relshared' in requirements:
+            sharedpath = hgvfs.join(sharedpath)
+
+        sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
+
+        if not sharedvfs.exists():
+            raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
+                                    b'directory %s') % sharedvfs.base)
+
+        storebasepath = sharedvfs.base
+        cachepath = sharedvfs.join(b'cache')
+    else:
+        storebasepath = hgvfs.base
+        cachepath = hgvfs.join(b'cache')
+
+    # The store has changed over time and the exact layout is dictated by
+    # requirements. The store interface abstracts differences across all
+    # of them.
+    store = makestore(requirements, storebasepath,
+                      lambda base: vfsmod.vfs(base, cacheaudited=True))
+    hgvfs.createmode = store.createmode
+
+    storevfs = store.vfs
+    storevfs.options = resolvestorevfsoptions(ui, requirements)
+
+    # The cache vfs is used to manage cache files.
+    cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
+    cachevfs.createmode = store.createmode
+
+    # Now resolve the type for the repository object. We do this by repeatedly
+    # calling a factory function to produces types for specific aspects of the
+    # repo's operation. The aggregate returned types are used as base classes
+    # for a dynamically-derived type, which will represent our new repository.
+
+    bases = []
+    extrastate = {}
+
+    for iface, fn in REPO_INTERFACES:
+        # We pass all potentially useful state to give extensions tons of
+        # flexibility.
+        typ = fn(ui=ui,
+                 intents=intents,
+                 requirements=requirements,
+                 wdirvfs=wdirvfs,
+                 hgvfs=hgvfs,
+                 store=store,
+                 storevfs=storevfs,
+                 storeoptions=storevfs.options,
+                 cachevfs=cachevfs,
+                 extensionmodulenames=extensionmodulenames,
+                 extrastate=extrastate,
+                 baseclasses=bases)
+
+        if not isinstance(typ, type):
+            raise error.ProgrammingError('unable to construct type for %s' %
+                                         iface)
+
+        bases.append(typ)
+
+    # type() allows you to use characters in type names that wouldn't be
+    # recognized as Python symbols in source code. We abuse that to add
+    # rich information about our constructed repo.
+    name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
+        wdirvfs.base,
+        b','.join(sorted(requirements))))
+
+    cls = type(name, tuple(bases), {})
+
+    return cls(
+        baseui=baseui,
+        ui=ui,
+        origroot=path,
+        wdirvfs=wdirvfs,
+        hgvfs=hgvfs,
+        requirements=requirements,
+        supportedrequirements=supportedrequirements,
+        sharedpath=storebasepath,
+        store=store,
+        cachevfs=cachevfs,
+        intents=intents)
+
+def gathersupportedrequirements(ui):
+    """Determine the complete set of recognized requirements."""
+    # Start with all requirements supported by this file.
+    supported = set(localrepository._basesupported)
+
+    # Execute ``featuresetupfuncs`` entries if they belong to an extension
+    # relevant to this ui instance.
+    modules = {m.__name__ for n, m in extensions.extensions(ui)}
+
+    for fn in featuresetupfuncs:
+        if fn.__module__ in modules:
+            fn(ui, supported)
+
+    # Add derived requirements from registered compression engines.
+    for name in util.compengines:
+        engine = util.compengines[name]
+        if engine.revlogheader():
+            supported.add(b'exp-compression-%s' % name)
+
+    return supported
+
+def ensurerequirementsrecognized(requirements, supported):
+    """Validate that a set of local requirements is recognized.
+
+    Receives a set of requirements. Raises an ``error.RepoError`` if there
+    exists any requirement in that set that currently loaded code doesn't
+    recognize.
+
+    Returns a set of supported requirements.
+    """
+    missing = set()
+
+    for requirement in requirements:
+        if requirement in supported:
+            continue
+
+        if not requirement or not requirement[0:1].isalnum():
+            raise error.RequirementError(_(b'.hg/requires file is corrupt'))
+
+        missing.add(requirement)
+
+    if missing:
+        raise error.RequirementError(
+            _(b'repository requires features unknown to this Mercurial: %s') %
+            b' '.join(sorted(missing)),
+            hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
+                   b'for more information'))
+
+def ensurerequirementscompatible(ui, requirements):
+    """Validates that a set of recognized requirements is mutually compatible.
+
+    Some requirements may not be compatible with others or require
+    config options that aren't enabled. This function is called during
+    repository opening to ensure that the set of requirements needed
+    to open a repository is sane and compatible with config options.
+
+    Extensions can monkeypatch this function to perform additional
+    checking.
+
+    ``error.RepoError`` should be raised on failure.
+    """
+    if b'exp-sparse' in requirements and not sparse.enabled:
+        raise error.RepoError(_(b'repository is using sparse feature but '
+                                b'sparse is not enabled; enable the '
+                                b'"sparse" extensions to access'))
+
+def makestore(requirements, path, vfstype):
+    """Construct a storage object for a repository."""
+    if b'store' in requirements:
+        if b'fncache' in requirements:
+            return storemod.fncachestore(path, vfstype,
+                                         b'dotencode' in requirements)
+
+        return storemod.encodedstore(path, vfstype)
+
+    return storemod.basicstore(path, vfstype)
+
+def resolvestorevfsoptions(ui, requirements):
+    """Resolve the options to pass to the store vfs opener.
+
+    The returned dict is used to influence behavior of the storage layer.
+    """
+    options = {}
+
+    if b'treemanifest' in requirements:
+        options[b'treemanifest'] = True
+
+    # experimental config: format.manifestcachesize
+    manifestcachesize = ui.configint(b'format', b'manifestcachesize')
+    if manifestcachesize is not None:
+        options[b'manifestcachesize'] = manifestcachesize
+
+    # In the absence of another requirement superseding a revlog-related
+    # requirement, we have to assume the repo is using revlog version 0.
+    # This revlog format is super old and we don't bother trying to parse
+    # opener options for it because those options wouldn't do anything
+    # meaningful on such old repos.
+    if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
+        options.update(resolverevlogstorevfsoptions(ui, requirements))
+
+    return options
+
+def resolverevlogstorevfsoptions(ui, requirements):
+    """Resolve opener options specific to revlogs."""
+
+    options = {}
+
+    if b'revlogv1' in requirements:
+        options[b'revlogv1'] = True
+    if REVLOGV2_REQUIREMENT in requirements:
+        options[b'revlogv2'] = True
+
+    if b'generaldelta' in requirements:
+        options[b'generaldelta'] = True
+
+    # experimental config: format.chunkcachesize
+    chunkcachesize = ui.configint(b'format', b'chunkcachesize')
+    if chunkcachesize is not None:
+        options[b'chunkcachesize'] = chunkcachesize
+
+    deltabothparents = ui.configbool(b'storage',
+                                     b'revlog.optimize-delta-parent-choice')
+    options[b'deltabothparents'] = deltabothparents
+
+    options[b'lazydeltabase'] = not scmutil.gddeltaconfig(ui)
+
+    chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
+    if 0 <= chainspan:
+        options[b'maxdeltachainspan'] = chainspan
+
+    mmapindexthreshold = ui.configbytes(b'experimental',
+                                        b'mmapindexthreshold')
+    if mmapindexthreshold is not None:
+        options[b'mmapindexthreshold'] = mmapindexthreshold
+
+    withsparseread = ui.configbool(b'experimental', b'sparse-read')
+    srdensitythres = float(ui.config(b'experimental',
+                                     b'sparse-read.density-threshold'))
+    srmingapsize = ui.configbytes(b'experimental',
+                                  b'sparse-read.min-gap-size')
+    options[b'with-sparse-read'] = withsparseread
+    options[b'sparse-read-density-threshold'] = srdensitythres
+    options[b'sparse-read-min-gap-size'] = srmingapsize
+
+    sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
+    options[b'sparse-revlog'] = sparserevlog
+    if sparserevlog:
+        options[b'generaldelta'] = True
+
+    maxchainlen = None
+    if sparserevlog:
+        maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
+    # experimental config: format.maxchainlen
+    maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
+    if maxchainlen is not None:
+        options[b'maxchainlen'] = maxchainlen
+
+    for r in requirements:
+        if r.startswith(b'exp-compression-'):
+            options[b'compengine'] = r[len(b'exp-compression-'):]
+
+    if repository.NARROW_REQUIREMENT in requirements:
+        options[b'enableellipsis'] = True
+
+    return options
+
+def makemain(**kwargs):
+    """Produce a type conforming to ``ilocalrepositorymain``."""
+    return localrepository
+
+@interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
+class revlogfilestorage(object):
+    """File storage when using revlogs."""
+
+    def file(self, path):
+        if path[0] == b'/':
+            path = path[1:]
+
+        return filelog.filelog(self.svfs, path)
+
+@interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
+class revlognarrowfilestorage(object):
+    """File storage when using revlogs and narrow files."""
+
+    def file(self, path):
+        if path[0] == b'/':
+            path = path[1:]
+
+        return filelog.narrowfilelog(self.svfs, path, self.narrowmatch())
+
+def makefilestorage(requirements, **kwargs):
+    """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
+    if repository.NARROW_REQUIREMENT in requirements:
+        return revlognarrowfilestorage
+    else:
+        return revlogfilestorage
+
+# List of repository interfaces and factory functions for them. Each
+# will be called in order during ``makelocalrepository()`` to iteratively
+# derive the final type for a local repository instance.
+REPO_INTERFACES = [
+    (repository.ilocalrepositorymain, makemain),
+    (repository.ilocalrepositoryfilestorage, makefilestorage),
+]
+
+@interfaceutil.implementer(repository.ilocalrepositorymain)
 class localrepository(object):
+    """Main class for representing local repositories.
+
+    All local repositories are instances of this class.
+
+    Constructed on its own, instances of this class are not usable as
+    repository objects. To obtain a usable repository object, call
+    ``hg.repository()``, ``localrepo.instance()``, or
+    ``localrepo.makelocalrepository()``. The latter is the lowest-level.
+    ``instance()`` adds support for creating new repositories.
+    ``hg.repository()`` adds more extension integration, including calling
+    ``reposetup()``. Generally speaking, ``hg.repository()`` should be
+    used.
+    """
 
     # obsolete experimental requirements:
     #  - manifestv2: An experimental new manifest format that allowed
@@ -394,11 +806,7 @@
         'relshared',
         'dotencode',
         'exp-sparse',
-    }
-    openerreqs = {
-        'revlogv1',
-        'generaldelta',
-        'treemanifest',
+        'internal-phase'
     }
 
     # list of prefix for file which can be written without 'wlock'
@@ -421,32 +829,71 @@
         'bisect.state',
     }
 
-    def __init__(self, baseui, path, create=False, intents=None):
-        self.requirements = set()
+    def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
+                 supportedrequirements, sharedpath, store, cachevfs,
+                 intents=None):
+        """Create a new local repository instance.
+
+        Most callers should use ``hg.repository()``, ``localrepo.instance()``,
+        or ``localrepo.makelocalrepository()`` for obtaining a new repository
+        object.
+
+        Arguments:
+
+        baseui
+           ``ui.ui`` instance that ``ui`` argument was based off of.
+
+        ui
+           ``ui.ui`` instance for use by the repository.
+
+        origroot
+           ``bytes`` path to working directory root of this repository.
+
+        wdirvfs
+           ``vfs.vfs`` rooted at the working directory.
+
+        hgvfs
+           ``vfs.vfs`` rooted at .hg/
+
+        requirements
+           ``set`` of bytestrings representing repository opening requirements.
+
+        supportedrequirements
+           ``set`` of bytestrings representing repository requirements that we
+           know how to open. May be a supetset of ``requirements``.
+
+        sharedpath
+           ``bytes`` Defining path to storage base directory. Points to a
+           ``.hg/`` directory somewhere.
+
+        store
+           ``store.basicstore`` (or derived) instance providing access to
+           versioned storage.
+
+        cachevfs
+           ``vfs.vfs`` used for cache files.
+
+        intents
+           ``set`` of system strings indicating what this repo will be used
+           for.
+        """
+        self.baseui = baseui
+        self.ui = ui
+        self.origroot = origroot
+        # vfs rooted at working directory.
+        self.wvfs = wdirvfs
+        self.root = wdirvfs.base
+        # vfs rooted at .hg/. Used to access most non-store paths.
+        self.vfs = hgvfs
+        self.path = hgvfs.base
+        self.requirements = requirements
+        self.supported = supportedrequirements
+        self.sharedpath = sharedpath
+        self.store = store
+        self.cachevfs = cachevfs
+
         self.filtername = None
-        # wvfs: rooted at the repository root, used to access the working copy
-        self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
-        # vfs: rooted at .hg, used to access repo files outside of .hg/store
-        self.vfs = None
-        # svfs: usually rooted at .hg/store, used to access repository history
-        # If this is a shared repository, this vfs may point to another
-        # repository's .hg/store directory.
-        self.svfs = None
-        self.root = self.wvfs.base
-        self.path = self.wvfs.join(".hg")
-        self.origroot = path
-        # This is only used by context.workingctx.match in order to
-        # detect files in subrepos.
-        self.auditor = pathutil.pathauditor(
-            self.root, callback=self._checknested)
-        # This is only used by context.basectx.match in order to detect
-        # files in subrepos.
-        self.nofsauditor = pathutil.pathauditor(
-            self.root, callback=self._checknested, realfs=False, cached=True)
-        self.baseui = baseui
-        self.ui = baseui.copy()
-        self.ui.copy = baseui.copy # prevent copying repo configuration
-        self.vfs = vfsmod.vfs(self.path, cacheaudited=True)
+
         if (self.ui.configbool('devel', 'all-warnings') or
             self.ui.configbool('devel', 'check-locks')):
             self.vfs.audit = self._getvfsward(self.vfs.audit)
@@ -454,98 +901,18 @@
         # Callback are in the form: func(repo, roots) --> processed root.
         # This list it to be filled by extension during repo setup
         self._phasedefaults = []
-        try:
-            self.ui.readconfig(self.vfs.join("hgrc"), self.root)
-            self._loadextensions()
-        except IOError:
-            pass
-
-        if featuresetupfuncs:
-            self.supported = set(self._basesupported) # use private copy
-            extmods = set(m.__name__ for n, m
-                          in extensions.extensions(self.ui))
-            for setupfunc in featuresetupfuncs:
-                if setupfunc.__module__ in extmods:
-                    setupfunc(self.ui, self.supported)
-        else:
-            self.supported = self._basesupported
+
         color.setup(self.ui)
 
-        # Add compression engines.
-        for name in util.compengines:
-            engine = util.compengines[name]
-            if engine.revlogheader():
-                self.supported.add('exp-compression-%s' % name)
-
-        if not self.vfs.isdir():
-            if create:
-                self.requirements = newreporequirements(self)
-
-                if not self.wvfs.exists():
-                    self.wvfs.makedirs()
-                self.vfs.makedir(notindexed=True)
-
-                if 'store' in self.requirements:
-                    self.vfs.mkdir("store")
-
-                    # create an invalid changelog
-                    self.vfs.append(
-                        "00changelog.i",
-                        '\0\0\0\2' # represents revlogv2
-                        ' dummy changelog to prevent using the old repo layout'
-                    )
-            else:
-                raise error.RepoError(_("repository %s not found") % path)
-        elif create:
-            raise error.RepoError(_("repository %s already exists") % path)
-        else:
-            try:
-                self.requirements = scmutil.readrequires(
-                        self.vfs, self.supported)
-            except IOError as inst:
-                if inst.errno != errno.ENOENT:
-                    raise
-
-        cachepath = self.vfs.join('cache')
-        self.sharedpath = self.path
-        try:
-            sharedpath = self.vfs.read("sharedpath").rstrip('\n')
-            if 'relshared' in self.requirements:
-                sharedpath = self.vfs.join(sharedpath)
-            vfs = vfsmod.vfs(sharedpath, realpath=True)
-            cachepath = vfs.join('cache')
-            s = vfs.base
-            if not vfs.exists():
-                raise error.RepoError(
-                    _('.hg/sharedpath points to nonexistent directory %s') % s)
-            self.sharedpath = s
-        except IOError as inst:
-            if inst.errno != errno.ENOENT:
-                raise
-
-        if 'exp-sparse' in self.requirements and not sparse.enabled:
-            raise error.RepoError(_('repository is using sparse feature but '
-                                    'sparse is not enabled; enable the '
-                                    '"sparse" extensions to access'))
-
-        self.store = store.store(
-            self.requirements, self.sharedpath,
-            lambda base: vfsmod.vfs(base, cacheaudited=True))
         self.spath = self.store.path
         self.svfs = self.store.vfs
         self.sjoin = self.store.join
-        self.vfs.createmode = self.store.createmode
-        self.cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
-        self.cachevfs.createmode = self.store.createmode
         if (self.ui.configbool('devel', 'all-warnings') or
             self.ui.configbool('devel', 'check-locks')):
             if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
                 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
             else: # standard vfs
                 self.svfs.audit = self._getsvfsward(self.svfs.audit)
-        self._applyopenerreqs()
-        if create:
-            self._writerequirements()
 
         self._dirstatevalidatewarned = False
 
@@ -638,9 +1005,6 @@
     def close(self):
         self._writecaches()
 
-    def _loadextensions(self):
-        extensions.loadall(self.ui)
-
     def _writecaches(self):
         if self._revbranchcache:
             self._revbranchcache.write()
@@ -653,56 +1017,25 @@
             caps.add('bundle2=' + urlreq.quote(capsblob))
         return caps
 
-    def _applyopenerreqs(self):
-        self.svfs.options = dict((r, 1) for r in self.requirements
-                                           if r in self.openerreqs)
-        # experimental config: format.chunkcachesize
-        chunkcachesize = self.ui.configint('format', 'chunkcachesize')
-        if chunkcachesize is not None:
-            self.svfs.options['chunkcachesize'] = chunkcachesize
-        # experimental config: format.maxchainlen
-        maxchainlen = self.ui.configint('format', 'maxchainlen')
-        if maxchainlen is not None:
-            self.svfs.options['maxchainlen'] = maxchainlen
-        # experimental config: format.manifestcachesize
-        manifestcachesize = self.ui.configint('format', 'manifestcachesize')
-        if manifestcachesize is not None:
-            self.svfs.options['manifestcachesize'] = manifestcachesize
-        deltabothparents = self.ui.configbool('storage',
-            'revlog.optimize-delta-parent-choice')
-        self.svfs.options['deltabothparents'] = deltabothparents
-        self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
-        chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan')
-        if 0 <= chainspan:
-            self.svfs.options['maxdeltachainspan'] = chainspan
-        mmapindexthreshold = self.ui.configbytes('experimental',
-                                                 'mmapindexthreshold')
-        if mmapindexthreshold is not None:
-            self.svfs.options['mmapindexthreshold'] = mmapindexthreshold
-        withsparseread = self.ui.configbool('experimental', 'sparse-read')
-        srdensitythres = float(self.ui.config('experimental',
-                                              'sparse-read.density-threshold'))
-        srmingapsize = self.ui.configbytes('experimental',
-                                           'sparse-read.min-gap-size')
-        self.svfs.options['with-sparse-read'] = withsparseread
-        self.svfs.options['sparse-read-density-threshold'] = srdensitythres
-        self.svfs.options['sparse-read-min-gap-size'] = srmingapsize
-        sparserevlog = SPARSEREVLOG_REQUIREMENT in self.requirements
-        self.svfs.options['sparse-revlog'] = sparserevlog
-        if sparserevlog:
-            self.svfs.options['generaldelta'] = True
-
-        for r in self.requirements:
-            if r.startswith('exp-compression-'):
-                self.svfs.options['compengine'] = r[len('exp-compression-'):]
-
-        # TODO move "revlogv2" to openerreqs once finalized.
-        if REVLOGV2_REQUIREMENT in self.requirements:
-            self.svfs.options['revlogv2'] = True
-
     def _writerequirements(self):
         scmutil.writerequires(self.vfs, self.requirements)
 
+    # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
+    # self -> auditor -> self._checknested -> self
+
+    @property
+    def auditor(self):
+        # This is only used by context.workingctx.match in order to
+        # detect files in subrepos.
+        return pathutil.pathauditor(self.root, callback=self._checknested)
+
+    @property
+    def nofsauditor(self):
+        # This is only used by context.basectx.match in order to detect
+        # files in subrepos.
+        return pathutil.pathauditor(self.root, callback=self._checknested,
+                                    realfs=False, cached=True)
+
     def _checknested(self, path):
         """Determine if path is a legal nested repository."""
         if not path.startswith(self.root):
@@ -779,15 +1112,10 @@
         return changelog.changelog(self.svfs,
                                    trypending=txnutil.mayhavepending(self.root))
 
-    def _constructmanifest(self):
-        # This is a temporary function while we migrate from manifest to
-        # manifestlog. It allows bundlerepo and unionrepo to intercept the
-        # manifest creation.
-        return manifest.manifestrevlog(self.svfs)
-
     @storecache('00manifest.i')
     def manifestlog(self):
-        return manifest.manifestlog(self.svfs, self)
+        rootstore = manifest.manifestrevlog(self.svfs)
+        return manifest.manifestlog(self.svfs, self, rootstore)
 
     @repofilecache('dirstate')
     def dirstate(self):
@@ -811,21 +1139,17 @@
                                " working parent %s!\n") % short(node))
             return nullid
 
-    @repofilecache(narrowspec.FILENAME)
+    @storecache(narrowspec.FILENAME)
     def narrowpats(self):
         """matcher patterns for this repository's narrowspec
 
         A tuple of (includes, excludes).
         """
-        source = self
-        if self.shared():
-            from . import hg
-            source = hg.sharedreposource(self)
-        return narrowspec.load(source)
-
-    @repofilecache(narrowspec.FILENAME)
+        return narrowspec.load(self)
+
+    @storecache(narrowspec.FILENAME)
     def _narrowmatch(self):
-        if changegroup.NARROW_REQUIREMENT not in self.requirements:
+        if repository.NARROW_REQUIREMENT not in self.requirements:
             return matchmod.always(self.root, '')
         include, exclude = self.narrowpats
         return narrowspec.match(self.root, include=include, exclude=exclude)
@@ -835,11 +1159,7 @@
         return self._narrowmatch
 
     def setnarrowpats(self, newincludes, newexcludes):
-        target = self
-        if self.shared():
-            from . import hg
-            target = hg.sharedreposource(self)
-        narrowspec.save(target, newincludes, newexcludes)
+        narrowspec.save(self, newincludes, newexcludes)
         self.invalidate(clearfilecache=True)
 
     def __getitem__(self, changeid):
@@ -850,7 +1170,7 @@
         if isinstance(changeid, slice):
             # wdirrev isn't contiguous so the slice shouldn't include it
             return [context.changectx(self, i)
-                    for i in xrange(*changeid.indices(len(self)))
+                    for i in pycompat.xrange(*changeid.indices(len(self)))
                     if i not in self.changelog.filteredrevs]
         try:
             return context.changectx(self, changeid)
@@ -860,7 +1180,8 @@
     def __contains__(self, changeid):
         """True if the given changeid exists
 
-        error.LookupError is raised if an ambiguous node specified.
+        error.AmbiguousPrefixLookupError is raised if an ambiguous node
+        specified.
         """
         try:
             self[changeid]
@@ -1122,11 +1443,6 @@
     def wjoin(self, f, *insidef):
         return self.vfs.reljoin(self.root, f, *insidef)
 
-    def file(self, f):
-        if f[0] == '/':
-            f = f[1:]
-        return filelog.filelog(self.svfs, f)
-
     def setparents(self, p1, p2=nullid):
         with self.dirstate.parentchange():
             copies = self.dirstate.setparents(p1, p2)
@@ -1372,6 +1688,7 @@
             else:
                 # discard all changes (including ones already written
                 # out) in this transaction
+                narrowspec.restorebackup(self, 'journal.narrowspec')
                 repo.dirstate.restorebackup(None, 'journal.dirstate')
 
                 repo.invalidate(clearfilecache=True)
@@ -1385,7 +1702,7 @@
                                      releasefn=releasefn,
                                      checkambigfiles=_cachedfiles,
                                      name=desc)
-        tr.changes['revs'] = xrange(0, 0)
+        tr.changes['origrepolen'] = len(self)
         tr.changes['obsmarkers'] = set()
         tr.changes['phases'] = {}
         tr.changes['bookmarks'] = {}
@@ -1460,6 +1777,7 @@
     @unfilteredmethod
     def _writejournal(self, desc):
         self.dirstate.savebackup(None, 'journal.dirstate')
+        narrowspec.savebackup(self, 'journal.narrowspec')
         self.vfs.write("journal.branch",
                           encoding.fromlocal(self.dirstate.branch()))
         self.vfs.write("journal.desc",
@@ -1547,6 +1865,7 @@
             # prevent dirstateguard from overwriting already restored one
             dsguard.close()
 
+            narrowspec.restorebackup(self, 'undo.narrowspec')
             self.dirstate.restorebackup(None, 'undo.dirstate')
             try:
                 branch = self.vfs.read('undo.branch')
@@ -1601,7 +1920,7 @@
             # later call to `destroyed` will refresh them.
             return
 
-        if tr is None or tr.changes['revs']:
+        if tr is None or tr.changes['origrepolen'] < len(self):
             # updating the unfiltered branchmap should refresh all the others,
             self.ui.debug('updating the branch cache\n')
             branchmap.updatecache(self.filtered('served'))
@@ -1612,6 +1931,10 @@
                 rbc.branchinfo(r)
             rbc.write()
 
+            # ensure the working copy parents are in the manifestfulltextcache
+            for ctx in self['.'].parents():
+                ctx.manifest()  # accessing the manifest is enough
+
     def invalidatecaches(self):
 
         if '_tagscache' in vars(self):
@@ -2026,6 +2349,11 @@
     def commitctx(self, ctx, error=False):
         """Add a new revision to current repository.
         Revision information is passed via the context argument.
+
+        ctx.files() should list all files involved in this commit, i.e.
+        modified/added/removed files. On merge, it may be wider than the
+        ctx.files() to be committed, since any file nodes derived directly
+        from p1 or p2 are excluded from the committed ctx.files().
         """
 
         tr = None
@@ -2039,6 +2367,7 @@
 
             if ctx.manifestnode():
                 # reuse an existing manifest revision
+                self.ui.debug('reusing known manifest\n')
                 mn = ctx.manifestnode()
                 files = ctx.files()
             elif ctx.files():
@@ -2077,16 +2406,38 @@
                         raise
 
                 # update manifest
-                self.ui.note(_("committing manifest\n"))
                 removed = [f for f in sorted(removed) if f in m1 or f in m2]
                 drop = [f for f in removed if f in m]
                 for f in drop:
                     del m[f]
-                mn = mctx.write(trp, linkrev,
-                                p1.manifestnode(), p2.manifestnode(),
-                                added, drop)
                 files = changed + removed
+                md = None
+                if not files:
+                    # if no "files" actually changed in terms of the changelog,
+                    # try hard to detect unmodified manifest entry so that the
+                    # exact same commit can be reproduced later on convert.
+                    md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
+                if not files and md:
+                    self.ui.debug('not reusing manifest (no file change in '
+                                  'changelog, but manifest differs)\n')
+                if files or md:
+                    self.ui.note(_("committing manifest\n"))
+                    # we're using narrowmatch here since it's already applied at
+                    # other stages (such as dirstate.walk), so we're already
+                    # ignoring things outside of narrowspec in most cases. The
+                    # one case where we might have files outside the narrowspec
+                    # at this point is merges, and we already error out in the
+                    # case where the merge has files outside of the narrowspec,
+                    # so this is safe.
+                    mn = mctx.write(trp, linkrev,
+                                    p1.manifestnode(), p2.manifestnode(),
+                                    added, drop, match=self.narrowmatch())
+                else:
+                    self.ui.debug('reusing manifest form p1 (listed files '
+                                  'actually unchanged)\n')
+                    mn = p1.manifestnode()
             else:
+                self.ui.debug('reusing manifest from p1 (no file change)\n')
                 mn = p1.manifestnode()
                 files = []
 
@@ -2345,20 +2696,24 @@
     assert name.startswith('journal')
     return os.path.join(base, name.replace('journal', 'undo', 1))
 
-def instance(ui, path, create, intents=None):
-    return localrepository(ui, util.urllocalpath(path), create,
-                           intents=intents)
+def instance(ui, path, create, intents=None, createopts=None):
+    localpath = util.urllocalpath(path)
+    if create:
+        createrepository(ui, localpath, createopts=createopts)
+
+    return makelocalrepository(ui, localpath, intents=intents)
 
 def islocal(path):
     return True
 
-def newreporequirements(repo):
+def newreporequirements(ui, createopts=None):
     """Determine the set of requirements for a new local repository.
 
     Extensions can wrap this function to specify custom requirements for
     new repositories.
     """
-    ui = repo.ui
+    createopts = createopts or {}
+
     requirements = {'revlogv1'}
     if ui.configbool('format', 'usestore'):
         requirements.add('store')
@@ -2393,5 +2748,103 @@
         # generaldelta is implied by revlogv2.
         requirements.discard('generaldelta')
         requirements.add(REVLOGV2_REQUIREMENT)
+    # experimental config: format.internal-phase
+    if ui.configbool('format', 'internal-phase'):
+        requirements.add('internal-phase')
+
+    if createopts.get('narrowfiles'):
+        requirements.add(repository.NARROW_REQUIREMENT)
 
     return requirements
+
+def filterknowncreateopts(ui, createopts):
+    """Filters a dict of repo creation options against options that are known.
+
+    Receives a dict of repo creation options and returns a dict of those
+    options that we don't know how to handle.
+
+    This function is called as part of repository creation. If the
+    returned dict contains any items, repository creation will not
+    be allowed, as it means there was a request to create a repository
+    with options not recognized by loaded code.
+
+    Extensions can wrap this function to filter out creation options
+    they know how to handle.
+    """
+    known = {'narrowfiles'}
+
+    return {k: v for k, v in createopts.items() if k not in known}
+
+def createrepository(ui, path, createopts=None):
+    """Create a new repository in a vfs.
+
+    ``path`` path to the new repo's working directory.
+    ``createopts`` options for the new repository.
+    """
+    createopts = createopts or {}
+
+    unknownopts = filterknowncreateopts(ui, createopts)
+
+    if not isinstance(unknownopts, dict):
+        raise error.ProgrammingError('filterknowncreateopts() did not return '
+                                     'a dict')
+
+    if unknownopts:
+        raise error.Abort(_('unable to create repository because of unknown '
+                            'creation option: %s') %
+                          ', '.sorted(unknownopts),
+                          hint=_('is a required extension not loaded?'))
+
+    requirements = newreporequirements(ui, createopts=createopts)
+
+    wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
+    if not wdirvfs.exists():
+        wdirvfs.makedirs()
+
+    hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
+    if hgvfs.exists():
+        raise error.RepoError(_('repository %s already exists') % path)
+
+    hgvfs.makedir(notindexed=True)
+
+    if b'store' in requirements:
+        hgvfs.mkdir(b'store')
+
+        # We create an invalid changelog outside the store so very old
+        # Mercurial versions (which didn't know about the requirements
+        # file) encounter an error on reading the changelog. This
+        # effectively locks out old clients and prevents them from
+        # mucking with a repo in an unknown format.
+        #
+        # The revlog header has version 2, which won't be recognized by
+        # such old clients.
+        hgvfs.append(b'00changelog.i',
+                     b'\0\0\0\2 dummy changelog to prevent using the old repo '
+                     b'layout')
+
+    scmutil.writerequires(hgvfs, requirements)
+
+def poisonrepository(repo):
+    """Poison a repository instance so it can no longer be used."""
+    # Perform any cleanup on the instance.
+    repo.close()
+
+    # Our strategy is to replace the type of the object with one that
+    # has all attribute lookups result in error.
+    #
+    # But we have to allow the close() method because some constructors
+    # of repos call close() on repo references.
+    class poisonedrepository(object):
+        def __getattribute__(self, item):
+            if item == r'close':
+                return object.__getattribute__(self, item)
+
+            raise error.ProgrammingError('repo instances should not be used '
+                                         'after unshare')
+
+        def close(self):
+            pass
+
+    # We may have a repoview, which intercepts __setattr__. So be sure
+    # we operate at the lowest level possible.
+    object.__setattr__(repo, r'__class__', poisonedrepository)
--- a/mercurial/logcmdutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/logcmdutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -13,6 +13,8 @@
 from .i18n import _
 from .node import (
     nullid,
+    wdirid,
+    wdirrev,
 )
 
 from . import (
@@ -191,7 +193,6 @@
     def _show(self, ctx, copies, props):
         '''show a single changeset or file revision'''
         changenode = ctx.node()
-        rev = ctx.rev()
 
         if self.ui.quiet:
             self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
@@ -226,9 +227,13 @@
             self.ui.write(columns['parent'] % scmutil.formatchangeid(pctx),
                           label=label)
 
-        if self.ui.debugflag and rev is not None:
+        if self.ui.debugflag:
             mnode = ctx.manifestnode()
-            mrev = self.repo.manifestlog.rev(mnode)
+            if mnode is None:
+                mnode = wdirid
+                mrev = wdirrev
+            else:
+                mrev = self.repo.manifestlog.rev(mnode)
             self.ui.write(columns['manifest']
                           % scmutil.formatrevnode(self.ui, mrev, mnode),
                           label='ui.debug log.manifest')
@@ -325,15 +330,9 @@
         '''show a single changeset or file revision'''
         fm = self._fm
         fm.startitem()
-
-        # TODO: maybe this should be wdirrev/wdirnode?
-        rev = ctx.rev()
-        if rev is None:
-            hexnode = None
-        else:
-            hexnode = fm.hexfunc(ctx.node())
-        fm.data(rev=rev,
-                node=hexnode)
+        fm.context(ctx=ctx)
+        fm.data(rev=scmutil.intrev(ctx),
+                node=fm.hexfunc(scmutil.binnode(ctx)))
 
         if self.ui.quiet:
             return
@@ -349,11 +348,7 @@
                                        for c in ctx.parents()], name='node'))
 
         if self.ui.debugflag:
-            if rev is None:
-                hexnode = None
-            else:
-                hexnode = fm.hexfunc(ctx.manifestnode())
-            fm.data(manifest=hexnode,
+            fm.data(manifest=fm.hexfunc(ctx.manifestnode() or wdirid),
                     extra=fm.formatdict(ctx.extra()))
 
             files = ctx.p1().status(ctx)
--- a/mercurial/mail.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/mail.py	Wed Sep 26 20:33:09 2018 +0900
@@ -73,15 +73,24 @@
 
     def _get_socket(self, host, port, timeout):
         if self.debuglevel > 0:
-            self._ui.debug('connect: %r\n' % (host, port))
+            self._ui.debug('connect: %r\n' % ((host, port),))
         new_socket = socket.create_connection((host, port), timeout)
         new_socket = sslutil.wrapsocket(new_socket,
                                         self.keyfile, self.certfile,
                                         ui=self._ui,
                                         serverhostname=self._host)
-        self.file = smtplib.SSLFakeFile(new_socket)
+        self.file = new_socket.makefile(r'rb')
         return new_socket
 
+def _pyhastls():
+    """Returns true iff Python has TLS support, false otherwise."""
+    try:
+        import ssl
+        getattr(ssl, 'HAS_TLS', False)
+        return True
+    except ImportError:
+        return False
+
 def _smtp(ui):
     '''build an smtp connection and return a function to send mail'''
     local_hostname = ui.config('smtp', 'local_hostname')
@@ -89,7 +98,7 @@
     # backward compatible: when tls = true, we use starttls.
     starttls = tls == 'starttls' or stringutil.parsebool(tls)
     smtps = tls == 'smtps'
-    if (starttls or smtps) and not util.safehasattr(socket, 'ssl'):
+    if (starttls or smtps) and not _pyhastls():
         raise error.Abort(_("can't use TLS: Python SSL support not installed"))
     mailhost = ui.config('smtp', 'host')
     if not mailhost:
@@ -143,8 +152,9 @@
 def _sendmail(ui, sender, recipients, msg):
     '''send mail using sendmail.'''
     program = ui.config('email', 'method')
-    cmdline = '%s -f %s %s' % (program, stringutil.email(sender),
-                               ' '.join(map(stringutil.email, recipients)))
+    stremail = lambda x: stringutil.email(encoding.strtolocal(x))
+    cmdline = '%s -f %s %s' % (program, stremail(sender),
+                               ' '.join(map(stremail, recipients)))
     ui.note(_('sending mail: %s\n') % cmdline)
     fp = procutil.popen(cmdline, 'wb')
     fp.write(util.tonativeeol(msg))
@@ -160,7 +170,8 @@
     # Should be time.asctime(), but Windows prints 2-characters day
     # of month instead of one. Make them print the same thing.
     date = time.strftime(r'%a %b %d %H:%M:%S %Y', time.localtime())
-    fp.write('From %s %s\n' % (sender, date))
+    fp.write('From %s %s\n' % (encoding.strtolocal(sender),
+                               encoding.strtolocal(date)))
     fp.write(msg)
     fp.write('\n\n')
     fp.close()
@@ -209,7 +220,7 @@
 
     cs = ['us-ascii', 'utf-8', encoding.encoding, encoding.fallbackencoding]
     if display:
-        return mimetextqp(s, subtype, 'us-ascii')
+        cs = ['us-ascii']
     for charset in cs:
         try:
             s.decode(pycompat.sysstr(charset))
@@ -252,10 +263,27 @@
     order. Tries both encoding and fallbackencoding for input. Only as
     last resort send as is in fake ascii.
     Caveat: Do not use for mail parts containing patches!'''
+    sendcharsets = charsets or _charsets(ui)
+    if not isinstance(s, bytes):
+        # We have unicode data, which we need to try and encode to
+        # some reasonable-ish encoding. Try the encodings the user
+        # wants, and fall back to garbage-in-ascii.
+        for ocs in sendcharsets:
+            try:
+                return s.encode(pycompat.sysstr(ocs)), ocs
+            except UnicodeEncodeError:
+                pass
+            except LookupError:
+                ui.warn(_('ignoring invalid sendcharset: %s\n') % ocs)
+        else:
+            # Everything failed, ascii-armor what we've got and send it.
+            return s.encode('ascii', 'backslashreplace')
+    # We have a bytes of unknown encoding. We'll try and guess a valid
+    # encoding, falling back to pretending we had ascii even though we
+    # know that's wrong.
     try:
         s.decode('ascii')
     except UnicodeDecodeError:
-        sendcharsets = charsets or _charsets(ui)
         for ics in (encoding.encoding, encoding.fallbackencoding):
             try:
                 u = s.decode(ics)
@@ -263,7 +291,7 @@
                 continue
             for ocs in sendcharsets:
                 try:
-                    return u.encode(ocs), ocs
+                    return u.encode(pycompat.sysstr(ocs)), ocs
                 except UnicodeEncodeError:
                     pass
                 except LookupError:
@@ -280,40 +308,46 @@
     return s
 
 def _addressencode(ui, name, addr, charsets=None):
+    assert isinstance(addr, bytes)
     name = headencode(ui, name, charsets)
     try:
         acc, dom = addr.split('@')
-        acc = acc.encode('ascii')
-        dom = dom.decode(encoding.encoding).encode('idna')
+        acc.decode('ascii')
+        dom = dom.decode(pycompat.sysstr(encoding.encoding)).encode('idna')
         addr = '%s@%s' % (acc, dom)
     except UnicodeDecodeError:
         raise error.Abort(_('invalid email address: %s') % addr)
     except ValueError:
         try:
             # too strict?
-            addr = addr.encode('ascii')
+            addr.decode('ascii')
         except UnicodeDecodeError:
             raise error.Abort(_('invalid local address: %s') % addr)
-    return email.utils.formataddr((name, addr))
+    return pycompat.bytesurl(
+        email.utils.formataddr((name, encoding.strfromlocal(addr))))
 
 def addressencode(ui, address, charsets=None, display=False):
     '''Turns address into RFC-2047 compliant header.'''
     if display or not address:
         return address or ''
-    name, addr = email.utils.parseaddr(address)
-    return _addressencode(ui, name, addr, charsets)
+    name, addr = email.utils.parseaddr(encoding.strfromlocal(address))
+    return _addressencode(ui, name, encoding.strtolocal(addr), charsets)
 
 def addrlistencode(ui, addrs, charsets=None, display=False):
     '''Turns a list of addresses into a list of RFC-2047 compliant headers.
     A single element of input list may contain multiple addresses, but output
     always has one address per item'''
+    for a in addrs:
+        assert isinstance(a, bytes), (r'%r unexpectedly not a bytestr' % a)
     if display:
         return [a.strip() for a in addrs if a.strip()]
 
     result = []
-    for name, addr in email.utils.getaddresses(addrs):
+    for name, addr in email.utils.getaddresses(
+            [encoding.strfromlocal(a) for a in addrs]):
         if name or addr:
-            result.append(_addressencode(ui, name, addr, charsets))
+            r = _addressencode(ui, name, encoding.strtolocal(addr), charsets)
+            result.append(r)
     return result
 
 def mimeencode(ui, s, charsets=None, display=False):
--- a/mercurial/manifest.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/manifest.py	Wed Sep 26 20:33:09 2018 +0900
@@ -10,11 +10,14 @@
 import heapq
 import itertools
 import struct
+import weakref
 
 from .i18n import _
 from .node import (
     bin,
     hex,
+    nullid,
+    nullrev,
 )
 from . import (
     error,
@@ -54,12 +57,11 @@
 def _text(it):
     files = []
     lines = []
-    _hex = revlog.hex
     for f, n, fl in it:
         files.append(f)
         # if this is changed to support newlines in filenames,
         # be sure to check the templates/ dir again (especially *-raw.tmpl)
-        lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
+        lines.append("%s\0%s%s\n" % (f, hex(n), fl))
 
     _checkforbidden(files)
     return ''.join(lines)
@@ -565,7 +567,7 @@
                 start, end = _msearch(addbuf, f, start)
                 if not todelete:
                     h, fl = self._lm[f]
-                    l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
+                    l = "%s\0%s%s\n" % (f, hex(h), fl)
                 else:
                     if start == end:
                         # item we want to delete was not found, error out
@@ -641,7 +643,7 @@
     """Check filenames for illegal characters."""
     for f in l:
         if '\n' in f or '\r' in f:
-            raise error.RevlogError(
+            raise error.StorageError(
                 _("'\\n' and '\\r' disallowed in filenames: %r")
                 % pycompat.bytestr(f))
 
@@ -679,11 +681,12 @@
 class treemanifest(object):
     def __init__(self, dir='', text=''):
         self._dir = dir
-        self._node = revlog.nullid
+        self._node = nullid
         self._loadfunc = _noop
         self._copyfunc = _noop
         self._dirty = False
         self._dirs = {}
+        self._lazydirs = {}
         # Using _lazymanifest here is a little slower than plain old dicts
         self._files = {}
         self._flags = {}
@@ -697,9 +700,39 @@
     def _subpath(self, path):
         return self._dir + path
 
+    def _loadalllazy(self):
+        for k, (path, node, readsubtree) in self._lazydirs.iteritems():
+            self._dirs[k] = readsubtree(path, node)
+        self._lazydirs = {}
+
+    def _loadlazy(self, d):
+        path, node, readsubtree = self._lazydirs[d]
+        self._dirs[d] = readsubtree(path, node)
+        del self._lazydirs[d]
+
+    def _loadchildrensetlazy(self, visit):
+        if not visit:
+            return None
+        if visit == 'all' or visit == 'this':
+            self._loadalllazy()
+            return None
+
+        todel = []
+        for k in visit:
+            kslash = k + '/'
+            ld = self._lazydirs.get(kslash)
+            if ld:
+                path, node, readsubtree = ld
+                self._dirs[kslash] = readsubtree(path, node)
+                todel.append(kslash)
+        for kslash in todel:
+            del self._lazydirs[kslash]
+        return visit
+
     def __len__(self):
         self._load()
         size = len(self._files)
+        self._loadalllazy()
         for m in self._dirs.values():
             size += m.__len__()
         return size
@@ -712,12 +745,17 @@
 
     def _isempty(self):
         self._load() # for consistency; already loaded by all callers
-        return (not self._files and (not self._dirs or
-                all(m._isempty() for m in self._dirs.values())))
+        # See if we can skip loading everything.
+        if self._files or (self._dirs and
+                           any(not m._isempty() for m in self._dirs.values())):
+            return False
+        self._loadalllazy()
+        return (not self._dirs or
+                all(m._isempty() for m in self._dirs.values()))
 
     def __repr__(self):
         return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
-                (self._dir, revlog.hex(self._node),
+                (self._dir, hex(self._node),
                  bool(self._loadfunc is _noop),
                  self._dirty, id(self)))
 
@@ -739,6 +777,7 @@
 
     def iterentries(self):
         self._load()
+        self._loadalllazy()
         for p, n in sorted(itertools.chain(self._dirs.items(),
                                            self._files.items())):
             if p in self._files:
@@ -749,6 +788,7 @@
 
     def items(self):
         self._load()
+        self._loadalllazy()
         for p, n in sorted(itertools.chain(self._dirs.items(),
                                            self._files.items())):
             if p in self._files:
@@ -761,6 +801,7 @@
 
     def iterkeys(self):
         self._load()
+        self._loadalllazy()
         for p in sorted(itertools.chain(self._dirs, self._files)):
             if p in self._files:
                 yield self._subpath(p)
@@ -780,8 +821,12 @@
         self._load()
         dir, subpath = _splittopdir(f)
         if dir:
+            if dir in self._lazydirs:
+                self._loadlazy(dir)
+
             if dir not in self._dirs:
                 return False
+
             return self._dirs[dir].__contains__(subpath)
         else:
             return f in self._files
@@ -790,6 +835,9 @@
         self._load()
         dir, subpath = _splittopdir(f)
         if dir:
+            if dir in self._lazydirs:
+                self._loadlazy(dir)
+
             if dir not in self._dirs:
                 return default
             return self._dirs[dir].get(subpath, default)
@@ -800,6 +848,9 @@
         self._load()
         dir, subpath = _splittopdir(f)
         if dir:
+            if dir in self._lazydirs:
+                self._loadlazy(dir)
+
             return self._dirs[dir].__getitem__(subpath)
         else:
             return self._files[f]
@@ -808,11 +859,14 @@
         self._load()
         dir, subpath = _splittopdir(f)
         if dir:
+            if dir in self._lazydirs:
+                self._loadlazy(dir)
+
             if dir not in self._dirs:
                 return ''
             return self._dirs[dir].flags(subpath)
         else:
-            if f in self._dirs:
+            if f in self._lazydirs or f in self._dirs:
                 return ''
             return self._flags.get(f, '')
 
@@ -820,6 +874,9 @@
         self._load()
         dir, subpath = _splittopdir(f)
         if dir:
+            if dir in self._lazydirs:
+                self._loadlazy(dir)
+
             return self._dirs[dir].find(subpath)
         else:
             return self._files[f], self._flags.get(f, '')
@@ -828,6 +885,9 @@
         self._load()
         dir, subpath = _splittopdir(f)
         if dir:
+            if dir in self._lazydirs:
+                self._loadlazy(dir)
+
             self._dirs[dir].__delitem__(subpath)
             # If the directory is now empty, remove it
             if self._dirs[dir]._isempty():
@@ -843,6 +903,8 @@
         self._load()
         dir, subpath = _splittopdir(f)
         if dir:
+            if dir in self._lazydirs:
+                self._loadlazy(dir)
             if dir not in self._dirs:
                 self._dirs[dir] = treemanifest(self._subpath(dir))
             self._dirs[dir].__setitem__(subpath, n)
@@ -863,6 +925,8 @@
         self._load()
         dir, subpath = _splittopdir(f)
         if dir:
+            if dir in self._lazydirs:
+                self._loadlazy(dir)
             if dir not in self._dirs:
                 self._dirs[dir] = treemanifest(self._subpath(dir))
             self._dirs[dir].setflag(subpath, flags)
@@ -877,8 +941,12 @@
         if self._copyfunc is _noop:
             def _copyfunc(s):
                 self._load()
-                for d in self._dirs:
-                    s._dirs[d] = self._dirs[d].copy()
+                # OPT: it'd be nice to not load everything here. Unfortunately
+                # this makes a mess of the "dirty" state tracking if we don't.
+                self._loadalllazy()
+                sdirs = s._dirs
+                for d, v in self._dirs.iteritems():
+                    sdirs[d] = v.copy()
                 s._files = dict.copy(self._files)
                 s._flags = dict.copy(self._flags)
             if self._loadfunc is _noop:
@@ -891,7 +959,7 @@
 
     def filesnotin(self, m2, match=None):
         '''Set of files in this manifest that are not in the other'''
-        if match:
+        if match and not match.always():
             m1 = self.matches(match)
             m2 = m2.matches(match)
             return m1.filesnotin(m2)
@@ -902,6 +970,8 @@
                 return
             t1._load()
             t2._load()
+            t1._loadalllazy()
+            t2._loadalllazy()
             for d, m1 in t1._dirs.iteritems():
                 if d in t2._dirs:
                     m2 = t2._dirs[d]
@@ -927,10 +997,13 @@
         self._load()
         topdir, subdir = _splittopdir(dir)
         if topdir:
+            if topdir in self._lazydirs:
+                self._loadlazy(topdir)
             if topdir in self._dirs:
                 return self._dirs[topdir].hasdir(subdir)
             return False
-        return (dir + '/') in self._dirs
+        dirslash = dir + '/'
+        return dirslash in self._dirs or dirslash in self._lazydirs
 
     def walk(self, match):
         '''Generates matching file names.
@@ -963,19 +1036,22 @@
 
     def _walk(self, match):
         '''Recursively generates matching file names for walk().'''
-        if not match.visitdir(self._dir[:-1] or '.'):
+        visit = match.visitchildrenset(self._dir[:-1] or '.')
+        if not visit:
             return
 
         # yield this dir's files and walk its submanifests
         self._load()
+        visit = self._loadchildrensetlazy(visit)
         for p in sorted(list(self._dirs) + list(self._files)):
             if p in self._files:
                 fullp = self._subpath(p)
                 if match(fullp):
                     yield fullp
             else:
-                for f in self._dirs[p]._walk(match):
-                    yield f
+                if not visit or p[:-1] in visit:
+                    for f in self._dirs[p]._walk(match):
+                        yield f
 
     def matches(self, match):
         '''generate a new manifest filtered by the match argument'''
@@ -988,7 +1064,7 @@
         '''recursively generate a new manifest filtered by the match argument.
         '''
 
-        visit = match.visitdir(self._dir[:-1] or '.')
+        visit = match.visitchildrenset(self._dir[:-1] or '.')
         if visit == 'all':
             return self.copy()
         ret = treemanifest(self._dir)
@@ -997,14 +1073,26 @@
 
         self._load()
         for fn in self._files:
+            # While visitchildrenset *usually* lists only subdirs, this is
+            # actually up to the matcher and may have some files in the set().
+            # If visit == 'this', we should obviously look at the files in this
+            # directory; if visit is a set, and fn is in it, we should inspect
+            # fn (but no need to inspect things not in the set).
+            if visit != 'this' and fn not in visit:
+                continue
             fullp = self._subpath(fn)
+            # visitchildrenset isn't perfect, we still need to call the regular
+            # matcher code to further filter results.
             if not match(fullp):
                 continue
             ret._files[fn] = self._files[fn]
             if fn in self._flags:
                 ret._flags[fn] = self._flags[fn]
 
+        visit = self._loadchildrensetlazy(visit)
         for dir, subm in self._dirs.iteritems():
+            if visit and dir[:-1] not in visit:
+                continue
             m = subm._matches(match)
             if not m._isempty():
                 ret._dirs[dir] = m
@@ -1028,7 +1116,7 @@
         the nodeid will be None and the flags will be the empty
         string.
         '''
-        if match:
+        if match and not match.always():
             m1 = self.matches(match)
             m2 = m2.matches(match)
             return m1.diff(m2, clean=clean)
@@ -1039,6 +1127,9 @@
                 return
             t1._load()
             t2._load()
+            # OPT: do we need to load everything?
+            t1._loadalllazy()
+            t2._loadalllazy()
             for d, m1 in t1._dirs.iteritems():
                 m2 = t2._dirs.get(d, emptytree)
                 _diff(m1, m2)
@@ -1068,10 +1159,12 @@
         return not self._dirty and not m2._dirty and self._node == m2._node
 
     def parse(self, text, readsubtree):
+        selflazy = self._lazydirs
+        subpath = self._subpath
         for f, n, fl in _parse(text):
             if fl == 't':
                 f = f + '/'
-                self._dirs[f] = readsubtree(self._subpath(f), n)
+                selflazy[f] = (subpath(f), n, readsubtree)
             elif '/' in f:
                 # This is a flat manifest, so use __setitem__ and setflag rather
                 # than assigning directly to _files and _flags, so we can
@@ -1098,9 +1191,11 @@
         """
         self._load()
         flags = self.flags
+        lazydirs = [(d[:-1], node, 't') for
+                    d, (path, node, readsubtree) in self._lazydirs.iteritems()]
         dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
         files = [(f, self._files[f], flags(f)) for f in self._files]
-        return _text(sorted(dirs + files))
+        return _text(sorted(dirs + files + lazydirs))
 
     def read(self, gettext, readsubtree):
         def _load_for_read(s):
@@ -1108,17 +1203,32 @@
             s._dirty = False
         self._loadfunc = _load_for_read
 
-    def writesubtrees(self, m1, m2, writesubtree):
+    def writesubtrees(self, m1, m2, writesubtree, match):
         self._load() # for consistency; should never have any effect here
         m1._load()
         m2._load()
         emptytree = treemanifest()
+        def getnode(m, d):
+            ld = m._lazydirs.get(d)
+            if ld:
+                return ld[1]
+            return m._dirs.get(d, emptytree)._node
+
+        # we should have always loaded everything by the time we get here for
+        # `self`, but possibly not in `m1` or `m2`.
+        assert not self._lazydirs
+        # let's skip investigating things that `match` says we do not need.
+        visit = match.visitchildrenset(self._dir[:-1] or '.')
+        if visit == 'this' or visit == 'all':
+            visit = None
         for d, subm in self._dirs.iteritems():
-            subp1 = m1._dirs.get(d, emptytree)._node
-            subp2 = m2._dirs.get(d, emptytree)._node
-            if subp1 == revlog.nullid:
+            if visit and d[:-1] not in visit:
+                continue
+            subp1 = getnode(m1, d)
+            subp2 = getnode(m2, d)
+            if subp1 == nullid:
                 subp1, subp2 = subp2, subp1
-            writesubtree(subm, subp1, subp2)
+            writesubtree(subm, subp1, subp2, match)
 
     def walksubtrees(self, matcher=None):
         """Returns an iterator of the subtrees of this manifest, including this
@@ -1132,15 +1242,127 @@
             yield self
 
         self._load()
+        # OPT: use visitchildrenset to avoid loading everything.
+        self._loadalllazy()
         for d, subm in self._dirs.iteritems():
             for subtree in subm.walksubtrees(matcher=matcher):
                 yield subtree
 
-class manifestrevlog(revlog.revlog):
+class manifestfulltextcache(util.lrucachedict):
+    """File-backed LRU cache for the manifest cache
+
+    File consists of entries, up to EOF:
+
+    - 20 bytes node, 4 bytes length, <length> manifest data
+
+    These are written in reverse cache order (oldest to newest).
+
+    """
+    def __init__(self, max):
+        super(manifestfulltextcache, self).__init__(max)
+        self._dirty = False
+        self._read = False
+        self._opener = None
+
+    def read(self):
+        if self._read or self._opener is None:
+            return
+
+        try:
+            with self._opener('manifestfulltextcache') as fp:
+                set = super(manifestfulltextcache, self).__setitem__
+                # ignore trailing data, this is a cache, corruption is skipped
+                while True:
+                    node = fp.read(20)
+                    if len(node) < 20:
+                        break
+                    try:
+                        size = struct.unpack('>L', fp.read(4))[0]
+                    except struct.error:
+                        break
+                    value = bytearray(fp.read(size))
+                    if len(value) != size:
+                        break
+                    set(node, value)
+        except IOError:
+            # the file is allowed to be missing
+            pass
+
+        self._read = True
+        self._dirty = False
+
+    def write(self):
+        if not self._dirty or self._opener is None:
+            return
+        # rotate backwards to the first used node
+        with self._opener(
+                'manifestfulltextcache', 'w', atomictemp=True, checkambig=True
+            ) as fp:
+            node = self._head.prev
+            while True:
+                if node.key in self._cache:
+                    fp.write(node.key)
+                    fp.write(struct.pack('>L', len(node.value)))
+                    fp.write(node.value)
+                if node is self._head:
+                    break
+                node = node.prev
+
+    def __len__(self):
+        if not self._read:
+            self.read()
+        return super(manifestfulltextcache, self).__len__()
+
+    def __contains__(self, k):
+        if not self._read:
+            self.read()
+        return super(manifestfulltextcache, self).__contains__(k)
+
+    def __iter__(self):
+        if not self._read:
+            self.read()
+        return super(manifestfulltextcache, self).__iter__()
+
+    def __getitem__(self, k):
+        if not self._read:
+            self.read()
+        # the cache lru order can change on read
+        setdirty = self._cache.get(k) is not self._head
+        value = super(manifestfulltextcache, self).__getitem__(k)
+        if setdirty:
+            self._dirty = True
+        return value
+
+    def __setitem__(self, k, v):
+        if not self._read:
+            self.read()
+        super(manifestfulltextcache, self).__setitem__(k, v)
+        self._dirty = True
+
+    def __delitem__(self, k):
+        if not self._read:
+            self.read()
+        super(manifestfulltextcache, self).__delitem__(k)
+        self._dirty = True
+
+    def get(self, k, default=None):
+        if not self._read:
+            self.read()
+        return super(manifestfulltextcache, self).get(k, default=default)
+
+    def clear(self, clear_persisted_data=False):
+        super(manifestfulltextcache, self).clear()
+        if clear_persisted_data:
+            self._dirty = True
+            self.write()
+        self._read = False
+
+@interfaceutil.implementer(repository.imanifeststorage)
+class manifestrevlog(object):
     '''A revlog that stores manifest texts. This is responsible for caching the
     full-text manifest contents.
     '''
-    def __init__(self, opener, dir='', dirlogcache=None, indexfile=None,
+    def __init__(self, opener, tree='', dirlogcache=None, indexfile=None,
                  treemanifest=False):
         """Constructs a new manifest revlog
 
@@ -1164,36 +1386,63 @@
 
         self._treeondisk = optiontreemanifest or treemanifest
 
-        self._fulltextcache = util.lrucachedict(cachesize)
+        self._fulltextcache = manifestfulltextcache(cachesize)
 
-        if dir:
+        if tree:
             assert self._treeondisk, 'opts is %r' % opts
 
         if indexfile is None:
             indexfile = '00manifest.i'
-            if dir:
-                indexfile = "meta/" + dir + indexfile
+            if tree:
+                indexfile = "meta/" + tree + indexfile
 
-        self._dir = dir
+        self.tree = tree
+
         # The dirlogcache is kept on the root manifest log
-        if dir:
+        if tree:
             self._dirlogcache = dirlogcache
         else:
             self._dirlogcache = {'': self}
 
-        super(manifestrevlog, self).__init__(opener, indexfile,
-                                             # only root indexfile is cached
-                                             checkambig=not bool(dir),
-                                             mmaplargeindex=True)
+        self._revlog = revlog.revlog(opener, indexfile,
+                                     # only root indexfile is cached
+                                     checkambig=not bool(tree),
+                                     mmaplargeindex=True)
+
+        self.index = self._revlog.index
+        self.version = self._revlog.version
+        self._generaldelta = self._revlog._generaldelta
+
+    def _setupmanifestcachehooks(self, repo):
+        """Persist the manifestfulltextcache on lock release"""
+        if not util.safehasattr(repo, '_lockref'):
+            return
+
+        self._fulltextcache._opener = repo.cachevfs
+        reporef = weakref.ref(repo)
+        manifestrevlogref = weakref.ref(self)
+
+        def persistmanifestcache():
+            repo = reporef()
+            self = manifestrevlogref()
+            if repo is None or self is None:
+                return
+            if repo.manifestlog.getstorage(b'') is not self:
+                # there's a different manifest in play now, abort
+                return
+            self._fulltextcache.write()
+
+        if repo._currentlock(repo._lockref) is not None:
+            repo._afterlock(persistmanifestcache)
 
     @property
     def fulltextcache(self):
         return self._fulltextcache
 
-    def clearcaches(self):
-        super(manifestrevlog, self).clearcaches()
-        self._fulltextcache.clear()
-        self._dirlogcache = {'': self}
+    def clearcaches(self, clear_persisted_data=False):
+        self._revlog.clearcaches()
+        self._fulltextcache.clear(clear_persisted_data=clear_persisted_data)
+        self._dirlogcache = {self.tree: self}
 
     def dirlog(self, d):
         if d:
@@ -1205,7 +1454,8 @@
             self._dirlogcache[d] = mfrevlog
         return self._dirlogcache[d]
 
-    def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
+    def add(self, m, transaction, link, p1, p2, added, removed, readtree=None,
+            match=None):
         if p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta'):
             # If our first parent is in the manifest cache, we can
             # compute a delta here using properties we know about the
@@ -1218,9 +1468,10 @@
                                [(x, True) for x in removed])
 
             arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
-            cachedelta = self.rev(p1), deltatext
+            cachedelta = self._revlog.rev(p1), deltatext
             text = util.buffer(arraytext)
-            n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
+            n = self._revlog.addrevision(text, transaction, link, p1, p2,
+                                         cachedelta)
         else:
             # The first parent manifest isn't already loaded, so we'll
             # just encode a fulltext of the manifest and pass that
@@ -1228,13 +1479,15 @@
             # process.
             if self._treeondisk:
                 assert readtree, "readtree must be set for treemanifest writes"
-                m1 = readtree(self._dir, p1)
-                m2 = readtree(self._dir, p2)
-                n = self._addtree(m, transaction, link, m1, m2, readtree)
+                assert match, "match must be specified for treemanifest writes"
+                m1 = readtree(self.tree, p1)
+                m2 = readtree(self.tree, p2)
+                n = self._addtree(m, transaction, link, m1, m2, readtree,
+                                  match=match)
                 arraytext = None
             else:
                 text = m.text()
-                n = self.addrevision(text, transaction, link, p1, p2)
+                n = self._revlog.addrevision(text, transaction, link, p1, p2)
                 arraytext = bytearray(text)
 
         if arraytext is not None:
@@ -1242,19 +1495,20 @@
 
         return n
 
-    def _addtree(self, m, transaction, link, m1, m2, readtree):
+    def _addtree(self, m, transaction, link, m1, m2, readtree, match):
         # If the manifest is unchanged compared to one parent,
         # don't write a new revision
-        if self._dir != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(m2)):
+        if self.tree != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(
+            m2)):
             return m.node()
-        def writesubtree(subm, subp1, subp2):
+        def writesubtree(subm, subp1, subp2, match):
             sublog = self.dirlog(subm.dir())
             sublog.add(subm, transaction, link, subp1, subp2, None, None,
-                       readtree=readtree)
-        m.writesubtrees(m1, m2, writesubtree)
+                       readtree=readtree, match=match)
+        m.writesubtrees(m1, m2, writesubtree, match)
         text = m.dirtext()
         n = None
-        if self._dir != '':
+        if self.tree != '':
             # Double-check whether contents are unchanged to one parent
             if text == m1.dirtext():
                 n = m1.node()
@@ -1262,12 +1516,90 @@
                 n = m2.node()
 
         if not n:
-            n = self.addrevision(text, transaction, link, m1.node(), m2.node())
+            n = self._revlog.addrevision(text, transaction, link, m1.node(),
+                                         m2.node())
 
         # Save nodeid so parent manifest can calculate its nodeid
         m.setnode(n)
         return n
 
+    def __len__(self):
+        return len(self._revlog)
+
+    def __iter__(self):
+        return self._revlog.__iter__()
+
+    def rev(self, node):
+        return self._revlog.rev(node)
+
+    def node(self, rev):
+        return self._revlog.node(rev)
+
+    def lookup(self, value):
+        return self._revlog.lookup(value)
+
+    def parentrevs(self, rev):
+        return self._revlog.parentrevs(rev)
+
+    def parents(self, node):
+        return self._revlog.parents(node)
+
+    def linkrev(self, rev):
+        return self._revlog.linkrev(rev)
+
+    def checksize(self):
+        return self._revlog.checksize()
+
+    def revision(self, node, _df=None, raw=False):
+        return self._revlog.revision(node, _df=_df, raw=raw)
+
+    def revdiff(self, rev1, rev2):
+        return self._revlog.revdiff(rev1, rev2)
+
+    def cmp(self, node, text):
+        return self._revlog.cmp(node, text)
+
+    def deltaparent(self, rev):
+        return self._revlog.deltaparent(rev)
+
+    def emitrevisiondeltas(self, requests):
+        return self._revlog.emitrevisiondeltas(requests)
+
+    def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
+        return self._revlog.addgroup(deltas, linkmapper, transaction,
+                                     addrevisioncb=addrevisioncb)
+
+    def getstrippoint(self, minlink):
+        return self._revlog.getstrippoint(minlink)
+
+    def strip(self, minlink, transaction):
+        return self._revlog.strip(minlink, transaction)
+
+    def files(self):
+        return self._revlog.files()
+
+    def clone(self, tr, destrevlog, **kwargs):
+        if not isinstance(destrevlog, manifestrevlog):
+            raise error.ProgrammingError('expected manifestrevlog to clone()')
+
+        return self._revlog.clone(tr, destrevlog._revlog, **kwargs)
+
+    @property
+    def indexfile(self):
+        return self._revlog.indexfile
+
+    @indexfile.setter
+    def indexfile(self, value):
+        self._revlog.indexfile = value
+
+    @property
+    def opener(self):
+        return self._revlog.opener
+
+    @opener.setter
+    def opener(self, value):
+        self._revlog.opener = value
+
 @interfaceutil.implementer(repository.imanifestlog)
 class manifestlog(object):
     """A collection class representing the collection of manifest snapshots
@@ -1277,7 +1609,7 @@
     of the list of files in the given commit. Consumers of the output of this
     class do not care about the implementation details of the actual manifests
     they receive (i.e. tree or flat or lazily loaded, etc)."""
-    def __init__(self, opener, repo):
+    def __init__(self, opener, repo, rootstore):
         usetreemanifest = False
         cachesize = 4
 
@@ -1285,9 +1617,11 @@
         if opts is not None:
             usetreemanifest = opts.get('treemanifest', usetreemanifest)
             cachesize = opts.get('manifestcachesize', cachesize)
-        self._treeinmem = usetreemanifest
+
+        self._treemanifests = usetreemanifest
 
-        self._revlog = repo._constructmanifest()
+        self._rootstore = rootstore
+        self._rootstore._setupmanifestcachehooks(repo)
         self._narrowmatch = repo.narrowmatch()
 
         # A cache of the manifestctx or treemanifestctx for each directory
@@ -1302,58 +1636,58 @@
         """
         return self.get('', node)
 
-    def get(self, dir, node, verify=True):
+    def get(self, tree, node, verify=True):
         """Retrieves the manifest instance for the given node. Throws a
         LookupError if not found.
 
         `verify` - if True an exception will be thrown if the node is not in
                    the revlog
         """
-        if node in self._dirmancache.get(dir, ()):
-            return self._dirmancache[dir][node]
+        if node in self._dirmancache.get(tree, ()):
+            return self._dirmancache[tree][node]
 
         if not self._narrowmatch.always():
-            if not self._narrowmatch.visitdir(dir[:-1] or '.'):
-                return excludeddirmanifestctx(dir, node)
-        if dir:
-            if self._revlog._treeondisk:
+            if not self._narrowmatch.visitdir(tree[:-1] or '.'):
+                return excludeddirmanifestctx(tree, node)
+        if tree:
+            if self._rootstore._treeondisk:
                 if verify:
-                    dirlog = self._revlog.dirlog(dir)
-                    if node not in dirlog.nodemap:
-                        raise LookupError(node, dirlog.indexfile,
-                                          _('no node'))
-                m = treemanifestctx(self, dir, node)
+                    # Side-effect is LookupError is raised if node doesn't
+                    # exist.
+                    self.getstorage(tree).rev(node)
+
+                m = treemanifestctx(self, tree, node)
             else:
                 raise error.Abort(
                         _("cannot ask for manifest directory '%s' in a flat "
-                          "manifest") % dir)
+                          "manifest") % tree)
         else:
             if verify:
-                if node not in self._revlog.nodemap:
-                    raise LookupError(node, self._revlog.indexfile,
-                                      _('no node'))
-            if self._treeinmem:
+                # Side-effect is LookupError is raised if node doesn't exist.
+                self._rootstore.rev(node)
+
+            if self._treemanifests:
                 m = treemanifestctx(self, '', node)
             else:
                 m = manifestctx(self, node)
 
-        if node != revlog.nullid:
-            mancache = self._dirmancache.get(dir)
+        if node != nullid:
+            mancache = self._dirmancache.get(tree)
             if not mancache:
                 mancache = util.lrucachedict(self._cachesize)
-                self._dirmancache[dir] = mancache
+                self._dirmancache[tree] = mancache
             mancache[node] = m
         return m
 
-    def clearcaches(self):
+    def getstorage(self, tree):
+        return self._rootstore.dirlog(tree)
+
+    def clearcaches(self, clear_persisted_data=False):
         self._dirmancache.clear()
-        self._revlog.clearcaches()
+        self._rootstore.clearcaches(clear_persisted_data=clear_persisted_data)
 
     def rev(self, node):
-        return self._revlog.rev(node)
-
-    def addgroup(self, deltas, linkmapper, transaction):
-        return self._revlog.addgroup(deltas, linkmapper, transaction)
+        return self._rootstore.rev(node)
 
 @interfaceutil.implementer(repository.imanifestrevisionwritable)
 class memmanifestctx(object):
@@ -1361,8 +1695,8 @@
         self._manifestlog = manifestlog
         self._manifestdict = manifestdict()
 
-    def _revlog(self):
-        return self._manifestlog._revlog
+    def _storage(self):
+        return self._manifestlog.getstorage(b'')
 
     def new(self):
         return memmanifestctx(self._manifestlog)
@@ -1375,9 +1709,9 @@
     def read(self):
         return self._manifestdict
 
-    def write(self, transaction, link, p1, p2, added, removed):
-        return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
-                                  added, removed)
+    def write(self, transaction, link, p1, p2, added, removed, match=None):
+        return self._storage().add(self._manifestdict, transaction, link,
+                                   p1, p2, added, removed, match=match)
 
 @interfaceutil.implementer(repository.imanifestrevisionstored)
 class manifestctx(object):
@@ -1393,12 +1727,12 @@
         # TODO: We eventually want p1, p2, and linkrev exposed on this class,
         # but let's add it later when something needs it and we can load it
         # lazily.
-        #self.p1, self.p2 = revlog.parents(node)
-        #rev = revlog.rev(node)
-        #self.linkrev = revlog.linkrev(rev)
+        #self.p1, self.p2 = store.parents(node)
+        #rev = store.rev(node)
+        #self.linkrev = store.linkrev(rev)
 
-    def _revlog(self):
-        return self._manifestlog._revlog
+    def _storage(self):
+        return self._manifestlog.getstorage(b'')
 
     def node(self):
         return self._node
@@ -1413,17 +1747,20 @@
 
     @propertycache
     def parents(self):
-        return self._revlog().parents(self._node)
+        return self._storage().parents(self._node)
 
     def read(self):
         if self._data is None:
-            if self._node == revlog.nullid:
+            if self._node == nullid:
                 self._data = manifestdict()
             else:
-                rl = self._revlog()
-                text = rl.revision(self._node)
-                arraytext = bytearray(text)
-                rl._fulltextcache[self._node] = arraytext
+                store = self._storage()
+                if self._node in store.fulltextcache:
+                    text = pycompat.bytestr(store.fulltextcache[self._node])
+                else:
+                    text = store.revision(self._node)
+                    arraytext = bytearray(text)
+                    store.fulltextcache[self._node] = arraytext
                 self._data = manifestdict(text)
         return self._data
 
@@ -1434,10 +1771,10 @@
 
         If `shallow` is True, nothing changes since this is a flat manifest.
         '''
-        rl = self._revlog()
-        r = rl.rev(self._node)
-        deltaparent = rl.deltaparent(r)
-        if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
+        store = self._storage()
+        r = store.rev(self._node)
+        deltaparent = store.deltaparent(r)
+        if deltaparent != nullrev and deltaparent in store.parentrevs(r):
             return self.readdelta()
         return self.read()
 
@@ -1448,9 +1785,9 @@
 
         Changing the value of `shallow` has no effect on flat manifests.
         '''
-        revlog = self._revlog()
-        r = revlog.rev(self._node)
-        d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
+        store = self._storage()
+        r = store.rev(self._node)
+        d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
         return manifestdict(d)
 
     def find(self, key):
@@ -1463,8 +1800,8 @@
         self._dir = dir
         self._treemanifest = treemanifest()
 
-    def _revlog(self):
-        return self._manifestlog._revlog
+    def _storage(self):
+        return self._manifestlog.getstorage(b'')
 
     def new(self, dir=''):
         return memtreemanifestctx(self._manifestlog, dir=dir)
@@ -1477,11 +1814,12 @@
     def read(self):
         return self._treemanifest
 
-    def write(self, transaction, link, p1, p2, added, removed):
+    def write(self, transaction, link, p1, p2, added, removed, match=None):
         def readtree(dir, node):
             return self._manifestlog.get(dir, node).read()
-        return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
-                                  added, removed, readtree=readtree)
+        return self._storage().add(self._treemanifest, transaction, link,
+                                   p1, p2, added, removed, readtree=readtree,
+                                   match=match)
 
 @interfaceutil.implementer(repository.imanifestrevisionstored)
 class treemanifestctx(object):
@@ -1495,26 +1833,27 @@
         # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
         # we can instantiate treemanifestctx objects for directories we don't
         # have on disk.
-        #self.p1, self.p2 = revlog.parents(node)
-        #rev = revlog.rev(node)
-        #self.linkrev = revlog.linkrev(rev)
+        #self.p1, self.p2 = store.parents(node)
+        #rev = store.rev(node)
+        #self.linkrev = store.linkrev(rev)
 
-    def _revlog(self):
+    def _storage(self):
         narrowmatch = self._manifestlog._narrowmatch
         if not narrowmatch.always():
             if not narrowmatch.visitdir(self._dir[:-1] or '.'):
                 return excludedmanifestrevlog(self._dir)
-        return self._manifestlog._revlog.dirlog(self._dir)
+        return self._manifestlog.getstorage(self._dir)
 
     def read(self):
         if self._data is None:
-            rl = self._revlog()
-            if self._node == revlog.nullid:
+            store = self._storage()
+            if self._node == nullid:
                 self._data = treemanifest()
-            elif rl._treeondisk:
+            # TODO accessing non-public API
+            elif store._treeondisk:
                 m = treemanifest(dir=self._dir)
                 def gettext():
-                    return rl.revision(self._node)
+                    return store.revision(self._node)
                 def readsubtree(dir, subm):
                     # Set verify to False since we need to be able to create
                     # subtrees for trees that don't exist on disk.
@@ -1523,9 +1862,12 @@
                 m.setnode(self._node)
                 self._data = m
             else:
-                text = rl.revision(self._node)
-                arraytext = bytearray(text)
-                rl.fulltextcache[self._node] = arraytext
+                if self._node in store.fulltextcache:
+                    text = pycompat.bytestr(store.fulltextcache[self._node])
+                else:
+                    text = store.revision(self._node)
+                    arraytext = bytearray(text)
+                    store.fulltextcache[self._node] = arraytext
                 self._data = treemanifest(dir=self._dir, text=text)
 
         return self._data
@@ -1543,7 +1885,7 @@
 
     @propertycache
     def parents(self):
-        return self._revlog().parents(self._node)
+        return self._storage().parents(self._node)
 
     def readdelta(self, shallow=False):
         '''Returns a manifest containing just the entries that are present
@@ -1556,15 +1898,15 @@
         the subdirectory will be reported among files and distinguished only by
         its 't' flag.
         '''
-        revlog = self._revlog()
+        store = self._storage()
         if shallow:
-            r = revlog.rev(self._node)
-            d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
+            r = store.rev(self._node)
+            d = mdiff.patchtext(store.revdiff(store.deltaparent(r), r))
             return manifestdict(d)
         else:
             # Need to perform a slow delta
-            r0 = revlog.deltaparent(revlog.rev(self._node))
-            m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
+            r0 = store.deltaparent(store.rev(self._node))
+            m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
             m1 = self.read()
             md = treemanifest(dir=self._dir)
             for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
@@ -1582,15 +1924,15 @@
         If `shallow` is True, it only returns the entries from this manifest,
         and not any submanifests.
         '''
-        rl = self._revlog()
-        r = rl.rev(self._node)
-        deltaparent = rl.deltaparent(r)
-        if (deltaparent != revlog.nullrev and
-            deltaparent in rl.parentrevs(r)):
+        store = self._storage()
+        r = store.rev(self._node)
+        deltaparent = store.deltaparent(r)
+        if (deltaparent != nullrev and
+            deltaparent in store.parentrevs(r)):
             return self.readdelta(shallow=shallow)
 
         if shallow:
-            return manifestdict(rl.revision(self._node))
+            return manifestdict(store.revision(self._node))
         else:
             return self.read()
 
--- a/mercurial/match.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/match.py	Wed Sep 26 20:33:09 2018 +0900
@@ -8,6 +8,7 @@
 from __future__ import absolute_import, print_function
 
 import copy
+import itertools
 import os
 import re
 
@@ -331,6 +332,49 @@
         '''
         return True
 
+    def visitchildrenset(self, dir):
+        '''Decides whether a directory should be visited based on whether it
+        has potential matches in it or one of its subdirectories, and
+        potentially lists which subdirectories of that directory should be
+        visited. This is based on the match's primary, included, and excluded
+        patterns.
+
+        This function is very similar to 'visitdir', and the following mapping
+        can be applied:
+
+             visitdir | visitchildrenlist
+            ----------+-------------------
+             False    | set()
+             'all'    | 'all'
+             True     | 'this' OR non-empty set of subdirs -or files- to visit
+
+        Example:
+          Assume matchers ['path:foo/bar', 'rootfilesin:qux'], we would return
+          the following values (assuming the implementation of visitchildrenset
+          is capable of recognizing this; some implementations are not).
+
+          '.' -> {'foo', 'qux'}
+          'baz' -> set()
+          'foo' -> {'bar'}
+          # Ideally this would be 'all', but since the prefix nature of matchers
+          # is applied to the entire matcher, we have to downgrade this to
+          # 'this' due to the non-prefix 'rootfilesin'-kind matcher being mixed
+          # in.
+          'foo/bar' -> 'this'
+          'qux' -> 'this'
+
+        Important:
+          Most matchers do not know if they're representing files or
+          directories. They see ['path:dir/f'] and don't know whether 'f' is a
+          file or a directory, so visitchildrenset('dir') for most matchers will
+          return {'f'}, but if the matcher knows it's a file (like exactmatcher
+          does), it may return 'this'. Do not rely on the return being a set
+          indicating that there are no files in this dir to investigate (or
+          equivalently that if there are files to investigate in 'dir' that it
+          will always return 'this').
+        '''
+        return 'this'
+
     def always(self):
         '''Matcher will match everything and .files() will be empty --
         optimization might be possible.'''
@@ -367,6 +411,9 @@
     def visitdir(self, dir):
         return 'all'
 
+    def visitchildrenset(self, dir):
+        return 'all'
+
     def __repr__(self):
         return r'<alwaysmatcher>'
 
@@ -390,6 +437,9 @@
     def visitdir(self, dir):
         return False
 
+    def visitchildrenset(self, dir):
+        return set()
+
     def __repr__(self):
         return r'<nevermatcher>'
 
@@ -430,6 +480,15 @@
                 any(parentdir in self._fileset
                     for parentdir in util.finddirs(dir)))
 
+    def visitchildrenset(self, dir):
+        ret = self.visitdir(dir)
+        if ret is True:
+            return 'this'
+        elif not ret:
+            return set()
+        assert ret == 'all'
+        return 'all'
+
     def prefix(self):
         return self._prefix
 
@@ -437,6 +496,46 @@
     def __repr__(self):
         return ('<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats))
 
+# This is basically a reimplementation of util.dirs that stores the children
+# instead of just a count of them, plus a small optional optimization to avoid
+# some directories we don't need.
+class _dirchildren(object):
+    def __init__(self, paths, onlyinclude=None):
+        self._dirs = {}
+        self._onlyinclude = onlyinclude or []
+        addpath = self.addpath
+        for f in paths:
+            addpath(f)
+
+    def addpath(self, path):
+        if path == '.':
+            return
+        dirs = self._dirs
+        findsplitdirs = _dirchildren._findsplitdirs
+        for d, b in findsplitdirs(path):
+            if d not in self._onlyinclude:
+                continue
+            dirs.setdefault(d, set()).add(b)
+
+    @staticmethod
+    def _findsplitdirs(path):
+        # yields (dirname, basename) tuples, walking back to the root.  This is
+        # very similar to util.finddirs, except:
+        #  - produces a (dirname, basename) tuple, not just 'dirname'
+        #  - includes root dir
+        # Unlike manifest._splittopdir, this does not suffix `dirname` with a
+        # slash, and produces '.' for the root instead of ''.
+        oldpos = len(path)
+        pos = path.rfind('/')
+        while pos != -1:
+            yield path[:pos], path[pos + 1:oldpos]
+            oldpos = pos
+            pos = path.rfind('/', 0, pos)
+        yield '.', path[:oldpos]
+
+    def get(self, path):
+        return self._dirs.get(path, set())
+
 class includematcher(basematcher):
 
     def __init__(self, root, cwd, kindpats, listsubrepos=False, badfn=None):
@@ -445,11 +544,14 @@
         self._pats, self.matchfn = _buildmatch(kindpats, '(?:/|$)',
                                                listsubrepos, root)
         self._prefix = _prefix(kindpats)
-        roots, dirs = _rootsanddirs(kindpats)
+        roots, dirs, parents = _rootsdirsandparents(kindpats)
         # roots are directories which are recursively included.
         self._roots = set(roots)
         # dirs are directories which are non-recursively included.
         self._dirs = set(dirs)
+        # parents are directories which are non-recursively included because
+        # they are needed to get to items in _dirs or _roots.
+        self._parents = set(parents)
 
     def visitdir(self, dir):
         if self._prefix and dir in self._roots:
@@ -457,9 +559,38 @@
         return ('.' in self._roots or
                 dir in self._roots or
                 dir in self._dirs or
+                dir in self._parents or
                 any(parentdir in self._roots
                     for parentdir in util.finddirs(dir)))
 
+    @propertycache
+    def _allparentschildren(self):
+        # It may seem odd that we add dirs, roots, and parents, and then
+        # restrict to only parents. This is to catch the case of:
+        #   dirs = ['foo/bar']
+        #   parents = ['foo']
+        # if we asked for the children of 'foo', but had only added
+        # self._parents, we wouldn't be able to respond ['bar'].
+        return _dirchildren(
+                itertools.chain(self._dirs, self._roots, self._parents),
+                onlyinclude=self._parents)
+
+    def visitchildrenset(self, dir):
+        if self._prefix and dir in self._roots:
+            return 'all'
+        # Note: this does *not* include the 'dir in self._parents' case from
+        # visitdir, that's handled below.
+        if ('.' in self._roots or
+            dir in self._roots or
+            dir in self._dirs or
+            any(parentdir in self._roots
+                for parentdir in util.finddirs(dir))):
+            return 'this'
+
+        if dir in self._parents:
+            return self._allparentschildren.get(dir) or set()
+        return set()
+
     @encoding.strmethod
     def __repr__(self):
         return ('<includematcher includes=%r>' % pycompat.bytestr(self._pats))
@@ -486,6 +617,26 @@
     def visitdir(self, dir):
         return dir in self._dirs
 
+    def visitchildrenset(self, dir):
+        if not self._fileset or dir not in self._dirs:
+            return set()
+
+        candidates = self._fileset | self._dirs - {'.'}
+        if dir != '.':
+            d = dir + '/'
+            candidates = set(c[len(d):] for c in candidates if
+                             c.startswith(d))
+        # self._dirs includes all of the directories, recursively, so if
+        # we're attempting to match foo/bar/baz.txt, it'll have '.', 'foo',
+        # 'foo/bar' in it. Thus we can safely ignore a candidate that has a
+        # '/' in it, indicating a it's for a subdir-of-a-subdir; the
+        # immediate subdir will be in there without a slash.
+        ret = {c for c in candidates if '/' not in c}
+        # We really do not expect ret to be empty, since that would imply that
+        # there's something in _dirs that didn't have a file in _fileset.
+        assert ret
+        return ret
+
     def isexact(self):
         return True
 
@@ -527,6 +678,31 @@
             return False
         return bool(self._m1.visitdir(dir))
 
+    def visitchildrenset(self, dir):
+        m2_set = self._m2.visitchildrenset(dir)
+        if m2_set == 'all':
+            return set()
+        m1_set = self._m1.visitchildrenset(dir)
+        # Possible values for m1: 'all', 'this', set(...), set()
+        # Possible values for m2:        'this', set(...), set()
+        # If m2 has nothing under here that we care about, return m1, even if
+        # it's 'all'. This is a change in behavior from visitdir, which would
+        # return True, not 'all', for some reason.
+        if not m2_set:
+            return m1_set
+        if m1_set in ['all', 'this']:
+            # Never return 'all' here if m2_set is any kind of non-empty (either
+            # 'this' or set(foo)), since m2 might return set() for a
+            # subdirectory.
+            return 'this'
+        # Possible values for m1:         set(...), set()
+        # Possible values for m2: 'this', set(...)
+        # We ignore m2's set results. They're possibly incorrect:
+        #  m1 = path:dir/subdir, m2=rootfilesin:dir, visitchildrenset('.'):
+        #    m1 returns {'dir'}, m2 returns {'dir'}, if we subtracted we'd
+        #    return set(), which is *not* correct, we still need to visit 'dir'!
+        return m1_set
+
     def isexact(self):
         return self._m1.isexact()
 
@@ -591,6 +767,25 @@
         # bool() because visit1=True + visit2='all' should not be 'all'
         return bool(visit1 and self._m2.visitdir(dir))
 
+    def visitchildrenset(self, dir):
+        m1_set = self._m1.visitchildrenset(dir)
+        if not m1_set:
+            return set()
+        m2_set = self._m2.visitchildrenset(dir)
+        if not m2_set:
+            return set()
+
+        if m1_set == 'all':
+            return m2_set
+        elif m2_set == 'all':
+            return m1_set
+
+        if m1_set == 'this' or m2_set == 'this':
+            return 'this'
+
+        assert isinstance(m1_set, set) and isinstance(m2_set, set)
+        return m1_set.intersection(m2_set)
+
     def always(self):
         return self._m1.always() and self._m2.always()
 
@@ -672,6 +867,13 @@
             dir = self._path + "/" + dir
         return self._matcher.visitdir(dir)
 
+    def visitchildrenset(self, dir):
+        if dir == '.':
+            dir = self._path
+        else:
+            dir = self._path + "/" + dir
+        return self._matcher.visitchildrenset(dir)
+
     def always(self):
         return self._always
 
@@ -744,6 +946,15 @@
             return self._matcher.visitdir(dir[len(self._pathprefix):])
         return dir in self._pathdirs
 
+    def visitchildrenset(self, dir):
+        if dir == self._path:
+            return self._matcher.visitchildrenset('.')
+        if dir.startswith(self._pathprefix):
+            return self._matcher.visitchildrenset(dir[len(self._pathprefix):])
+        if dir in self._pathdirs:
+            return 'this'
+        return set()
+
     def isexact(self):
         return self._matcher.isexact()
 
@@ -784,6 +995,25 @@
             r |= v
         return r
 
+    def visitchildrenset(self, dir):
+        r = set()
+        this = False
+        for m in self._matchers:
+            v = m.visitchildrenset(dir)
+            if not v:
+                continue
+            if v == 'all':
+                return v
+            if this or v == 'this':
+                this = True
+                # don't break, we might have an 'all' in here.
+                continue
+            assert isinstance(v, set)
+            r = r.union(v)
+        if this:
+            return 'this'
+        return r
+
     @encoding.strmethod
     def __repr__(self):
         return ('<unionmatcher matchers=%r>' % self._matchers)
@@ -1004,40 +1234,46 @@
     roots, dirs = _patternrootsanddirs(kindpats)
     return roots
 
-def _rootsanddirs(kindpats):
+def _rootsdirsandparents(kindpats):
     '''Returns roots and exact directories from patterns.
 
-    roots are directories to match recursively, whereas exact directories should
-    be matched non-recursively. The returned (roots, dirs) tuple will also
-    include directories that need to be implicitly considered as either, such as
-    parent directories.
+    `roots` are directories to match recursively, `dirs` should
+    be matched non-recursively, and `parents` are the implicitly required
+    directories to walk to items in either roots or dirs.
 
-    >>> _rootsanddirs(
+    Returns a tuple of (roots, dirs, parents).
+
+    >>> _rootsdirsandparents(
     ...     [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
     ...      (b'glob', b'g*', b'')])
-    (['g/h', 'g/h', '.'], ['g', '.'])
-    >>> _rootsanddirs(
+    (['g/h', 'g/h', '.'], [], ['g', '.'])
+    >>> _rootsdirsandparents(
     ...     [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
-    ([], ['g/h', '.', 'g', '.'])
-    >>> _rootsanddirs(
+    ([], ['g/h', '.'], ['g', '.'])
+    >>> _rootsdirsandparents(
     ...     [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
     ...      (b'path', b'', b'')])
-    (['r', 'p/p', '.'], ['p', '.'])
-    >>> _rootsanddirs(
+    (['r', 'p/p', '.'], [], ['p', '.'])
+    >>> _rootsdirsandparents(
     ...     [(b'relglob', b'rg*', b''), (b're', b're/', b''),
     ...      (b'relre', b'rr', b'')])
-    (['.', '.', '.'], ['.'])
+    (['.', '.', '.'], [], ['.'])
     '''
     r, d = _patternrootsanddirs(kindpats)
 
+    p = []
     # Append the parents as non-recursive/exact directories, since they must be
     # scanned to get to either the roots or the other exact directories.
-    d.extend(util.dirs(d))
-    d.extend(util.dirs(r))
+    p.extend(util.dirs(d))
+    p.extend(util.dirs(r))
     # util.dirs() does not include the root directory, so add it manually
-    d.append('.')
+    p.append('.')
 
-    return r, d
+    # FIXME: all uses of this function convert these to sets, do so before
+    # returning.
+    # FIXME: all uses of this function do not need anything in 'roots' and
+    # 'dirs' to also be in 'parents', consider removing them before returning.
+    return r, d, p
 
 def _explicitfiles(kindpats):
     '''Returns the potential explicit filenames from the patterns.
--- a/mercurial/mdiff.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/mdiff.py	Wed Sep 26 20:33:09 2018 +0900
@@ -357,7 +357,7 @@
             # walk backwards from the start of the context up to the start of
             # the previous hunk context until we find a line starting with an
             # alphanumeric char.
-            for i in xrange(astart - 1, lastpos - 1, -1):
+            for i in pycompat.xrange(astart - 1, lastpos - 1, -1):
                 if l1[i][0:1].isalnum():
                     func = b' ' + l1[i].rstrip()
                     # split long function name if ASCII. otherwise we have no
@@ -381,7 +381,7 @@
         hunklines = (
             ["@@ -%d,%d +%d,%d @@%s\n" % (hunkrange + (func,))]
             + delta
-            + [' ' + l1[x] for x in xrange(a2, aend)]
+            + [' ' + l1[x] for x in pycompat.xrange(a2, aend)]
         )
         # If either file ends without a newline and the last line of
         # that file is part of a hunk, a marker is printed. If the
@@ -390,7 +390,7 @@
         # which the hunk can end in a shared line without a newline.
         skip = False
         if not t1.endswith('\n') and astart + alen == len(l1) + 1:
-            for i in xrange(len(hunklines) - 1, -1, -1):
+            for i in pycompat.xrange(len(hunklines) - 1, -1, -1):
                 if hunklines[i].startswith(('-', ' ')):
                     if hunklines[i].startswith(' '):
                         skip = True
@@ -398,7 +398,7 @@
                     hunklines.insert(i + 1, _missing_newline_marker)
                     break
         if not skip and not t2.endswith('\n') and bstart + blen == len(l2) + 1:
-            for i in xrange(len(hunklines) - 1, -1, -1):
+            for i in pycompat.xrange(len(hunklines) - 1, -1, -1):
                 if hunklines[i].startswith('+'):
                     hunklines[i] += '\n'
                     hunklines.insert(i + 1, _missing_newline_marker)
--- a/mercurial/merge.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/merge.py	Wed Sep 26 20:33:09 2018 +0900
@@ -27,6 +27,7 @@
 )
 from . import (
     copies,
+    encoding,
     error,
     filemerge,
     match as matchmod,
@@ -1436,7 +1437,7 @@
 
 def _getcwd():
     try:
-        return pycompat.getcwd()
+        return encoding.getcwd()
     except OSError as err:
         if err.errno == errno.ENOENT:
             return None
@@ -2240,3 +2241,71 @@
         # fix up dirstate for copies and renames
         copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
     return stats
+
+def purge(repo, matcher, ignored=False, removeemptydirs=True,
+          removefiles=True, abortonerror=False, noop=False):
+    """Purge the working directory of untracked files.
+
+    ``matcher`` is a matcher configured to scan the working directory -
+    potentially a subset.
+
+    ``ignored`` controls whether ignored files should also be purged.
+
+    ``removeemptydirs`` controls whether empty directories should be removed.
+
+    ``removefiles`` controls whether files are removed.
+
+    ``abortonerror`` causes an exception to be raised if an error occurs
+    deleting a file or directory.
+
+    ``noop`` controls whether to actually remove files. If not defined, actions
+    will be taken.
+
+    Returns an iterable of relative paths in the working directory that were
+    or would be removed.
+    """
+
+    def remove(removefn, path):
+        try:
+            removefn(path)
+        except OSError:
+            m = _('%s cannot be removed') % path
+            if abortonerror:
+                raise error.Abort(m)
+            else:
+                repo.ui.warn(_('warning: %s\n') % m)
+
+    # There's no API to copy a matcher. So mutate the passed matcher and
+    # restore it when we're done.
+    oldexplicitdir = matcher.explicitdir
+    oldtraversedir = matcher.traversedir
+
+    res = []
+
+    try:
+        if removeemptydirs:
+            directories = []
+            matcher.explicitdir = matcher.traversedir = directories.append
+
+        status = repo.status(match=matcher, ignored=ignored, unknown=True)
+
+        if removefiles:
+            for f in sorted(status.unknown + status.ignored):
+                if not noop:
+                    repo.ui.note(_('removing file %s\n') % f)
+                    remove(repo.wvfs.unlink, f)
+                res.append(f)
+
+        if removeemptydirs:
+            for f in sorted(directories, reverse=True):
+                if matcher(f) and not repo.wvfs.listdir(f):
+                    if not noop:
+                        repo.ui.note(_('removing directory %s\n') % f)
+                        remove(repo.wvfs.rmdir, f)
+                    res.append(f)
+
+        return res
+
+    finally:
+        matcher.explicitdir = oldexplicitdir
+        matcher.traversedir = oldtraversedir
--- a/mercurial/minifileset.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/minifileset.py	Wed Sep 26 20:33:09 2018 +0900
@@ -11,20 +11,23 @@
 from . import (
     error,
     fileset,
+    filesetlang,
     pycompat,
 )
 
 def _sizep(x):
     # i18n: "size" is a keyword
-    expr = fileset.getstring(x, _("size requires an expression"))
+    expr = filesetlang.getstring(x, _("size requires an expression"))
     return fileset.sizematcher(expr)
 
 def _compile(tree):
     if not tree:
         raise error.ParseError(_("missing argument"))
     op = tree[0]
-    if op in {'symbol', 'string', 'kindpat'}:
-        name = fileset.getpattern(tree, {'path'}, _('invalid file pattern'))
+    if op == 'withstatus':
+        return _compile(tree[1])
+    elif op in {'symbol', 'string', 'kindpat'}:
+        name = filesetlang.getpattern(tree, {'path'}, _('invalid file pattern'))
         if name.startswith('**'): # file extension test, ex. "**.tar.gz"
             ext = name[2:]
             for c in pycompat.bytestr(ext):
@@ -39,18 +42,15 @@
             return f
         raise error.ParseError(_("unsupported file pattern: %s") % name,
                                hint=_('paths must be prefixed with "path:"'))
-    elif op == 'or':
-        func1 = _compile(tree[1])
-        func2 = _compile(tree[2])
-        return lambda n, s: func1(n, s) or func2(n, s)
+    elif op in {'or', 'patterns'}:
+        funcs = [_compile(x) for x in tree[1:]]
+        return lambda n, s: any(f(n, s) for f in funcs)
     elif op == 'and':
         func1 = _compile(tree[1])
         func2 = _compile(tree[2])
         return lambda n, s: func1(n, s) and func2(n, s)
     elif op == 'not':
         return lambda n, s: not _compile(tree[1])(n, s)
-    elif op == 'group':
-        return _compile(tree[1])
     elif op == 'func':
         symbols = {
             'all': lambda n, s: True,
@@ -58,7 +58,7 @@
             'size': lambda n, s: _sizep(tree[2])(s),
         }
 
-        name = fileset.getsymbol(tree[1])
+        name = filesetlang.getsymbol(tree[1])
         if name in symbols:
             return symbols[name]
 
@@ -67,11 +67,9 @@
         func1 = _compile(tree[1])
         func2 = _compile(tree[2])
         return lambda n, s: func1(n, s) and not func2(n, s)
-    elif op == 'negate':
-        raise error.ParseError(_("can't use negate operator in this context"))
     elif op == 'list':
         raise error.ParseError(_("can't use a list in this context"),
-                               hint=_('see hg help "filesets.x or y"'))
+                               hint=_('see \'hg help "filesets.x or y"\''))
     raise error.ProgrammingError('illegal tree: %r' % (tree,))
 
 def compile(text):
@@ -88,5 +86,7 @@
     files whose name ends with ".zip", and all files under "bin" in the repo
     root except for "bin/README".
     """
-    tree = fileset.parse(text)
+    tree = filesetlang.parse(text)
+    tree = filesetlang.analyze(tree)
+    tree = filesetlang.optimize(tree)
     return _compile(tree)
--- a/mercurial/minirst.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/minirst.py	Wed Sep 26 20:33:09 2018 +0900
@@ -316,7 +316,7 @@
 
             # column markers are ASCII so we can calculate column
             # position in bytes
-            columns = [x for x in xrange(len(div))
+            columns = [x for x in pycompat.xrange(len(div))
                        if div[x:x + 1] == '=' and (x == 0 or
                                                    div[x - 1:x] == ' ')]
             rows = []
@@ -663,69 +663,79 @@
     text = ''.join(formatblock(b, width) for b in blocks)
     return text
 
+def formatplain(blocks, width):
+    """Format parsed blocks as plain text"""
+    return ''.join(formatblock(b, width) for b in blocks)
+
 def format(text, width=80, indent=0, keep=None, style='plain', section=None):
     """Parse and format the text according to width."""
     blocks, pruned = parse(text, indent, keep or [])
-    parents = []
     if section:
-        sections = getsections(blocks)
-        blocks = []
-        i = 0
-        lastparents = []
-        synthetic = []
-        collapse = True
-        while i < len(sections):
-            name, nest, b = sections[i]
-            del parents[nest:]
-            parents.append(i)
-            if name == section:
-                if lastparents != parents:
-                    llen = len(lastparents)
-                    plen = len(parents)
-                    if llen and llen != plen:
-                        collapse = False
-                    s = []
-                    for j in xrange(3, plen - 1):
-                        parent = parents[j]
-                        if (j >= llen or
-                            lastparents[j] != parent):
-                            s.append(len(blocks))
-                            sec = sections[parent][2]
-                            blocks.append(sec[0])
-                            blocks.append(sec[-1])
-                    if s:
-                        synthetic.append(s)
+        blocks = filtersections(blocks, section)
+    if style == 'html':
+        return formathtml(blocks)
+    else:
+        return formatplain(blocks, width=width)
+
+def filtersections(blocks, section):
+    """Select parsed blocks under the specified section
 
-                lastparents = parents[:]
-                blocks.extend(b)
+    The section name is separated by a dot, and matches the suffix of the
+    full section path.
+    """
+    parents = []
+    sections = _getsections(blocks)
+    blocks = []
+    i = 0
+    lastparents = []
+    synthetic = []
+    collapse = True
+    while i < len(sections):
+        path, nest, b = sections[i]
+        del parents[nest:]
+        parents.append(i)
+        if path == section or path.endswith('.' + section):
+            if lastparents != parents:
+                llen = len(lastparents)
+                plen = len(parents)
+                if llen and llen != plen:
+                    collapse = False
+                s = []
+                for j in pycompat.xrange(3, plen - 1):
+                    parent = parents[j]
+                    if (j >= llen or
+                        lastparents[j] != parent):
+                        s.append(len(blocks))
+                        sec = sections[parent][2]
+                        blocks.append(sec[0])
+                        blocks.append(sec[-1])
+                if s:
+                    synthetic.append(s)
 
-                ## Also show all subnested sections
-                while i + 1 < len(sections) and sections[i + 1][1] > nest:
-                    i += 1
-                    blocks.extend(sections[i][2])
-            i += 1
-        if collapse:
-            synthetic.reverse()
-            for s in synthetic:
-                path = [blocks[syn]['lines'][0] for syn in s]
-                real = s[-1] + 2
-                realline = blocks[real]['lines']
-                realline[0] = ('"%s"' %
-                               '.'.join(path + [realline[0]]).replace('"', ''))
-                del blocks[s[0]:real]
+            lastparents = parents[:]
+            blocks.extend(b)
 
-    if style == 'html':
-        text = formathtml(blocks)
-    else:
-        text = ''.join(formatblock(b, width) for b in blocks)
-    if keep is None:
-        return text
-    else:
-        return text, pruned
+            ## Also show all subnested sections
+            while i + 1 < len(sections) and sections[i + 1][1] > nest:
+                i += 1
+                blocks.extend(sections[i][2])
+        i += 1
+    if collapse:
+        synthetic.reverse()
+        for s in synthetic:
+            path = [blocks[syn]['lines'][0] for syn in s]
+            real = s[-1] + 2
+            realline = blocks[real]['lines']
+            realline[0] = ('"%s"' %
+                           '.'.join(path + [realline[0]]).replace('"', ''))
+            del blocks[s[0]:real]
 
-def getsections(blocks):
-    '''return a list of (section name, nesting level, blocks) tuples'''
+    return blocks
+
+def _getsections(blocks):
+    '''return a list of (section path, nesting level, blocks) tuples'''
     nest = ""
+    names = ()
     level = 0
     secs = []
 
@@ -746,7 +756,8 @@
                 nest += i
             level = nest.index(i) + 1
             nest = nest[:level]
-            secs.append((getname(b), level, [b]))
+            names = names[:level] + (getname(b),)
+            secs.append(('.'.join(names), level, [b]))
         elif b['type'] in ('definition', 'field'):
             i = ' '
             if i not in nest:
@@ -767,7 +778,8 @@
                     elif siblingindent == indent:
                         level = sec[1]
                         break
-            secs.append((getname(b), level, [b]))
+            names = names[:level] + (getname(b),)
+            secs.append(('.'.join(names), level, [b]))
         else:
             if not secs:
                 # add an initial empty section
@@ -793,15 +805,6 @@
             secs[-1][2].append(b)
     return secs
 
-def decorateblocks(blocks, width):
-    '''generate a list of (section name, line text) pairs for search'''
-    lines = []
-    for s in getsections(blocks):
-        section = s[0]
-        text = formatblocks(s[2], width)
-        lines.append([(section, l) for l in text.splitlines(True)])
-    return lines
-
 def maketable(data, indent=0, header=False):
     '''Generate an RST table for the given table data as a list of lines'''
 
--- a/mercurial/narrowspec.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/narrowspec.py	Wed Sep 26 20:33:09 2018 +0900
@@ -13,54 +13,23 @@
 from . import (
     error,
     match as matchmod,
+    repository,
+    sparse,
     util,
 )
 
 FILENAME = 'narrowspec'
 
-def _parsestoredpatterns(text):
-    """Parses the narrowspec format that's stored on disk."""
-    patlist = None
-    includepats = []
-    excludepats = []
-    for l in text.splitlines():
-        if l == '[includes]':
-            if patlist is None:
-                patlist = includepats
-            else:
-                raise error.Abort(_('narrowspec includes section must appear '
-                                    'at most once, before excludes'))
-        elif l == '[excludes]':
-            if patlist is not excludepats:
-                patlist = excludepats
-            else:
-                raise error.Abort(_('narrowspec excludes section must appear '
-                                    'at most once'))
-        else:
-            patlist.append(l)
-
-    return set(includepats), set(excludepats)
-
-def parseserverpatterns(text):
-    """Parses the narrowspec format that's returned by the server."""
-    includepats = set()
-    excludepats = set()
-
-    # We get one entry per line, in the format "<key> <value>".
-    # It's OK for value to contain other spaces.
-    for kp in (l.split(' ', 1) for l in text.splitlines()):
-        if len(kp) != 2:
-            raise error.Abort(_('Invalid narrowspec pattern line: "%s"') % kp)
-        key = kp[0]
-        pat = kp[1]
-        if key == 'include':
-            includepats.add(pat)
-        elif key == 'exclude':
-            excludepats.add(pat)
-        else:
-            raise error.Abort(_('Invalid key "%s" in server response') % key)
-
-    return includepats, excludepats
+# Pattern prefixes that are allowed in narrow patterns. This list MUST
+# only contain patterns that are fast and safe to evaluate. Keep in mind
+# that patterns are supplied by clients and executed on remote servers
+# as part of wire protocol commands. That means that changes to this
+# data structure influence the wire protocol and should not be taken
+# lightly - especially removals.
+VALID_PREFIXES = (
+    b'path:',
+    b'rootfilesin:',
+)
 
 def normalizesplitpattern(kind, pat):
     """Returns the normalized version of a pattern and kind.
@@ -103,14 +72,48 @@
     return '%s:%s' % normalizesplitpattern(kind, pat)
 
 def parsepatterns(pats):
-    """Parses a list of patterns into a typed pattern set."""
-    return set(normalizepattern(p) for p in pats)
+    """Parses an iterable of patterns into a typed pattern set.
+
+    Patterns are assumed to be ``path:`` if no prefix is present.
+    For safety and performance reasons, only some prefixes are allowed.
+    See ``validatepatterns()``.
+
+    This function should be used on patterns that come from the user to
+    normalize and validate them to the internal data structure used for
+    representing patterns.
+    """
+    res = {normalizepattern(orig) for orig in pats}
+    validatepatterns(res)
+    return res
+
+def validatepatterns(pats):
+    """Validate that patterns are in the expected data structure and format.
+
+    And that is a set of normalized patterns beginning with ``path:`` or
+    ``rootfilesin:``.
+
+    This function should be used to validate internal data structures
+    and patterns that are loaded from sources that use the internal,
+    prefixed pattern representation (but can't necessarily be fully trusted).
+    """
+    if not isinstance(pats, set):
+        raise error.ProgrammingError('narrow patterns should be a set; '
+                                     'got %r' % pats)
+
+    for pat in pats:
+        if not pat.startswith(VALID_PREFIXES):
+            # Use a Mercurial exception because this can happen due to user
+            # bugs (e.g. manually updating spec file).
+            raise error.Abort(_('invalid prefix on narrow pattern: %s') % pat,
+                              hint=_('narrow patterns must begin with one of '
+                                     'the following: %s') %
+                                   ', '.join(VALID_PREFIXES))
 
 def format(includes, excludes):
-    output = '[includes]\n'
+    output = '[include]\n'
     for i in sorted(includes - excludes):
         output += i + '\n'
-    output += '[excludes]\n'
+    output += '[exclude]\n'
     for e in sorted(excludes):
         output += e + '\n'
     return output
@@ -124,26 +127,49 @@
     return matchmod.match(root, '', [], include=include or [],
                           exclude=exclude or [])
 
-def needsexpansion(includes):
-    return [i for i in includes if i.startswith('include:')]
-
 def load(repo):
     try:
-        spec = repo.vfs.read(FILENAME)
+        spec = repo.svfs.read(FILENAME)
     except IOError as e:
         # Treat "narrowspec does not exist" the same as "narrowspec file exists
         # and is empty".
         if e.errno == errno.ENOENT:
-            # Without this the next call to load will use the cached
-            # non-existence of the file, which can cause some odd issues.
-            repo.invalidate(clearfilecache=True)
             return set(), set()
         raise
-    return _parsestoredpatterns(spec)
+    # maybe we should care about the profiles returned too
+    includepats, excludepats, profiles = sparse.parseconfig(repo.ui, spec,
+                                                            'narrow')
+    if profiles:
+        raise error.Abort(_("including other spec files using '%include' is not"
+                            " supported in narrowspec"))
+
+    validatepatterns(includepats)
+    validatepatterns(excludepats)
+
+    return includepats, excludepats
 
 def save(repo, includepats, excludepats):
+    validatepatterns(includepats)
+    validatepatterns(excludepats)
     spec = format(includepats, excludepats)
-    repo.vfs.write(FILENAME, spec)
+    repo.svfs.write(FILENAME, spec)
+
+def savebackup(repo, backupname):
+    if repository.NARROW_REQUIREMENT not in repo.requirements:
+        return
+    vfs = repo.vfs
+    vfs.tryunlink(backupname)
+    util.copyfile(repo.svfs.join(FILENAME), vfs.join(backupname), hardlink=True)
+
+def restorebackup(repo, backupname):
+    if repository.NARROW_REQUIREMENT not in repo.requirements:
+        return
+    util.rename(repo.vfs.join(backupname), repo.svfs.join(FILENAME))
+
+def clearbackup(repo, backupname):
+    if repository.NARROW_REQUIREMENT not in repo.requirements:
+        return
+    repo.vfs.unlink(backupname)
 
 def restrictpatterns(req_includes, req_excludes, repo_includes, repo_excludes):
     r""" Restricts the patterns according to repo settings,
--- a/mercurial/node.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/node.py	Wed Sep 26 20:33:09 2018 +0900
@@ -21,20 +21,25 @@
         raise TypeError(e)
 
 nullrev = -1
+# In hex, this is '0000000000000000000000000000000000000000'
 nullid = b"\0" * 20
 nullhex = hex(nullid)
 
 # Phony node value to stand-in for new files in some uses of
 # manifests.
-newnodeid = '!' * 20
-addednodeid = ('0' * 15) + 'added'
-modifiednodeid = ('0' * 12) + 'modified'
+# In hex, this is '2121212121212121212121212121212121212121'
+newnodeid = '!!!!!!!!!!!!!!!!!!!!'
+# In hex, this is '3030303030303030303030303030306164646564'
+addednodeid = '000000000000000added'
+# In hex, this is '3030303030303030303030306d6f646966696564'
+modifiednodeid = '000000000000modified'
 
 wdirfilenodeids = {newnodeid, addednodeid, modifiednodeid}
 
 # pseudo identifiers for working directory
 # (they are experimental, so don't add too many dependencies on them)
 wdirrev = 0x7fffffff
+# In hex, this is 'ffffffffffffffffffffffffffffffffffffffff'
 wdirid = b"\xff" * 20
 wdirhex = hex(wdirid)
 
--- a/mercurial/obsolete.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/obsolete.py	Wed Sep 26 20:33:09 2018 +0900
@@ -394,7 +394,7 @@
         off = o3 + metasize * nummeta
         metapairsize = unpack('>' + (metafmt * nummeta), data[o3:off])
         metadata = []
-        for idx in xrange(0, len(metapairsize), 2):
+        for idx in pycompat.xrange(0, len(metapairsize), 2):
             o1 = off + metapairsize[idx]
             o2 = o1 + metapairsize[idx + 1]
             metadata.append((data[off:o1], data[o1:o2]))
--- a/mercurial/obsutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/obsutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -464,14 +464,14 @@
     phase = repo._phasecache.phase
     succsmarkers = repo.obsstore.successors.get
     public = phases.public
-    addedmarkers = tr.changes.get('obsmarkers')
-    addedrevs = tr.changes.get('revs')
+    addedmarkers = tr.changes['obsmarkers']
+    origrepolen = tr.changes['origrepolen']
     seenrevs = set()
     obsoleted = set()
     for mark in addedmarkers:
         node = mark[0]
         rev = torev(node)
-        if rev is None or rev in seenrevs or rev in addedrevs:
+        if rev is None or rev in seenrevs or rev >= origrepolen:
             continue
         seenrevs.add(rev)
         if phase(repo, rev) == public:
--- a/mercurial/parser.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/parser.py	Wed Sep 26 20:33:09 2018 +0900
@@ -20,7 +20,6 @@
 
 from .i18n import _
 from . import (
-    encoding,
     error,
     pycompat,
     util,
@@ -198,16 +197,11 @@
         # mangle Python's exception into our format
         raise error.ParseError(pycompat.bytestr(e).lower())
 
-def _brepr(obj):
-    if isinstance(obj, bytes):
-        return b"'%s'" % stringutil.escapestr(obj)
-    return encoding.strtolocal(repr(obj))
-
 def _prettyformat(tree, leafnodes, level, lines):
     if not isinstance(tree, tuple):
-        lines.append((level, _brepr(tree)))
+        lines.append((level, stringutil.pprint(tree)))
     elif tree[0] in leafnodes:
-        rs = map(_brepr, tree[1:])
+        rs = map(stringutil.pprint, tree[1:])
         lines.append((level, '(%s %s)' % (tree[0], ' '.join(rs))))
     else:
         lines.append((level, '(%s' % tree[0]))
--- a/mercurial/patch.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/patch.py	Wed Sep 26 20:33:09 2018 +0900
@@ -815,7 +815,7 @@
         for x, s in enumerate(self.lines):
             self.hash.setdefault(s, []).append(x)
 
-        for fuzzlen in xrange(self.ui.configint("patch", "fuzz") + 1):
+        for fuzzlen in pycompat.xrange(self.ui.configint("patch", "fuzz") + 1):
             for toponly in [True, False]:
                 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
                 oldstart = oldstart + self.offset + self.skew
@@ -1286,7 +1286,7 @@
         self.lena = int(aend) - self.starta
         if self.starta:
             self.lena += 1
-        for x in xrange(self.lena):
+        for x in pycompat.xrange(self.lena):
             l = lr.readline()
             if l.startswith('---'):
                 # lines addition, old block is empty
@@ -1320,7 +1320,7 @@
         if self.startb:
             self.lenb += 1
         hunki = 1
-        for x in xrange(self.lenb):
+        for x in pycompat.xrange(self.lenb):
             l = lr.readline()
             if l.startswith('\ '):
                 # XXX: the only way to hit this is with an invalid line range.
@@ -1396,14 +1396,14 @@
             top = 0
             bot = 0
             hlen = len(self.hunk)
-            for x in xrange(hlen - 1):
+            for x in pycompat.xrange(hlen - 1):
                 # the hunk starts with the @@ line, so use x+1
                 if self.hunk[x + 1].startswith(' '):
                     top += 1
                 else:
                     break
             if not toponly:
-                for x in xrange(hlen - 1):
+                for x in pycompat.xrange(hlen - 1):
                     if self.hunk[hlen - bot - 1].startswith(' '):
                         bot += 1
                     else:
@@ -2326,7 +2326,7 @@
         relfiltered = True
 
     if not changes:
-        changes = repo.status(ctx1, ctx2, match=match)
+        changes = ctx1.status(ctx2, match=match)
     modified, added, removed = changes[:3]
 
     if not modified and not added and not removed:
@@ -2431,9 +2431,9 @@
     a = ''
     b = ''
     for line in hunklines:
-        if line[0] == '-':
+        if line[0:1] == '-':
             a += line[1:]
-        elif line[0] == '+':
+        elif line[0:1] == '+':
             b += line[1:]
         else:
             raise error.ProgrammingError('unexpected hunk line: %s' % line)
--- a/mercurial/phases.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/phases.py	Wed Sep 26 20:33:09 2018 +0900
@@ -123,11 +123,26 @@
 
 _fphasesentry = struct.Struct('>i20s')
 
-allphases = public, draft, secret = range(3)
+INTERNAL_FLAG = 64 # Phases for mercurial internal usage only
+HIDEABLE_FLAG = 32 # Phases that are hideable
+
+# record phase index
+public, draft, secret = range(3)
+internal = INTERNAL_FLAG | HIDEABLE_FLAG
+allphases = range(internal + 1)
 trackedphases = allphases[1:]
-phasenames = ['public', 'draft', 'secret']
+# record phase names
+phasenames = [None] * len(allphases)
+phasenames[:3] = ['public', 'draft', 'secret']
+phasenames[internal] = 'internal'
+# record phase property
 mutablephases = tuple(allphases[1:])
 remotehiddenphases = tuple(allphases[2:])
+localhiddenphases = tuple(p for p in allphases if p & HIDEABLE_FLAG)
+
+def supportinternal(repo):
+    """True if the internal phase can be used on a repository"""
+    return 'internal-phase' in repo.requirements
 
 def _readroots(repo, phasedefaults=None):
     """Read phase roots from disk
@@ -272,19 +287,16 @@
         repo = repo.unfiltered()
         cl = repo.changelog
         self._phasesets = [set() for phase in allphases]
-        roots = pycompat.maplist(cl.rev, self.phaseroots[secret])
-        if roots:
-            ps = set(cl.descendants(roots))
-            for root in roots:
-                ps.add(root)
-            self._phasesets[secret] = ps
-        roots = pycompat.maplist(cl.rev, self.phaseroots[draft])
-        if roots:
-            ps = set(cl.descendants(roots))
-            for root in roots:
-                ps.add(root)
-            ps.difference_update(self._phasesets[secret])
-            self._phasesets[draft] = ps
+        lowerroots = set()
+        for phase in reversed(trackedphases):
+            roots = pycompat.maplist(cl.rev, self.phaseroots[phase])
+            if roots:
+                ps = set(cl.descendants(roots))
+                for root in roots:
+                    ps.add(root)
+                ps.difference_update(lowerroots)
+                lowerroots.update(ps)
+                self._phasesets[phase] = ps
         self._loadedrevslen = len(cl)
 
     def loadphaserevs(self, repo):
@@ -374,7 +386,7 @@
 
         changes = set() # set of revisions to be changed
         delroots = [] # set of root deleted by this path
-        for phase in xrange(targetphase + 1, len(allphases)):
+        for phase in pycompat.xrange(targetphase + 1, len(allphases)):
             # filter nodes that are not in a compatible phase already
             nodes = [n for n in nodes
                      if self.phase(repo, repo[n].rev()) >= phase]
@@ -420,7 +432,7 @@
             affected = set(repo.revs('(%ln::) - (%ln::)', new, old))
 
             # find the phase of the affected revision
-            for phase in xrange(targetphase, -1, -1):
+            for phase in pycompat.xrange(targetphase, -1, -1):
                 if phase:
                     roots = oldroots[phase]
                     revs = set(repo.revs('%ln::%ld', roots, affected))
@@ -434,6 +446,9 @@
     def _retractboundary(self, repo, tr, targetphase, nodes):
         # Be careful to preserve shallow-copied values: do not update
         # phaseroots values, replace them.
+        if targetphase == internal and not supportinternal(repo):
+            msg = 'this repository does not support the internal phase'
+            raise error.ProgrammingError(msg)
 
         repo = repo.unfiltered()
         currentroots = self.phaseroots[targetphase]
@@ -589,7 +604,7 @@
     headsbyphase = [[] for i in allphases]
     # No need to keep track of secret phase; any heads in the subset that
     # are not mentioned are implicitly secret.
-    for phase in allphases[:-1]:
+    for phase in allphases[:secret]:
         revset = "heads(%%ln & %s())" % phasenames[phase]
         headsbyphase[phase] = [cl.node(r) for r in repo.revs(revset, subset)]
     return headsbyphase
@@ -602,8 +617,8 @@
     # to update. This avoid creating empty transaction during no-op operation.
 
     for phase in allphases[:-1]:
-        revset = '%%ln - %s()' % phasenames[phase]
-        heads = [c.node() for c in repo.set(revset, headsbyphase[phase])]
+        revset = '%ln - _phase(%s)'
+        heads = [c.node() for c in repo.set(revset, headsbyphase[phase], phase)]
         if heads:
             advanceboundary(repo, trgetter(), phase, heads)
 
--- a/mercurial/policy.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/policy.py	Wed Sep 26 20:33:09 2018 +0900
@@ -69,7 +69,7 @@
     (r'cext', r'bdiff'): 3,
     (r'cext', r'mpatch'): 1,
     (r'cext', r'osutil'): 4,
-    (r'cext', r'parsers'): 10,
+    (r'cext', r'parsers'): 11,
 }
 
 # map import request to other package or module
--- a/mercurial/pure/osutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/pure/osutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -14,6 +14,7 @@
 import stat as statmod
 
 from .. import (
+    encoding,
     pycompat,
 )
 
@@ -150,7 +151,7 @@
         rfds = ctypes.cast(cmsg.cmsg_data, ctypes.POINTER(ctypes.c_int))
         rfdscount = ((cmsg.cmsg_len - _cmsghdr.cmsg_data.offset) /
                      ctypes.sizeof(ctypes.c_int))
-        return [rfds[i] for i in xrange(rfdscount)]
+        return [rfds[i] for i in pycompat.xrange(rfdscount)]
 
 else:
     import msvcrt
@@ -193,7 +194,8 @@
 
     def _raiseioerror(name):
         err = ctypes.WinError()
-        raise IOError(err.errno, '%s: %s' % (name, err.strerror))
+        raise IOError(err.errno, r'%s: %s' % (encoding.strfromlocal(name),
+                                              err.strerror))
 
     class posixfile(object):
         '''a file object aiming for POSIX-like semantics
@@ -207,14 +209,14 @@
         remains but cannot be opened again or be recreated under the same name,
         until all reading processes have closed the file.'''
 
-        def __init__(self, name, mode='r', bufsize=-1):
-            if 'b' in mode:
+        def __init__(self, name, mode=b'r', bufsize=-1):
+            if b'b' in mode:
                 flags = _O_BINARY
             else:
                 flags = _O_TEXT
 
-            m0 = mode[0]
-            if m0 == 'r' and '+' not in mode:
+            m0 = mode[0:1]
+            if m0 == b'r' and b'+' not in mode:
                 flags |= _O_RDONLY
                 access = _GENERIC_READ
             else:
@@ -223,15 +225,15 @@
                 flags |= _O_RDWR
                 access = _GENERIC_READ | _GENERIC_WRITE
 
-            if m0 == 'r':
+            if m0 == b'r':
                 creation = _OPEN_EXISTING
-            elif m0 == 'w':
+            elif m0 == b'w':
                 creation = _CREATE_ALWAYS
-            elif m0 == 'a':
+            elif m0 == b'a':
                 creation = _OPEN_ALWAYS
                 flags |= _O_APPEND
             else:
-                raise ValueError("invalid mode: %s" % mode)
+                raise ValueError(r"invalid mode: %s" % pycompat.sysstr(mode))
 
             fh = _kernel32.CreateFileA(name, access,
                     _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE,
--- a/mercurial/pure/parsers.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/pure/parsers.py	Wed Sep 26 20:33:09 2018 +0900
@@ -39,25 +39,21 @@
 
 class BaseIndexObject(object):
     def __len__(self):
-        return self._lgt + len(self._extra) + 1
+        return self._lgt + len(self._extra)
 
-    def insert(self, i, tup):
-        assert i == -1
+    def append(self, tup):
         self._extra.append(tup)
 
-    def _fix_index(self, i):
+    def _check_index(self, i):
         if not isinstance(i, int):
             raise TypeError("expecting int indexes")
-        if i < 0:
-            i = len(self) + i
         if i < 0 or i >= len(self):
             raise IndexError
-        return i
 
     def __getitem__(self, i):
-        i = self._fix_index(i)
-        if i == len(self) - 1:
+        if i == -1:
             return (0, 0, 0, -1, -1, -1, -1, nullid)
+        self._check_index(i)
         if i >= self._lgt:
             return self._extra[i - self._lgt]
         index = self._calculate_index(i)
@@ -82,7 +78,8 @@
     def __delitem__(self, i):
         if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
             raise ValueError("deleting slices only supports a:-1 with step 1")
-        i = self._fix_index(i.start)
+        i = i.start
+        self._check_index(i)
         if i < self._lgt:
             self._data = self._data[:i * indexsize]
             self._lgt = i
@@ -116,7 +113,8 @@
     def __delitem__(self, i):
         if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
             raise ValueError("deleting slices only supports a:-1 with step 1")
-        i = self._fix_index(i.start)
+        i = i.start
+        self._check_index(i)
         if i < self._lgt:
             self._offsets = self._offsets[:i]
             self._lgt = i
--- a/mercurial/pvec.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/pvec.py	Wed Sep 26 20:33:09 2018 +0900
@@ -52,6 +52,7 @@
 
 from .node import nullrev
 from . import (
+    pycompat,
     util,
 )
 
@@ -72,7 +73,7 @@
 
 def _str(v, l):
     bs = ""
-    for p in xrange(l):
+    for p in pycompat.xrange(l):
         bs = chr(v & 255) + bs
         v >>= 8
     return bs
@@ -91,7 +92,7 @@
             c += 1
         x >>= 1
     return c
-_htab = [_hweight(x) for x in xrange(256)]
+_htab = [_hweight(x) for x in pycompat.xrange(256)]
 
 def _hamming(a, b):
     '''find the hamming distance between two longs'''
@@ -152,7 +153,7 @@
     pvc = r._pveccache
     if ctx.rev() not in pvc:
         cl = r.changelog
-        for n in xrange(ctx.rev() + 1):
+        for n in pycompat.xrange(ctx.rev() + 1):
             if n not in pvc:
                 node = cl.node(n)
                 p1, p2 = cl.parentrevs(n)
--- a/mercurial/pycompat.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/pycompat.py	Wed Sep 26 20:33:09 2018 +0900
@@ -97,9 +97,7 @@
     osaltsep = os.altsep
     if osaltsep:
         osaltsep = osaltsep.encode('ascii')
-    # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
-    # returns bytes.
-    getcwd = os.getcwdb
+
     sysplatform = sys.platform.encode('ascii')
     sysexecutable = sys.executable
     if sysexecutable:
@@ -120,6 +118,8 @@
     rawinput = input
     getargspec = inspect.getfullargspec
 
+    long = int
+
     # TODO: .buffer might not exist if std streams were replaced; we'll need
     # a silly wrapper to make a bytes stream backed by a unicode one.
     stdin = sys.stdin.buffer
@@ -136,7 +136,7 @@
     if getattr(sys, 'argv', None) is not None:
         sysargv = list(map(os.fsencode, sys.argv))
 
-    bytechr = struct.Struct('>B').pack
+    bytechr = struct.Struct(r'>B').pack
     byterepr = b'%r'.__mod__
 
     class bytestr(bytes):
@@ -280,7 +280,7 @@
     xrange = builtins.range
     unicode = str
 
-    def open(name, mode='r', buffering=-1, encoding=None):
+    def open(name, mode=b'r', buffering=-1, encoding=None):
         return builtins.open(name, sysstr(mode), buffering, encoding)
 
     safehasattr = _wrapattrfunc(builtins.hasattr)
@@ -331,6 +331,7 @@
 else:
     import cStringIO
 
+    xrange = xrange
     unicode = unicode
     bytechr = chr
     byterepr = repr
@@ -356,7 +357,7 @@
             return filename
         else:
             raise TypeError(
-                "expect str, not %s" % type(filename).__name__)
+                r"expect str, not %s" % type(filename).__name__)
 
     # In Python 2, fsdecode() has a very chance to receive bytes. So it's
     # better not to touch Python 2 part as it's already working fine.
@@ -383,13 +384,13 @@
     ospardir = os.pardir
     ossep = os.sep
     osaltsep = os.altsep
+    long = long
     stdin = sys.stdin
     stdout = sys.stdout
     stderr = sys.stderr
     if getattr(sys, 'argv', None) is not None:
         sysargv = sys.argv
     sysplatform = sys.platform
-    getcwd = os.getcwd
     sysexecutable = sys.executable
     shlexsplit = shlex.split
     bytesio = cStringIO.StringIO
@@ -400,11 +401,11 @@
     rawinput = raw_input
     getargspec = inspect.getargspec
 
-isjython = sysplatform.startswith('java')
+isjython = sysplatform.startswith(b'java')
 
-isdarwin = sysplatform == 'darwin'
-isposix = osname == 'posix'
-iswindows = osname == 'nt'
+isdarwin = sysplatform == b'darwin'
+isposix = osname == b'posix'
+iswindows = osname == b'nt'
 
 def getoptb(args, shortlist, namelist):
     return _getoptbwrapper(getopt.getopt, args, shortlist, namelist)
--- a/mercurial/registrar.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/registrar.py	Wed Sep 26 20:33:09 2018 +0900
@@ -247,6 +247,19 @@
      implies 'matchctx.status()' at runtime or not (False, by
      default).
 
+    Optional argument 'weight' indicates the estimated run-time cost, useful
+    for static optimization, default is 1. Higher weight means more expensive.
+    There are predefined weights in the 'filesetlang' module.
+
+    ====== =============================================================
+    Weight Description and examples
+    ====== =============================================================
+    0.5    basic match patterns (e.g. a symbol)
+    10     computing status (e.g. added()) or accessing a few files
+    30     reading file content for each (e.g. grep())
+    50     scanning working directory (ignored())
+    ====== =============================================================
+
     'filesetpredicate' instance in example above can be used to
     decorate multiple functions.
 
@@ -259,8 +272,9 @@
     _getname = _funcregistrarbase._parsefuncdecl
     _docformat = "``%s``\n    %s"
 
-    def _extrasetup(self, name, func, callstatus=False):
+    def _extrasetup(self, name, func, callstatus=False, weight=1):
         func._callstatus = callstatus
+        func._weight = weight
 
 class _templateregistrarbase(_funcregistrarbase):
     """Base of decorator to register functions as template specific one
@@ -281,7 +295,7 @@
             '''
             pass
 
-        # old API
+        # old API (DEPRECATED)
         @templatekeyword('mykeyword')
         def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
             '''Explanation of this template keyword ....
@@ -385,7 +399,8 @@
         internalmerge = registrar.internalmerge()
 
         @internalmerge('mymerge', internalmerge.mergeonly,
-                       onfailure=None, precheck=None):
+                       onfailure=None, precheck=None,
+                       binary=False, symlink=False):
         def mymergefunc(repo, mynode, orig, fcd, fco, fca,
                         toolconf, files, labels=None):
             '''Explanation of this internal merge tool ....
@@ -416,6 +431,12 @@
     'files' and 'labels'. If it returns false value, merging is aborted
     immediately (and file is marked as "unresolved").
 
+    Optional argument 'binary' is a binary files capability of internal
+    merge tool. 'nomerge' merge type implies binary=True.
+
+    Optional argument 'symlink' is a symlinks capability of inetrnal
+    merge function. 'nomerge' merge type implies symlink=True.
+
     'internalmerge' instance in example above can be used to
     decorate multiple functions.
 
@@ -433,7 +454,14 @@
     fullmerge = 'fullmerge'  # both premerge and merge
 
     def _extrasetup(self, name, func, mergetype,
-                    onfailure=None, precheck=None):
+                    onfailure=None, precheck=None,
+                    binary=False, symlink=False):
         func.mergetype = mergetype
         func.onfailure = onfailure
         func.precheck = precheck
+
+        binarycap = binary or mergetype == self.nomerge
+        symlinkcap = symlink or mergetype == self.nomerge
+
+        # actual capabilities, which this internal merge tool has
+        func.capabilities = {"binary": binarycap, "symlink": symlinkcap}
--- a/mercurial/repair.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/repair.py	Wed Sep 26 20:33:09 2018 +0900
@@ -24,6 +24,8 @@
     exchange,
     obsolete,
     obsutil,
+    phases,
+    pycompat,
     util,
 )
 from .utils import (
@@ -70,7 +72,7 @@
     """find out the filelogs affected by the strip"""
     files = set()
 
-    for x in xrange(striprev, len(repo)):
+    for x in pycompat.xrange(striprev, len(repo)):
         files.update(repo[x].files())
 
     return sorted(files)
@@ -80,7 +82,7 @@
     return [revlog.linkrev(r) for r in brokenset]
 
 def _collectmanifest(repo, striprev):
-    return _collectrevlog(repo.manifestlog._revlog, striprev)
+    return _collectrevlog(repo.manifestlog.getstorage(b''), striprev)
 
 def _collectbrokencsets(repo, files, striprev):
     """return the changesets which will be broken by the truncation"""
@@ -189,7 +191,11 @@
     with ui.uninterruptable():
         try:
             with repo.transaction("strip") as tr:
-                offset = len(tr.entries)
+                # TODO this code violates the interface abstraction of the
+                # transaction and makes assumptions that file storage is
+                # using append-only files. We'll need some kind of storage
+                # API to handle stripping for us.
+                offset = len(tr._entries)
 
                 tr.startgroup()
                 cl.strip(striprev, tr)
@@ -199,8 +205,8 @@
                     repo.file(fn).strip(striprev, tr)
                 tr.endgroup()
 
-                for i in xrange(offset, len(tr.entries)):
-                    file, troffset, ignore = tr.entries[i]
+                for i in pycompat.xrange(offset, len(tr._entries)):
+                    file, troffset, ignore = tr._entries[i]
                     with repo.svfs(file, 'a', checkambig=True) as fp:
                         fp.truncate(troffset)
                     if troffset == 0:
@@ -271,7 +277,8 @@
     # orphaned = affected - wanted
     # affected = descendants(roots(wanted))
     # wanted = revs
-    tostrip = set(repo.revs('%ld-(::((roots(%ld)::)-%ld))', revs, revs, revs))
+    revset = '%ld - ( ::( (roots(%ld):: and not _phase(%s)) -%ld) )'
+    tostrip = set(repo.revs(revset, revs, revs, phases.internal, revs))
     notstrip = revs - tostrip
     if notstrip:
         nodestr = ', '.join(sorted(short(repo[n].node()) for n in notstrip))
@@ -297,31 +304,31 @@
         if roots:
             strip(self.ui, self.repo, roots, self.backup, self.topic)
 
-def delayedstrip(ui, repo, nodelist, topic=None):
+def delayedstrip(ui, repo, nodelist, topic=None, backup=True):
     """like strip, but works inside transaction and won't strip irreverent revs
 
     nodelist must explicitly contain all descendants. Otherwise a warning will
     be printed that some nodes are not stripped.
 
-    Always do a backup. The last non-None "topic" will be used as the backup
-    topic name. The default backup topic name is "backup".
+    Will do a backup if `backup` is True. The last non-None "topic" will be
+    used as the backup topic name. The default backup topic name is "backup".
     """
     tr = repo.currenttransaction()
     if not tr:
         nodes = safestriproots(ui, repo, nodelist)
-        return strip(ui, repo, nodes, True, topic)
+        return strip(ui, repo, nodes, backup=backup, topic=topic)
     # transaction postclose callbacks are called in alphabet order.
     # use '\xff' as prefix so we are likely to be called last.
     callback = tr.getpostclose('\xffstrip')
     if callback is None:
-        callback = stripcallback(ui, repo, True, topic)
+        callback = stripcallback(ui, repo, backup=backup, topic=topic)
         tr.addpostclose('\xffstrip', callback)
     if topic:
         callback.topic = topic
     callback.addnodes(nodelist)
 
 def stripmanifest(repo, striprev, tr, files):
-    revlog = repo.manifestlog._revlog
+    revlog = repo.manifestlog.getstorage(b'')
     revlog.strip(striprev, tr)
     striptrees(repo, tr, striprev, files)
 
@@ -332,7 +339,7 @@
             if (unencoded.startswith('meta/') and
                 unencoded.endswith('00manifest.i')):
                 dir = unencoded[5:-12]
-                repo.manifestlog._revlog.dirlog(dir).strip(striprev, tr)
+                repo.manifestlog.getstorage(dir).strip(striprev, tr)
 
 def rebuildfncache(ui, repo):
     """Rebuilds the fncache file from repo history.
--- a/mercurial/repository.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/repository.py	Wed Sep 26 20:33:09 2018 +0900
@@ -15,6 +15,10 @@
     interfaceutil,
 )
 
+# When narrowing is finalized and no longer subject to format changes,
+# we should move this to just "narrow" or similar.
+NARROW_REQUIREMENT = 'narrowhg-experimental'
+
 class ipeerconnection(interfaceutil.Interface):
     """Represents a "connection" to a repository.
 
@@ -314,6 +318,87 @@
             _('cannot %s; remote repository does not support the %r '
               'capability') % (purpose, name))
 
+class irevisiondelta(interfaceutil.Interface):
+    """Represents a delta between one revision and another.
+
+    Instances convey enough information to allow a revision to be exchanged
+    with another repository.
+
+    Instances represent the fulltext revision data or a delta against
+    another revision. Therefore the ``revision`` and ``delta`` attributes
+    are mutually exclusive.
+
+    Typically used for changegroup generation.
+    """
+
+    node = interfaceutil.Attribute(
+        """20 byte node of this revision.""")
+
+    p1node = interfaceutil.Attribute(
+        """20 byte node of 1st parent of this revision.""")
+
+    p2node = interfaceutil.Attribute(
+        """20 byte node of 2nd parent of this revision.""")
+
+    linknode = interfaceutil.Attribute(
+        """20 byte node of the changelog revision this node is linked to.""")
+
+    flags = interfaceutil.Attribute(
+        """2 bytes of integer flags that apply to this revision.""")
+
+    basenode = interfaceutil.Attribute(
+        """20 byte node of the revision this data is a delta against.
+
+        ``nullid`` indicates that the revision is a full revision and not
+        a delta.
+        """)
+
+    baserevisionsize = interfaceutil.Attribute(
+        """Size of base revision this delta is against.
+
+        May be ``None`` if ``basenode`` is ``nullid``.
+        """)
+
+    revision = interfaceutil.Attribute(
+        """Raw fulltext of revision data for this node.""")
+
+    delta = interfaceutil.Attribute(
+        """Delta between ``basenode`` and ``node``.
+
+        Stored in the bdiff delta format.
+        """)
+
+class irevisiondeltarequest(interfaceutil.Interface):
+    """Represents a request to generate an ``irevisiondelta``."""
+
+    node = interfaceutil.Attribute(
+        """20 byte node of revision being requested.""")
+
+    p1node = interfaceutil.Attribute(
+        """20 byte node of 1st parent of revision.""")
+
+    p2node = interfaceutil.Attribute(
+        """20 byte node of 2nd parent of revision.""")
+
+    linknode = interfaceutil.Attribute(
+        """20 byte node to store in ``linknode`` attribute.""")
+
+    basenode = interfaceutil.Attribute(
+        """Base revision that delta should be generated against.
+
+        If ``nullid``, the derived ``irevisiondelta`` should have its
+        ``revision`` field populated and no delta should be generated.
+
+        If ``None``, the delta may be generated against any revision that
+        is an ancestor of this revision. Or a full revision may be used.
+
+        If any other value, the delta should be produced against that
+        revision.
+        """)
+
+    ellipsis = interfaceutil.Attribute(
+        """Boolean on whether the ellipsis flag should be set.""")
+
 class ifilerevisionssequence(interfaceutil.Interface):
     """Contains index data for all revisions of a file.
 
@@ -440,14 +525,6 @@
         If ``nullrev`` is in the set, this is equivalent to ``revs()``.
         """
 
-    def headrevs():
-        """Obtain a list of revision numbers that are DAG heads.
-
-        The list is sorted oldest to newest.
-
-        TODO determine if sorting is required.
-        """
-
     def heads(start=None, stop=None):
         """Obtain a list of nodes that are DAG heads, with control.
 
@@ -467,9 +544,6 @@
     def deltaparent(rev):
         """"Return the revision that is a suitable parent to delta against."""
 
-    def candelta(baserev, rev):
-        """"Whether a delta can be generated between two revisions."""
-
 class ifiledata(interfaceutil.Interface):
     """Storage interface for data storage of a specific file.
 
@@ -489,7 +563,7 @@
     def checkhash(fulltext, node, p1=None, p2=None, rev=None):
         """Validate the stored hash of a given fulltext and node.
 
-        Raises ``error.RevlogError`` is hash validation fails.
+        Raises ``error.StorageError`` is hash validation fails.
         """
 
     def revision(node, raw=False):
@@ -536,6 +610,30 @@
         revision data.
         """
 
+    def emitrevisiondeltas(requests):
+        """Produce ``irevisiondelta`` from ``irevisiondeltarequest``s.
+
+        Given an iterable of objects conforming to the ``irevisiondeltarequest``
+        interface, emits objects conforming to the ``irevisiondelta``
+        interface.
+
+        This method is a generator.
+
+        ``irevisiondelta`` should be emitted in the same order of
+        ``irevisiondeltarequest`` that was passed in.
+
+        The emitted objects MUST conform by the results of
+        ``irevisiondeltarequest``. Namely, they must respect any requests
+        for building a delta from a specific ``basenode`` if defined.
+
+        When sending deltas, implementations must take into account whether
+        the client has the base delta before encoding a delta against that
+        revision. A revision encountered previously in ``requests`` is
+        always a suitable base revision. An example of a bad delta is a delta
+        against a non-ancestor revision. Another example of a bad delta is a
+        delta against a censored revision.
+        """
+
 class ifilemutation(interfaceutil.Interface):
     """Storage interface for mutation events of a tracked file."""
 
@@ -585,6 +683,23 @@
         even if it existed in the store previously.
         """
 
+    def censorrevision(tr, node, tombstone=b''):
+        """Remove the content of a single revision.
+
+        The specified ``node`` will have its content purged from storage.
+        Future attempts to access the revision data for this node will
+        result in failure.
+
+        A ``tombstone`` message can optionally be stored. This message may be
+        displayed to users when they attempt to access the missing revision
+        data.
+
+        Storage backends may have stored deltas against the previous content
+        in this revision. As part of censoring a revision, these storage
+        backends are expected to rewrite any internally stored deltas such
+        that they no longer reference the deleted content.
+        """
+
     def getstrippoint(minlink):
         """Find the minimum revision that must be stripped to strip a linkrev.
 
@@ -614,14 +729,6 @@
         TODO this feels revlog centric and could likely be removed.
         """)
 
-    storedeltachains = interfaceutil.Attribute(
-        """Whether the store stores deltas.
-
-        TODO deltachains are revlog centric. This can probably removed
-        once there are better abstractions for obtaining/writing
-        data.
-        """)
-
     _generaldelta = interfaceutil.Attribute(
         """Whether deltas can be against any parent revision.
 
@@ -880,18 +987,205 @@
 class imanifestrevisionwritable(imanifestrevisionbase):
     """Interface representing a manifest revision that can be committed."""
 
-    def write(transaction, linkrev, p1node, p2node, added, removed):
+    def write(transaction, linkrev, p1node, p2node, added, removed, match=None):
         """Add this revision to storage.
 
         Takes a transaction object, the changeset revision number it will
         be associated with, its parent nodes, and lists of added and
         removed paths.
 
+        If match is provided, storage can choose not to inspect or write out
+        items that do not match. Storage is still required to be able to provide
+        the full manifest in the future for any directories written (these
+        manifests should not be "narrowed on disk").
+
         Returns the binary node of the created revision.
         """
 
+class imanifeststorage(interfaceutil.Interface):
+    """Storage interface for manifest data."""
+
+    tree = interfaceutil.Attribute(
+        """The path to the directory this manifest tracks.
+
+        The empty bytestring represents the root manifest.
+        """)
+
+    index = interfaceutil.Attribute(
+        """An ``ifilerevisionssequence`` instance.""")
+
+    indexfile = interfaceutil.Attribute(
+        """Path of revlog index file.
+
+        TODO this is revlog specific and should not be exposed.
+        """)
+
+    opener = interfaceutil.Attribute(
+        """VFS opener to use to access underlying files used for storage.
+
+        TODO this is revlog specific and should not be exposed.
+        """)
+
+    version = interfaceutil.Attribute(
+        """Revlog version number.
+
+        TODO this is revlog specific and should not be exposed.
+        """)
+
+    _generaldelta = interfaceutil.Attribute(
+        """Whether generaldelta storage is being used.
+
+        TODO this is revlog specific and should not be exposed.
+        """)
+
+    fulltextcache = interfaceutil.Attribute(
+        """Dict with cache of fulltexts.
+
+        TODO this doesn't feel appropriate for the storage interface.
+        """)
+
+    def __len__():
+        """Obtain the number of revisions stored for this manifest."""
+
+    def __iter__():
+        """Iterate over revision numbers for this manifest."""
+
+    def rev(node):
+        """Obtain the revision number given a binary node.
+
+        Raises ``error.LookupError`` if the node is not known.
+        """
+
+    def node(rev):
+        """Obtain the node value given a revision number.
+
+        Raises ``error.LookupError`` if the revision is not known.
+        """
+
+    def lookup(value):
+        """Attempt to resolve a value to a node.
+
+        Value can be a binary node, hex node, revision number, or a bytes
+        that can be converted to an integer.
+
+        Raises ``error.LookupError`` if a ndoe could not be resolved.
+
+        TODO this is only used by debug* commands and can probably be deleted
+        easily.
+        """
+
+    def parents(node):
+        """Returns a 2-tuple of parent nodes for a node.
+
+        Values will be ``nullid`` if the parent is empty.
+        """
+
+    def parentrevs(rev):
+        """Like parents() but operates on revision numbers."""
+
+    def linkrev(rev):
+        """Obtain the changeset revision number a revision is linked to."""
+
+    def revision(node, _df=None, raw=False):
+        """Obtain fulltext data for a node."""
+
+    def revdiff(rev1, rev2):
+        """Obtain a delta between two revision numbers.
+
+        The returned data is the result of ``bdiff.bdiff()`` on the raw
+        revision data.
+        """
+
+    def cmp(node, fulltext):
+        """Compare fulltext to another revision.
+
+        Returns True if the fulltext is different from what is stored.
+        """
+
+    def emitrevisiondeltas(requests):
+        """Produce ``irevisiondelta`` from ``irevisiondeltarequest``s.
+
+        See the documentation for ``ifiledata`` for more.
+        """
+
+    def addgroup(deltas, linkmapper, transaction, addrevisioncb=None):
+        """Process a series of deltas for storage.
+
+        See the documentation in ``ifilemutation`` for more.
+        """
+
+    def getstrippoint(minlink):
+        """Find minimum revision that must be stripped to strip a linkrev.
+
+        See the documentation in ``ifilemutation`` for more.
+        """
+
+    def strip(minlink, transaction):
+        """Remove storage of items starting at a linkrev.
+
+        See the documentation in ``ifilemutation`` for more.
+        """
+
+    def checksize():
+        """Obtain the expected sizes of backing files.
+
+        TODO this is used by verify and it should not be part of the interface.
+        """
+
+    def files():
+        """Obtain paths that are backing storage for this manifest.
+
+        TODO this is used by verify and there should probably be a better API
+        for this functionality.
+        """
+
+    def deltaparent(rev):
+        """Obtain the revision that a revision is delta'd against.
+
+        TODO delta encoding is an implementation detail of storage and should
+        not be exposed to the storage interface.
+        """
+
+    def clone(tr, dest, **kwargs):
+        """Clone this instance to another."""
+
+    def clearcaches(clear_persisted_data=False):
+        """Clear any caches associated with this instance."""
+
+    def dirlog(d):
+        """Obtain a manifest storage instance for a tree."""
+
+    def add(m, transaction, link, p1, p2, added, removed, readtree=None,
+            match=None):
+        """Add a revision to storage.
+
+        ``m`` is an object conforming to ``imanifestdict``.
+
+        ``link`` is the linkrev revision number.
+
+        ``p1`` and ``p2`` are the parent revision numbers.
+
+        ``added`` and ``removed`` are iterables of added and removed paths,
+        respectively.
+
+        ``readtree`` is a function that can be used to read the child tree(s)
+        when recursively writing the full tree structure when using
+        treemanifets.
+
+        ``match`` is a matcher that can be used to hint to storage that not all
+        paths must be inspected; this is an optimization and can be safely
+        ignored. Note that the storage must still be able to reproduce a full
+        manifest including files that did not match.
+        """
+
 class imanifestlog(interfaceutil.Interface):
-    """Interface representing a collection of manifest snapshots."""
+    """Interface representing a collection of manifest snapshots.
+
+    Represents the root manifest in a repository.
+
+    Also serves as a means to access nested tree manifests and to cache
+    tree manifests.
+    """
 
     def __getitem__(node):
         """Obtain a manifest instance for a given binary node.
@@ -902,15 +1196,15 @@
         interface.
         """
 
-    def get(dir, node, verify=True):
+    def get(tree, node, verify=True):
         """Retrieve the manifest instance for a given directory and binary node.
 
         ``node`` always refers to the node of the root manifest (which will be
         the only manifest if flat manifests are being used).
 
-        If ``dir`` is the empty string, the root manifest is returned. Otherwise
-        the manifest for the specified directory will be returned (requires
-        tree manifests).
+        If ``tree`` is the empty string, the root manifest is returned.
+        Otherwise the manifest for the specified directory will be returned
+        (requires tree manifests).
 
         If ``verify`` is True, ``LookupError`` is raised if the node is not
         known.
@@ -919,6 +1213,15 @@
         interface.
         """
 
+    def getstorage(tree):
+        """Retrieve an interface to storage for a particular tree.
+
+        If ``tree`` is the empty bytestring, storage for the root manifest will
+        be returned. Otherwise storage for a tree manifest is returned.
+
+        TODO formalize interface for returned object.
+        """
+
     def clearcaches():
         """Clear caches associated with this collection."""
 
@@ -928,24 +1231,21 @@
         Raises ``error.LookupError`` if the node is not known.
         """
 
-    def addgroup(deltas, linkmapper, transaction):
-        """Process a series of deltas for storage.
-
-        ``deltas`` is an iterable of 7-tuples of
-        (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
-        to add.
+class ilocalrepositoryfilestorage(interfaceutil.Interface):
+    """Local repository sub-interface providing access to tracked file storage.
 
-        The ``delta`` field contains ``mpatch`` data to apply to a base
-        revision, identified by ``deltabase``. The base node can be
-        ``nullid``, in which case the header from the delta can be ignored
-        and the delta used as the fulltext.
+    This interface defines how a repository accesses storage for a single
+    tracked file path.
+    """
 
-        Returns a list of nodes that were processed. A node will be in the list
-        even if it existed in the store previously.
+    def file(f):
+        """Obtain a filelog for a tracked path.
+
+        The returned type conforms to the ``ifilestorage`` interface.
         """
 
-class completelocalrepository(interfaceutil.Interface):
-    """Monolithic interface for local repositories.
+class ilocalrepositorymain(interfaceutil.Interface):
+    """Main interface for local repositories.
 
     This currently captures the reality of things - not how things should be.
     """
@@ -956,12 +1256,6 @@
         This is actually a class attribute and is shared among all instances.
         """)
 
-    openerreqs = interfaceutil.Attribute(
-        """Set of requirements that are passed to the opener.
-
-        This is actually a class attribute and is shared among all instances.
-        """)
-
     supported = interfaceutil.Attribute(
         """Set of requirements that this repo is capable of opening.""")
 
@@ -1167,12 +1461,6 @@
     def wjoin(f, *insidef):
         """Calls self.vfs.reljoin(self.root, f, *insidef)"""
 
-    def file(f):
-        """Obtain a filelog for a tracked path.
-
-        The returned type conforms to the ``ifilestorage`` interface.
-        """
-
     def setparents(p1, p2):
         """Set the parent nodes of the working directory."""
 
@@ -1300,3 +1588,7 @@
 
     def savecommitmessage(text):
         pass
+
+class completelocalrepository(ilocalrepositorymain,
+                              ilocalrepositoryfilestorage):
+    """Complete interface for a local repository."""
--- a/mercurial/repoview.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/repoview.py	Wed Sep 26 20:33:09 2018 +0900
@@ -28,7 +28,10 @@
     branchmap (see mercurial.branchmap.subsettable), you cannot set "public"
     changesets as "hideable". Doing so would break multiple code assertions and
     lead to crashes."""
-    return obsolete.getrevs(repo, 'obsolete')
+    obsoletes = obsolete.getrevs(repo, 'obsolete')
+    internals = repo._phasecache.getrevset(repo, phases.localhiddenphases)
+    internals = frozenset(internals)
+    return obsoletes | internals
 
 def pinnedrevs(repo):
     """revisions blocking hidden changesets from being filtered
@@ -128,7 +131,7 @@
             firstmutable = min(firstmutable, min(cl.rev(r) for r in roots))
     # protect from nullrev root
     firstmutable = max(0, firstmutable)
-    return frozenset(xrange(firstmutable, len(cl)))
+    return frozenset(pycompat.xrange(firstmutable, len(cl)))
 
 # function to compute filtered set
 #
@@ -210,7 +213,7 @@
         unfichangelog = unfi.changelog
         # bypass call to changelog.method
         unfiindex = unfichangelog.index
-        unfilen = len(unfiindex) - 1
+        unfilen = len(unfiindex)
         unfinode = unfiindex[unfilen - 1][7]
 
         revs = filterrevs(unfi, self.filtername, self._visibilityexceptions)
--- a/mercurial/revlog.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/revlog.py	Wed Sep 26 20:33:09 2018 +0900
@@ -17,7 +17,6 @@
 import contextlib
 import errno
 import hashlib
-import heapq
 import os
 import re
 import struct
@@ -27,6 +26,7 @@
 from .node import (
     bin,
     hex,
+    nullhex,
     nullid,
     nullrev,
     wdirfilenodeids,
@@ -35,6 +35,25 @@
     wdirrev,
 )
 from .i18n import _
+from .revlogutils.constants import (
+    FLAG_GENERALDELTA,
+    FLAG_INLINE_DATA,
+    REVIDX_DEFAULT_FLAGS,
+    REVIDX_ELLIPSIS,
+    REVIDX_EXTSTORED,
+    REVIDX_FLAGS_ORDER,
+    REVIDX_ISCENSORED,
+    REVIDX_KNOWN_FLAGS,
+    REVIDX_RAWTEXT_CHANGING_FLAGS,
+    REVLOGV0,
+    REVLOGV1,
+    REVLOGV1_FLAGS,
+    REVLOGV2,
+    REVLOGV2_FLAGS,
+    REVLOG_DEFAULT_FLAGS,
+    REVLOG_DEFAULT_FORMAT,
+    REVLOG_DEFAULT_VERSION,
+)
 from .thirdparty import (
     attr,
 )
@@ -44,61 +63,68 @@
     mdiff,
     policy,
     pycompat,
+    repository,
     templatefilters,
     util,
 )
+from .revlogutils import (
+    deltas as deltautil,
+)
 from .utils import (
+    interfaceutil,
     stringutil,
 )
 
+# blanked usage of all the name to prevent pyflakes constraints
+# We need these name available in the module for extensions.
+REVLOGV0
+REVLOGV1
+REVLOGV2
+FLAG_INLINE_DATA
+FLAG_GENERALDELTA
+REVLOG_DEFAULT_FLAGS
+REVLOG_DEFAULT_FORMAT
+REVLOG_DEFAULT_VERSION
+REVLOGV1_FLAGS
+REVLOGV2_FLAGS
+REVIDX_ISCENSORED
+REVIDX_ELLIPSIS
+REVIDX_EXTSTORED
+REVIDX_DEFAULT_FLAGS
+REVIDX_FLAGS_ORDER
+REVIDX_KNOWN_FLAGS
+REVIDX_RAWTEXT_CHANGING_FLAGS
+
 parsers = policy.importmod(r'parsers')
 
 # Aliased for performance.
 _zlibdecompress = zlib.decompress
 
-# revlog header flags
-REVLOGV0 = 0
-REVLOGV1 = 1
-# Dummy value until file format is finalized.
-# Reminder: change the bounds check in revlog.__init__ when this is changed.
-REVLOGV2 = 0xDEAD
-FLAG_INLINE_DATA = (1 << 16)
-FLAG_GENERALDELTA = (1 << 17)
-REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
-REVLOG_DEFAULT_FORMAT = REVLOGV1
-REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
-REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
-REVLOGV2_FLAGS = REVLOGV1_FLAGS
-
-# revlog index flags
-REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
-REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
-REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
-REVIDX_DEFAULT_FLAGS = 0
-# stable order in which flags need to be processed and their processors applied
-REVIDX_FLAGS_ORDER = [
-    REVIDX_ISCENSORED,
-    REVIDX_ELLIPSIS,
-    REVIDX_EXTSTORED,
-]
-REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
-# bitmark for flags that could cause rawdata content change
-REVIDX_RAWTEXT_CHANGING_FLAGS = REVIDX_ISCENSORED | REVIDX_EXTSTORED
-
 # max size of revlog with inline data
 _maxinline = 131072
 _chunksize = 1048576
 
-RevlogError = error.RevlogError
-LookupError = error.LookupError
-CensoredNodeError = error.CensoredNodeError
-ProgrammingError = error.ProgrammingError
-
 # Store flag processors (cf. 'addflagprocessor()' to register)
 _flagprocessors = {
     REVIDX_ISCENSORED: None,
 }
 
+# Flag processors for REVIDX_ELLIPSIS.
+def ellipsisreadprocessor(rl, text):
+    return text, False
+
+def ellipsiswriteprocessor(rl, text):
+    return text, False
+
+def ellipsisrawprocessor(rl, text):
+    return False
+
+ellipsisprocessor = (
+    ellipsisreadprocessor,
+    ellipsiswriteprocessor,
+    ellipsisrawprocessor,
+)
+
 _mdre = re.compile('\1\n')
 def parsemeta(text):
     """return (metadatadict, metadatasize)"""
@@ -149,10 +175,10 @@
     """
     if not flag & REVIDX_KNOWN_FLAGS:
         msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
-        raise ProgrammingError(msg)
+        raise error.ProgrammingError(msg)
     if flag not in REVIDX_FLAGS_ORDER:
         msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
-        raise ProgrammingError(msg)
+        raise error.ProgrammingError(msg)
     if flag in _flagprocessors:
         msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
         raise error.Abort(msg)
@@ -196,579 +222,6 @@
     s.update(text)
     return s.digest()
 
-class _testrevlog(object):
-    """minimalist fake revlog to use in doctests"""
-
-    def __init__(self, data, density=0.5, mingap=0):
-        """data is an list of revision payload boundaries"""
-        self._data = data
-        self._srdensitythreshold = density
-        self._srmingapsize = mingap
-
-    def start(self, rev):
-        if rev == 0:
-            return 0
-        return self._data[rev - 1]
-
-    def end(self, rev):
-        return self._data[rev]
-
-    def length(self, rev):
-        return self.end(rev) - self.start(rev)
-
-    def __len__(self):
-        return len(self._data)
-
-def _trimchunk(revlog, revs, startidx, endidx=None):
-    """returns revs[startidx:endidx] without empty trailing revs
-
-    Doctest Setup
-    >>> revlog = _testrevlog([
-    ...  5,  #0
-    ...  10, #1
-    ...  12, #2
-    ...  12, #3 (empty)
-    ...  17, #4
-    ...  21, #5
-    ...  21, #6 (empty)
-    ... ])
-
-    Contiguous cases:
-    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
-    [0, 1, 2, 3, 4, 5]
-    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
-    [0, 1, 2, 3, 4]
-    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
-    [0, 1, 2]
-    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
-    [2]
-    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
-    [3, 4, 5]
-    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
-    [3, 4]
-
-    Discontiguous cases:
-    >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
-    [1, 3, 5]
-    >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
-    [1]
-    >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
-    [3, 5]
-    >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
-    [3, 5]
-    """
-    length = revlog.length
-
-    if endidx is None:
-        endidx = len(revs)
-
-    # If we have a non-emtpy delta candidate, there are nothing to trim
-    if revs[endidx - 1] < len(revlog):
-        # Trim empty revs at the end, except the very first revision of a chain
-        while (endidx > 1
-                and endidx > startidx
-                and length(revs[endidx - 1]) == 0):
-            endidx -= 1
-
-    return revs[startidx:endidx]
-
-def _segmentspan(revlog, revs, deltainfo=None):
-    """Get the byte span of a segment of revisions
-
-    revs is a sorted array of revision numbers
-
-    >>> revlog = _testrevlog([
-    ...  5,  #0
-    ...  10, #1
-    ...  12, #2
-    ...  12, #3 (empty)
-    ...  17, #4
-    ... ])
-
-    >>> _segmentspan(revlog, [0, 1, 2, 3, 4])
-    17
-    >>> _segmentspan(revlog, [0, 4])
-    17
-    >>> _segmentspan(revlog, [3, 4])
-    5
-    >>> _segmentspan(revlog, [1, 2, 3,])
-    7
-    >>> _segmentspan(revlog, [1, 3])
-    7
-    """
-    if not revs:
-        return 0
-    if deltainfo is not None and len(revlog) <= revs[-1]:
-        if len(revs) == 1:
-            return deltainfo.deltalen
-        offset = revlog.end(len(revlog) - 1)
-        end = deltainfo.deltalen + offset
-    else:
-        end = revlog.end(revs[-1])
-    return end - revlog.start(revs[0])
-
-def _slicechunk(revlog, revs, deltainfo=None, targetsize=None):
-    """slice revs to reduce the amount of unrelated data to be read from disk.
-
-    ``revs`` is sliced into groups that should be read in one time.
-    Assume that revs are sorted.
-
-    The initial chunk is sliced until the overall density (payload/chunks-span
-    ratio) is above `revlog._srdensitythreshold`. No gap smaller than
-    `revlog._srmingapsize` is skipped.
-
-    If `targetsize` is set, no chunk larger than `targetsize` will be yield.
-    For consistency with other slicing choice, this limit won't go lower than
-    `revlog._srmingapsize`.
-
-    If individual revisions chunk are larger than this limit, they will still
-    be raised individually.
-
-    >>> revlog = _testrevlog([
-    ...  5,  #00 (5)
-    ...  10, #01 (5)
-    ...  12, #02 (2)
-    ...  12, #03 (empty)
-    ...  27, #04 (15)
-    ...  31, #05 (4)
-    ...  31, #06 (empty)
-    ...  42, #07 (11)
-    ...  47, #08 (5)
-    ...  47, #09 (empty)
-    ...  48, #10 (1)
-    ...  51, #11 (3)
-    ...  74, #12 (23)
-    ...  85, #13 (11)
-    ...  86, #14 (1)
-    ...  91, #15 (5)
-    ... ])
-
-    >>> list(_slicechunk(revlog, list(range(16))))
-    [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
-    >>> list(_slicechunk(revlog, [0, 15]))
-    [[0], [15]]
-    >>> list(_slicechunk(revlog, [0, 11, 15]))
-    [[0], [11], [15]]
-    >>> list(_slicechunk(revlog, [0, 11, 13, 15]))
-    [[0], [11, 13, 15]]
-    >>> list(_slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
-    [[1, 2], [5, 8, 10, 11], [14]]
-
-    Slicing with a maximum chunk size
-    >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
-    [[0], [11], [13], [15]]
-    >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
-    [[0], [11], [13, 15]]
-    """
-    if targetsize is not None:
-        targetsize = max(targetsize, revlog._srmingapsize)
-    # targetsize should not be specified when evaluating delta candidates:
-    # * targetsize is used to ensure we stay within specification when reading,
-    # * deltainfo is used to pick are good delta chain when writing.
-    if not (deltainfo is None or targetsize is None):
-        msg = 'cannot use `targetsize` with a `deltainfo`'
-        raise error.ProgrammingError(msg)
-    for chunk in _slicechunktodensity(revlog, revs,
-                                      deltainfo,
-                                      revlog._srdensitythreshold,
-                                      revlog._srmingapsize):
-        for subchunk in _slicechunktosize(revlog, chunk, targetsize):
-            yield subchunk
-
-def _slicechunktosize(revlog, revs, targetsize=None):
-    """slice revs to match the target size
-
-    This is intended to be used on chunk that density slicing selected by that
-    are still too large compared to the read garantee of revlog. This might
-    happens when "minimal gap size" interrupted the slicing or when chain are
-    built in a way that create large blocks next to each other.
-
-    >>> revlog = _testrevlog([
-    ...  3,  #0 (3)
-    ...  5,  #1 (2)
-    ...  6,  #2 (1)
-    ...  8,  #3 (2)
-    ...  8,  #4 (empty)
-    ...  11, #5 (3)
-    ...  12, #6 (1)
-    ...  13, #7 (1)
-    ...  14, #8 (1)
-    ... ])
-
-    Cases where chunk is already small enough
-    >>> list(_slicechunktosize(revlog, [0], 3))
-    [[0]]
-    >>> list(_slicechunktosize(revlog, [6, 7], 3))
-    [[6, 7]]
-    >>> list(_slicechunktosize(revlog, [0], None))
-    [[0]]
-    >>> list(_slicechunktosize(revlog, [6, 7], None))
-    [[6, 7]]
-
-    cases where we need actual slicing
-    >>> list(_slicechunktosize(revlog, [0, 1], 3))
-    [[0], [1]]
-    >>> list(_slicechunktosize(revlog, [1, 3], 3))
-    [[1], [3]]
-    >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
-    [[1, 2], [3]]
-    >>> list(_slicechunktosize(revlog, [3, 5], 3))
-    [[3], [5]]
-    >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
-    [[3], [5]]
-    >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
-    [[5], [6, 7, 8]]
-    >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
-    [[0], [1, 2], [3], [5], [6, 7, 8]]
-
-    Case with too large individual chunk (must return valid chunk)
-    >>> list(_slicechunktosize(revlog, [0, 1], 2))
-    [[0], [1]]
-    >>> list(_slicechunktosize(revlog, [1, 3], 1))
-    [[1], [3]]
-    >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
-    [[3], [5]]
-    """
-    assert targetsize is None or 0 <= targetsize
-    if targetsize is None or _segmentspan(revlog, revs) <= targetsize:
-        yield revs
-        return
-
-    startrevidx = 0
-    startdata = revlog.start(revs[0])
-    endrevidx = 0
-    iterrevs = enumerate(revs)
-    next(iterrevs) # skip first rev.
-    for idx, r in iterrevs:
-        span = revlog.end(r) - startdata
-        if span <= targetsize:
-            endrevidx = idx
-        else:
-            chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
-            if chunk:
-                yield chunk
-            startrevidx = idx
-            startdata = revlog.start(r)
-            endrevidx = idx
-    yield _trimchunk(revlog, revs, startrevidx)
-
-def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
-                         mingapsize=0):
-    """slice revs to reduce the amount of unrelated data to be read from disk.
-
-    ``revs`` is sliced into groups that should be read in one time.
-    Assume that revs are sorted.
-
-    ``deltainfo`` is a _deltainfo instance of a revision that we would append
-    to the top of the revlog.
-
-    The initial chunk is sliced until the overall density (payload/chunks-span
-    ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
-    skipped.
-
-    >>> revlog = _testrevlog([
-    ...  5,  #00 (5)
-    ...  10, #01 (5)
-    ...  12, #02 (2)
-    ...  12, #03 (empty)
-    ...  27, #04 (15)
-    ...  31, #05 (4)
-    ...  31, #06 (empty)
-    ...  42, #07 (11)
-    ...  47, #08 (5)
-    ...  47, #09 (empty)
-    ...  48, #10 (1)
-    ...  51, #11 (3)
-    ...  74, #12 (23)
-    ...  85, #13 (11)
-    ...  86, #14 (1)
-    ...  91, #15 (5)
-    ... ])
-
-    >>> list(_slicechunktodensity(revlog, list(range(16))))
-    [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
-    >>> list(_slicechunktodensity(revlog, [0, 15]))
-    [[0], [15]]
-    >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
-    [[0], [11], [15]]
-    >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
-    [[0], [11, 13, 15]]
-    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
-    [[1, 2], [5, 8, 10, 11], [14]]
-    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
-    ...                           mingapsize=20))
-    [[1, 2, 3, 5, 8, 10, 11], [14]]
-    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
-    ...                           targetdensity=0.95))
-    [[1, 2], [5], [8, 10, 11], [14]]
-    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
-    ...                           targetdensity=0.95, mingapsize=12))
-    [[1, 2], [5, 8, 10, 11], [14]]
-    """
-    start = revlog.start
-    length = revlog.length
-
-    if len(revs) <= 1:
-        yield revs
-        return
-
-    nextrev = len(revlog)
-    nextoffset = revlog.end(nextrev - 1)
-
-    if deltainfo is None:
-        deltachainspan = _segmentspan(revlog, revs)
-        chainpayload = sum(length(r) for r in revs)
-    else:
-        deltachainspan = deltainfo.distance
-        chainpayload = deltainfo.compresseddeltalen
-
-    if deltachainspan < mingapsize:
-        yield revs
-        return
-
-    readdata = deltachainspan
-
-    if deltachainspan:
-        density = chainpayload / float(deltachainspan)
-    else:
-        density = 1.0
-
-    if density >= targetdensity:
-        yield revs
-        return
-
-    if deltainfo is not None and deltainfo.deltalen:
-        revs = list(revs)
-        revs.append(nextrev)
-
-    # Store the gaps in a heap to have them sorted by decreasing size
-    gapsheap = []
-    heapq.heapify(gapsheap)
-    prevend = None
-    for i, rev in enumerate(revs):
-        if rev < nextrev:
-            revstart = start(rev)
-            revlen = length(rev)
-        else:
-            revstart = nextoffset
-            revlen = deltainfo.deltalen
-
-        # Skip empty revisions to form larger holes
-        if revlen == 0:
-            continue
-
-        if prevend is not None:
-            gapsize = revstart - prevend
-            # only consider holes that are large enough
-            if gapsize > mingapsize:
-                heapq.heappush(gapsheap, (-gapsize, i))
-
-        prevend = revstart + revlen
-
-    # Collect the indices of the largest holes until the density is acceptable
-    indicesheap = []
-    heapq.heapify(indicesheap)
-    while gapsheap and density < targetdensity:
-        oppgapsize, gapidx = heapq.heappop(gapsheap)
-
-        heapq.heappush(indicesheap, gapidx)
-
-        # the gap sizes are stored as negatives to be sorted decreasingly
-        # by the heap
-        readdata -= (-oppgapsize)
-        if readdata > 0:
-            density = chainpayload / float(readdata)
-        else:
-            density = 1.0
-
-    # Cut the revs at collected indices
-    previdx = 0
-    while indicesheap:
-        idx = heapq.heappop(indicesheap)
-
-        chunk = _trimchunk(revlog, revs, previdx, idx)
-        if chunk:
-            yield chunk
-
-        previdx = idx
-
-    chunk = _trimchunk(revlog, revs, previdx)
-    if chunk:
-        yield chunk
-
-@attr.s(slots=True, frozen=True)
-class _deltainfo(object):
-    distance = attr.ib()
-    deltalen = attr.ib()
-    data = attr.ib()
-    base = attr.ib()
-    chainbase = attr.ib()
-    chainlen = attr.ib()
-    compresseddeltalen = attr.ib()
-
-class _deltacomputer(object):
-    def __init__(self, revlog):
-        self.revlog = revlog
-
-    def _getcandidaterevs(self, p1, p2, cachedelta):
-        """
-        Provides revisions that present an interest to be diffed against,
-        grouped by level of easiness.
-        """
-        revlog = self.revlog
-        gdelta = revlog._generaldelta
-        curr = len(revlog)
-        prev = curr - 1
-        p1r, p2r = revlog.rev(p1), revlog.rev(p2)
-
-        # should we try to build a delta?
-        if prev != nullrev and revlog.storedeltachains:
-            tested = set()
-            # This condition is true most of the time when processing
-            # changegroup data into a generaldelta repo. The only time it
-            # isn't true is if this is the first revision in a delta chain
-            # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
-            if cachedelta and gdelta and revlog._lazydeltabase:
-                # Assume what we received from the server is a good choice
-                # build delta will reuse the cache
-                yield (cachedelta[0],)
-                tested.add(cachedelta[0])
-
-            if gdelta:
-                # exclude already lazy tested base if any
-                parents = [p for p in (p1r, p2r)
-                           if p != nullrev and p not in tested]
-
-                if not revlog._deltabothparents and len(parents) == 2:
-                    parents.sort()
-                    # To minimize the chance of having to build a fulltext,
-                    # pick first whichever parent is closest to us (max rev)
-                    yield (parents[1],)
-                    # then the other one (min rev) if the first did not fit
-                    yield (parents[0],)
-                    tested.update(parents)
-                elif len(parents) > 0:
-                    # Test all parents (1 or 2), and keep the best candidate
-                    yield parents
-                    tested.update(parents)
-
-            if prev not in tested:
-                # other approach failed try against prev to hopefully save us a
-                # fulltext.
-                yield (prev,)
-                tested.add(prev)
-
-    def buildtext(self, revinfo, fh):
-        """Builds a fulltext version of a revision
-
-        revinfo: _revisioninfo instance that contains all needed info
-        fh:      file handle to either the .i or the .d revlog file,
-                 depending on whether it is inlined or not
-        """
-        btext = revinfo.btext
-        if btext[0] is not None:
-            return btext[0]
-
-        revlog = self.revlog
-        cachedelta = revinfo.cachedelta
-        flags = revinfo.flags
-        node = revinfo.node
-
-        baserev = cachedelta[0]
-        delta = cachedelta[1]
-        # special case deltas which replace entire base; no need to decode
-        # base revision. this neatly avoids censored bases, which throw when
-        # they're decoded.
-        hlen = struct.calcsize(">lll")
-        if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
-                                                   len(delta) - hlen):
-            btext[0] = delta[hlen:]
-        else:
-            # deltabase is rawtext before changed by flag processors, which is
-            # equivalent to non-raw text
-            basetext = revlog.revision(baserev, _df=fh, raw=False)
-            btext[0] = mdiff.patch(basetext, delta)
-
-        try:
-            res = revlog._processflags(btext[0], flags, 'read', raw=True)
-            btext[0], validatehash = res
-            if validatehash:
-                revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
-            if flags & REVIDX_ISCENSORED:
-                raise RevlogError(_('node %s is not censored') % node)
-        except CensoredNodeError:
-            # must pass the censored index flag to add censored revisions
-            if not flags & REVIDX_ISCENSORED:
-                raise
-        return btext[0]
-
-    def _builddeltadiff(self, base, revinfo, fh):
-        revlog = self.revlog
-        t = self.buildtext(revinfo, fh)
-        if revlog.iscensored(base):
-            # deltas based on a censored revision must replace the
-            # full content in one patch, so delta works everywhere
-            header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
-            delta = header + t
-        else:
-            ptext = revlog.revision(base, _df=fh, raw=True)
-            delta = mdiff.textdiff(ptext, t)
-
-        return delta
-
-    def _builddeltainfo(self, revinfo, base, fh):
-        # can we use the cached delta?
-        if revinfo.cachedelta and revinfo.cachedelta[0] == base:
-            delta = revinfo.cachedelta[1]
-        else:
-            delta = self._builddeltadiff(base, revinfo, fh)
-        revlog = self.revlog
-        header, data = revlog.compress(delta)
-        deltalen = len(header) + len(data)
-        chainbase = revlog.chainbase(base)
-        offset = revlog.end(len(revlog) - 1)
-        dist = deltalen + offset - revlog.start(chainbase)
-        if revlog._generaldelta:
-            deltabase = base
-        else:
-            deltabase = chainbase
-        chainlen, compresseddeltalen = revlog._chaininfo(base)
-        chainlen += 1
-        compresseddeltalen += deltalen
-        return _deltainfo(dist, deltalen, (header, data), deltabase,
-                         chainbase, chainlen, compresseddeltalen)
-
-    def finddeltainfo(self, revinfo, fh):
-        """Find an acceptable delta against a candidate revision
-
-        revinfo: information about the revision (instance of _revisioninfo)
-        fh:      file handle to either the .i or the .d revlog file,
-                 depending on whether it is inlined or not
-
-        Returns the first acceptable candidate revision, as ordered by
-        _getcandidaterevs
-        """
-        cachedelta = revinfo.cachedelta
-        p1 = revinfo.p1
-        p2 = revinfo.p2
-        revlog = self.revlog
-
-        deltainfo = None
-        for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
-            nominateddeltas = []
-            for candidaterev in candidaterevs:
-                # no delta for rawtext-changing revs (see "candelta" for why)
-                if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
-                    continue
-                candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
-                if revlog._isgooddeltainfo(candidatedelta, revinfo):
-                    nominateddeltas.append(candidatedelta)
-            if nominateddeltas:
-                deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
-                break
-
-        return deltainfo
-
 @attr.s(slots=True, frozen=True)
 class _revisioninfo(object):
     """Information about a revision that allows building its fulltext
@@ -788,6 +241,19 @@
     cachedelta = attr.ib()
     flags = attr.ib()
 
+@interfaceutil.implementer(repository.irevisiondelta)
+@attr.s(slots=True, frozen=True)
+class revlogrevisiondelta(object):
+    node = attr.ib()
+    p1node = attr.ib()
+    p2node = attr.ib()
+    basenode = attr.ib()
+    linknode = attr.ib()
+    flags = attr.ib()
+    baserevisionsize = attr.ib()
+    revision = attr.ib()
+    delta = attr.ib()
+
 # index v0:
 #  4 bytes: offset
 #  4 bytes: compressed length
@@ -800,6 +266,12 @@
 indexformatv0_pack = indexformatv0.pack
 indexformatv0_unpack = indexformatv0.unpack
 
+class revlogoldindex(list):
+    def __getitem__(self, i):
+        if i == -1:
+            return (0, 0, 0, -1, -1, -1, -1, nullid)
+        return list.__getitem__(self, i)
+
 class revlogoldio(object):
     def __init__(self):
         self.size = indexformatv0.size
@@ -821,14 +293,12 @@
             nodemap[e[6]] = n
             n += 1
 
-        # add the magic null revision at -1
-        index.append((0, 0, 0, -1, -1, -1, -1, nullid))
-
-        return index, nodemap, None
+        return revlogoldindex(index), nodemap, None
 
     def packentry(self, entry, node, version, rev):
         if gettype(entry[0]):
-            raise RevlogError(_('index entry flags need revlog version 1'))
+            raise error.RevlogError(_('index entry flags need revlog '
+                                      'version 1'))
         e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
               node(entry[5]), node(entry[6]), entry[7])
         return indexformatv0_pack(*e2)
@@ -940,6 +410,10 @@
         self._srdensitythreshold = 0.50
         self._srmingapsize = 262144
 
+        # Make copy of flag processors so each revlog instance can support
+        # custom flags.
+        self._flagprocessors = dict(_flagprocessors)
+
         mmapindexthreshold = None
         v = REVLOG_DEFAULT_VERSION
         opts = getattr(opener, 'options', None)
@@ -973,13 +447,15 @@
                 self._srdensitythreshold = opts['sparse-read-density-threshold']
             if 'sparse-read-min-gap-size' in opts:
                 self._srmingapsize = opts['sparse-read-min-gap-size']
+            if opts.get('enableellipsis'):
+                self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
 
         if self._chunkcachesize <= 0:
-            raise RevlogError(_('revlog chunk cache size %r is not greater '
-                                'than 0') % self._chunkcachesize)
+            raise error.RevlogError(_('revlog chunk cache size %r is not '
+                                      'greater than 0') % self._chunkcachesize)
         elif self._chunkcachesize & (self._chunkcachesize - 1):
-            raise RevlogError(_('revlog chunk cache size %r is not a power '
-                                'of 2') % self._chunkcachesize)
+            raise error.RevlogError(_('revlog chunk cache size %r is not a '
+                                      'power of 2') % self._chunkcachesize)
 
         indexdata = ''
         self._initempty = True
@@ -1004,24 +480,24 @@
         fmt = v & 0xFFFF
         if fmt == REVLOGV0:
             if flags:
-                raise RevlogError(_('unknown flags (%#04x) in version %d '
-                                    'revlog %s') %
-                                  (flags >> 16, fmt, self.indexfile))
+                raise error.RevlogError(_('unknown flags (%#04x) in version %d '
+                                          'revlog %s') %
+                                        (flags >> 16, fmt, self.indexfile))
         elif fmt == REVLOGV1:
             if flags & ~REVLOGV1_FLAGS:
-                raise RevlogError(_('unknown flags (%#04x) in version %d '
-                                    'revlog %s') %
-                                  (flags >> 16, fmt, self.indexfile))
+                raise error.RevlogError(_('unknown flags (%#04x) in version %d '
+                                          'revlog %s') %
+                                        (flags >> 16, fmt, self.indexfile))
         elif fmt == REVLOGV2:
             if flags & ~REVLOGV2_FLAGS:
-                raise RevlogError(_('unknown flags (%#04x) in version %d '
-                                    'revlog %s') %
-                                  (flags >> 16, fmt, self.indexfile))
+                raise error.RevlogError(_('unknown flags (%#04x) in version %d '
+                                          'revlog %s') %
+                                        (flags >> 16, fmt, self.indexfile))
         else:
-            raise RevlogError(_('unknown version (%d) in revlog %s') %
-                              (fmt, self.indexfile))
+            raise error.RevlogError(_('unknown version (%d) in revlog %s') %
+                                    (fmt, self.indexfile))
 
-        self.storedeltachains = True
+        self._storedeltachains = True
 
         self._io = revlogio()
         if self.version == REVLOGV0:
@@ -1029,7 +505,8 @@
         try:
             d = self._io.parseindex(indexdata, self._inline)
         except (ValueError, IndexError):
-            raise RevlogError(_("index %s is corrupted") % (self.indexfile))
+            raise error.RevlogError(_("index %s is corrupted") %
+                                    self.indexfile)
         self.index, nodemap, self._chunkcache = d
         if nodemap is not None:
             self.nodemap = self._nodecache = nodemap
@@ -1071,27 +548,33 @@
                 yield fp
 
     def tip(self):
-        return self.node(len(self.index) - 2)
+        return self.node(len(self.index) - 1)
     def __contains__(self, rev):
         return 0 <= rev < len(self)
     def __len__(self):
-        return len(self.index) - 1
+        return len(self.index)
     def __iter__(self):
-        return iter(xrange(len(self)))
+        return iter(pycompat.xrange(len(self)))
     def revs(self, start=0, stop=None):
         """iterate over all rev in this revlog (from start to stop)"""
         step = 1
+        length = len(self)
         if stop is not None:
             if start > stop:
                 step = -1
             stop += step
+            if stop > length:
+                stop = length
         else:
-            stop = len(self)
-        return xrange(start, stop, step)
+            stop = length
+        return pycompat.xrange(start, stop, step)
 
     @util.propertycache
     def nodemap(self):
-        self.rev(self.node(0))
+        if self.index:
+            # populate mapping down to the initial node
+            node0 = self.index[0][7]  # get around changelog filtering
+            self.rev(node0)
         return self._nodecache
 
     def hasnode(self, node):
@@ -1130,21 +613,21 @@
             return self._nodecache[node]
         except TypeError:
             raise
-        except RevlogError:
+        except error.RevlogError:
             # parsers.c radix tree lookup failed
             if node == wdirid or node in wdirfilenodeids:
                 raise error.WdirUnsupported
-            raise LookupError(node, self.indexfile, _('no node'))
+            raise error.LookupError(node, self.indexfile, _('no node'))
         except KeyError:
             # pure python cache lookup failed
             n = self._nodecache
             i = self.index
             p = self._nodepos
             if p is None:
-                p = len(i) - 2
+                p = len(i) - 1
             else:
                 assert p < len(i)
-            for r in xrange(p, -1, -1):
+            for r in pycompat.xrange(p, -1, -1):
                 v = i[r][7]
                 n[v] = r
                 if v == node:
@@ -1152,7 +635,7 @@
                     return r
             if node == wdirid or node in wdirfilenodeids:
                 raise error.WdirUnsupported
-            raise LookupError(node, self.indexfile, _('no node'))
+            raise error.LookupError(node, self.indexfile, _('no node'))
 
     # Accessors for index entries.
 
@@ -1711,11 +1194,6 @@
         a, b = self.rev(a), self.rev(b)
         return self.isancestorrev(a, b)
 
-    def descendant(self, a, b):
-        msg = 'revlog.descendant is deprecated, use revlog.isancestorrev'
-        util.nouideprecwarn(msg, '4.7')
-        return self.isancestorrev(a, b)
-
     def isancestorrev(self, a, b):
         """return True if revision a is an ancestor of revision b
 
@@ -1755,7 +1233,7 @@
                 node = id
                 self.rev(node) # quick search the index
                 return node
-            except LookupError:
+            except error.LookupError:
                 pass # may be partial hex id
         try:
             # str(rev)
@@ -1775,7 +1253,7 @@
                 node = bin(id)
                 self.rev(node)
                 return node
-            except (TypeError, LookupError):
+            except (TypeError, error.LookupError):
                 pass
 
     def _partialmatch(self, id):
@@ -1786,18 +1264,18 @@
             if partial and self.hasnode(partial):
                 if maybewdir:
                     # single 'ff...' match in radix tree, ambiguous with wdir
-                    raise RevlogError
+                    raise error.RevlogError
                 return partial
             if maybewdir:
                 # no 'ff...' match in radix tree, wdir identified
                 raise error.WdirUnsupported
             return None
-        except RevlogError:
+        except error.RevlogError:
             # parsers.c radix tree lookup gave multiple matches
             # fast path: for unfiltered changelog, radix tree is accurate
             if not getattr(self, 'filteredrevs', None):
-                raise LookupError(id, self.indexfile,
-                                  _('ambiguous identifier'))
+                raise error.AmbiguousPrefixLookupError(
+                    id, self.indexfile, _('ambiguous identifier'))
             # fall through to slow path that filters hidden revisions
         except (AttributeError, ValueError):
             # we are pure python, or key was too short to search radix tree
@@ -1814,12 +1292,14 @@
                 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
                 nl = [n for n in nl if hex(n).startswith(id) and
                       self.hasnode(n)]
+                if nullhex.startswith(id):
+                    nl.append(nullid)
                 if len(nl) > 0:
                     if len(nl) == 1 and not maybewdir:
                         self._pcache[id] = nl[0]
                         return nl[0]
-                    raise LookupError(id, self.indexfile,
-                                      _('ambiguous identifier'))
+                    raise error.AmbiguousPrefixLookupError(
+                        id, self.indexfile, _('ambiguous identifier'))
                 if maybewdir:
                     raise error.WdirUnsupported
                 return None
@@ -1838,20 +1318,20 @@
         if n:
             return n
 
-        raise LookupError(id, self.indexfile, _('no match found'))
+        raise error.LookupError(id, self.indexfile, _('no match found'))
 
     def shortest(self, node, minlength=1):
         """Find the shortest unambiguous prefix that matches node."""
         def isvalid(prefix):
             try:
                 node = self._partialmatch(prefix)
-            except error.RevlogError:
+            except error.AmbiguousPrefixLookupError:
                 return False
             except error.WdirUnsupported:
                 # single 'ff...' match
                 return True
             if node is None:
-                raise LookupError(node, self.indexfile, _('no node'))
+                raise error.LookupError(node, self.indexfile, _('no node'))
             return True
 
         def maybewdir(prefix):
@@ -1870,9 +1350,9 @@
             try:
                 length = max(self.index.shortest(node), minlength)
                 return disambiguate(hexnode, length)
-            except RevlogError:
+            except error.RevlogError:
                 if node != wdirid:
-                    raise LookupError(node, self.indexfile, _('no node'))
+                    raise error.LookupError(node, self.indexfile, _('no node'))
             except AttributeError:
                 # Fall through to pure code
                 pass
@@ -2030,7 +1510,8 @@
         if not self._withsparseread:
             slicedchunks = (revs,)
         else:
-            slicedchunks = _slicechunk(self, revs, targetsize=targetsize)
+            slicedchunks = deltautil.slicechunk(self, revs,
+                                                targetsize=targetsize)
 
         for revschunk in slicedchunks:
             firstrev = revschunk[0]
@@ -2070,6 +1551,25 @@
         else:
             return rev - 1
 
+    def issnapshot(self, rev):
+        """tells whether rev is a snapshot
+        """
+        if rev == nullrev:
+            return True
+        deltap = self.deltaparent(rev)
+        if deltap == nullrev:
+            return True
+        p1, p2 = self.parentrevs(rev)
+        if deltap in (p1, p2):
+            return False
+        return self.issnapshot(deltap)
+
+    def snapshotdepth(self, rev):
+        """number of snapshot in the chain before this one"""
+        if not self.issnapshot(rev):
+            raise error.ProgrammingError('revision %d not a snapshot')
+        return len(self._deltachain(rev)[0]) - 1
+
     def revdiff(self, rev1, rev2):
         """return or calculate a delta between two revisions
 
@@ -2191,11 +1691,12 @@
         if flags == 0:
             return text, True
         if not operation in ('read', 'write'):
-            raise ProgrammingError(_("invalid '%s' operation ") % (operation))
+            raise error.ProgrammingError(_("invalid '%s' operation") %
+                                         operation)
         # Check all flags are known.
         if flags & ~REVIDX_KNOWN_FLAGS:
-            raise RevlogError(_("incompatible revision flag '%#x'") %
-                              (flags & ~REVIDX_KNOWN_FLAGS))
+            raise error.RevlogError(_("incompatible revision flag '%#x'") %
+                                    (flags & ~REVIDX_KNOWN_FLAGS))
         validatehash = True
         # Depending on the operation (read or write), the order might be
         # reversed due to non-commutative transforms.
@@ -2209,11 +1710,11 @@
             if flag & flags:
                 vhash = True
 
-                if flag not in _flagprocessors:
+                if flag not in self._flagprocessors:
                     message = _("missing processor for flag '%#x'") % (flag)
-                    raise RevlogError(message)
+                    raise error.RevlogError(message)
 
-                processor = _flagprocessors[flag]
+                processor = self._flagprocessors[flag]
                 if processor is not None:
                     readtransform, writetransform, rawtransform = processor
 
@@ -2240,9 +1741,9 @@
                 revornode = rev
                 if revornode is None:
                     revornode = templatefilters.short(hex(node))
-                raise RevlogError(_("integrity check failed on %s:%s")
+                raise error.RevlogError(_("integrity check failed on %s:%s")
                     % (self.indexfile, pycompat.bytestr(revornode)))
-        except RevlogError:
+        except error.RevlogError:
             if self._censorable and _censoredtext(text):
                 raise error.CensoredNodeError(self.indexfile, node, text)
             raise
@@ -2254,13 +1755,15 @@
         revlog has grown too large to be an inline revlog, it will convert it
         to use multiple index and data files.
         """
-        if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
+        tiprev = len(self) - 1
+        if (not self._inline or
+            (self.start(tiprev) + self.length(tiprev)) < _maxinline):
             return
 
         trinfo = tr.find(self.indexfile)
         if trinfo is None:
-            raise RevlogError(_("%s not found in the transaction")
-                              % self.indexfile)
+            raise error.RevlogError(_("%s not found in the transaction")
+                                    % self.indexfile)
 
         trindex = trinfo[2]
         if trindex is not None:
@@ -2268,7 +1771,7 @@
         else:
             # revlog was stripped at start of transaction, use all leftover data
             trindex = len(self) - 1
-            dataoff = self.end(-2)
+            dataoff = self.end(tiprev)
 
         tr.add(self.datafile, dataoff)
 
@@ -2307,12 +1810,12 @@
             computed by default as hash(text, p1, p2), however subclasses might
             use different hashing method (and override checkhash() in such case)
         flags - the known flags to set on the revision
-        deltacomputer - an optional _deltacomputer instance shared between
+        deltacomputer - an optional deltacomputer instance shared between
             multiple calls
         """
         if link == nullrev:
-            raise RevlogError(_("attempted to add linkrev -1 to %s")
-                              % self.indexfile)
+            raise error.RevlogError(_("attempted to add linkrev -1 to %s")
+                                    % self.indexfile)
 
         if flags:
             node = node or self.hash(text, p1, p2)
@@ -2325,7 +1828,7 @@
             cachedelta = None
 
         if len(rawtext) > _maxentrysize:
-            raise RevlogError(
+            raise error.RevlogError(
                 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
                 % (self.indexfile, len(rawtext)))
 
@@ -2410,8 +1913,8 @@
             try:
                 return _zlibdecompress(data)
             except zlib.error as e:
-                raise RevlogError(_('revlog decompress error: %s') %
-                                  stringutil.forcebytestr(e))
+                raise error.RevlogError(_('revlog decompress error: %s') %
+                                        stringutil.forcebytestr(e))
         # '\0' is more common than 'u' so it goes first.
         elif t == '\0':
             return data
@@ -2426,58 +1929,10 @@
                 compressor = engine.revlogcompressor()
                 self._decompressors[t] = compressor
             except KeyError:
-                raise RevlogError(_('unknown compression type %r') % t)
+                raise error.RevlogError(_('unknown compression type %r') % t)
 
         return compressor.decompress(data)
 
-    def _isgooddeltainfo(self, deltainfo, revinfo):
-        """Returns True if the given delta is good. Good means that it is within
-        the disk span, disk size, and chain length bounds that we know to be
-        performant."""
-        if deltainfo is None:
-            return False
-
-        # - 'deltainfo.distance' is the distance from the base revision --
-        #   bounding it limits the amount of I/O we need to do.
-        # - 'deltainfo.compresseddeltalen' is the sum of the total size of
-        #   deltas we need to apply -- bounding it limits the amount of CPU
-        #   we consume.
-
-        if self._sparserevlog:
-            # As sparse-read will be used, we can consider that the distance,
-            # instead of being the span of the whole chunk,
-            # is the span of the largest read chunk
-            base = deltainfo.base
-
-            if base != nullrev:
-                deltachain = self._deltachain(base)[0]
-            else:
-                deltachain = []
-
-            chunks = _slicechunk(self, deltachain, deltainfo)
-            all_span = [_segmentspan(self, revs, deltainfo) for revs in chunks]
-            distance = max(all_span)
-        else:
-            distance = deltainfo.distance
-
-        textlen = revinfo.textlen
-        defaultmax = textlen * 4
-        maxdist = self._maxdeltachainspan
-        if not maxdist:
-            maxdist = distance # ensure the conditional pass
-        maxdist = max(maxdist, defaultmax)
-        if self._sparserevlog and maxdist < self._srmingapsize:
-            # In multiple place, we are ignoring irrelevant data range below a
-            # certain size. Be also apply this tradeoff here and relax span
-            # constraint for small enought content.
-            maxdist = self._srmingapsize
-        if (distance > maxdist or deltainfo.deltalen > textlen or
-            deltainfo.compresseddeltalen > textlen * 2 or
-            (self._maxchainlen and deltainfo.chainlen > self._maxchainlen)):
-            return False
-
-        return True
-
     def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
                      cachedelta, ifh, dfh, alwayscache=False,
                      deltacomputer=None):
@@ -2495,11 +1950,11 @@
           if both are set, they must correspond to each other.
         """
         if node == nullid:
-            raise RevlogError(_("%s: attempt to add null revision") %
-                              (self.indexfile))
+            raise error.RevlogError(_("%s: attempt to add null revision") %
+                                    self.indexfile)
         if node == wdirid or node in wdirfilenodeids:
-            raise RevlogError(_("%s: attempt to add wdir revision") %
-                              (self.indexfile))
+            raise error.RevlogError(_("%s: attempt to add wdir revision") %
+                                    self.indexfile)
 
         if self._inline:
             fh = ifh
@@ -2525,43 +1980,29 @@
             textlen = len(rawtext)
 
         if deltacomputer is None:
-            deltacomputer = _deltacomputer(self)
+            deltacomputer = deltautil.deltacomputer(self)
 
         revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
 
-        # no delta for flag processor revision (see "candelta" for why)
-        # not calling candelta since only one revision needs test, also to
-        # avoid overhead fetching flags again.
-        if flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
-            deltainfo = None
-        else:
-            deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
+        deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
 
-        if deltainfo is not None:
-            base = deltainfo.base
-            chainbase = deltainfo.chainbase
-            data = deltainfo.data
-            l = deltainfo.deltalen
-        else:
-            rawtext = deltacomputer.buildtext(revinfo, fh)
-            data = self.compress(rawtext)
-            l = len(data[1]) + len(data[0])
-            base = chainbase = curr
-
-        e = (offset_type(offset, flags), l, textlen,
-             base, link, p1r, p2r, node)
-        self.index.insert(-1, e)
+        e = (offset_type(offset, flags), deltainfo.deltalen, textlen,
+             deltainfo.base, link, p1r, p2r, node)
+        self.index.append(e)
         self.nodemap[node] = curr
 
         entry = self._io.packentry(e, self.node, self.version, curr)
-        self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
+        self._writeentry(transaction, ifh, dfh, entry, deltainfo.data,
+                         link, offset)
+
+        rawtext = btext[0]
 
         if alwayscache and rawtext is None:
-            rawtext = deltacomputer._buildtext(revinfo, fh)
+            rawtext = deltacomputer.buildtext(revinfo, fh)
 
         if type(rawtext) == bytes: # only accept immutable objects
             self._cache = (node, curr, rawtext)
-        self._chainbasecache[curr] = chainbase
+        self._chainbasecache[curr] = deltainfo.chainbase
         return node
 
     def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
@@ -2627,7 +2068,7 @@
                 dfh.flush()
             ifh.flush()
         try:
-            deltacomputer = _deltacomputer(self)
+            deltacomputer = deltautil.deltacomputer(self)
             # loop through our set of deltas
             for data in deltas:
                 node, p1, p2, linknode, deltabase, delta, flags = data
@@ -2642,12 +2083,12 @@
 
                 for p in (p1, p2):
                     if p not in self.nodemap:
-                        raise LookupError(p, self.indexfile,
-                                          _('unknown parent'))
+                        raise error.LookupError(p, self.indexfile,
+                                                _('unknown parent'))
 
                 if deltabase not in self.nodemap:
-                    raise LookupError(deltabase, self.indexfile,
-                                      _('unknown delta base'))
+                    raise error.LookupError(deltabase, self.indexfile,
+                                            _('unknown delta base'))
 
                 baserev = self.rev(deltabase)
 
@@ -2798,7 +2239,7 @@
         self._cache = None
         self._chaininfocache = {}
         self._chunkclear()
-        for x in xrange(rev, len(self)):
+        for x in pycompat.xrange(rev, len(self)):
             del self.nodemap[self.node(x)]
 
         del self.index[rev:-1]
@@ -2846,6 +2287,87 @@
             res.append(self.datafile)
         return res
 
+    def emitrevisiondeltas(self, requests):
+        frev = self.rev
+
+        prevrev = None
+        for request in requests:
+            node = request.node
+            rev = frev(node)
+
+            if prevrev is None:
+                prevrev = self.index[rev][5]
+
+            # Requesting a full revision.
+            if request.basenode == nullid:
+                baserev = nullrev
+            # Requesting an explicit revision.
+            elif request.basenode is not None:
+                baserev = frev(request.basenode)
+            # Allowing us to choose.
+            else:
+                p1rev, p2rev = self.parentrevs(rev)
+                deltaparentrev = self.deltaparent(rev)
+
+                # Avoid sending full revisions when delta parent is null. Pick
+                # prev in that case. It's tempting to pick p1 in this case, as
+                # p1 will be smaller in the common case. However, computing a
+                # delta against p1 may require resolving the raw text of p1,
+                # which could be expensive. The revlog caches should have prev
+                # cached, meaning less CPU for delta generation. There is
+                # likely room to add a flag and/or config option to control this
+                # behavior.
+                if deltaparentrev == nullrev and self._storedeltachains:
+                    baserev = prevrev
+
+                # Revlog is configured to use full snapshot for a reason.
+                # Stick to full snapshot.
+                elif deltaparentrev == nullrev:
+                    baserev = nullrev
+
+                # Pick previous when we can't be sure the base is available
+                # on consumer.
+                elif deltaparentrev not in (p1rev, p2rev, prevrev):
+                    baserev = prevrev
+                else:
+                    baserev = deltaparentrev
+
+                if baserev != nullrev and not self.candelta(baserev, rev):
+                    baserev = nullrev
+
+            revision = None
+            delta = None
+            baserevisionsize = None
+
+            if self.iscensored(baserev) or self.iscensored(rev):
+                try:
+                    revision = self.revision(node, raw=True)
+                except error.CensoredNodeError as e:
+                    revision = e.tombstone
+
+                if baserev != nullrev:
+                    baserevisionsize = self.rawsize(baserev)
+
+            elif baserev == nullrev:
+                revision = self.revision(node, raw=True)
+            else:
+                delta = self.revdiff(baserev, rev)
+
+            extraflags = REVIDX_ELLIPSIS if request.ellipsis else 0
+
+            yield revlogrevisiondelta(
+                node=node,
+                p1node=request.p1node,
+                p2node=request.p2node,
+                linknode=request.linknode,
+                basenode=self.node(baserev),
+                flags=self.flags(rev) | extraflags,
+                baserevisionsize=baserevisionsize,
+                revision=revision,
+                delta=delta)
+
+            prevrev = rev
+
     DELTAREUSEALWAYS = 'always'
     DELTAREUSESAMEREVS = 'samerevs'
     DELTAREUSENEVER = 'never'
@@ -2919,7 +2441,7 @@
             populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
                                                 self.DELTAREUSESAMEREVS)
 
-            deltacomputer = _deltacomputer(destrevlog)
+            deltacomputer = deltautil.deltacomputer(destrevlog)
             index = self.index
             for rev in self:
                 entry = index[rev]
@@ -2970,3 +2492,92 @@
         finally:
             destrevlog._lazydeltabase = oldlazydeltabase
             destrevlog._deltabothparents = oldamd
+
+    def censorrevision(self, node, tombstone=b''):
+        if (self.version & 0xFFFF) == REVLOGV0:
+            raise error.RevlogError(_('cannot censor with version %d revlogs') %
+                                    self.version)
+
+        rev = self.rev(node)
+        tombstone = packmeta({b'censored': tombstone}, b'')
+
+        if len(tombstone) > self.rawsize(rev):
+            raise error.Abort(_('censor tombstone must be no longer than '
+                                'censored data'))
+
+        # Using two files instead of one makes it easy to rewrite entry-by-entry
+        idxread = self.opener(self.indexfile, 'r')
+        idxwrite = self.opener(self.indexfile, 'wb', atomictemp=True)
+        if self.version & FLAG_INLINE_DATA:
+            dataread, datawrite = idxread, idxwrite
+        else:
+            dataread = self.opener(self.datafile, 'r')
+            datawrite = self.opener(self.datafile, 'wb', atomictemp=True)
+
+        # Copy all revlog data up to the entry to be censored.
+        offset = self.start(rev)
+
+        for chunk in util.filechunkiter(idxread, limit=rev * self._io.size):
+            idxwrite.write(chunk)
+        for chunk in util.filechunkiter(dataread, limit=offset):
+            datawrite.write(chunk)
+
+        def rewriteindex(r, newoffs, newdata=None):
+            """Rewrite the index entry with a new data offset and new data.
+
+            The newdata argument, if given, is a tuple of three positive
+            integers: (new compressed, new uncompressed, added flag bits).
+            """
+            offlags, comp, uncomp, base, link, p1, p2, nodeid = self.index[r]
+            flags = gettype(offlags)
+            if newdata:
+                comp, uncomp, nflags = newdata
+                flags |= nflags
+            offlags = offset_type(newoffs, flags)
+            e = (offlags, comp, uncomp, r, link, p1, p2, nodeid)
+            idxwrite.write(self._io.packentry(e, None, self.version, r))
+            idxread.seek(self._io.size, 1)
+
+        def rewrite(r, offs, data, nflags=REVIDX_DEFAULT_FLAGS):
+            """Write the given fulltext with the given data offset.
+
+            Returns:
+                The integer number of data bytes written, for tracking data
+                offsets.
+            """
+            flag, compdata = self.compress(data)
+            newcomp = len(flag) + len(compdata)
+            rewriteindex(r, offs, (newcomp, len(data), nflags))
+            datawrite.write(flag)
+            datawrite.write(compdata)
+            dataread.seek(self.length(r), 1)
+            return newcomp
+
+        # Rewrite censored entry with (padded) tombstone data.
+        pad = ' ' * (self.rawsize(rev) - len(tombstone))
+        offset += rewrite(rev, offset, tombstone + pad, REVIDX_ISCENSORED)
+
+        # Rewrite all following filelog revisions fixing up offsets and deltas.
+        for srev in pycompat.xrange(rev + 1, len(self)):
+            if rev in self.parentrevs(srev):
+                # Immediate children of censored node must be re-added as
+                # fulltext.
+                try:
+                    revdata = self.revision(srev)
+                except error.CensoredNodeError as e:
+                    revdata = e.tombstone
+                dlen = rewrite(srev, offset, revdata)
+            else:
+                # Copy any other revision data verbatim after fixing up the
+                # offset.
+                rewriteindex(srev, offset)
+                dlen = self.length(srev)
+                for chunk in util.filechunkiter(dataread, limit=dlen):
+                    datawrite.write(chunk)
+            offset += dlen
+
+        idxread.close()
+        idxwrite.close()
+        if dataread is not idxread:
+            dataread.close()
+            datawrite.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/revlogutils/constants.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,46 @@
+# revlogdeltas.py - constant used for revlog logic
+#
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+# Copyright 2018 Octobus <contact@octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""Helper class to compute deltas stored inside revlogs"""
+
+from __future__ import absolute_import
+
+from .. import (
+    util,
+)
+
+# revlog header flags
+REVLOGV0 = 0
+REVLOGV1 = 1
+# Dummy value until file format is finalized.
+# Reminder: change the bounds check in revlog.__init__ when this is changed.
+REVLOGV2 = 0xDEAD
+FLAG_INLINE_DATA = (1 << 16)
+FLAG_GENERALDELTA = (1 << 17)
+REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
+REVLOG_DEFAULT_FORMAT = REVLOGV1
+REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
+REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
+REVLOGV2_FLAGS = REVLOGV1_FLAGS
+
+# revlog index flags
+REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
+REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
+REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
+REVIDX_DEFAULT_FLAGS = 0
+# stable order in which flags need to be processed and their processors applied
+REVIDX_FLAGS_ORDER = [
+    REVIDX_ISCENSORED,
+    REVIDX_ELLIPSIS,
+    REVIDX_EXTSTORED,
+]
+REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
+# bitmark for flags that could cause rawdata content change
+REVIDX_RAWTEXT_CHANGING_FLAGS = REVIDX_ISCENSORED | REVIDX_EXTSTORED
+
+SPARSE_REVLOG_MAX_CHAIN_LENGTH = 1000
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/revlogutils/deltas.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,939 @@
+# revlogdeltas.py - Logic around delta computation for revlog
+#
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+# Copyright 2018 Octobus <contact@octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+"""Helper class to compute deltas stored inside revlogs"""
+
+from __future__ import absolute_import
+
+import collections
+import heapq
+import struct
+
+# import stuff from node for others to import from revlog
+from ..node import (
+    nullrev,
+)
+from ..i18n import _
+
+from .constants import (
+    REVIDX_ISCENSORED,
+    REVIDX_RAWTEXT_CHANGING_FLAGS,
+)
+
+from ..thirdparty import (
+    attr,
+)
+
+from .. import (
+    error,
+    mdiff,
+)
+
+# maximum <delta-chain-data>/<revision-text-length> ratio
+LIMIT_DELTA2TEXT = 2
+
+class _testrevlog(object):
+    """minimalist fake revlog to use in doctests"""
+
+    def __init__(self, data, density=0.5, mingap=0):
+        """data is an list of revision payload boundaries"""
+        self._data = data
+        self._srdensitythreshold = density
+        self._srmingapsize = mingap
+
+    def start(self, rev):
+        if rev == 0:
+            return 0
+        return self._data[rev - 1]
+
+    def end(self, rev):
+        return self._data[rev]
+
+    def length(self, rev):
+        return self.end(rev) - self.start(rev)
+
+    def __len__(self):
+        return len(self._data)
+
+def slicechunk(revlog, revs, deltainfo=None, targetsize=None):
+    """slice revs to reduce the amount of unrelated data to be read from disk.
+
+    ``revs`` is sliced into groups that should be read in one time.
+    Assume that revs are sorted.
+
+    The initial chunk is sliced until the overall density (payload/chunks-span
+    ratio) is above `revlog._srdensitythreshold`. No gap smaller than
+    `revlog._srmingapsize` is skipped.
+
+    If `targetsize` is set, no chunk larger than `targetsize` will be yield.
+    For consistency with other slicing choice, this limit won't go lower than
+    `revlog._srmingapsize`.
+
+    If individual revisions chunk are larger than this limit, they will still
+    be raised individually.
+
+    >>> revlog = _testrevlog([
+    ...  5,  #00 (5)
+    ...  10, #01 (5)
+    ...  12, #02 (2)
+    ...  12, #03 (empty)
+    ...  27, #04 (15)
+    ...  31, #05 (4)
+    ...  31, #06 (empty)
+    ...  42, #07 (11)
+    ...  47, #08 (5)
+    ...  47, #09 (empty)
+    ...  48, #10 (1)
+    ...  51, #11 (3)
+    ...  74, #12 (23)
+    ...  85, #13 (11)
+    ...  86, #14 (1)
+    ...  91, #15 (5)
+    ... ])
+
+    >>> list(slicechunk(revlog, list(range(16))))
+    [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
+    >>> list(slicechunk(revlog, [0, 15]))
+    [[0], [15]]
+    >>> list(slicechunk(revlog, [0, 11, 15]))
+    [[0], [11], [15]]
+    >>> list(slicechunk(revlog, [0, 11, 13, 15]))
+    [[0], [11, 13, 15]]
+    >>> list(slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
+    [[1, 2], [5, 8, 10, 11], [14]]
+
+    Slicing with a maximum chunk size
+    >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
+    [[0], [11], [13], [15]]
+    >>> list(slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
+    [[0], [11], [13, 15]]
+    """
+    if targetsize is not None:
+        targetsize = max(targetsize, revlog._srmingapsize)
+    # targetsize should not be specified when evaluating delta candidates:
+    # * targetsize is used to ensure we stay within specification when reading,
+    # * deltainfo is used to pick are good delta chain when writing.
+    if not (deltainfo is None or targetsize is None):
+        msg = 'cannot use `targetsize` with a `deltainfo`'
+        raise error.ProgrammingError(msg)
+    for chunk in _slicechunktodensity(revlog, revs,
+                                      deltainfo,
+                                      revlog._srdensitythreshold,
+                                      revlog._srmingapsize):
+        for subchunk in _slicechunktosize(revlog, chunk, targetsize):
+            yield subchunk
+
+def _slicechunktosize(revlog, revs, targetsize=None):
+    """slice revs to match the target size
+
+    This is intended to be used on chunk that density slicing selected by that
+    are still too large compared to the read garantee of revlog. This might
+    happens when "minimal gap size" interrupted the slicing or when chain are
+    built in a way that create large blocks next to each other.
+
+    >>> revlog = _testrevlog([
+    ...  3,  #0 (3)
+    ...  5,  #1 (2)
+    ...  6,  #2 (1)
+    ...  8,  #3 (2)
+    ...  8,  #4 (empty)
+    ...  11, #5 (3)
+    ...  12, #6 (1)
+    ...  13, #7 (1)
+    ...  14, #8 (1)
+    ... ])
+
+    Cases where chunk is already small enough
+    >>> list(_slicechunktosize(revlog, [0], 3))
+    [[0]]
+    >>> list(_slicechunktosize(revlog, [6, 7], 3))
+    [[6, 7]]
+    >>> list(_slicechunktosize(revlog, [0], None))
+    [[0]]
+    >>> list(_slicechunktosize(revlog, [6, 7], None))
+    [[6, 7]]
+
+    cases where we need actual slicing
+    >>> list(_slicechunktosize(revlog, [0, 1], 3))
+    [[0], [1]]
+    >>> list(_slicechunktosize(revlog, [1, 3], 3))
+    [[1], [3]]
+    >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
+    [[1, 2], [3]]
+    >>> list(_slicechunktosize(revlog, [3, 5], 3))
+    [[3], [5]]
+    >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
+    [[3], [5]]
+    >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
+    [[5], [6, 7, 8]]
+    >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
+    [[0], [1, 2], [3], [5], [6, 7, 8]]
+
+    Case with too large individual chunk (must return valid chunk)
+    >>> list(_slicechunktosize(revlog, [0, 1], 2))
+    [[0], [1]]
+    >>> list(_slicechunktosize(revlog, [1, 3], 1))
+    [[1], [3]]
+    >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
+    [[3], [5]]
+    """
+    assert targetsize is None or 0 <= targetsize
+    if targetsize is None or segmentspan(revlog, revs) <= targetsize:
+        yield revs
+        return
+
+    startrevidx = 0
+    startdata = revlog.start(revs[0])
+    endrevidx = 0
+    iterrevs = enumerate(revs)
+    next(iterrevs) # skip first rev.
+    for idx, r in iterrevs:
+        span = revlog.end(r) - startdata
+        if span <= targetsize:
+            endrevidx = idx
+        else:
+            chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
+            if chunk:
+                yield chunk
+            startrevidx = idx
+            startdata = revlog.start(r)
+            endrevidx = idx
+    yield _trimchunk(revlog, revs, startrevidx)
+
+def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
+                         mingapsize=0):
+    """slice revs to reduce the amount of unrelated data to be read from disk.
+
+    ``revs`` is sliced into groups that should be read in one time.
+    Assume that revs are sorted.
+
+    ``deltainfo`` is a _deltainfo instance of a revision that we would append
+    to the top of the revlog.
+
+    The initial chunk is sliced until the overall density (payload/chunks-span
+    ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
+    skipped.
+
+    >>> revlog = _testrevlog([
+    ...  5,  #00 (5)
+    ...  10, #01 (5)
+    ...  12, #02 (2)
+    ...  12, #03 (empty)
+    ...  27, #04 (15)
+    ...  31, #05 (4)
+    ...  31, #06 (empty)
+    ...  42, #07 (11)
+    ...  47, #08 (5)
+    ...  47, #09 (empty)
+    ...  48, #10 (1)
+    ...  51, #11 (3)
+    ...  74, #12 (23)
+    ...  85, #13 (11)
+    ...  86, #14 (1)
+    ...  91, #15 (5)
+    ... ])
+
+    >>> list(_slicechunktodensity(revlog, list(range(16))))
+    [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
+    >>> list(_slicechunktodensity(revlog, [0, 15]))
+    [[0], [15]]
+    >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
+    [[0], [11], [15]]
+    >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
+    [[0], [11, 13, 15]]
+    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
+    [[1, 2], [5, 8, 10, 11], [14]]
+    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
+    ...                           mingapsize=20))
+    [[1, 2, 3, 5, 8, 10, 11], [14]]
+    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
+    ...                           targetdensity=0.95))
+    [[1, 2], [5], [8, 10, 11], [14]]
+    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
+    ...                           targetdensity=0.95, mingapsize=12))
+    [[1, 2], [5, 8, 10, 11], [14]]
+    """
+    start = revlog.start
+    length = revlog.length
+
+    if len(revs) <= 1:
+        yield revs
+        return
+
+    nextrev = len(revlog)
+    nextoffset = revlog.end(nextrev - 1)
+
+    if deltainfo is None:
+        deltachainspan = segmentspan(revlog, revs)
+        chainpayload = sum(length(r) for r in revs)
+    else:
+        deltachainspan = deltainfo.distance
+        chainpayload = deltainfo.compresseddeltalen
+
+    if deltachainspan < mingapsize:
+        yield revs
+        return
+
+    readdata = deltachainspan
+
+    if deltachainspan:
+        density = chainpayload / float(deltachainspan)
+    else:
+        density = 1.0
+
+    if density >= targetdensity:
+        yield revs
+        return
+
+    if deltainfo is not None and deltainfo.deltalen:
+        revs = list(revs)
+        revs.append(nextrev)
+
+    # Store the gaps in a heap to have them sorted by decreasing size
+    gapsheap = []
+    heapq.heapify(gapsheap)
+    prevend = None
+    for i, rev in enumerate(revs):
+        if rev < nextrev:
+            revstart = start(rev)
+            revlen = length(rev)
+        else:
+            revstart = nextoffset
+            revlen = deltainfo.deltalen
+
+        # Skip empty revisions to form larger holes
+        if revlen == 0:
+            continue
+
+        if prevend is not None:
+            gapsize = revstart - prevend
+            # only consider holes that are large enough
+            if gapsize > mingapsize:
+                heapq.heappush(gapsheap, (-gapsize, i))
+
+        prevend = revstart + revlen
+
+    # Collect the indices of the largest holes until the density is acceptable
+    indicesheap = []
+    heapq.heapify(indicesheap)
+    while gapsheap and density < targetdensity:
+        oppgapsize, gapidx = heapq.heappop(gapsheap)
+
+        heapq.heappush(indicesheap, gapidx)
+
+        # the gap sizes are stored as negatives to be sorted decreasingly
+        # by the heap
+        readdata -= (-oppgapsize)
+        if readdata > 0:
+            density = chainpayload / float(readdata)
+        else:
+            density = 1.0
+
+    # Cut the revs at collected indices
+    previdx = 0
+    while indicesheap:
+        idx = heapq.heappop(indicesheap)
+
+        chunk = _trimchunk(revlog, revs, previdx, idx)
+        if chunk:
+            yield chunk
+
+        previdx = idx
+
+    chunk = _trimchunk(revlog, revs, previdx)
+    if chunk:
+        yield chunk
+
+def _trimchunk(revlog, revs, startidx, endidx=None):
+    """returns revs[startidx:endidx] without empty trailing revs
+
+    Doctest Setup
+    >>> revlog = _testrevlog([
+    ...  5,  #0
+    ...  10, #1
+    ...  12, #2
+    ...  12, #3 (empty)
+    ...  17, #4
+    ...  21, #5
+    ...  21, #6 (empty)
+    ... ])
+
+    Contiguous cases:
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
+    [0, 1, 2, 3, 4, 5]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
+    [0, 1, 2, 3, 4]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
+    [0, 1, 2]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
+    [2]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
+    [3, 4, 5]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
+    [3, 4]
+
+    Discontiguous cases:
+    >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
+    [1, 3, 5]
+    >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
+    [1]
+    >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
+    [3, 5]
+    >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
+    [3, 5]
+    """
+    length = revlog.length
+
+    if endidx is None:
+        endidx = len(revs)
+
+    # If we have a non-emtpy delta candidate, there are nothing to trim
+    if revs[endidx - 1] < len(revlog):
+        # Trim empty revs at the end, except the very first revision of a chain
+        while (endidx > 1
+                and endidx > startidx
+                and length(revs[endidx - 1]) == 0):
+            endidx -= 1
+
+    return revs[startidx:endidx]
+
+def segmentspan(revlog, revs, deltainfo=None):
+    """Get the byte span of a segment of revisions
+
+    revs is a sorted array of revision numbers
+
+    >>> revlog = _testrevlog([
+    ...  5,  #0
+    ...  10, #1
+    ...  12, #2
+    ...  12, #3 (empty)
+    ...  17, #4
+    ... ])
+
+    >>> segmentspan(revlog, [0, 1, 2, 3, 4])
+    17
+    >>> segmentspan(revlog, [0, 4])
+    17
+    >>> segmentspan(revlog, [3, 4])
+    5
+    >>> segmentspan(revlog, [1, 2, 3,])
+    7
+    >>> segmentspan(revlog, [1, 3])
+    7
+    """
+    if not revs:
+        return 0
+    if deltainfo is not None and len(revlog) <= revs[-1]:
+        if len(revs) == 1:
+            return deltainfo.deltalen
+        offset = revlog.end(len(revlog) - 1)
+        end = deltainfo.deltalen + offset
+    else:
+        end = revlog.end(revs[-1])
+    return end - revlog.start(revs[0])
+
+def _textfromdelta(fh, revlog, baserev, delta, p1, p2, flags, expectednode):
+    """build full text from a (base, delta) pair and other metadata"""
+    # special case deltas which replace entire base; no need to decode
+    # base revision. this neatly avoids censored bases, which throw when
+    # they're decoded.
+    hlen = struct.calcsize(">lll")
+    if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
+                                               len(delta) - hlen):
+        fulltext = delta[hlen:]
+    else:
+        # deltabase is rawtext before changed by flag processors, which is
+        # equivalent to non-raw text
+        basetext = revlog.revision(baserev, _df=fh, raw=False)
+        fulltext = mdiff.patch(basetext, delta)
+
+    try:
+        res = revlog._processflags(fulltext, flags, 'read', raw=True)
+        fulltext, validatehash = res
+        if validatehash:
+            revlog.checkhash(fulltext, expectednode, p1=p1, p2=p2)
+        if flags & REVIDX_ISCENSORED:
+            raise error.StorageError(_('node %s is not censored') %
+                                     expectednode)
+    except error.CensoredNodeError:
+        # must pass the censored index flag to add censored revisions
+        if not flags & REVIDX_ISCENSORED:
+            raise
+    return fulltext
+
+@attr.s(slots=True, frozen=True)
+class _deltainfo(object):
+    distance = attr.ib()
+    deltalen = attr.ib()
+    data = attr.ib()
+    base = attr.ib()
+    chainbase = attr.ib()
+    chainlen = attr.ib()
+    compresseddeltalen = attr.ib()
+    snapshotdepth = attr.ib()
+
+def isgooddeltainfo(revlog, deltainfo, revinfo):
+    """Returns True if the given delta is good. Good means that it is within
+    the disk span, disk size, and chain length bounds that we know to be
+    performant."""
+    if deltainfo is None:
+        return False
+
+    # - 'deltainfo.distance' is the distance from the base revision --
+    #   bounding it limits the amount of I/O we need to do.
+    # - 'deltainfo.compresseddeltalen' is the sum of the total size of
+    #   deltas we need to apply -- bounding it limits the amount of CPU
+    #   we consume.
+
+    if revlog._sparserevlog:
+        # As sparse-read will be used, we can consider that the distance,
+        # instead of being the span of the whole chunk,
+        # is the span of the largest read chunk
+        base = deltainfo.base
+
+        if base != nullrev:
+            deltachain = revlog._deltachain(base)[0]
+        else:
+            deltachain = []
+
+        # search for the first non-snapshot revision
+        for idx, r in enumerate(deltachain):
+            if not revlog.issnapshot(r):
+                break
+        deltachain = deltachain[idx:]
+        chunks = slicechunk(revlog, deltachain, deltainfo)
+        all_span = [segmentspan(revlog, revs, deltainfo)
+                    for revs in chunks]
+        distance = max(all_span)
+    else:
+        distance = deltainfo.distance
+
+    textlen = revinfo.textlen
+    defaultmax = textlen * 4
+    maxdist = revlog._maxdeltachainspan
+    if not maxdist:
+        maxdist = distance # ensure the conditional pass
+    maxdist = max(maxdist, defaultmax)
+    if revlog._sparserevlog and maxdist < revlog._srmingapsize:
+        # In multiple place, we are ignoring irrelevant data range below a
+        # certain size. Be also apply this tradeoff here and relax span
+        # constraint for small enought content.
+        maxdist = revlog._srmingapsize
+
+    # Bad delta from read span:
+    #
+    #   If the span of data read is larger than the maximum allowed.
+    if maxdist < distance:
+        return False
+
+    # Bad delta from new delta size:
+    #
+    #   If the delta size is larger than the target text, storing the
+    #   delta will be inefficient.
+    if textlen < deltainfo.deltalen:
+        return False
+
+    # Bad delta from cumulated payload size:
+    #
+    #   If the sum of delta get larger than K * target text length.
+    if textlen * LIMIT_DELTA2TEXT < deltainfo.compresseddeltalen:
+        return False
+
+    # Bad delta from chain length:
+    #
+    #   If the number of delta in the chain gets too high.
+    if (revlog._maxchainlen
+            and revlog._maxchainlen < deltainfo.chainlen):
+        return False
+
+    # bad delta from intermediate snapshot size limit
+    #
+    #   If an intermediate snapshot size is higher than the limit.  The
+    #   limit exist to prevent endless chain of intermediate delta to be
+    #   created.
+    if (deltainfo.snapshotdepth is not None and
+            (textlen >> deltainfo.snapshotdepth) < deltainfo.deltalen):
+        return False
+
+    # bad delta if new intermediate snapshot is larger than the previous
+    # snapshot
+    if (deltainfo.snapshotdepth
+            and revlog.length(deltainfo.base) < deltainfo.deltalen):
+        return False
+
+    return True
+
+def _candidategroups(revlog, textlen, p1, p2, cachedelta):
+    """Provides group of revision to be tested as delta base
+
+    This top level function focus on emitting groups with unique and worthwhile
+    content. See _raw_candidate_groups for details about the group order.
+    """
+    # should we try to build a delta?
+    if not (len(revlog) and revlog._storedeltachains):
+        yield None
+        return
+
+    deltalength = revlog.length
+    deltaparent = revlog.deltaparent
+    good = None
+
+    deltas_limit = textlen * LIMIT_DELTA2TEXT
+
+    tested = set([nullrev])
+    candidates = _refinedgroups(revlog, p1, p2, cachedelta)
+    while True:
+        temptative = candidates.send(good)
+        if temptative is None:
+            break
+        group = []
+        for rev in temptative:
+            # skip over empty delta (no need to include them in a chain)
+            while not (rev == nullrev or rev in tested or deltalength(rev)):
+                tested.add(rev)
+                rev = deltaparent(rev)
+            # filter out revision we tested already
+            if rev in tested:
+                continue
+            tested.add(rev)
+            # filter out delta base that will never produce good delta
+            if deltas_limit < revlog.length(rev):
+                continue
+            # no need to try a delta against nullrev, this will be done as a
+            # last resort.
+            if rev == nullrev:
+                continue
+            # no delta for rawtext-changing revs (see "candelta" for why)
+            if revlog.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
+                continue
+            group.append(rev)
+        if group:
+            # XXX: in the sparse revlog case, group can become large,
+            #      impacting performances. Some bounding or slicing mecanism
+            #      would help to reduce this impact.
+            good = yield tuple(group)
+    yield None
+
+def _findsnapshots(revlog, cache, start_rev):
+    """find snapshot from start_rev to tip"""
+    deltaparent = revlog.deltaparent
+    issnapshot = revlog.issnapshot
+    for rev in revlog.revs(start_rev):
+        if issnapshot(rev):
+            cache[deltaparent(rev)].append(rev)
+
+def _refinedgroups(revlog, p1, p2, cachedelta):
+    good = None
+    # First we try to reuse a the delta contained in the bundle.
+    # (or from the source revlog)
+    #
+    # This logic only applies to general delta repositories and can be disabled
+    # through configuration. Disabling reuse source delta is useful when
+    # we want to make sure we recomputed "optimal" deltas.
+    if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
+        # Assume what we received from the server is a good choice
+        # build delta will reuse the cache
+        good = yield (cachedelta[0],)
+        if good is not None:
+            yield None
+            return
+    for candidates in _rawgroups(revlog, p1, p2, cachedelta):
+        good = yield candidates
+        if good is not None:
+            break
+
+    # if we have a refinable value, try to refine it
+    if good is not None and good not in (p1, p2) and revlog.issnapshot(good):
+        # refine snapshot down
+        previous = None
+        while previous != good:
+            previous = good
+            base = revlog.deltaparent(good)
+            if base == nullrev:
+                break
+            good = yield (base,)
+        # refine snapshot up
+        #
+        # XXX the _findsnapshots call can be expensive and is "duplicated" with
+        # the one done in `_rawgroups`. Once we start working on performance,
+        # we should make the two logics share this computation.
+        snapshots = collections.defaultdict(list)
+        _findsnapshots(revlog, snapshots, good + 1)
+        previous = None
+        while good != previous:
+            previous = good
+            children = tuple(sorted(c for c in snapshots[good]))
+            good = yield children
+
+    # we have found nothing
+    yield None
+
+def _rawgroups(revlog, p1, p2, cachedelta):
+    """Provides group of revision to be tested as delta base
+
+    This lower level function focus on emitting delta theorically interresting
+    without looking it any practical details.
+
+    The group order aims at providing fast or small candidates first.
+    """
+    gdelta = revlog._generaldelta
+    sparse = revlog._sparserevlog
+    curr = len(revlog)
+    prev = curr - 1
+    deltachain = lambda rev: revlog._deltachain(rev)[0]
+
+    if gdelta:
+        # exclude already lazy tested base if any
+        parents = [p for p in (p1, p2) if p != nullrev]
+
+        if not revlog._deltabothparents and len(parents) == 2:
+            parents.sort()
+            # To minimize the chance of having to build a fulltext,
+            # pick first whichever parent is closest to us (max rev)
+            yield (parents[1],)
+            # then the other one (min rev) if the first did not fit
+            yield (parents[0],)
+        elif len(parents) > 0:
+            # Test all parents (1 or 2), and keep the best candidate
+            yield parents
+
+    if sparse and parents:
+        snapshots = collections.defaultdict(list) # map: base-rev: snapshot-rev
+        # See if we can use an existing snapshot in the parent chains to use as
+        # a base for a new intermediate-snapshot
+        #
+        # search for snapshot in parents delta chain
+        # map: snapshot-level: snapshot-rev
+        parents_snaps = collections.defaultdict(set)
+        candidate_chains = [deltachain(p) for p in parents]
+        for chain in candidate_chains:
+            for idx, s in enumerate(chain):
+                if not revlog.issnapshot(s):
+                    break
+                parents_snaps[idx].add(s)
+        snapfloor = min(parents_snaps[0]) + 1
+        _findsnapshots(revlog, snapshots, snapfloor)
+        # search for the highest "unrelated" revision
+        #
+        # Adding snapshots used by "unrelated" revision increase the odd we
+        # reuse an independant, yet better snapshot chain.
+        #
+        # XXX instead of building a set of revisions, we could lazily enumerate
+        # over the chains. That would be more efficient, however we stick to
+        # simple code for now.
+        all_revs = set()
+        for chain in candidate_chains:
+            all_revs.update(chain)
+        other = None
+        for r in revlog.revs(prev, snapfloor):
+            if r not in all_revs:
+                other = r
+                break
+        if other is not None:
+            # To avoid unfair competition, we won't use unrelated intermediate
+            # snapshot that are deeper than the ones from the parent delta
+            # chain.
+            max_depth = max(parents_snaps.keys())
+            chain = deltachain(other)
+            for idx, s in enumerate(chain):
+                if s < snapfloor:
+                    continue
+                if max_depth < idx:
+                    break
+                if not revlog.issnapshot(s):
+                    break
+                parents_snaps[idx].add(s)
+        # Test them as possible intermediate snapshot base
+        # We test them from highest to lowest level. High level one are more
+        # likely to result in small delta
+        floor = None
+        for idx, snaps in sorted(parents_snaps.items(), reverse=True):
+            siblings = set()
+            for s in snaps:
+                siblings.update(snapshots[s])
+            # Before considering making a new intermediate snapshot, we check
+            # if an existing snapshot, children of base we consider, would be
+            # suitable.
+            #
+            # It give a change to reuse a delta chain "unrelated" to the
+            # current revision instead of starting our own. Without such
+            # re-use, topological branches would keep reopening new chains.
+            # Creating more and more snapshot as the repository grow.
+
+            if floor is not None:
+                # We only do this for siblings created after the one in our
+                # parent's delta chain. Those created before has less chances
+                # to be valid base since our ancestors had to create a new
+                # snapshot.
+                siblings = [r for r in siblings if floor < r]
+            yield tuple(sorted(siblings))
+            # then test the base from our parent's delta chain.
+            yield tuple(sorted(snaps))
+            floor = min(snaps)
+        # No suitable base found in the parent chain, search if any full
+        # snapshots emitted since parent's base would be a suitable base for an
+        # intermediate snapshot.
+        #
+        # It give a chance to reuse a delta chain unrelated to the current
+        # revisions instead of starting our own. Without such re-use,
+        # topological branches would keep reopening new full chains. Creating
+        # more and more snapshot as the repository grow.
+        yield tuple(snapshots[nullrev])
+
+    if not sparse:
+        # other approach failed try against prev to hopefully save us a
+        # fulltext.
+        yield (prev,)
+
+class deltacomputer(object):
+    def __init__(self, revlog):
+        self.revlog = revlog
+
+    def buildtext(self, revinfo, fh):
+        """Builds a fulltext version of a revision
+
+        revinfo: _revisioninfo instance that contains all needed info
+        fh:      file handle to either the .i or the .d revlog file,
+                 depending on whether it is inlined or not
+        """
+        btext = revinfo.btext
+        if btext[0] is not None:
+            return btext[0]
+
+        revlog = self.revlog
+        cachedelta = revinfo.cachedelta
+        baserev = cachedelta[0]
+        delta = cachedelta[1]
+
+        fulltext = btext[0] = _textfromdelta(fh, revlog, baserev, delta,
+                                             revinfo.p1, revinfo.p2,
+                                             revinfo.flags, revinfo.node)
+        return fulltext
+
+    def _builddeltadiff(self, base, revinfo, fh):
+        revlog = self.revlog
+        t = self.buildtext(revinfo, fh)
+        if revlog.iscensored(base):
+            # deltas based on a censored revision must replace the
+            # full content in one patch, so delta works everywhere
+            header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
+            delta = header + t
+        else:
+            ptext = revlog.revision(base, _df=fh, raw=True)
+            delta = mdiff.textdiff(ptext, t)
+
+        return delta
+
+    def _builddeltainfo(self, revinfo, base, fh):
+        # can we use the cached delta?
+        delta = None
+        if revinfo.cachedelta:
+            cachebase, cachediff = revinfo.cachedelta
+            #check if the diff still apply
+            currentbase = cachebase
+            while (currentbase != nullrev
+                    and currentbase != base
+                    and self.revlog.length(currentbase) == 0):
+                currentbase = self.revlog.deltaparent(currentbase)
+            if currentbase == base:
+                delta = revinfo.cachedelta[1]
+        if delta is None:
+            delta = self._builddeltadiff(base, revinfo, fh)
+        revlog = self.revlog
+        header, data = revlog.compress(delta)
+        deltalen = len(header) + len(data)
+        chainbase = revlog.chainbase(base)
+        offset = revlog.end(len(revlog) - 1)
+        dist = deltalen + offset - revlog.start(chainbase)
+        if revlog._generaldelta:
+            deltabase = base
+        else:
+            deltabase = chainbase
+        chainlen, compresseddeltalen = revlog._chaininfo(base)
+        chainlen += 1
+        compresseddeltalen += deltalen
+
+        revlog = self.revlog
+        snapshotdepth = None
+        if deltabase == nullrev:
+            snapshotdepth = 0
+        elif revlog._sparserevlog and revlog.issnapshot(deltabase):
+            # A delta chain should always be one full snapshot,
+            # zero or more semi-snapshots, and zero or more deltas
+            p1, p2 = revlog.rev(revinfo.p1), revlog.rev(revinfo.p2)
+            if deltabase not in (p1, p2) and revlog.issnapshot(deltabase):
+                snapshotdepth = len(revlog._deltachain(deltabase)[0])
+
+        return _deltainfo(dist, deltalen, (header, data), deltabase,
+                          chainbase, chainlen, compresseddeltalen,
+                          snapshotdepth)
+
+    def _fullsnapshotinfo(self, fh, revinfo):
+        curr = len(self.revlog)
+        rawtext = self.buildtext(revinfo, fh)
+        data = self.revlog.compress(rawtext)
+        compresseddeltalen = deltalen = dist = len(data[1]) + len(data[0])
+        deltabase = chainbase = curr
+        snapshotdepth = 0
+        chainlen = 1
+
+        return _deltainfo(dist, deltalen, data, deltabase,
+                          chainbase, chainlen, compresseddeltalen,
+                          snapshotdepth)
+
+    def finddeltainfo(self, revinfo, fh):
+        """Find an acceptable delta against a candidate revision
+
+        revinfo: information about the revision (instance of _revisioninfo)
+        fh:      file handle to either the .i or the .d revlog file,
+                 depending on whether it is inlined or not
+
+        Returns the first acceptable candidate revision, as ordered by
+        _candidategroups
+
+        If no suitable deltabase is found, we return delta info for a full
+        snapshot.
+        """
+        if not revinfo.textlen:
+            return self._fullsnapshotinfo(fh, revinfo)
+
+        # no delta for flag processor revision (see "candelta" for why)
+        # not calling candelta since only one revision needs test, also to
+        # avoid overhead fetching flags again.
+        if revinfo.flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
+            return self._fullsnapshotinfo(fh, revinfo)
+
+        cachedelta = revinfo.cachedelta
+        p1 = revinfo.p1
+        p2 = revinfo.p2
+        revlog = self.revlog
+
+        deltainfo = None
+        p1r, p2r = revlog.rev(p1), revlog.rev(p2)
+        groups = _candidategroups(self.revlog, revinfo.textlen,
+                                             p1r, p2r, cachedelta)
+        candidaterevs = next(groups)
+        while candidaterevs is not None:
+            nominateddeltas = []
+            if deltainfo is not None:
+                # if we already found a good delta,
+                # challenge it against refined candidates
+                nominateddeltas.append(deltainfo)
+            for candidaterev in candidaterevs:
+                candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
+                if isgooddeltainfo(self.revlog, candidatedelta, revinfo):
+                    nominateddeltas.append(candidatedelta)
+            if nominateddeltas:
+                deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
+            if deltainfo is not None:
+                candidaterevs = groups.send(deltainfo.base)
+            else:
+                candidaterevs = next(groups)
+
+        if deltainfo is None:
+            deltainfo = self._fullsnapshotinfo(fh, revinfo)
+        return deltainfo
--- a/mercurial/revset.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/revset.py	Wed Sep 26 20:33:09 2018 +0900
@@ -242,7 +242,7 @@
 
 def listset(repo, subset, *xs, **opts):
     raise error.ParseError(_("can't use a list in this context"),
-                           hint=_('see hg help "revsets.x or y"'))
+                           hint=_('see \'hg help "revsets.x or y"\''))
 
 def keyvaluepair(repo, subset, k, v, order):
     raise error.ParseError(_("can't use a key-value pair in this context"))
@@ -454,6 +454,8 @@
         kind, pattern, matcher = stringutil.stringmatcher(bm)
         bms = set()
         if kind == 'literal':
+            if bm == pattern:
+                pattern = repo._bookmarks.expandname(pattern)
             bmrev = repo._bookmarks.get(pattern, None)
             if not bmrev:
                 raise error.RepoLookupError(_("bookmark '%s' does not exist")
@@ -1558,6 +1560,12 @@
     """helper to select all rev in <targets> phases"""
     return repo._phasecache.getrevset(repo, targets, subset)
 
+@predicate('_phase(idx)', safe=True)
+def phase(repo, subset, x):
+    l = getargs(x, 1, 1, ("_phase requires one argument"))
+    target = getinteger(l[0], ("_phase expects a number"))
+    return _phase(repo, subset, target)
+
 @predicate('draft()', safe=True)
 def draft(repo, subset, x):
     """Changeset in draft phase."""
--- a/mercurial/revsetlang.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/revsetlang.py	Wed Sep 26 20:33:09 2018 +0900
@@ -63,7 +63,7 @@
 _syminitletters = set(pycompat.iterbytestr(
     string.ascii_letters.encode('ascii') +
     string.digits.encode('ascii') +
-    '._@')) | set(map(pycompat.bytechr, xrange(128, 256)))
+    '._@')) | set(map(pycompat.bytechr, pycompat.xrange(128, 256)))
 
 # default set of valid characters for non-initial letters of symbols
 _symletters = _syminitletters | set(pycompat.iterbytestr('-/'))
@@ -177,7 +177,7 @@
                         if p: # possible consecutive -
                             yield ('symbol', p, s)
                         s += len(p)
-                        yield ('-', None, pos)
+                        yield ('-', None, s)
                         s += 1
                     if parts[-1]: # possible trailing -
                         yield ('symbol', parts[-1], s)
--- a/mercurial/scmutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/scmutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -34,6 +34,7 @@
     obsutil,
     pathutil,
     phases,
+    policy,
     pycompat,
     revsetlang,
     similar,
@@ -52,6 +53,8 @@
 else:
     from . import scmposix as scmplatform
 
+parsers = policy.importmod(r'parsers')
+
 termsize = scmplatform.termsize
 
 class status(tuple):
@@ -169,64 +172,64 @@
             reason = _('timed out waiting for lock held by %r') % inst.locker
         else:
             reason = _('lock held by %r') % inst.locker
-        ui.warn(_("abort: %s: %s\n")
-                % (inst.desc or stringutil.forcebytestr(inst.filename), reason))
+        ui.error(_("abort: %s: %s\n") % (
+            inst.desc or stringutil.forcebytestr(inst.filename), reason))
         if not inst.locker:
-            ui.warn(_("(lock might be very busy)\n"))
+            ui.error(_("(lock might be very busy)\n"))
     except error.LockUnavailable as inst:
-        ui.warn(_("abort: could not lock %s: %s\n") %
-                (inst.desc or stringutil.forcebytestr(inst.filename),
-                 encoding.strtolocal(inst.strerror)))
+        ui.error(_("abort: could not lock %s: %s\n") %
+                 (inst.desc or stringutil.forcebytestr(inst.filename),
+                  encoding.strtolocal(inst.strerror)))
     except error.OutOfBandError as inst:
         if inst.args:
             msg = _("abort: remote error:\n")
         else:
             msg = _("abort: remote error\n")
-        ui.warn(msg)
+        ui.error(msg)
         if inst.args:
-            ui.warn(''.join(inst.args))
+            ui.error(''.join(inst.args))
         if inst.hint:
-            ui.warn('(%s)\n' % inst.hint)
+            ui.error('(%s)\n' % inst.hint)
     except error.RepoError as inst:
-        ui.warn(_("abort: %s!\n") % inst)
+        ui.error(_("abort: %s!\n") % inst)
         if inst.hint:
-            ui.warn(_("(%s)\n") % inst.hint)
+            ui.error(_("(%s)\n") % inst.hint)
     except error.ResponseError as inst:
-        ui.warn(_("abort: %s") % inst.args[0])
+        ui.error(_("abort: %s") % inst.args[0])
         msg = inst.args[1]
         if isinstance(msg, type(u'')):
             msg = pycompat.sysbytes(msg)
         if not isinstance(msg, bytes):
-            ui.warn(" %r\n" % (msg,))
+            ui.error(" %r\n" % (msg,))
         elif not msg:
-            ui.warn(_(" empty string\n"))
+            ui.error(_(" empty string\n"))
         else:
-            ui.warn("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
+            ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
     except error.CensoredNodeError as inst:
-        ui.warn(_("abort: file censored %s!\n") % inst)
-    except error.RevlogError as inst:
-        ui.warn(_("abort: %s!\n") % inst)
+        ui.error(_("abort: file censored %s!\n") % inst)
+    except error.StorageError as inst:
+        ui.error(_("abort: %s!\n") % inst)
     except error.InterventionRequired as inst:
-        ui.warn("%s\n" % inst)
+        ui.error("%s\n" % inst)
         if inst.hint:
-            ui.warn(_("(%s)\n") % inst.hint)
+            ui.error(_("(%s)\n") % inst.hint)
         return 1
     except error.WdirUnsupported:
-        ui.warn(_("abort: working directory revision cannot be specified\n"))
+        ui.error(_("abort: working directory revision cannot be specified\n"))
     except error.Abort as inst:
-        ui.warn(_("abort: %s\n") % inst)
+        ui.error(_("abort: %s\n") % inst)
         if inst.hint:
-            ui.warn(_("(%s)\n") % inst.hint)
+            ui.error(_("(%s)\n") % inst.hint)
     except ImportError as inst:
-        ui.warn(_("abort: %s!\n") % stringutil.forcebytestr(inst))
+        ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
         m = stringutil.forcebytestr(inst).split()[-1]
         if m in "mpatch bdiff".split():
-            ui.warn(_("(did you forget to compile extensions?)\n"))
+            ui.error(_("(did you forget to compile extensions?)\n"))
         elif m in "zlib".split():
-            ui.warn(_("(is your Python install correct?)\n"))
+            ui.error(_("(is your Python install correct?)\n"))
     except IOError as inst:
         if util.safehasattr(inst, "code"):
-            ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst))
+            ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
         elif util.safehasattr(inst, "reason"):
             try: # usually it is in the form (errno, strerror)
                 reason = inst.reason.args[1]
@@ -236,34 +239,34 @@
             if isinstance(reason, pycompat.unicode):
                 # SSLError of Python 2.7.9 contains a unicode
                 reason = encoding.unitolocal(reason)
-            ui.warn(_("abort: error: %s\n") % reason)
+            ui.error(_("abort: error: %s\n") % reason)
         elif (util.safehasattr(inst, "args")
               and inst.args and inst.args[0] == errno.EPIPE):
             pass
         elif getattr(inst, "strerror", None):
             if getattr(inst, "filename", None):
-                ui.warn(_("abort: %s: %s\n") % (
+                ui.error(_("abort: %s: %s\n") % (
                     encoding.strtolocal(inst.strerror),
                     stringutil.forcebytestr(inst.filename)))
             else:
-                ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
+                ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
         else:
             raise
     except OSError as inst:
         if getattr(inst, "filename", None) is not None:
-            ui.warn(_("abort: %s: '%s'\n") % (
+            ui.error(_("abort: %s: '%s'\n") % (
                 encoding.strtolocal(inst.strerror),
                 stringutil.forcebytestr(inst.filename)))
         else:
-            ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
+            ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
     except MemoryError:
-        ui.warn(_("abort: out of memory\n"))
+        ui.error(_("abort: out of memory\n"))
     except SystemExit as inst:
         # Commands shouldn't sys.exit directly, but give a return code.
         # Just in case catch this and and pass exit code to caller.
         return inst.code
     except socket.error as inst:
-        ui.warn(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
+        ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
 
     return -1
 
@@ -437,41 +440,111 @@
     return '%d:%s' % (rev, hexfunc(node))
 
 def resolvehexnodeidprefix(repo, prefix):
-    # Uses unfiltered repo because it's faster when prefix is ambiguous/
-    # This matches the shortesthexnodeidprefix() function below.
-    node = repo.unfiltered().changelog._partialmatch(prefix)
+    if (prefix.startswith('x') and
+        repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
+        prefix = prefix[1:]
+    try:
+        # Uses unfiltered repo because it's faster when prefix is ambiguous/
+        # This matches the shortesthexnodeidprefix() function below.
+        node = repo.unfiltered().changelog._partialmatch(prefix)
+    except error.AmbiguousPrefixLookupError:
+        revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
+        if revset:
+            # Clear config to avoid infinite recursion
+            configoverrides = {('experimental',
+                                'revisions.disambiguatewithin'): None}
+            with repo.ui.configoverride(configoverrides):
+                revs = repo.anyrevs([revset], user=True)
+                matches = []
+                for rev in revs:
+                    node = repo.changelog.node(rev)
+                    if hex(node).startswith(prefix):
+                        matches.append(node)
+                if len(matches) == 1:
+                    return matches[0]
+        raise
     if node is None:
         return
     repo.changelog.rev(node)  # make sure node isn't filtered
     return node
 
-def shortesthexnodeidprefix(repo, node, minlength=1):
-    """Find the shortest unambiguous prefix that matches hexnode."""
+def mayberevnum(repo, prefix):
+    """Checks if the given prefix may be mistaken for a revision number"""
+    try:
+        i = int(prefix)
+        # if we are a pure int, then starting with zero will not be
+        # confused as a rev; or, obviously, if the int is larger
+        # than the value of the tip rev
+        if prefix[0:1] == b'0' or i >= len(repo):
+            return False
+        return True
+    except ValueError:
+        return False
+
+def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
+    """Find the shortest unambiguous prefix that matches hexnode.
+
+    If "cache" is not None, it must be a dictionary that can be used for
+    caching between calls to this method.
+    """
     # _partialmatch() of filtered changelog could take O(len(repo)) time,
     # which would be unacceptably slow. so we look for hash collision in
     # unfiltered space, which means some hashes may be slightly longer.
-    cl = repo.unfiltered().changelog
-
-    def isrev(prefix):
-        try:
-            i = int(prefix)
-            # if we are a pure int, then starting with zero will not be
-            # confused as a rev; or, obviously, if the int is larger
-            # than the value of the tip rev
-            if prefix[0:1] == b'0' or i > len(cl):
-                return False
-            return True
-        except ValueError:
-            return False
 
     def disambiguate(prefix):
         """Disambiguate against revnums."""
+        if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
+            if mayberevnum(repo, prefix):
+                return 'x' + prefix
+            else:
+                return prefix
+
         hexnode = hex(node)
         for length in range(len(prefix), len(hexnode) + 1):
             prefix = hexnode[:length]
-            if not isrev(prefix):
+            if not mayberevnum(repo, prefix):
                 return prefix
 
+    cl = repo.unfiltered().changelog
+    revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
+    if revset:
+        revs = None
+        if cache is not None:
+            revs = cache.get('disambiguationrevset')
+        if revs is None:
+            revs = repo.anyrevs([revset], user=True)
+            if cache is not None:
+                cache['disambiguationrevset'] = revs
+        if cl.rev(node) in revs:
+            hexnode = hex(node)
+            nodetree = None
+            if cache is not None:
+                nodetree = cache.get('disambiguationnodetree')
+            if not nodetree:
+                try:
+                    nodetree = parsers.nodetree(cl.index, len(revs))
+                except AttributeError:
+                    # no native nodetree
+                    pass
+                else:
+                    for r in revs:
+                        nodetree.insert(r)
+                    if cache is not None:
+                        cache['disambiguationnodetree'] = nodetree
+            if nodetree is not None:
+                length = max(nodetree.shortest(node), minlength)
+                prefix = hexnode[:length]
+                return disambiguate(prefix)
+            for length in range(minlength, len(hexnode) + 1):
+                matches = []
+                prefix = hexnode[:length]
+                for rev in revs:
+                    otherhexnode = repo[rev].hex()
+                    if prefix == otherhexnode[:length]:
+                        matches.append(otherhexnode)
+                if len(matches) == 1:
+                    return disambiguate(prefix)
+
     try:
         return disambiguate(cl.shortest(node, minlength))
     except error.LookupError:
@@ -480,8 +553,8 @@
 def isrevsymbol(repo, symbol):
     """Checks if a symbol exists in the repo.
 
-    See revsymbol() for details. Raises error.LookupError if the symbol is an
-    ambiguous nodeid prefix.
+    See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
+    symbol is an ambiguous nodeid prefix.
     """
     try:
         revsymbol(repo, symbol)
@@ -780,7 +853,7 @@
         return self._revcontains(self._torev(node))
 
 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
-                 fixphase=False, targetphase=None):
+                 fixphase=False, targetphase=None, backup=True):
     """do common cleanups when old nodes are replaced by new nodes
 
     That includes writing obsmarkers or stripping nodes, and moving bookmarks.
@@ -905,7 +978,8 @@
             from . import repair # avoid import cycle
             tostrip = list(replacements)
             if tostrip:
-                repair.delayedstrip(repo.ui, repo, tostrip, operation)
+                repair.delayedstrip(repo.ui, repo, tostrip, operation,
+                                    backup=backup)
 
 def addremove(repo, matcher, prefix, opts=None):
     if opts is None:
@@ -952,9 +1026,11 @@
         if repo.ui.verbose or not m.exact(abs):
             if abs in unknownset:
                 status = _('adding %s\n') % m.uipath(abs)
+                label = 'addremove.added'
             else:
                 status = _('removing %s\n') % m.uipath(abs)
-            repo.ui.status(status)
+                label = 'addremove.removed'
+            repo.ui.status(status, label=label)
 
     renames = _findrenames(repo, m, added + unknown, removed + deleted,
                            similarity)
@@ -1069,25 +1145,6 @@
         elif not dryrun:
             wctx.copy(origsrc, dst)
 
-def readrequires(opener, supported):
-    '''Reads and parses .hg/requires and checks if all entries found
-    are in the list of supported features.'''
-    requirements = set(opener.read("requires").splitlines())
-    missings = []
-    for r in requirements:
-        if r not in supported:
-            if not r or not r[0:1].isalnum():
-                raise error.RequirementError(_(".hg/requires file is corrupt"))
-            missings.append(r)
-    missings.sort()
-    if missings:
-        raise error.RequirementError(
-            _("repository requires features unknown to this Mercurial: %s")
-            % " ".join(missings),
-            hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
-                   " for more information"))
-    return requirements
-
 def writerequires(opener, requirements):
     with opener('requires', 'w') as fp:
         for r in sorted(requirements):
@@ -1282,9 +1339,11 @@
         if spec.startswith("shell:"):
             # external commands should be run relative to the repo root
             cmd = spec[6:]
-            proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
+            proc = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmd),
+                                    shell=True, bufsize=-1,
                                     close_fds=procutil.closefds,
-                                    stdout=subprocess.PIPE, cwd=repo.root)
+                                    stdout=subprocess.PIPE,
+                                    cwd=procutil.tonativestr(repo.root))
             src = proc.stdout
         else:
             # treat as a URL or file
@@ -1542,13 +1601,13 @@
         @reportsummary
         def reportnewcs(repo, tr):
             """Report the range of new revisions pulled/unbundled."""
-            newrevs = tr.changes.get('revs', xrange(0, 0))
-            if not newrevs:
+            origrepolen = tr.changes.get('origrepolen', len(repo))
+            if origrepolen >= len(repo):
                 return
 
             # Compute the bounds of new revisions' range, excluding obsoletes.
             unfi = repo.unfiltered()
-            revs = unfi.revs('%ld and not obsolete()', newrevs)
+            revs = unfi.revs('%d: and not obsolete()', origrepolen)
             if not revs:
                 # Got only obsoletes.
                 return
@@ -1558,23 +1617,35 @@
                 revrange = minrev
             else:
                 revrange = '%s:%s' % (minrev, maxrev)
-            repo.ui.status(_('new changesets %s\n') % revrange)
+            draft = len(repo.revs('%ld and draft()', revs))
+            secret = len(repo.revs('%ld and secret()', revs))
+            if not (draft or secret):
+                msg = _('new changesets %s\n') % revrange
+            elif draft and secret:
+                msg = _('new changesets %s (%d drafts, %d secrets)\n')
+                msg %= (revrange, draft, secret)
+            elif draft:
+                msg = _('new changesets %s (%d drafts)\n')
+                msg %= (revrange, draft)
+            elif secret:
+                msg = _('new changesets %s (%d secrets)\n')
+                msg %= (revrange, secret)
+            else:
+                raise error.ProgrammingError('entered unreachable condition')
+            repo.ui.status(msg)
 
         @reportsummary
         def reportphasechanges(repo, tr):
             """Report statistics of phase changes for changesets pre-existing
             pull/unbundle.
             """
-            # TODO set() is only appropriate for 4.7 since revs post
-            # 45e05d39d9ce is a pycompat.membershiprange, which has O(n)
-            # membership testing.
-            newrevs = set(tr.changes.get('revs', xrange(0, 0)))
+            origrepolen = tr.changes.get('origrepolen', len(repo))
             phasetracking = tr.changes.get('phases', {})
             if not phasetracking:
                 return
             published = [
                 rev for rev, (old, new) in phasetracking.iteritems()
-                if new == phases.public and rev not in newrevs
+                if new == phases.public and rev < origrepolen
             ]
             if not published:
                 return
--- a/mercurial/server.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/server.py	Wed Sep 26 20:33:09 2018 +0900
@@ -79,7 +79,7 @@
             runargs.append('--daemon-postexec=unlink:%s' % lockpath)
             # Don't pass --cwd to the child process, because we've already
             # changed directory.
-            for i in xrange(1, len(runargs)):
+            for i in pycompat.xrange(1, len(runargs)):
                 if runargs[i].startswith('--cwd='):
                     del runargs[i]
                     break
--- a/mercurial/setdiscovery.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/setdiscovery.py	Wed Sep 26 20:33:09 2018 +0900
@@ -51,30 +51,25 @@
     nullrev,
 )
 from . import (
-    dagutil,
     error,
     util,
 )
 
-def _updatesample(dag, nodes, sample, quicksamplesize=0):
+def _updatesample(revs, heads, sample, parentfn, quicksamplesize=0):
     """update an existing sample to match the expected size
 
-    The sample is updated with nodes exponentially distant from each head of the
-    <nodes> set. (H~1, H~2, H~4, H~8, etc).
+    The sample is updated with revs exponentially distant from each head of the
+    <revs> set. (H~1, H~2, H~4, H~8, etc).
 
     If a target size is specified, the sampling will stop once this size is
-    reached. Otherwise sampling will happen until roots of the <nodes> set are
+    reached. Otherwise sampling will happen until roots of the <revs> set are
     reached.
 
-    :dag: a dag object from dagutil
-    :nodes:  set of nodes we want to discover (if None, assume the whole dag)
+    :revs:  set of revs we want to discover (if None, assume the whole dag)
+    :heads: set of DAG head revs
     :sample: a sample to update
+    :parentfn: a callable to resolve parents for a revision
     :quicksamplesize: optional target size of the sample"""
-    # if nodes is empty we scan the entire graph
-    if nodes:
-        heads = dag.headsetofconnecteds(nodes)
-    else:
-        heads = dag.heads()
     dist = {}
     visit = collections.deque(heads)
     seen = set()
@@ -91,37 +86,69 @@
             if quicksamplesize and (len(sample) >= quicksamplesize):
                 return
         seen.add(curr)
-        for p in dag.parents(curr):
-            if not nodes or p in nodes:
+
+        for p in parentfn(curr):
+            if p != nullrev and (not revs or p in revs):
                 dist.setdefault(p, d + 1)
                 visit.append(p)
 
-def _takequicksample(dag, nodes, size):
+def _takequicksample(repo, headrevs, revs, size):
     """takes a quick sample of size <size>
 
     It is meant for initial sampling and focuses on querying heads and close
     ancestors of heads.
 
     :dag: a dag object
-    :nodes: set of nodes to discover
+    :headrevs: set of head revisions in local DAG to consider
+    :revs: set of revs to discover
     :size: the maximum size of the sample"""
-    sample = dag.headsetofconnecteds(nodes)
+    sample = set(repo.revs('heads(%ld)', revs))
+
     if len(sample) >= size:
         return _limitsample(sample, size)
-    _updatesample(dag, None, sample, quicksamplesize=size)
+
+    _updatesample(None, headrevs, sample, repo.changelog.parentrevs,
+                  quicksamplesize=size)
     return sample
 
-def _takefullsample(dag, nodes, size):
-    sample = dag.headsetofconnecteds(nodes)
+def _takefullsample(repo, headrevs, revs, size):
+    sample = set(repo.revs('heads(%ld)', revs))
+
     # update from heads
-    _updatesample(dag, nodes, sample)
+    revsheads = set(repo.revs('heads(%ld)', revs))
+    _updatesample(revs, revsheads, sample, repo.changelog.parentrevs)
+
     # update from roots
-    _updatesample(dag.inverse(), nodes, sample)
+    revsroots = set(repo.revs('roots(%ld)', revs))
+
+    # _updatesample() essentially does interaction over revisions to look up
+    # their children. This lookup is expensive and doing it in a loop is
+    # quadratic. We precompute the children for all relevant revisions and
+    # make the lookup in _updatesample() a simple dict lookup.
+    #
+    # Because this function can be called multiple times during discovery, we
+    # may still perform redundant work and there is room to optimize this by
+    # keeping a persistent cache of children across invocations.
+    children = {}
+
+    parentrevs = repo.changelog.parentrevs
+    for rev in repo.changelog.revs(start=min(revsroots)):
+        # Always ensure revision has an entry so we don't need to worry about
+        # missing keys.
+        children.setdefault(rev, [])
+
+        for prev in parentrevs(rev):
+            if prev == nullrev:
+                continue
+
+            children.setdefault(prev, []).append(rev)
+
+    _updatesample(revs, revsroots, sample, children.__getitem__)
     assert sample
     sample = _limitsample(sample, size)
     if len(sample) < size:
         more = size - len(sample)
-        sample.update(random.sample(list(nodes - sample), more))
+        sample.update(random.sample(list(revs - sample), more))
     return sample
 
 def _limitsample(sample, desiredlen):
@@ -142,16 +169,17 @@
 
     roundtrips = 0
     cl = local.changelog
-    localsubset = None
+    clnode = cl.node
+    clrev = cl.rev
+
     if ancestorsof is not None:
-        rev = local.changelog.rev
-        localsubset = [rev(n) for n in ancestorsof]
-    dag = dagutil.revlogdag(cl, localsubset=localsubset)
+        ownheads = [clrev(n) for n in ancestorsof]
+    else:
+        ownheads = [rev for rev in cl.headrevs() if rev != nullrev]
 
     # early exit if we know all the specified remote heads already
     ui.debug("query 1; heads\n")
     roundtrips += 1
-    ownheads = dag.heads()
     sample = _limitsample(ownheads, initialsamplesize)
     # indices between sample and externalized version must match
     sample = list(sample)
@@ -159,7 +187,7 @@
     with remote.commandexecutor() as e:
         fheads = e.callcommand('heads', {})
         fknown = e.callcommand('known', {
-            'nodes': dag.externalizeall(sample),
+            'nodes': [clnode(r) for r in sample],
         })
 
     srvheadhashes, yesno = fheads.result(), fknown.result()
@@ -173,15 +201,25 @@
     # compatibility reasons)
     ui.status(_("searching for changes\n"))
 
-    srvheads = dag.internalizeall(srvheadhashes, filterunknown=True)
+    srvheads = []
+    for node in srvheadhashes:
+        if node == nullid:
+            continue
+
+        try:
+            srvheads.append(clrev(node))
+        # Catches unknown and filtered nodes.
+        except error.LookupError:
+            continue
+
     if len(srvheads) == len(srvheadhashes):
         ui.debug("all remote heads known locally\n")
-        return (srvheadhashes, False, srvheadhashes,)
+        return srvheadhashes, False, srvheadhashes
 
     if len(sample) == len(ownheads) and all(yesno):
         ui.note(_("all local heads known remotely\n"))
-        ownheadhashes = dag.externalizeall(ownheads)
-        return (ownheadhashes, True, srvheadhashes,)
+        ownheadhashes = [clnode(r) for r in ownheads]
+        return ownheadhashes, True, srvheadhashes
 
     # full blown discovery
 
@@ -202,7 +240,12 @@
 
         if sample:
             missinginsample = [n for i, n in enumerate(sample) if not yesno[i]]
-            missing.update(dag.descendantset(missinginsample, missing))
+
+            if missing:
+                missing.update(local.revs('descendants(%ld) - descendants(%ld)',
+                                          missinginsample, missing))
+            else:
+                missing.update(local.revs('descendants(%ld)', missinginsample))
 
             undecided.difference_update(missing)
 
@@ -224,7 +267,7 @@
         if len(undecided) < targetsize:
             sample = list(undecided)
         else:
-            sample = samplefunc(dag, undecided, targetsize)
+            sample = samplefunc(local, ownheads, undecided, targetsize)
 
         roundtrips += 1
         progress.update(roundtrips)
@@ -235,7 +278,7 @@
 
         with remote.commandexecutor() as e:
             yesno = e.callcommand('known', {
-                'nodes': dag.externalizeall(sample),
+                'nodes': [clnode(r) for r in sample],
             }).result()
 
         full = True
@@ -247,10 +290,8 @@
 
     # heads(common) == heads(common.bases) since common represents common.bases
     # and all its ancestors
-    result = dag.headsetofconnecteds(common.bases)
-    # common.bases can include nullrev, but our contract requires us to not
-    # return any heads in that case, so discard that
-    result.discard(nullrev)
+    # The presence of nullrev will confuse heads(). So filter it out.
+    result = set(local.revs('heads(%ld)', common.bases - {nullrev}))
     elapsed = util.timer() - start
     progress.complete()
     ui.debug("%d total queries in %.4fs\n" % (roundtrips, elapsed))
@@ -268,4 +309,5 @@
         return ({nullid}, True, srvheadhashes,)
 
     anyincoming = (srvheadhashes != [nullid])
-    return dag.externalizeall(result), anyincoming, srvheadhashes
+    result = {clnode(r) for r in result}
+    return result, anyincoming, srvheadhashes
--- a/mercurial/simplemerge.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/simplemerge.py	Wed Sep 26 20:33:09 2018 +0900
@@ -58,7 +58,8 @@
     """
     if (aend - astart) != (bend - bstart):
         return False
-    for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
+    for ia, ib in zip(pycompat.xrange(astart, aend),
+                      pycompat.xrange(bstart, bend)):
         if a[ia] != b[ib]:
             return False
     else:
--- a/mercurial/smartset.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/smartset.py	Wed Sep 26 20:33:09 2018 +0900
@@ -152,11 +152,11 @@
         # but start > stop is allowed, which should be an empty set.
         ys = []
         it = iter(self)
-        for x in xrange(start):
+        for x in pycompat.xrange(start):
             y = next(it, None)
             if y is None:
                 break
-        for x in xrange(stop - start):
+        for x in pycompat.xrange(stop - start):
             y = next(it, None)
             if y is None:
                 break
@@ -1005,13 +1005,13 @@
             return self.fastdesc()
 
     def fastasc(self):
-        iterrange = xrange(self._start, self._end)
+        iterrange = pycompat.xrange(self._start, self._end)
         if self._hiddenrevs:
             return self._iterfilter(iterrange)
         return iter(iterrange)
 
     def fastdesc(self):
-        iterrange = xrange(self._end - 1, self._start - 1, -1)
+        iterrange = pycompat.xrange(self._end - 1, self._start - 1, -1)
         if self._hiddenrevs:
             return self._iterfilter(iterrange)
         return iter(iterrange)
--- a/mercurial/sparse.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/sparse.py	Wed Sep 26 20:33:09 2018 +0900
@@ -31,9 +31,11 @@
 # a per-repo option, possibly a repo requirement.
 enabled = False
 
-def parseconfig(ui, raw):
+def parseconfig(ui, raw, action):
     """Parse sparse config file content.
 
+    action is the command which is trigerring this read, can be narrow, sparse
+
     Returns a tuple of includes, excludes, and profiles.
     """
     includes = set()
@@ -54,8 +56,8 @@
         elif line == '[include]':
             if havesection and current != includes:
                 # TODO pass filename into this API so we can report it.
-                raise error.Abort(_('sparse config cannot have includes ' +
-                                    'after excludes'))
+                raise error.Abort(_('%(action)s config cannot have includes '
+                                    'after excludes') % {'action': action})
             havesection = True
             current = includes
             continue
@@ -64,14 +66,16 @@
             current = excludes
         elif line:
             if current is None:
-                raise error.Abort(_('sparse config entry outside of '
-                                    'section: %s') % line,
+                raise error.Abort(_('%(action)s config entry outside of '
+                                    'section: %(line)s')
+                                  % {'action': action, 'line': line},
                                   hint=_('add an [include] or [exclude] line '
                                          'to declare the entry type'))
 
             if line.strip().startswith('/'):
-                ui.warn(_('warning: sparse profile cannot use' +
-                          ' paths starting with /, ignoring %s\n') % line)
+                ui.warn(_('warning: %(action)s profile cannot use'
+                          ' paths starting with /, ignoring %(line)s\n')
+                        % {'action': action, 'line': line})
                 continue
             current.add(line)
 
@@ -102,7 +106,7 @@
         raise error.Abort(_('cannot parse sparse patterns from working '
                             'directory'))
 
-    includes, excludes, profiles = parseconfig(repo.ui, raw)
+    includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
     ctx = repo[rev]
 
     if profiles:
@@ -128,7 +132,7 @@
                     repo.ui.debug(msg)
                 continue
 
-            pincludes, pexcludes, subprofs = parseconfig(repo.ui, raw)
+            pincludes, pexcludes, subprofs = parseconfig(repo.ui, raw, 'sparse')
             includes.update(pincludes)
             excludes.update(pexcludes)
             profiles.update(subprofs)
@@ -357,6 +361,11 @@
         elif file in wctx:
             prunedactions[file] = ('r', args, msg)
 
+        if branchmerge and type == mergemod.ACTION_MERGE:
+            f1, f2, fa, move, anc = args
+            if not sparsematch(f1):
+                temporaryfiles.append(f1)
+
     if len(temporaryfiles) > 0:
         repo.ui.status(_('temporarily included %d file(s) in the sparse '
                          'checkout for merging\n') % len(temporaryfiles))
@@ -516,7 +525,7 @@
                                 force=False, removing=False):
     """Update the sparse config and working directory state."""
     raw = repo.vfs.tryread('sparse')
-    oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw)
+    oldincludes, oldexcludes, oldprofiles = parseconfig(repo.ui, raw, 'sparse')
 
     oldstatus = repo.status()
     oldmatch = matcher(repo)
@@ -556,7 +565,7 @@
     """
     with repo.wlock():
         raw = repo.vfs.tryread('sparse')
-        includes, excludes, profiles = parseconfig(repo.ui, raw)
+        includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
 
         if not includes and not excludes:
             return
@@ -572,7 +581,7 @@
     with repo.wlock():
         # read current configuration
         raw = repo.vfs.tryread('sparse')
-        includes, excludes, profiles = parseconfig(repo.ui, raw)
+        includes, excludes, profiles = parseconfig(repo.ui, raw, 'sparse')
         aincludes, aexcludes, aprofiles = activeconfig(repo)
 
         # Import rules on top; only take in rules that are not yet
@@ -582,7 +591,8 @@
             with util.posixfile(util.expandpath(p), mode='rb') as fh:
                 raw = fh.read()
 
-            iincludes, iexcludes, iprofiles = parseconfig(repo.ui, raw)
+            iincludes, iexcludes, iprofiles = parseconfig(repo.ui, raw,
+                                                          'sparse')
             oldsize = len(includes) + len(excludes) + len(profiles)
             includes.update(iincludes - aincludes)
             excludes.update(iexcludes - aexcludes)
@@ -615,7 +625,8 @@
     """
     with repo.wlock():
         raw = repo.vfs.tryread('sparse')
-        oldinclude, oldexclude, oldprofiles = parseconfig(repo.ui, raw)
+        oldinclude, oldexclude, oldprofiles = parseconfig(repo.ui, raw,
+                                                          'sparse')
 
         if reset:
             newinclude = set()
--- a/mercurial/sshpeer.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/sshpeer.py	Wed Sep 26 20:33:09 2018 +0900
@@ -597,7 +597,7 @@
         raise error.RepoError(_('unknown version of SSH protocol: %s') %
                               protoname)
 
-def instance(ui, path, create, intents=None):
+def instance(ui, path, create, intents=None, createopts=None):
     """Create an SSH peer.
 
     The returned object conforms to the ``wireprotov1peer.wirepeer`` interface.
@@ -620,6 +620,14 @@
     args = procutil.sshargs(sshcmd, u.host, u.user, u.port)
 
     if create:
+        # We /could/ do this, but only if the remote init command knows how to
+        # handle them. We don't yet make any assumptions about that. And without
+        # querying the remote, there's no way of knowing if the remote even
+        # supports said requested feature.
+        if createopts:
+            raise error.RepoError(_('cannot create remote SSH repositories '
+                                    'with extra options'))
+
         cmd = '%s %s %s' % (sshcmd, args,
             procutil.shellquote('%s init %s' %
                 (_serverquote(remotecmd), _serverquote(remotepath))))
--- a/mercurial/state.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/state.py	Wed Sep 26 20:33:09 2018 +0900
@@ -19,12 +19,13 @@
 
 from __future__ import absolute_import
 
-from .thirdparty import cbor
-
 from . import (
     error,
     util,
 )
+from .utils import (
+    cborutil,
+)
 
 class cmdstate(object):
     """a wrapper class to store the state of commands like `rebase`, `graft`,
@@ -62,7 +63,8 @@
 
         with self._repo.vfs(self.fname, 'wb', atomictemp=True) as fp:
             fp.write('%d\n' % version)
-            cbor.dump(data, fp, canonical=True)
+            for chunk in cborutil.streamencode(data):
+                fp.write(chunk)
 
     def _read(self):
         """reads the state file and returns a dictionary which contain
@@ -73,7 +75,8 @@
             except ValueError:
                 raise error.CorruptedState("unknown version of state file"
                                            " found")
-            return cbor.load(fp)
+
+            return cborutil.decodeall(fp.read())[0]
 
     def delete(self):
         """drop the state file if exists"""
--- a/mercurial/statichttprepo.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/statichttprepo.py	Wed Sep 26 20:33:09 2018 +0900
@@ -19,8 +19,6 @@
     manifest,
     namespaces,
     pathutil,
-    scmutil,
-    store,
     url,
     util,
     vfs as vfsmod,
@@ -136,7 +134,8 @@
     def canpush(self):
         return False
 
-class statichttprepository(localrepo.localrepository):
+class statichttprepository(localrepo.localrepository,
+                           localrepo.revlogfilestorage):
     supported = localrepo.localrepository._basesupported
 
     def __init__(self, ui, path):
@@ -156,7 +155,7 @@
         self.filtername = None
 
         try:
-            requirements = scmutil.readrequires(self.vfs, self.supported)
+            requirements = set(self.vfs.read(b'requires').splitlines())
         except IOError as inst:
             if inst.errno != errno.ENOENT:
                 raise
@@ -174,15 +173,21 @@
                 msg = _("'%s' does not appear to be an hg repository") % path
                 raise error.RepoError(msg)
 
+        supportedrequirements = localrepo.gathersupportedrequirements(ui)
+        localrepo.ensurerequirementsrecognized(requirements,
+                                               supportedrequirements)
+        localrepo.ensurerequirementscompatible(ui, requirements)
+
         # setup store
-        self.store = store.store(requirements, self.path, vfsclass)
+        self.store = localrepo.makestore(requirements, self.path, vfsclass)
         self.spath = self.store.path
         self.svfs = self.store.opener
         self.sjoin = self.store.join
         self._filecache = {}
         self.requirements = requirements
 
-        self.manifestlog = manifest.manifestlog(self.svfs, self)
+        rootmanifest = manifest.manifestrevlog(self.svfs)
+        self.manifestlog = manifest.manifestlog(self.svfs, self, rootmanifest)
         self.changelog = changelog.changelog(self.svfs)
         self._tags = None
         self.nodetagscache = None
@@ -215,7 +220,7 @@
     def _writecaches(self):
         pass # statichttprepository are read only
 
-def instance(ui, path, create, intents=None):
+def instance(ui, path, create, intents=None, createopts=None):
     if create:
         raise error.Abort(_('cannot create new static-http repository'))
     return statichttprepository(ui, path[7:])
--- a/mercurial/statprof.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/statprof.py	Wed Sep 26 20:33:09 2018 +0900
@@ -356,7 +356,7 @@
             stack = sample.stack
             sites = ['\1'.join([s.path, str(s.lineno), s.function])
                      for s in stack]
-            file.write(time + '\0' + '\0'.join(sites) + '\n')
+            file.write("%s\0%s\n" % (time, '\0'.join(sites)))
 
 def load_data(path):
     lines = open(path, 'r').read().splitlines()
--- a/mercurial/store.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/store.py	Wed Sep 26 20:33:09 2018 +0900
@@ -118,7 +118,7 @@
     def decode(s):
         i = 0
         while i < len(s):
-            for l in xrange(1, 4):
+            for l in pycompat.xrange(1, 4):
                 try:
                     yield dmap[s[i:i + l]]
                     i += l
@@ -127,7 +127,8 @@
                     pass
             else:
                 raise KeyError
-    return (lambda s: ''.join([cmap[s[c:c + 1]] for c in xrange(len(s))]),
+    return (lambda s: ''.join([cmap[s[c:c + 1]]
+                               for c in pycompat.xrange(len(s))]),
             lambda s: ''.join(list(decode(s))))
 
 _encodefname, _decodefname = _buildencodefun()
@@ -159,7 +160,7 @@
     'the~07quick~adshot'
     '''
     xchr = pycompat.bytechr
-    cmap = dict([(xchr(x), xchr(x)) for x in xrange(127)])
+    cmap = dict([(xchr(x), xchr(x)) for x in pycompat.xrange(127)])
     for x in _reserved():
         cmap[xchr(x)] = "~%02x" % x
     for x in range(ord("A"), ord("Z") + 1):
@@ -316,8 +317,8 @@
         mode = None
     return mode
 
-_data = ('data meta 00manifest.d 00manifest.i 00changelog.d 00changelog.i'
-         ' phaseroots obsstore')
+_data = ('narrowspec data meta 00manifest.d 00manifest.i'
+         ' 00changelog.d 00changelog.i phaseroots obsstore')
 
 def isrevlog(f, kind, st):
     return kind == stat.S_IFREG and f[-2:] in ('.i', '.d')
@@ -545,7 +546,7 @@
                     raise
 
     def copylist(self):
-        d = ('data meta dh fncache phaseroots obsstore'
+        d = ('narrowspec data meta dh fncache phaseroots obsstore'
              ' 00manifest.d 00manifest.i 00changelog.d 00changelog.i')
         return (['requires', '00changelog.i'] +
                 ['store/' + f for f in d.split()])
@@ -584,10 +585,3 @@
             if e.startswith(path) and self._exists(e):
                 return True
         return False
-
-def store(requirements, path, vfstype):
-    if 'store' in requirements:
-        if 'fncache' in requirements:
-            return fncachestore(path, vfstype, 'dotencode' in requirements)
-        return encodedstore(path, vfstype)
-    return basicstore(path, vfstype)
--- a/mercurial/streamclone.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/streamclone.py	Wed Sep 26 20:33:09 2018 +0900
@@ -10,7 +10,6 @@
 import contextlib
 import os
 import struct
-import warnings
 
 from .i18n import _
 from . import (
@@ -114,6 +113,8 @@
     A legacy stream clone will not be performed if a bundle2 stream clone is
     supported.
     """
+    from . import localrepo
+
     supported, requirements = canperformstreamclone(pullop)
 
     if not supported:
@@ -166,7 +167,8 @@
         # requirements from the streamed-in repository
         repo.requirements = requirements | (
                 repo.requirements - repo.supportedformats)
-        repo._applyopenerreqs()
+        repo.svfs.options = localrepo.resolvestorevfsoptions(
+            repo.ui, repo.requirements)
         repo._writerequirements()
 
         if rbranchmap:
@@ -358,7 +360,7 @@
 
         with repo.transaction('clone'):
             with repo.svfs.backgroundclosing(repo.ui, expectedcount=filecount):
-                for i in xrange(filecount):
+                for i in pycompat.xrange(filecount):
                     # XXX doesn't support '\n' or '\r' in filenames
                     l = fp.readline()
                     try:
@@ -565,12 +567,13 @@
 
 @contextlib.contextmanager
 def nested(*ctxs):
-    with warnings.catch_warnings():
-        # For some reason, Python decided 'nested' was deprecated without
-        # replacement. They officially advertised for filtering the deprecation
-        # warning for people who actually need the feature.
-        warnings.filterwarnings("ignore",category=DeprecationWarning)
-        with contextlib.nested(*ctxs):
+    this = ctxs[0]
+    rest = ctxs[1:]
+    with this:
+        if rest:
+            with nested(*rest):
+                yield
+        else:
             yield
 
 def consumev2(repo, fp, filecount, filesize):
@@ -624,6 +627,8 @@
         progress.complete()
 
 def applybundlev2(repo, fp, filecount, filesize, requirements):
+    from . import localrepo
+
     missingreqs = [r for r in requirements if r not in repo.supported]
     if missingreqs:
         raise error.Abort(_('unable to apply stream clone: '
@@ -637,5 +642,6 @@
     # requirements from the streamed-in repository
     repo.requirements = set(requirements) | (
             repo.requirements - repo.supportedformats)
-    repo._applyopenerreqs()
+    repo.svfs.options = localrepo.resolvestorevfsoptions(
+        repo.ui, repo.requirements)
     repo._writerequirements()
--- a/mercurial/subrepo.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/subrepo.py	Wed Sep 26 20:33:09 2018 +0900
@@ -951,9 +951,11 @@
             env['LANG'] = lc_all
             del env['LC_ALL']
         env['LC_MESSAGES'] = 'C'
-        p = subprocess.Popen(cmd, bufsize=-1, close_fds=procutil.closefds,
+        p = subprocess.Popen(pycompat.rapply(procutil.tonativestr, cmd),
+                             bufsize=-1, close_fds=procutil.closefds,
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,
-                              universal_newlines=True, env=env, **extrakw)
+                             universal_newlines=True,
+                             env=procutil.tonativeenv(env), **extrakw)
         stdout, stderr = p.communicate()
         stderr = stderr.strip()
         if not failok:
@@ -1268,8 +1270,12 @@
             # insert the argument in the front,
             # the end of git diff arguments is used for paths
             commands.insert(1, '--color')
-        p = subprocess.Popen([self._gitexecutable] + commands, bufsize=-1,
-                             cwd=cwd, env=env, close_fds=procutil.closefds,
+        p = subprocess.Popen(pycompat.rapply(procutil.tonativestr,
+                                             [self._gitexecutable] + commands),
+                             bufsize=-1,
+                             cwd=pycompat.rapply(procutil.tonativestr, cwd),
+                             env=procutil.tonativeenv(env),
+                             close_fds=procutil.closefds,
                              stdout=subprocess.PIPE, stderr=errpipe)
         if stream:
             return p.stdout, None
--- a/mercurial/templatefilters.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templatefilters.py	Wed Sep 26 20:33:09 2018 +0900
@@ -119,7 +119,7 @@
             b = b[:len(a)]
         if a == b:
             return a
-        for i in xrange(len(a)):
+        for i in pycompat.xrange(len(a)):
             if a[i] != b[i]:
                 return a[:i]
         return a
@@ -266,7 +266,7 @@
     num_lines = len(lines)
     endswithnewline = text[-1:] == '\n'
     def indenter():
-        for i in xrange(num_lines):
+        for i in pycompat.xrange(num_lines):
             l = lines[i]
             if i and l.strip():
                 yield prefix
--- a/mercurial/templatefuncs.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templatefuncs.py	Wed Sep 26 20:33:09 2018 +0900
@@ -140,7 +140,7 @@
     ctx = context.resource(mapping, 'ctx')
     m = ctx.match([raw])
     files = list(ctx.matches(m))
-    return templateutil.compatlist(context, mapping, "file", files)
+    return templateutil.compatfileslist(context, mapping, "file", files)
 
 @templatefunc('fill(text[, width[, initialident[, hangindent]]])')
 def fill(context, mapping, args):
@@ -575,7 +575,7 @@
     text = evalstring(context, mapping, args[0])
     style = evalstring(context, mapping, args[1])
 
-    return minirst.format(text, style=style, keep=['verbose'])[0]
+    return minirst.format(text, style=style, keep=['verbose'])
 
 @templatefunc('separate(sep, args...)', argspec='sep *args')
 def separate(context, mapping, args):
@@ -596,7 +596,7 @@
             yield sep
         yield argstr
 
-@templatefunc('shortest(node, minlength=4)', requires={'repo'})
+@templatefunc('shortest(node, minlength=4)', requires={'repo', 'cache'})
 def shortest(context, mapping, args):
     """Obtain the shortest representation of
     a node."""
@@ -629,8 +629,9 @@
             return hexnode
         if not node:
             return hexnode
+    cache = context.resource(mapping, 'cache')
     try:
-        return scmutil.shortesthexnodeidprefix(repo, node, minlength)
+        return scmutil.shortesthexnodeidprefix(repo, node, minlength, cache)
     except error.RepoLookupError:
         return hexnode
 
--- a/mercurial/templatekw.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templatekw.py	Wed Sep 26 20:33:09 2018 +0900
@@ -11,6 +11,8 @@
 from .node import (
     hex,
     nullid,
+    wdirid,
+    wdirrev,
 )
 
 from . import (
@@ -168,9 +170,8 @@
 
 @templatekeyword('author', requires={'ctx'})
 def showauthor(context, mapping):
-    """String. The unmodified author of the changeset."""
-    ctx = context.resource(mapping, 'ctx')
-    return ctx.user()
+    """Alias for ``{user}``"""
+    return showuser(context, mapping)
 
 @templatekeyword('bisect', requires={'repo', 'ctx'})
 def showbisect(context, mapping):
@@ -292,16 +293,31 @@
     return _hybrid(f, extras, makemap,
                    lambda k: '%s=%s' % (k, stringutil.escapestr(extras[k])))
 
-def _showfilesbystat(context, mapping, name, index):
-    repo = context.resource(mapping, 'repo')
+def _getfilestatus(context, mapping, listall=False):
     ctx = context.resource(mapping, 'ctx')
     revcache = context.resource(mapping, 'revcache')
-    if 'files' not in revcache:
-        revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
-    files = revcache['files'][index]
-    return compatlist(context, mapping, name, files, element='file')
+    if 'filestatus' not in revcache or revcache['filestatusall'] < listall:
+        stat = ctx.p1().status(ctx, listignored=listall, listclean=listall,
+                               listunknown=listall)
+        revcache['filestatus'] = stat
+        revcache['filestatusall'] = listall
+    return revcache['filestatus']
 
-@templatekeyword('file_adds', requires={'repo', 'ctx', 'revcache'})
+def _getfilestatusmap(context, mapping, listall=False):
+    revcache = context.resource(mapping, 'revcache')
+    if 'filestatusmap' not in revcache or revcache['filestatusall'] < listall:
+        stat = _getfilestatus(context, mapping, listall=listall)
+        revcache['filestatusmap'] = statmap = {}
+        for char, files in zip(pycompat.iterbytestr('MAR!?IC'), stat):
+            statmap.update((f, char) for f in files)
+    return revcache['filestatusmap']  # {path: statchar}
+
+def _showfilesbystat(context, mapping, name, index):
+    stat = _getfilestatus(context, mapping)
+    files = stat[index]
+    return templateutil.compatfileslist(context, mapping, name, files)
+
+@templatekeyword('file_adds', requires={'ctx', 'revcache'})
 def showfileadds(context, mapping):
     """List of strings. Files added by this changeset."""
     return _showfilesbystat(context, mapping, 'file_add', 1)
@@ -325,11 +341,8 @@
             rename = getrenamed(fn, ctx.rev())
             if rename:
                 copies.append((fn, rename))
-
-    copies = util.sortdict(copies)
-    return compatdict(context, mapping, 'file_copy', copies,
-                      key='name', value='source', fmt='%s (%s)',
-                      plural='file_copies')
+    return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
+                                             copies)
 
 # showfilecopiesswitch() displays file copies only if copy records are
 # provided before calling the templater, usually with a --copies
@@ -340,17 +353,15 @@
     only if the --copied switch is set.
     """
     copies = context.resource(mapping, 'revcache').get('copies') or []
-    copies = util.sortdict(copies)
-    return compatdict(context, mapping, 'file_copy', copies,
-                      key='name', value='source', fmt='%s (%s)',
-                      plural='file_copies')
+    return templateutil.compatfilecopiesdict(context, mapping, 'file_copy',
+                                             copies)
 
-@templatekeyword('file_dels', requires={'repo', 'ctx', 'revcache'})
+@templatekeyword('file_dels', requires={'ctx', 'revcache'})
 def showfiledels(context, mapping):
     """List of strings. Files removed by this changeset."""
     return _showfilesbystat(context, mapping, 'file_del', 2)
 
-@templatekeyword('file_mods', requires={'repo', 'ctx', 'revcache'})
+@templatekeyword('file_mods', requires={'ctx', 'revcache'})
 def showfilemods(context, mapping):
     """List of strings. Files modified by this changeset."""
     return _showfilesbystat(context, mapping, 'file_mod', 0)
@@ -361,7 +372,7 @@
     changeset.
     """
     ctx = context.resource(mapping, 'ctx')
-    return compatlist(context, mapping, 'file', ctx.files())
+    return templateutil.compatfileslist(context, mapping, 'file', ctx.files())
 
 @templatekeyword('graphnode', requires={'repo', 'ctx'})
 def showgraphnode(context, mapping):
@@ -466,14 +477,13 @@
     ctx = context.resource(mapping, 'ctx')
     mnode = ctx.manifestnode()
     if mnode is None:
-        # just avoid crash, we might want to use the 'ff...' hash in future
-        return
-    mrev = repo.manifestlog.rev(mnode)
+        mnode = wdirid
+        mrev = wdirrev
+    else:
+        mrev = repo.manifestlog.rev(mnode)
     mhex = hex(mnode)
     mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
     f = context.process('manifest', mapping)
-    # TODO: perhaps 'ctx' should be dropped from mapping because manifest
-    # rev and node are completely different from changeset's.
     return templateutil.hybriditem(f, None, f,
                                    lambda x: {'rev': mrev, 'node': mhex})
 
@@ -550,6 +560,12 @@
         return 'obsolete'
     return ''
 
+@templatekeyword('path', requires={'fctx'})
+def showpath(context, mapping):
+    """String. Repository-absolute path of the current file. (EXPERIMENTAL)"""
+    fctx = context.resource(mapping, 'fctx')
+    return fctx.path()
+
 @templatekeyword('peerurls', requires={'repo'})
 def showpeerurls(context, mapping):
     """A dictionary of repository locations defined in the [paths] section
@@ -583,6 +599,25 @@
     repo = context.resource(mapping, 'repo')
     return repo.root
 
+@templatekeyword('size', requires={'fctx'})
+def showsize(context, mapping):
+    """Integer. Size of the current file in bytes. (EXPERIMENTAL)"""
+    fctx = context.resource(mapping, 'fctx')
+    return fctx.size()
+
+# requires 'fctx' to denote {status} depends on (ctx, path) pair
+@templatekeyword('status', requires={'ctx', 'fctx', 'revcache'})
+def showstatus(context, mapping):
+    """String. Status code of the current file. (EXPERIMENTAL)"""
+    path = templateutil.runsymbol(context, mapping, 'path')
+    path = templateutil.stringify(context, mapping, path)
+    if not path:
+        return
+    statmap = _getfilestatusmap(context, mapping)
+    if path not in statmap:
+        statmap = _getfilestatusmap(context, mapping, listall=True)
+    return statmap.get(path)
+
 @templatekeyword("successorssets", requires={'repo', 'ctx'})
 def showsuccessorssets(context, mapping):
     """Returns a string of sets of successors for a changectx. Format used
@@ -758,6 +793,12 @@
     ui = context.resource(mapping, 'ui')
     return ui.termwidth()
 
+@templatekeyword('user', requires={'ctx'})
+def showuser(context, mapping):
+    """String. The unmodified author of the changeset."""
+    ctx = context.resource(mapping, 'ctx')
+    return ctx.user()
+
 @templatekeyword('instabilities', requires={'ctx'})
 def showinstabilities(context, mapping):
     """List of strings. Evolution instabilities affecting the changeset.
--- a/mercurial/templater.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templater.py	Wed Sep 26 20:33:09 2018 +0900
@@ -548,7 +548,7 @@
     __metaclass__ = abc.ABCMeta
 
     @abc.abstractmethod
-    def availablekeys(self, context, mapping):
+    def availablekeys(self, mapping):
         """Return a set of available resource keys based on the given mapping"""
 
     @abc.abstractmethod
@@ -556,7 +556,7 @@
         """Return a set of supported resource keys"""
 
     @abc.abstractmethod
-    def lookup(self, context, mapping, key):
+    def lookup(self, mapping, key):
         """Return a resource for the key if available; otherwise None"""
 
     @abc.abstractmethod
@@ -565,13 +565,13 @@
         with the given new mapping"""
 
 class nullresourcemapper(resourcemapper):
-    def availablekeys(self, context, mapping):
+    def availablekeys(self, mapping):
         return set()
 
     def knownkeys(self):
         return set()
 
-    def lookup(self, context, mapping, key):
+    def lookup(self, mapping, key):
         return None
 
     def populatemap(self, context, origmapping, newmapping):
@@ -618,7 +618,7 @@
         # do not copy symbols which overrides the defaults depending on
         # new resources, so the defaults will be re-evaluated (issue5612)
         knownres = self._resources.knownkeys()
-        newres = self._resources.availablekeys(self, newmapping)
+        newres = self._resources.availablekeys(newmapping)
         mapping = {k: v for k, v in origmapping.iteritems()
                    if (k in knownres  # not a symbol per self.symbol()
                        or newres.isdisjoint(self._defaultrequires(k)))}
@@ -645,7 +645,7 @@
 
     def availableresourcekeys(self, mapping):
         """Return a set of available resource keys based on the given mapping"""
-        return self._resources.availablekeys(self, mapping)
+        return self._resources.availablekeys(mapping)
 
     def knownresourcekeys(self):
         """Return a set of supported resource keys"""
@@ -654,7 +654,7 @@
     def resource(self, mapping, key):
         """Return internal data (e.g. cache) used for keyword/function
         evaluation"""
-        v = self._resources.lookup(self, mapping, key)
+        v = self._resources.lookup(mapping, key)
         if v is None:
             raise templateutil.ResourceUnavailable(
                 _('template resource not available: %s') % key)
--- a/mercurial/templates/json/map	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templates/json/map	Wed Sep 26 20:33:09 2018 +0900
@@ -1,4 +1,6 @@
+default = 'shortlog'
 mimetype = 'application/json'
+
 filerevision = '\{
   "node": {node|json},
   "path": {file|json},
@@ -239,3 +241,6 @@
   "lastchange": {lastchange|json},
   "labels": {labels|json}
   }'
+error = '\{
+  "error": {error|utf8|json}
+  }'
--- a/mercurial/templates/map-cmdline.bisect	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templates/map-cmdline.bisect	Wed Sep 26 20:33:09 2018 +0900
@@ -1,10 +1,10 @@
 %include map-cmdline.default
 
 [templates]
-changeset = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}\n'
+changeset = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{luser}{ldate}{summary}\n'
 changeset_quiet = '{lshortbisect} {rev}:{node|short}\n'
-changeset_verbose = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
-changeset_debug = '{fullcset}{lbisect}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
+changeset_verbose = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{luser}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
+changeset_debug = '{fullcset}{lbisect}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{luser}{ldate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
 
 # We take the zeroth word in order to omit "(implicit)" in the label
 bisectlabel = ' bisect.{word('0', bisect)}'
--- a/mercurial/templates/map-cmdline.default	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templates/map-cmdline.default	Wed Sep 26 20:33:09 2018 +0900
@@ -2,10 +2,10 @@
 # to replace some keywords with 'lkeyword', for 'labelled keyword'
 
 [templates]
-changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{summary}\n'
+changeset = '{cset}{branches}{bookmarks}{tags}{parents}{luser}{ldate}{ltroubles}{lobsfate}{summary}\n'
 changeset_quiet = '{lnode}'
-changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{lfiles}{lfile_copies_switch}{description}\n'
-changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{ltroubles}{lobsfate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
+changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{luser}{ldate}{ltroubles}{lobsfate}{lfiles}{lfile_copies_switch}{description}\n'
+changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{luser}{ldate}{ltroubles}{lobsfate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
 
 # File templates
 lfiles = '{if(files,
@@ -54,8 +54,8 @@
 bookmark = '{label("log.bookmark",
                    "bookmark:    {bookmark}")}\n'
 
-user = '{label("log.user",
-               "user:        {author}")}\n'
+luser = '{label("log.user",
+                "user:        {author}")}\n'
 
 summary = '{if(desc|strip, "{label('log.summary',
                                    'summary:     {desc|firstline}')}\n")}'
@@ -74,7 +74,7 @@
                                 {label('ui.note log.description',
                                        '{desc|strip}')}\n\n")}'
 
-status = '{status} {path}\n{if(copy, "  {copy}\n")}'
+status = '{status} {path|relpath}\n{if(source, "  {source|relpath}\n")}'
 
 # Obsfate templates, it would be removed once we introduce the obsfate
 # template fragment
--- a/mercurial/templates/map-cmdline.phases	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templates/map-cmdline.phases	Wed Sep 26 20:33:09 2018 +0900
@@ -1,5 +1,5 @@
 %include map-cmdline.default
 
 [templates]
-changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{summary}\n'
-changeset_verbose = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
+changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{luser}{ldate}{summary}\n'
+changeset_verbose = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{luser}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
--- a/mercurial/templates/map-cmdline.status	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templates/map-cmdline.status	Wed Sep 26 20:33:09 2018 +0900
@@ -2,9 +2,9 @@
 
 [templates]
 # Override base templates
-changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}{lfiles}\n'
-changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{description}{lfiles}\n'
-changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{extras}{description}{lfiles}\n'
+changeset = '{cset}{branches}{bookmarks}{tags}{parents}{luser}{ldate}{summary}{lfiles}\n'
+changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{luser}{ldate}{description}{lfiles}\n'
+changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{luser}{ldate}{extras}{description}{lfiles}\n'
 
 # Override the file templates
 lfiles = '{if(files,
--- a/mercurial/templateutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/templateutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -570,6 +570,32 @@
     f = _showcompatlist(context, mapping, name, data, plural, separator)
     return hybridlist(data, name=element or name, fmt=fmt, gen=f)
 
+def compatfilecopiesdict(context, mapping, name, copies):
+    """Wrap list of (dest, source) file names to support old-style list
+    template and field names
+
+    This exists for backward compatibility. Use hybriddict for new template
+    keywords.
+    """
+    # no need to provide {path} to old-style list template
+    c = [{'name': k, 'source': v} for k, v in copies]
+    f = _showcompatlist(context, mapping, name, c, plural='file_copies')
+    copies = util.sortdict(copies)
+    return hybrid(f, copies,
+                  lambda k: {'name': k, 'path': k, 'source': copies[k]},
+                  lambda k: '%s (%s)' % (k, copies[k]))
+
+def compatfileslist(context, mapping, name, files):
+    """Wrap list of file names to support old-style list template and field
+    names
+
+    This exists for backward compatibility. Use hybridlist for new template
+    keywords.
+    """
+    f = _showcompatlist(context, mapping, name, files)
+    return hybrid(f, files, lambda x: {'file': x, 'path': x},
+                  pycompat.identity)
+
 def _showcompatlist(context, mapping, name, values, plural=None, separator=' '):
     """Return a generator that renders old-style list template
 
@@ -810,8 +836,9 @@
     return data
 
 def _recursivesymbolblocker(key):
-    def showrecursion(**args):
+    def showrecursion(context, mapping):
         raise error.Abort(_("recursive reference '%s' in template") % key)
+    showrecursion._requires = ()  # mark as new-style templatekw
     return showrecursion
 
 def runsymbol(context, mapping, key, default=''):
@@ -827,12 +854,16 @@
             v = default
     if callable(v) and getattr(v, '_requires', None) is None:
         # old templatekw: expand all keywords and resources
-        # (TODO: deprecate this after porting web template keywords to new API)
-        props = {k: context._resources.lookup(context, mapping, k)
+        # (TODO: drop support for old-style functions. 'f._requires = ()'
+        #  can be removed.)
+        props = {k: context._resources.lookup(mapping, k)
                  for k in context._resources.knownkeys()}
         # pass context to _showcompatlist() through templatekw._showlist()
         props['templ'] = context
         props.update(mapping)
+        ui = props.get('ui')
+        if ui:
+            ui.deprecwarn("old-style template keyword '%s'" % key, '4.8')
         return v(**pycompat.strkwargs(props))
     if callable(v):
         # new templatekw
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/testing/storage.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,977 @@
+# storage.py - Testing of storage primitives.
+#
+# Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import unittest
+
+from ..node import (
+    hex,
+    nullid,
+    nullrev,
+)
+from .. import (
+    error,
+    mdiff,
+    revlog,
+)
+
+class basetestcase(unittest.TestCase):
+    if not getattr(unittest.TestCase, r'assertRaisesRegex', False):
+        assertRaisesRegex = (# camelcase-required
+            unittest.TestCase.assertRaisesRegexp)
+
+class revisiondeltarequest(object):
+    def __init__(self, node, p1, p2, linknode, basenode, ellipsis):
+        self.node = node
+        self.p1node = p1
+        self.p2node = p2
+        self.linknode = linknode
+        self.basenode = basenode
+        self.ellipsis = ellipsis
+
+class ifileindextests(basetestcase):
+    """Generic tests for the ifileindex interface.
+
+    All file storage backends for index data should conform to the tests in this
+    class.
+
+    Use ``makeifileindextests()`` to create an instance of this type.
+    """
+    def testempty(self):
+        f = self._makefilefn()
+        self.assertEqual(len(f), 0, 'new file store has 0 length by default')
+        self.assertEqual(list(f), [], 'iter yields nothing by default')
+
+        gen = iter(f)
+        with self.assertRaises(StopIteration):
+            next(gen)
+
+        # revs() should evaluate to an empty list.
+        self.assertEqual(list(f.revs()), [])
+
+        revs = iter(f.revs())
+        with self.assertRaises(StopIteration):
+            next(revs)
+
+        self.assertEqual(list(f.revs(start=20)), [])
+
+        # parents() and parentrevs() work with nullid/nullrev.
+        self.assertEqual(f.parents(nullid), (nullid, nullid))
+        self.assertEqual(f.parentrevs(nullrev), (nullrev, nullrev))
+
+        with self.assertRaises(error.LookupError):
+            f.parents(b'\x01' * 20)
+
+        for i in range(-5, 5):
+            if i == nullrev:
+                continue
+
+            with self.assertRaises(IndexError):
+                f.parentrevs(i)
+
+        # nullid/nullrev lookup always works.
+        self.assertEqual(f.rev(nullid), nullrev)
+        self.assertEqual(f.node(nullrev), nullid)
+
+        with self.assertRaises(error.LookupError):
+            f.rev(b'\x01' * 20)
+
+        for i in range(-5, 5):
+            if i == nullrev:
+                continue
+
+            with self.assertRaises(IndexError):
+                f.node(i)
+
+        self.assertEqual(f.lookup(nullid), nullid)
+        self.assertEqual(f.lookup(nullrev), nullid)
+        self.assertEqual(f.lookup(hex(nullid)), nullid)
+
+        # String converted to integer doesn't work for nullrev.
+        with self.assertRaises(error.LookupError):
+            f.lookup(b'%d' % nullrev)
+
+        self.assertEqual(f.linkrev(nullrev), nullrev)
+
+        for i in range(-5, 5):
+            if i == nullrev:
+                continue
+
+            with self.assertRaises(IndexError):
+                f.linkrev(i)
+
+        self.assertEqual(f.flags(nullrev), 0)
+
+        for i in range(-5, 5):
+            if i == nullrev:
+                continue
+
+            with self.assertRaises(IndexError):
+                f.flags(i)
+
+        self.assertFalse(f.iscensored(nullrev))
+
+        for i in range(-5, 5):
+            if i == nullrev:
+                continue
+
+            with self.assertRaises(IndexError):
+                f.iscensored(i)
+
+        self.assertEqual(list(f.commonancestorsheads(nullid, nullid)), [])
+
+        with self.assertRaises(ValueError):
+            self.assertEqual(list(f.descendants([])), [])
+
+        self.assertEqual(list(f.descendants([nullrev])), [])
+
+        self.assertEqual(f.heads(), [nullid])
+        self.assertEqual(f.heads(nullid), [nullid])
+        self.assertEqual(f.heads(None, [nullid]), [nullid])
+        self.assertEqual(f.heads(nullid, [nullid]), [nullid])
+
+        self.assertEqual(f.children(nullid), [])
+
+        with self.assertRaises(error.LookupError):
+            f.children(b'\x01' * 20)
+
+        self.assertEqual(f.deltaparent(nullrev), nullrev)
+
+        for i in range(-5, 5):
+            if i == nullrev:
+                continue
+
+            with self.assertRaises(IndexError):
+                f.deltaparent(i)
+
+    def testsinglerevision(self):
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            node = f.add(b'initial', None, tr, 0, nullid, nullid)
+
+        self.assertEqual(len(f), 1)
+        self.assertEqual(list(f), [0])
+
+        gen = iter(f)
+        self.assertEqual(next(gen), 0)
+
+        with self.assertRaises(StopIteration):
+            next(gen)
+
+        self.assertEqual(list(f.revs()), [0])
+        self.assertEqual(list(f.revs(start=1)), [])
+        self.assertEqual(list(f.revs(start=0)), [0])
+        self.assertEqual(list(f.revs(stop=0)), [0])
+        self.assertEqual(list(f.revs(stop=1)), [0])
+        self.assertEqual(list(f.revs(1, 1)), [])
+        # TODO buggy
+        self.assertEqual(list(f.revs(1, 0)), [1, 0])
+        self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
+
+        self.assertEqual(f.parents(node), (nullid, nullid))
+        self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
+
+        with self.assertRaises(error.LookupError):
+            f.parents(b'\x01' * 20)
+
+        with self.assertRaises(IndexError):
+            f.parentrevs(1)
+
+        self.assertEqual(f.rev(node), 0)
+
+        with self.assertRaises(error.LookupError):
+            f.rev(b'\x01' * 20)
+
+        self.assertEqual(f.node(0), node)
+
+        with self.assertRaises(IndexError):
+            f.node(1)
+
+        self.assertEqual(f.lookup(node), node)
+        self.assertEqual(f.lookup(0), node)
+        self.assertEqual(f.lookup(b'0'), node)
+        self.assertEqual(f.lookup(hex(node)), node)
+
+        self.assertEqual(f.linkrev(0), 0)
+
+        with self.assertRaises(IndexError):
+            f.linkrev(1)
+
+        self.assertEqual(f.flags(0), 0)
+
+        with self.assertRaises(IndexError):
+            f.flags(1)
+
+        self.assertFalse(f.iscensored(0))
+
+        with self.assertRaises(IndexError):
+            f.iscensored(1)
+
+        self.assertEqual(list(f.descendants([0])), [])
+
+        self.assertEqual(f.heads(), [node])
+        self.assertEqual(f.heads(node), [node])
+        self.assertEqual(f.heads(stop=[node]), [node])
+
+        with self.assertRaises(error.LookupError):
+            f.heads(stop=[b'\x01' * 20])
+
+        self.assertEqual(f.children(node), [])
+
+        self.assertEqual(f.deltaparent(0), nullrev)
+
+    def testmultiplerevisions(self):
+        fulltext0 = b'x' * 1024
+        fulltext1 = fulltext0 + b'y'
+        fulltext2 = b'y' + fulltext0 + b'z'
+
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
+            node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
+            node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
+
+        self.assertEqual(len(f), 3)
+        self.assertEqual(list(f), [0, 1, 2])
+
+        gen = iter(f)
+        self.assertEqual(next(gen), 0)
+        self.assertEqual(next(gen), 1)
+        self.assertEqual(next(gen), 2)
+
+        with self.assertRaises(StopIteration):
+            next(gen)
+
+        self.assertEqual(list(f.revs()), [0, 1, 2])
+        self.assertEqual(list(f.revs(0)), [0, 1, 2])
+        self.assertEqual(list(f.revs(1)), [1, 2])
+        self.assertEqual(list(f.revs(2)), [2])
+        self.assertEqual(list(f.revs(3)), [])
+        self.assertEqual(list(f.revs(stop=1)), [0, 1])
+        self.assertEqual(list(f.revs(stop=2)), [0, 1, 2])
+        self.assertEqual(list(f.revs(stop=3)), [0, 1, 2])
+        self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
+        self.assertEqual(list(f.revs(2, 1)), [2, 1])
+        # TODO this is wrong
+        self.assertEqual(list(f.revs(3, 2)), [3, 2])
+
+        self.assertEqual(f.parents(node0), (nullid, nullid))
+        self.assertEqual(f.parents(node1), (node0, nullid))
+        self.assertEqual(f.parents(node2), (node1, nullid))
+
+        self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
+        self.assertEqual(f.parentrevs(1), (0, nullrev))
+        self.assertEqual(f.parentrevs(2), (1, nullrev))
+
+        self.assertEqual(f.rev(node0), 0)
+        self.assertEqual(f.rev(node1), 1)
+        self.assertEqual(f.rev(node2), 2)
+
+        with self.assertRaises(error.LookupError):
+            f.rev(b'\x01' * 20)
+
+        self.assertEqual(f.node(0), node0)
+        self.assertEqual(f.node(1), node1)
+        self.assertEqual(f.node(2), node2)
+
+        with self.assertRaises(IndexError):
+            f.node(3)
+
+        self.assertEqual(f.lookup(node0), node0)
+        self.assertEqual(f.lookup(0), node0)
+        self.assertEqual(f.lookup(b'0'), node0)
+        self.assertEqual(f.lookup(hex(node0)), node0)
+
+        self.assertEqual(f.lookup(node1), node1)
+        self.assertEqual(f.lookup(1), node1)
+        self.assertEqual(f.lookup(b'1'), node1)
+        self.assertEqual(f.lookup(hex(node1)), node1)
+
+        self.assertEqual(f.linkrev(0), 0)
+        self.assertEqual(f.linkrev(1), 1)
+        self.assertEqual(f.linkrev(2), 3)
+
+        with self.assertRaises(IndexError):
+            f.linkrev(3)
+
+        self.assertEqual(f.flags(0), 0)
+        self.assertEqual(f.flags(1), 0)
+        self.assertEqual(f.flags(2), 0)
+
+        with self.assertRaises(IndexError):
+            f.flags(3)
+
+        self.assertFalse(f.iscensored(0))
+        self.assertFalse(f.iscensored(1))
+        self.assertFalse(f.iscensored(2))
+
+        with self.assertRaises(IndexError):
+            f.iscensored(3)
+
+        self.assertEqual(f.commonancestorsheads(node1, nullid), [])
+        self.assertEqual(f.commonancestorsheads(node1, node0), [node0])
+        self.assertEqual(f.commonancestorsheads(node1, node1), [node1])
+        self.assertEqual(f.commonancestorsheads(node0, node1), [node0])
+        self.assertEqual(f.commonancestorsheads(node1, node2), [node1])
+        self.assertEqual(f.commonancestorsheads(node2, node1), [node1])
+
+        self.assertEqual(list(f.descendants([0])), [1, 2])
+        self.assertEqual(list(f.descendants([1])), [2])
+        self.assertEqual(list(f.descendants([0, 1])), [1, 2])
+
+        self.assertEqual(f.heads(), [node2])
+        self.assertEqual(f.heads(node0), [node2])
+        self.assertEqual(f.heads(node1), [node2])
+        self.assertEqual(f.heads(node2), [node2])
+
+        # TODO this behavior seems wonky. Is it correct? If so, the
+        # docstring for heads() should be updated to reflect desired
+        # behavior.
+        self.assertEqual(f.heads(stop=[node1]), [node1, node2])
+        self.assertEqual(f.heads(stop=[node0]), [node0, node2])
+        self.assertEqual(f.heads(stop=[node1, node2]), [node1, node2])
+
+        with self.assertRaises(error.LookupError):
+            f.heads(stop=[b'\x01' * 20])
+
+        self.assertEqual(f.children(node0), [node1])
+        self.assertEqual(f.children(node1), [node2])
+        self.assertEqual(f.children(node2), [])
+
+        self.assertEqual(f.deltaparent(0), nullrev)
+        self.assertEqual(f.deltaparent(1), 0)
+        self.assertEqual(f.deltaparent(2), 1)
+
+    def testmultipleheads(self):
+        f = self._makefilefn()
+
+        with self._maketransactionfn() as tr:
+            node0 = f.add(b'0', None, tr, 0, nullid, nullid)
+            node1 = f.add(b'1', None, tr, 1, node0, nullid)
+            node2 = f.add(b'2', None, tr, 2, node1, nullid)
+            node3 = f.add(b'3', None, tr, 3, node0, nullid)
+            node4 = f.add(b'4', None, tr, 4, node3, nullid)
+            node5 = f.add(b'5', None, tr, 5, node0, nullid)
+
+        self.assertEqual(len(f), 6)
+
+        self.assertEqual(list(f.descendants([0])), [1, 2, 3, 4, 5])
+        self.assertEqual(list(f.descendants([1])), [2])
+        self.assertEqual(list(f.descendants([2])), [])
+        self.assertEqual(list(f.descendants([3])), [4])
+        self.assertEqual(list(f.descendants([0, 1])), [1, 2, 3, 4, 5])
+        self.assertEqual(list(f.descendants([1, 3])), [2, 4])
+
+        self.assertEqual(f.heads(), [node2, node4, node5])
+        self.assertEqual(f.heads(node0), [node2, node4, node5])
+        self.assertEqual(f.heads(node1), [node2])
+        self.assertEqual(f.heads(node2), [node2])
+        self.assertEqual(f.heads(node3), [node4])
+        self.assertEqual(f.heads(node4), [node4])
+        self.assertEqual(f.heads(node5), [node5])
+
+        # TODO this seems wrong.
+        self.assertEqual(f.heads(stop=[node0]), [node0, node2, node4, node5])
+        self.assertEqual(f.heads(stop=[node1]), [node1, node2, node4, node5])
+
+        self.assertEqual(f.children(node0), [node1, node3, node5])
+        self.assertEqual(f.children(node1), [node2])
+        self.assertEqual(f.children(node2), [])
+        self.assertEqual(f.children(node3), [node4])
+        self.assertEqual(f.children(node4), [])
+        self.assertEqual(f.children(node5), [])
+
+class ifiledatatests(basetestcase):
+    """Generic tests for the ifiledata interface.
+
+    All file storage backends for data should conform to the tests in this
+    class.
+
+    Use ``makeifiledatatests()`` to create an instance of this type.
+    """
+    def testempty(self):
+        f = self._makefilefn()
+
+        self.assertEqual(f.rawsize(nullrev), 0)
+
+        for i in range(-5, 5):
+            if i == nullrev:
+                continue
+
+            with self.assertRaises(IndexError):
+                f.rawsize(i)
+
+        self.assertEqual(f.size(nullrev), 0)
+
+        for i in range(-5, 5):
+            if i == nullrev:
+                continue
+
+            with self.assertRaises(IndexError):
+                f.size(i)
+
+        with self.assertRaises(error.StorageError):
+            f.checkhash(b'', nullid)
+
+        with self.assertRaises(error.LookupError):
+            f.checkhash(b'', b'\x01' * 20)
+
+        self.assertEqual(f.revision(nullid), b'')
+        self.assertEqual(f.revision(nullid, raw=True), b'')
+
+        with self.assertRaises(error.LookupError):
+            f.revision(b'\x01' * 20)
+
+        self.assertEqual(f.read(nullid), b'')
+
+        with self.assertRaises(error.LookupError):
+            f.read(b'\x01' * 20)
+
+        self.assertFalse(f.renamed(nullid))
+
+        with self.assertRaises(error.LookupError):
+            f.read(b'\x01' * 20)
+
+        self.assertTrue(f.cmp(nullid, b''))
+        self.assertTrue(f.cmp(nullid, b'foo'))
+
+        with self.assertRaises(error.LookupError):
+            f.cmp(b'\x01' * 20, b'irrelevant')
+
+        self.assertEqual(f.revdiff(nullrev, nullrev), b'')
+
+        with self.assertRaises(IndexError):
+            f.revdiff(0, nullrev)
+
+        with self.assertRaises(IndexError):
+            f.revdiff(nullrev, 0)
+
+        with self.assertRaises(IndexError):
+            f.revdiff(0, 0)
+
+        gen = f.emitrevisiondeltas([])
+        with self.assertRaises(StopIteration):
+            next(gen)
+
+        requests = [
+            revisiondeltarequest(nullid, nullid, nullid, nullid, nullid, False),
+        ]
+        gen = f.emitrevisiondeltas(requests)
+
+        delta = next(gen)
+
+        self.assertEqual(delta.node, nullid)
+        self.assertEqual(delta.p1node, nullid)
+        self.assertEqual(delta.p2node, nullid)
+        self.assertEqual(delta.linknode, nullid)
+        self.assertEqual(delta.basenode, nullid)
+        self.assertIsNone(delta.baserevisionsize)
+        self.assertEqual(delta.revision, b'')
+        self.assertIsNone(delta.delta)
+
+        with self.assertRaises(StopIteration):
+            next(gen)
+
+        requests = [
+            revisiondeltarequest(nullid, nullid, nullid, nullid, nullid, False),
+            revisiondeltarequest(nullid, b'\x01' * 20, b'\x02' * 20,
+                                 b'\x03' * 20, nullid, False)
+        ]
+
+        gen = f.emitrevisiondeltas(requests)
+
+        next(gen)
+        delta = next(gen)
+
+        self.assertEqual(delta.node, nullid)
+        self.assertEqual(delta.p1node, b'\x01' * 20)
+        self.assertEqual(delta.p2node, b'\x02' * 20)
+        self.assertEqual(delta.linknode, b'\x03' * 20)
+        self.assertEqual(delta.basenode, nullid)
+        self.assertIsNone(delta.baserevisionsize)
+        self.assertEqual(delta.revision, b'')
+        self.assertIsNone(delta.delta)
+
+        with self.assertRaises(StopIteration):
+            next(gen)
+
+    def testsinglerevision(self):
+        fulltext = b'initial'
+
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            node = f.add(fulltext, None, tr, 0, nullid, nullid)
+
+        self.assertEqual(f.rawsize(0), len(fulltext))
+
+        with self.assertRaises(IndexError):
+            f.rawsize(1)
+
+        self.assertEqual(f.size(0), len(fulltext))
+
+        with self.assertRaises(IndexError):
+            f.size(1)
+
+        f.checkhash(fulltext, node)
+        f.checkhash(fulltext, node, nullid, nullid)
+
+        with self.assertRaises(error.StorageError):
+            f.checkhash(fulltext + b'extra', node)
+
+        with self.assertRaises(error.StorageError):
+            f.checkhash(fulltext, node, b'\x01' * 20, nullid)
+
+        with self.assertRaises(error.StorageError):
+            f.checkhash(fulltext, node, nullid, b'\x01' * 20)
+
+        self.assertEqual(f.revision(node), fulltext)
+        self.assertEqual(f.revision(node, raw=True), fulltext)
+
+        self.assertEqual(f.read(node), fulltext)
+
+        self.assertFalse(f.renamed(node))
+
+        self.assertFalse(f.cmp(node, fulltext))
+        self.assertTrue(f.cmp(node, fulltext + b'extra'))
+
+        self.assertEqual(f.revdiff(0, 0), b'')
+        self.assertEqual(f.revdiff(nullrev, 0),
+                         b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07%s' %
+                         fulltext)
+
+        self.assertEqual(f.revdiff(0, nullrev),
+                         b'\x00\x00\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00')
+
+        requests = [
+            revisiondeltarequest(node, nullid, nullid, nullid, nullid, False),
+        ]
+        gen = f.emitrevisiondeltas(requests)
+
+        delta = next(gen)
+
+        self.assertEqual(delta.node, node)
+        self.assertEqual(delta.p1node, nullid)
+        self.assertEqual(delta.p2node, nullid)
+        self.assertEqual(delta.linknode, nullid)
+        self.assertEqual(delta.basenode, nullid)
+        self.assertIsNone(delta.baserevisionsize)
+        self.assertEqual(delta.revision, fulltext)
+        self.assertIsNone(delta.delta)
+
+        with self.assertRaises(StopIteration):
+            next(gen)
+
+    def testmultiplerevisions(self):
+        fulltext0 = b'x' * 1024
+        fulltext1 = fulltext0 + b'y'
+        fulltext2 = b'y' + fulltext0 + b'z'
+
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
+            node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
+            node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
+
+        self.assertEqual(f.rawsize(0), len(fulltext0))
+        self.assertEqual(f.rawsize(1), len(fulltext1))
+        self.assertEqual(f.rawsize(2), len(fulltext2))
+
+        with self.assertRaises(IndexError):
+            f.rawsize(3)
+
+        self.assertEqual(f.size(0), len(fulltext0))
+        self.assertEqual(f.size(1), len(fulltext1))
+        self.assertEqual(f.size(2), len(fulltext2))
+
+        with self.assertRaises(IndexError):
+            f.size(3)
+
+        f.checkhash(fulltext0, node0)
+        f.checkhash(fulltext1, node1)
+        f.checkhash(fulltext1, node1, node0, nullid)
+        f.checkhash(fulltext2, node2, node1, nullid)
+
+        with self.assertRaises(error.StorageError):
+            f.checkhash(fulltext1, b'\x01' * 20)
+
+        with self.assertRaises(error.StorageError):
+            f.checkhash(fulltext1 + b'extra', node1, node0, nullid)
+
+        with self.assertRaises(error.StorageError):
+            f.checkhash(fulltext1, node1, node0, node0)
+
+        self.assertEqual(f.revision(node0), fulltext0)
+        self.assertEqual(f.revision(node0, raw=True), fulltext0)
+        self.assertEqual(f.revision(node1), fulltext1)
+        self.assertEqual(f.revision(node1, raw=True), fulltext1)
+        self.assertEqual(f.revision(node2), fulltext2)
+        self.assertEqual(f.revision(node2, raw=True), fulltext2)
+
+        with self.assertRaises(error.LookupError):
+            f.revision(b'\x01' * 20)
+
+        self.assertEqual(f.read(node0), fulltext0)
+        self.assertEqual(f.read(node1), fulltext1)
+        self.assertEqual(f.read(node2), fulltext2)
+
+        with self.assertRaises(error.LookupError):
+            f.read(b'\x01' * 20)
+
+        self.assertFalse(f.renamed(node0))
+        self.assertFalse(f.renamed(node1))
+        self.assertFalse(f.renamed(node2))
+
+        with self.assertRaises(error.LookupError):
+            f.renamed(b'\x01' * 20)
+
+        self.assertFalse(f.cmp(node0, fulltext0))
+        self.assertFalse(f.cmp(node1, fulltext1))
+        self.assertFalse(f.cmp(node2, fulltext2))
+
+        self.assertTrue(f.cmp(node1, fulltext0))
+        self.assertTrue(f.cmp(node2, fulltext1))
+
+        with self.assertRaises(error.LookupError):
+            f.cmp(b'\x01' * 20, b'irrelevant')
+
+        self.assertEqual(f.revdiff(0, 1),
+                         b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
+                         fulltext1)
+
+        self.assertEqual(f.revdiff(0, 2),
+                         b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x02' +
+                         fulltext2)
+
+        requests = [
+            revisiondeltarequest(node0, nullid, nullid, b'\x01' * 20, nullid,
+                                 False),
+            revisiondeltarequest(node1, node0, nullid, b'\x02' * 20, node0,
+                                 False),
+            revisiondeltarequest(node2, node1, nullid, b'\x03' * 20, node1,
+                                 False),
+        ]
+        gen = f.emitrevisiondeltas(requests)
+
+        delta = next(gen)
+
+        self.assertEqual(delta.node, node0)
+        self.assertEqual(delta.p1node, nullid)
+        self.assertEqual(delta.p2node, nullid)
+        self.assertEqual(delta.linknode, b'\x01' * 20)
+        self.assertEqual(delta.basenode, nullid)
+        self.assertIsNone(delta.baserevisionsize)
+        self.assertEqual(delta.revision, fulltext0)
+        self.assertIsNone(delta.delta)
+
+        delta = next(gen)
+
+        self.assertEqual(delta.node, node1)
+        self.assertEqual(delta.p1node, node0)
+        self.assertEqual(delta.p2node, nullid)
+        self.assertEqual(delta.linknode, b'\x02' * 20)
+        self.assertEqual(delta.basenode, node0)
+        self.assertIsNone(delta.baserevisionsize)
+        self.assertIsNone(delta.revision)
+        self.assertEqual(delta.delta,
+                         b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
+                         fulltext1)
+
+        delta = next(gen)
+
+        self.assertEqual(delta.node, node2)
+        self.assertEqual(delta.p1node, node1)
+        self.assertEqual(delta.p2node, nullid)
+        self.assertEqual(delta.linknode, b'\x03' * 20)
+        self.assertEqual(delta.basenode, node1)
+        self.assertIsNone(delta.baserevisionsize)
+        self.assertIsNone(delta.revision)
+        self.assertEqual(delta.delta,
+                         b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
+                         fulltext2)
+
+        with self.assertRaises(StopIteration):
+            next(gen)
+
+    def testrenamed(self):
+        fulltext0 = b'foo'
+        fulltext1 = b'bar'
+        fulltext2 = b'baz'
+
+        meta1 = {
+            b'copy': b'source0',
+            b'copyrev': b'a' * 40,
+        }
+
+        meta2 = {
+            b'copy': b'source1',
+            b'copyrev': b'b' * 40,
+        }
+
+        stored1 = b''.join([
+            b'\x01\ncopy: source0\n',
+            b'copyrev: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n\x01\n',
+            fulltext1,
+        ])
+
+        stored2 = b''.join([
+            b'\x01\ncopy: source1\n',
+            b'copyrev: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n\x01\n',
+            fulltext2,
+        ])
+
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
+            node1 = f.add(fulltext1, meta1, tr, 1, node0, nullid)
+            node2 = f.add(fulltext2, meta2, tr, 2, nullid, nullid)
+
+        self.assertEqual(f.rawsize(1), len(stored1))
+        self.assertEqual(f.rawsize(2), len(stored2))
+
+        # Metadata header isn't recognized when parent isn't nullid.
+        self.assertEqual(f.size(1), len(stored1))
+        self.assertEqual(f.size(2), len(fulltext2))
+
+        self.assertEqual(f.revision(node1), stored1)
+        self.assertEqual(f.revision(node1, raw=True), stored1)
+        self.assertEqual(f.revision(node2), stored2)
+        self.assertEqual(f.revision(node2, raw=True), stored2)
+
+        self.assertEqual(f.read(node1), fulltext1)
+        self.assertEqual(f.read(node2), fulltext2)
+
+        # Returns False when first parent is set.
+        self.assertFalse(f.renamed(node1))
+        self.assertEqual(f.renamed(node2), (b'source1', b'\xbb' * 20))
+
+        self.assertTrue(f.cmp(node1, fulltext1))
+        self.assertTrue(f.cmp(node1, stored1))
+        self.assertFalse(f.cmp(node2, fulltext2))
+        self.assertTrue(f.cmp(node2, stored2))
+
+    def testmetadataprefix(self):
+        # Content with metadata prefix has extra prefix inserted in storage.
+        fulltext0 = b'\x01\nfoo'
+        stored0 = b'\x01\n\x01\n\x01\nfoo'
+
+        fulltext1 = b'\x01\nbar'
+        meta1 = {
+            b'copy': b'source0',
+            b'copyrev': b'b' * 40,
+        }
+        stored1 = b''.join([
+            b'\x01\ncopy: source0\n',
+            b'copyrev: %s\n' % (b'b' * 40),
+            b'\x01\n\x01\nbar',
+        ])
+
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            node0 = f.add(fulltext0, {}, tr, 0, nullid, nullid)
+            node1 = f.add(fulltext1, meta1, tr, 1, nullid, nullid)
+
+        self.assertEqual(f.rawsize(0), len(stored0))
+        self.assertEqual(f.rawsize(1), len(stored1))
+
+        # TODO this is buggy.
+        self.assertEqual(f.size(0), len(fulltext0) + 4)
+
+        self.assertEqual(f.size(1), len(fulltext1))
+
+        self.assertEqual(f.revision(node0), stored0)
+        self.assertEqual(f.revision(node0, raw=True), stored0)
+
+        self.assertEqual(f.revision(node1), stored1)
+        self.assertEqual(f.revision(node1, raw=True), stored1)
+
+        self.assertEqual(f.read(node0), fulltext0)
+        self.assertEqual(f.read(node1), fulltext1)
+
+        self.assertFalse(f.cmp(node0, fulltext0))
+        self.assertTrue(f.cmp(node0, stored0))
+
+        self.assertFalse(f.cmp(node1, fulltext1))
+        self.assertTrue(f.cmp(node1, stored0))
+
+    def testcensored(self):
+        f = self._makefilefn()
+
+        stored1 = revlog.packmeta({
+            b'censored': b'tombstone',
+        }, b'')
+
+        # TODO tests are incomplete because we need the node to be
+        # different due to presence of censor metadata. But we can't
+        # do this with addrevision().
+        with self._maketransactionfn() as tr:
+            node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
+            f.addrevision(stored1, tr, 1, node0, nullid,
+                          flags=revlog.REVIDX_ISCENSORED)
+
+        self.assertEqual(f.flags(1), revlog.REVIDX_ISCENSORED)
+        self.assertTrue(f.iscensored(1))
+
+        self.assertEqual(f.revision(1), stored1)
+        self.assertEqual(f.revision(1, raw=True), stored1)
+
+        self.assertEqual(f.read(1), b'')
+
+class ifilemutationtests(basetestcase):
+    """Generic tests for the ifilemutation interface.
+
+    All file storage backends that support writing should conform to this
+    interface.
+
+    Use ``makeifilemutationtests()`` to create an instance of this type.
+    """
+    def testaddnoop(self):
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
+            node1 = f.add(b'foo', None, tr, 0, nullid, nullid)
+            # Varying by linkrev shouldn't impact hash.
+            node2 = f.add(b'foo', None, tr, 1, nullid, nullid)
+
+        self.assertEqual(node1, node0)
+        self.assertEqual(node2, node0)
+        self.assertEqual(len(f), 1)
+
+    def testaddrevisionbadnode(self):
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            # Adding a revision with bad node value fails.
+            with self.assertRaises(error.StorageError):
+                f.addrevision(b'foo', tr, 0, nullid, nullid, node=b'\x01' * 20)
+
+    def testaddrevisionunknownflag(self):
+        f = self._makefilefn()
+        with self._maketransactionfn() as tr:
+            for i in range(15, 0, -1):
+                if (1 << i) & ~revlog.REVIDX_KNOWN_FLAGS:
+                    flags = 1 << i
+                    break
+
+            with self.assertRaises(error.StorageError):
+                f.addrevision(b'foo', tr, 0, nullid, nullid, flags=flags)
+
+    def testaddgroupsimple(self):
+        f = self._makefilefn()
+
+        callbackargs = []
+        def cb(*args, **kwargs):
+            callbackargs.append((args, kwargs))
+
+        def linkmapper(node):
+            return 0
+
+        with self._maketransactionfn() as tr:
+            nodes = f.addgroup([], None, tr, addrevisioncb=cb)
+
+        self.assertEqual(nodes, [])
+        self.assertEqual(callbackargs, [])
+        self.assertEqual(len(f), 0)
+
+        fulltext0 = b'foo'
+        delta0 = mdiff.trivialdiffheader(len(fulltext0)) + fulltext0
+
+        deltas = [
+            (b'\x01' * 20, nullid, nullid, nullid, nullid, delta0, 0),
+        ]
+
+        with self._maketransactionfn() as tr:
+            with self.assertRaises(error.StorageError):
+                f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
+
+            node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
+
+        f = self._makefilefn()
+
+        deltas = [
+            (node0, nullid, nullid, nullid, nullid, delta0, 0),
+        ]
+
+        with self._maketransactionfn() as tr:
+            nodes = f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
+
+        self.assertEqual(nodes, [
+            b'\x49\xd8\xcb\xb1\x5c\xe2\x57\x92\x04\x47'
+            b'\x00\x6b\x46\x97\x8b\x7a\xf9\x80\xa9\x79'])
+
+        self.assertEqual(len(callbackargs), 1)
+        self.assertEqual(callbackargs[0][0][1], nodes[0])
+
+        self.assertEqual(list(f.revs()), [0])
+        self.assertEqual(f.rev(nodes[0]), 0)
+        self.assertEqual(f.node(0), nodes[0])
+
+    def testaddgroupmultiple(self):
+        f = self._makefilefn()
+
+        fulltexts = [
+            b'foo',
+            b'bar',
+            b'x' * 1024,
+        ]
+
+        nodes = []
+        with self._maketransactionfn() as tr:
+            for fulltext in fulltexts:
+                nodes.append(f.add(fulltext, None, tr, 0, nullid, nullid))
+
+        f = self._makefilefn()
+        deltas = []
+        for i, fulltext in enumerate(fulltexts):
+            delta = mdiff.trivialdiffheader(len(fulltext)) + fulltext
+
+            deltas.append((nodes[i], nullid, nullid, nullid, nullid, delta, 0))
+
+        with self._maketransactionfn() as tr:
+            self.assertEqual(f.addgroup(deltas, lambda x: 0, tr), nodes)
+
+        self.assertEqual(len(f), len(deltas))
+        self.assertEqual(list(f.revs()), [0, 1, 2])
+        self.assertEqual(f.rev(nodes[0]), 0)
+        self.assertEqual(f.rev(nodes[1]), 1)
+        self.assertEqual(f.rev(nodes[2]), 2)
+        self.assertEqual(f.node(0), nodes[0])
+        self.assertEqual(f.node(1), nodes[1])
+        self.assertEqual(f.node(2), nodes[2])
+
+def makeifileindextests(makefilefn, maketransactionfn):
+    """Create a unittest.TestCase class suitable for testing file storage.
+
+    ``makefilefn`` is a callable which receives the test case as an
+    argument and returns an object implementing the ``ifilestorage`` interface.
+
+    ``maketransactionfn`` is a callable which receives the test case as an
+    argument and returns a transaction object.
+
+    Returns a type that is a ``unittest.TestCase`` that can be used for
+    testing the object implementing the file storage interface. Simply
+    assign the returned value to a module-level attribute and a test loader
+    should find and run it automatically.
+    """
+    d = {
+        r'_makefilefn': makefilefn,
+        r'_maketransactionfn': maketransactionfn,
+    }
+    return type(r'ifileindextests', (ifileindextests,), d)
+
+def makeifiledatatests(makefilefn, maketransactionfn):
+    d = {
+        r'_makefilefn': makefilefn,
+        r'_maketransactionfn': maketransactionfn,
+    }
+    return type(r'ifiledatatests', (ifiledatatests,), d)
+
+def makeifilemutationtests(makefilefn, maketransactionfn):
+    d = {
+        r'_makefilefn': makefilefn,
+        r'_maketransactionfn': maketransactionfn,
+    }
+    return type(r'ifilemutationtests', (ifilemutationtests,), d)
--- a/mercurial/transaction.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/transaction.py	Wed Sep 26 20:33:09 2018 +0900
@@ -38,7 +38,7 @@
 
 def active(func):
     def _active(self, *args, **kwds):
-        if self.count == 0:
+        if self._count == 0:
             raise error.Abort(_(
                 'cannot use transaction when it is already committed/aborted'))
         return func(self, *args, **kwds)
@@ -119,37 +119,37 @@
         which determine whether file stat ambiguity should be avoided
         for corresponded files.
         """
-        self.count = 1
-        self.usages = 1
-        self.report = report
+        self._count = 1
+        self._usages = 1
+        self._report = report
         # a vfs to the store content
-        self.opener = opener
+        self._opener = opener
         # a map to access file in various {location -> vfs}
         vfsmap = vfsmap.copy()
         vfsmap[''] = opener  # set default value
         self._vfsmap = vfsmap
-        self.after = after
-        self.entries = []
-        self.map = {}
-        self.journal = journalname
-        self.undoname = undoname
+        self._after = after
+        self._entries = []
+        self._map = {}
+        self._journal = journalname
+        self._undoname = undoname
         self._queue = []
         # A callback to validate transaction content before closing it.
         # should raise exception is anything is wrong.
         # target user is repository hooks.
         if validator is None:
             validator = lambda tr: None
-        self.validator = validator
+        self._validator = validator
         # A callback to do something just after releasing transaction.
         if releasefn is None:
             releasefn = lambda tr, success: None
-        self.releasefn = releasefn
+        self._releasefn = releasefn
 
-        self.checkambigfiles = set()
+        self._checkambigfiles = set()
         if checkambigfiles:
-            self.checkambigfiles.update(checkambigfiles)
+            self._checkambigfiles.update(checkambigfiles)
 
-        self.names = [name]
+        self._names = [name]
 
         # A dict dedicated to precisely tracking the changes introduced in the
         # transaction.
@@ -157,7 +157,7 @@
 
         # a dict of arguments to be passed to hooks
         self.hookargs = {}
-        self.file = opener.open(self.journal, "w")
+        self._file = opener.open(self._journal, "w")
 
         # a list of ('location', 'path', 'backuppath', cache) entries.
         # - if 'backuppath' is empty, no file existed at backup time
@@ -167,12 +167,12 @@
         # (cache is currently unused)
         self._backupentries = []
         self._backupmap = {}
-        self._backupjournal = "%s.backupfiles" % self.journal
+        self._backupjournal = "%s.backupfiles" % self._journal
         self._backupsfile = opener.open(self._backupjournal, 'w')
         self._backupsfile.write('%d\n' % version)
 
         if createmode is not None:
-            opener.chmod(self.journal, createmode & 0o666)
+            opener.chmod(self._journal, createmode & 0o666)
             opener.chmod(self._backupjournal, createmode & 0o666)
 
         # hold file generations to be performed on commit
@@ -189,12 +189,12 @@
         self._abortcallback = {}
 
     def __repr__(self):
-        name = r'/'.join(self.names)
+        name = r'/'.join(self._names)
         return (r'<transaction name=%s, count=%d, usages=%d>' %
-                (name, self.count, self.usages))
+                (name, self._count, self._usages))
 
     def __del__(self):
-        if self.journal:
+        if self._journal:
             self._abort()
 
     @active
@@ -218,7 +218,7 @@
     @active
     def add(self, file, offset, data=None):
         """record the state of an append-only file before update"""
-        if file in self.map or file in self._backupmap:
+        if file in self._map or file in self._backupmap:
             return
         if self._queue:
             self._queue[-1].append((file, offset, data))
@@ -228,13 +228,13 @@
 
     def _addentry(self, file, offset, data):
         """add a append-only entry to memory and on-disk state"""
-        if file in self.map or file in self._backupmap:
+        if file in self._map or file in self._backupmap:
             return
-        self.entries.append((file, offset, data))
-        self.map[file] = len(self.entries) - 1
+        self._entries.append((file, offset, data))
+        self._map[file] = len(self._entries) - 1
         # add enough data to the journal to do the truncate
-        self.file.write("%s\0%d\n" % (file, offset))
-        self.file.flush()
+        self._file.write("%s\0%d\n" % (file, offset))
+        self._file.flush()
 
     @active
     def addbackup(self, file, hardlink=True, location=''):
@@ -251,11 +251,11 @@
             msg = 'cannot use transaction.addbackup inside "group"'
             raise error.ProgrammingError(msg)
 
-        if file in self.map or file in self._backupmap:
+        if file in self._map or file in self._backupmap:
             return
         vfs = self._vfsmap[location]
         dirname, filename = vfs.split(file)
-        backupfilename = "%s.backup.%s" % (self.journal, filename)
+        backupfilename = "%s.backup.%s" % (self._journal, filename)
         backupfile = vfs.reljoin(dirname, backupfilename)
         if vfs.exists(file):
             filepath = vfs.join(file)
@@ -340,7 +340,7 @@
                         checkambig = False
                     else:
                         self.addbackup(name, location=location)
-                        checkambig = (name, location) in self.checkambigfiles
+                        checkambig = (name, location) in self._checkambigfiles
                     files.append(vfs(name, 'w', atomictemp=True,
                                      checkambig=checkambig))
                 genfunc(*files)
@@ -351,8 +351,8 @@
 
     @active
     def find(self, file):
-        if file in self.map:
-            return self.entries[self.map[file]]
+        if file in self._map:
+            return self._entries[self._map[file]]
         if file in self._backupmap:
             return self._backupentries[self._backupmap[file]]
         return None
@@ -364,31 +364,31 @@
         that are not pending in the queue
         '''
 
-        if file not in self.map:
+        if file not in self._map:
             raise KeyError(file)
-        index = self.map[file]
-        self.entries[index] = (file, offset, data)
-        self.file.write("%s\0%d\n" % (file, offset))
-        self.file.flush()
+        index = self._map[file]
+        self._entries[index] = (file, offset, data)
+        self._file.write("%s\0%d\n" % (file, offset))
+        self._file.flush()
 
     @active
     def nest(self, name=r'<unnamed>'):
-        self.count += 1
-        self.usages += 1
-        self.names.append(name)
+        self._count += 1
+        self._usages += 1
+        self._names.append(name)
         return self
 
     def release(self):
-        if self.count > 0:
-            self.usages -= 1
-        if self.names:
-            self.names.pop()
+        if self._count > 0:
+            self._usages -= 1
+        if self._names:
+            self._names.pop()
         # if the transaction scopes are left without being closed, fail
-        if self.count > 0 and self.usages == 0:
+        if self._count > 0 and self._usages == 0:
             self._abort()
 
     def running(self):
-        return self.count > 0
+        return self._count > 0
 
     def addpending(self, category, callback):
         """add a callback to be called when the transaction is pending
@@ -454,9 +454,9 @@
     @active
     def close(self):
         '''commit the transaction'''
-        if self.count == 1:
-            self.validator(self)  # will raise exception if needed
-            self.validator = None # Help prevent cycles.
+        if self._count == 1:
+            self._validator(self)  # will raise exception if needed
+            self._validator = None # Help prevent cycles.
             self._generatefiles(group=gengroupprefinalize)
             categories = sorted(self._finalizecallback)
             for cat in categories:
@@ -465,16 +465,16 @@
             self._finalizecallback = None
             self._generatefiles(group=gengrouppostfinalize)
 
-        self.count -= 1
-        if self.count != 0:
+        self._count -= 1
+        if self._count != 0:
             return
-        self.file.close()
+        self._file.close()
         self._backupsfile.close()
         # cleanup temporary files
         for l, f, b, c in self._backupentries:
             if l not in self._vfsmap and c:
-                self.report("couldn't remove %s: unknown cache location %s\n"
-                            % (b, l))
+                self._report("couldn't remove %s: unknown cache location %s\n"
+                             % (b, l))
                 continue
             vfs = self._vfsmap[l]
             if not f and b and vfs.exists(b):
@@ -484,21 +484,21 @@
                     if not c:
                         raise
                     # Abort may be raise by read only opener
-                    self.report("couldn't remove %s: %s\n"
-                                % (vfs.join(b), inst))
-        self.entries = []
+                    self._report("couldn't remove %s: %s\n"
+                                 % (vfs.join(b), inst))
+        self._entries = []
         self._writeundo()
-        if self.after:
-            self.after()
-            self.after = None # Help prevent cycles.
-        if self.opener.isfile(self._backupjournal):
-            self.opener.unlink(self._backupjournal)
-        if self.opener.isfile(self.journal):
-            self.opener.unlink(self.journal)
+        if self._after:
+            self._after()
+            self._after = None # Help prevent cycles.
+        if self._opener.isfile(self._backupjournal):
+            self._opener.unlink(self._backupjournal)
+        if self._opener.isfile(self._journal):
+            self._opener.unlink(self._journal)
         for l, _f, b, c in self._backupentries:
             if l not in self._vfsmap and c:
-                self.report("couldn't remove %s: unknown cache location"
-                            "%s\n" % (b, l))
+                self._report("couldn't remove %s: unknown cache location"
+                             "%s\n" % (b, l))
                 continue
             vfs = self._vfsmap[l]
             if b and vfs.exists(b):
@@ -508,13 +508,13 @@
                     if not c:
                         raise
                     # Abort may be raise by read only opener
-                    self.report("couldn't remove %s: %s\n"
-                                % (vfs.join(b), inst))
+                    self._report("couldn't remove %s: %s\n"
+                                 % (vfs.join(b), inst))
         self._backupentries = []
-        self.journal = None
+        self._journal = None
 
-        self.releasefn(self, True) # notify success of closing transaction
-        self.releasefn = None # Help prevent cycles.
+        self._releasefn(self, True) # notify success of closing transaction
+        self._releasefn = None # Help prevent cycles.
 
         # run post close action
         categories = sorted(self._postclosecallback)
@@ -532,9 +532,10 @@
 
     def _writeundo(self):
         """write transaction data for possible future undo call"""
-        if self.undoname is None:
+        if self._undoname is None:
             return
-        undobackupfile = self.opener.open("%s.backupfiles" % self.undoname, 'w')
+        undobackupfile = self._opener.open("%s.backupfiles" % self._undoname,
+                                           'w')
         undobackupfile.write('%d\n' % version)
         for l, f, b, c in self._backupentries:
             if not f:  # temporary file
@@ -543,13 +544,13 @@
                 u = ''
             else:
                 if l not in self._vfsmap and c:
-                    self.report("couldn't remove %s: unknown cache location"
-                                "%s\n" % (b, l))
+                    self._report("couldn't remove %s: unknown cache location"
+                                 "%s\n" % (b, l))
                     continue
                 vfs = self._vfsmap[l]
                 base, name = vfs.split(b)
-                assert name.startswith(self.journal), name
-                uname = name.replace(self.journal, self.undoname, 1)
+                assert name.startswith(self._journal), name
+                uname = name.replace(self._journal, self._undoname, 1)
                 u = vfs.reljoin(base, uname)
                 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
             undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c))
@@ -557,36 +558,36 @@
 
 
     def _abort(self):
-        self.count = 0
-        self.usages = 0
-        self.file.close()
+        self._count = 0
+        self._usages = 0
+        self._file.close()
         self._backupsfile.close()
 
         try:
-            if not self.entries and not self._backupentries:
+            if not self._entries and not self._backupentries:
                 if self._backupjournal:
-                    self.opener.unlink(self._backupjournal)
-                if self.journal:
-                    self.opener.unlink(self.journal)
+                    self._opener.unlink(self._backupjournal)
+                if self._journal:
+                    self._opener.unlink(self._journal)
                 return
 
-            self.report(_("transaction abort!\n"))
+            self._report(_("transaction abort!\n"))
 
             try:
                 for cat in sorted(self._abortcallback):
                     self._abortcallback[cat](self)
                 # Prevent double usage and help clear cycles.
                 self._abortcallback = None
-                _playback(self.journal, self.report, self.opener, self._vfsmap,
-                          self.entries, self._backupentries, False,
-                          checkambigfiles=self.checkambigfiles)
-                self.report(_("rollback completed\n"))
+                _playback(self._journal, self._report, self._opener,
+                          self._vfsmap, self._entries, self._backupentries,
+                          False, checkambigfiles=self._checkambigfiles)
+                self._report(_("rollback completed\n"))
             except BaseException:
-                self.report(_("rollback failed - please run hg recover\n"))
+                self._report(_("rollback failed - please run hg recover\n"))
         finally:
-            self.journal = None
-            self.releasefn(self, False) # notify failure of transaction
-            self.releasefn = None # Help prevent cycles.
+            self._journal = None
+            self._releasefn(self, False) # notify failure of transaction
+            self._releasefn = None # Help prevent cycles.
 
 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
     """Rolls back the transaction contained in the given file
--- a/mercurial/treediscovery.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/treediscovery.py	Wed Sep 26 20:33:09 2018 +0900
@@ -16,6 +16,7 @@
 )
 from . import (
     error,
+    pycompat,
 )
 
 def findcommonincoming(repo, remote, heads=None, force=False):
@@ -111,7 +112,7 @@
             progress.increment()
             repo.ui.debug("request %d: %s\n" %
                         (reqcnt, " ".join(map(short, r))))
-            for p in xrange(0, len(r), 10):
+            for p in pycompat.xrange(0, len(r), 10):
                 with remote.commandexecutor() as e:
                     branches = e.callcommand('branches', {
                         'nodes': r[p:p + 10],
--- a/mercurial/ui.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/ui.py	Wed Sep 26 20:33:09 2018 +0900
@@ -67,6 +67,9 @@
 update.check = noconflict
 # Show conflicts information in `hg status`
 status.verbose = True
+# Refuse to perform `hg resolve --mark` on files that still have conflict
+# markers
+resolve.mark-check = abort
 
 [diff]
 git = 1
@@ -392,7 +395,7 @@
     def readconfig(self, filename, root=None, trust=False,
                    sections=None, remap=None):
         try:
-            fp = open(filename, u'rb')
+            fp = open(filename, r'rb')
         except IOError:
             if not sections: # ignore unless we were looking for something
                 return
@@ -444,7 +447,7 @@
         if section in (None, 'paths'):
             # expand vars and ~
             # translate paths relative to root (or home) into absolute paths
-            root = root or pycompat.getcwd()
+            root = root or encoding.getcwd()
             for c in self._tcfg, self._ucfg, self._ocfg:
                 for n, p in c.items('paths'):
                     # Ignore sub-options.
@@ -1049,6 +1052,7 @@
             command in self.configlist('pager', 'ignore')
             or not self.configbool('ui', 'paginate')
             or not self.configbool('pager', 'attend-' + command, True)
+            or encoding.environ.get('TERM') == 'dumb'
             # TODO: if we want to allow HGPLAINEXCEPT=pager,
             # formatted() will need some adjustment.
             or not self.formatted()
@@ -1126,10 +1130,10 @@
 
         try:
             pager = subprocess.Popen(
-                command, shell=shell, bufsize=-1,
+                procutil.tonativestr(command), shell=shell, bufsize=-1,
                 close_fds=procutil.closefds, stdin=subprocess.PIPE,
                 stdout=procutil.stdout, stderr=procutil.stderr,
-                env=procutil.shellenviron(env))
+                env=procutil.tonativeenv(procutil.shellenviron(env)))
         except OSError as e:
             if e.errno == errno.ENOENT and not shell:
                 self.warn(_("missing pager command '%s', skipping pager\n")
@@ -1420,6 +1424,7 @@
                     return getpass.getpass('')
         except EOFError:
             raise error.ResponseExpected()
+
     def status(self, *msg, **opts):
         '''write status message to output (if ui.quiet is False)
 
@@ -1428,6 +1433,7 @@
         if not self.quiet:
             opts[r'label'] = opts.get(r'label', '') + ' ui.status'
             self.write(*msg, **opts)
+
     def warn(self, *msg, **opts):
         '''write warning message to output (stderr)
 
@@ -1435,6 +1441,15 @@
         '''
         opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
         self.write_err(*msg, **opts)
+
+    def error(self, *msg, **opts):
+        '''write error message to output (stderr)
+
+        This adds an output label of "ui.error".
+        '''
+        opts[r'label'] = opts.get(r'label', '') + ' ui.error'
+        self.write_err(*msg, **opts)
+
     def note(self, *msg, **opts):
         '''write note to output (if ui.verbose is True)
 
@@ -1443,6 +1458,7 @@
         if self.verbose:
             opts[r'label'] = opts.get(r'label', '') + ' ui.note'
             self.write(*msg, **opts)
+
     def debug(self, *msg, **opts):
         '''write debug message to output (if ui.debugflag is True)
 
--- a/mercurial/unionrepo.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/unionrepo.py	Wed Sep 26 20:33:09 2018 +0900
@@ -19,13 +19,13 @@
 from . import (
     changelog,
     cmdutil,
+    encoding,
     error,
     filelog,
     localrepo,
     manifest,
     mdiff,
     pathutil,
-    pycompat,
     revlog,
     util,
     vfs as vfsmod,
@@ -73,7 +73,7 @@
             # I have no idea if csize is valid in the base revlog context.
             e = (flags, None, rsize, base,
                  link, self.rev(p1node), self.rev(p2node), node)
-            self.index.insert(-1, e)
+            self.index.append(e)
             self.nodemap[node] = n
             self.bundlerevs.add(n)
             n += 1
@@ -192,19 +192,28 @@
     def canpush(self):
         return False
 
-class unionrepository(localrepo.localrepository):
-    def __init__(self, ui, path, path2):
-        localrepo.localrepository.__init__(self, ui, path)
+class unionrepository(object):
+    """Represents the union of data in 2 repositories.
+
+    Instances are not usable if constructed directly. Use ``instance()``
+    or ``makeunionrepository()`` to create a usable instance.
+    """
+    def __init__(self, repo2, url):
+        self.repo2 = repo2
+        self._url = url
+
         self.ui.setconfig('phases', 'publish', False, 'unionrepo')
 
-        self._url = 'union:%s+%s' % (util.expandpath(path),
-                                     util.expandpath(path2))
-        self.repo2 = localrepo.localrepository(ui, path2)
-
     @localrepo.unfilteredpropertycache
     def changelog(self):
         return unionchangelog(self.svfs, self.repo2.svfs)
 
+    @localrepo.unfilteredpropertycache
+    def manifestlog(self):
+        rootstore = unionmanifest(self.svfs, self.repo2.svfs,
+                                  self.unfiltered()._clrev)
+        return manifest.manifestlog(self.svfs, self, rootstore)
+
     def _clrev(self, rev2):
         """map from repo2 changelog rev to temporary rev in self.changelog"""
         node = self.repo2.changelog.node(rev2)
@@ -231,21 +240,21 @@
         return unionpeer(self)
 
     def getcwd(self):
-        return pycompat.getcwd() # always outside the repo
+        return encoding.getcwd() # always outside the repo
 
-def instance(ui, path, create, intents=None):
+def instance(ui, path, create, intents=None, createopts=None):
     if create:
         raise error.Abort(_('cannot create new union repository'))
     parentpath = ui.config("bundle", "mainreporoot")
     if not parentpath:
         # try to find the correct path to the working directory repo
-        parentpath = cmdutil.findrepo(pycompat.getcwd())
+        parentpath = cmdutil.findrepo(encoding.getcwd())
         if parentpath is None:
             parentpath = ''
     if parentpath:
         # Try to make the full path relative so we get a nice, short URL.
         # In particular, we don't want temp dir names in test outputs.
-        cwd = pycompat.getcwd()
+        cwd = encoding.getcwd()
         if parentpath == cwd:
             parentpath = ''
         else:
@@ -260,4 +269,22 @@
             repopath, repopath2 = s
     else:
         repopath, repopath2 = parentpath, path
-    return unionrepository(ui, repopath, repopath2)
+
+    return makeunionrepository(ui, repopath, repopath2)
+
+def makeunionrepository(ui, repopath1, repopath2):
+    """Make a union repository object from 2 local repo paths."""
+    repo1 = localrepo.instance(ui, repopath1, create=False)
+    repo2 = localrepo.instance(ui, repopath2, create=False)
+
+    url = 'union:%s+%s' % (util.expandpath(repopath1),
+                           util.expandpath(repopath2))
+
+    class derivedunionrepository(unionrepository, repo1.__class__):
+        pass
+
+    repo = repo1
+    repo.__class__ = derivedunionrepository
+    unionrepository.__init__(repo1, repo2, url)
+
+    return repo
--- a/mercurial/upgrade.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/upgrade.py	Wed Sep 26 20:33:09 2018 +0900
@@ -198,8 +198,8 @@
     _requirement = None
 
     @staticmethod
-    def _newreporequirements(repo):
-        return localrepo.newreporequirements(repo)
+    def _newreporequirements(ui):
+        return localrepo.newreporequirements(ui)
 
     @classmethod
     def fromrepo(cls, repo):
@@ -209,7 +209,7 @@
     @classmethod
     def fromconfig(cls, repo):
         assert cls._requirement is not None
-        return cls._requirement in cls._newreporequirements(repo)
+        return cls._requirement in cls._newreporequirements(repo.ui)
 
 @registerformatvariant
 class fncache(requirementformatvariant):
@@ -450,7 +450,7 @@
         return changelog.changelog(repo.svfs)
     elif path.endswith('00manifest.i'):
         mandir = path[:-len('00manifest.i')]
-        return manifest.manifestrevlog(repo.svfs, dir=mandir)
+        return manifest.manifestrevlog(repo.svfs, tree=mandir)
     else:
         #reverse of "/".join(("data", path + ".i"))
         return filelog.filelog(repo.svfs, path[5:-2])
@@ -751,7 +751,7 @@
 
     # FUTURE there is potentially a need to control the wanted requirements via
     # command arguments or via an extension hook point.
-    newreqs = localrepo.newreporequirements(repo)
+    newreqs = localrepo.newreporequirements(repo.ui)
     newreqs.update(preservedrequirements(repo))
 
     noremovereqs = (repo.requirements - newreqs -
--- a/mercurial/util.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/util.py	Wed Sep 26 20:33:09 2018 +0900
@@ -36,6 +36,10 @@
 import warnings
 import zlib
 
+from .thirdparty import (
+    attr,
+)
+from hgdemandimport import tracing
 from . import (
     encoding,
     error,
@@ -945,12 +949,12 @@
 
         self.fh.write('%s> gettimeout() -> %f\n' % (self.name, res))
 
-    def setsockopt(self, level, optname, value):
+    def setsockopt(self, res, level, optname, value):
         if not self.states:
             return
 
         self.fh.write('%s> setsockopt(%r, %r, %r) -> %r\n' % (
-            self.name, level, optname, value))
+            self.name, level, optname, value, res))
 
 def makeloggingsocket(logh, fh, name, reads=True, writes=True, states=True,
                       logdata=False, logdataapis=True):
@@ -1205,7 +1209,7 @@
     Holds a reference to nodes on either side as well as a key-value
     pair for the dictionary entry.
     """
-    __slots__ = (u'next', u'prev', u'key', u'value')
+    __slots__ = (u'next', u'prev', u'key', u'value', u'cost')
 
     def __init__(self):
         self.next = None
@@ -1213,10 +1217,13 @@
 
         self.key = _notset
         self.value = None
+        self.cost = 0
 
     def markempty(self):
         """Mark the node as emptied."""
         self.key = _notset
+        self.value = None
+        self.cost = 0
 
 class lrucachedict(object):
     """Dict that caches most recent accesses and sets.
@@ -1229,15 +1236,27 @@
     we recycle head.prev and make it the new head. Cache accesses result in
     the node being moved to before the existing head and being marked as the
     new head node.
+
+    Items in the cache can be inserted with an optional "cost" value. This is
+    simply an integer that is specified by the caller. The cache can be queried
+    for the total cost of all items presently in the cache.
+
+    The cache can also define a maximum cost. If a cache insertion would
+    cause the total cost of the cache to go beyond the maximum cost limit,
+    nodes will be evicted to make room for the new code. This can be used
+    to e.g. set a max memory limit and associate an estimated bytes size
+    cost to each item in the cache. By default, no maximum cost is enforced.
     """
-    def __init__(self, max):
+    def __init__(self, max, maxcost=0):
         self._cache = {}
 
         self._head = head = _lrucachenode()
         head.prev = head
         head.next = head
         self._size = 1
-        self._capacity = max
+        self.capacity = max
+        self.totalcost = 0
+        self.maxcost = maxcost
 
     def __len__(self):
         return len(self._cache)
@@ -1257,15 +1276,23 @@
         self._movetohead(node)
         return node.value
 
-    def __setitem__(self, k, v):
+    def insert(self, k, v, cost=0):
+        """Insert a new item in the cache with optional cost value."""
         node = self._cache.get(k)
         # Replace existing value and mark as newest.
         if node is not None:
+            self.totalcost -= node.cost
             node.value = v
+            node.cost = cost
+            self.totalcost += cost
             self._movetohead(node)
+
+            if self.maxcost:
+                self._enforcecostlimit()
+
             return
 
-        if self._size < self._capacity:
+        if self._size < self.capacity:
             node = self._addcapacity()
         else:
             # Grab the last/oldest item.
@@ -1273,17 +1300,27 @@
 
         # At capacity. Kill the old entry.
         if node.key is not _notset:
+            self.totalcost -= node.cost
             del self._cache[node.key]
 
         node.key = k
         node.value = v
+        node.cost = cost
+        self.totalcost += cost
         self._cache[k] = node
         # And mark it as newest entry. No need to adjust order since it
         # is already self._head.prev.
         self._head = node
 
+        if self.maxcost:
+            self._enforcecostlimit()
+
+    def __setitem__(self, k, v):
+        self.insert(k, v)
+
     def __delitem__(self, k):
         node = self._cache.pop(k)
+        self.totalcost -= node.cost
         node.markempty()
 
         # Temporarily mark as newest item before re-adjusting head to make
@@ -1295,27 +1332,73 @@
 
     def get(self, k, default=None):
         try:
-            return self._cache[k].value
+            return self.__getitem__(k)
         except KeyError:
             return default
 
     def clear(self):
         n = self._head
         while n.key is not _notset:
+            self.totalcost -= n.cost
             n.markempty()
             n = n.next
 
         self._cache.clear()
 
-    def copy(self):
-        result = lrucachedict(self._capacity)
+    def copy(self, capacity=None, maxcost=0):
+        """Create a new cache as a copy of the current one.
+
+        By default, the new cache has the same capacity as the existing one.
+        But, the cache capacity can be changed as part of performing the
+        copy.
+
+        Items in the copy have an insertion/access order matching this
+        instance.
+        """
+
+        capacity = capacity or self.capacity
+        maxcost = maxcost or self.maxcost
+        result = lrucachedict(capacity, maxcost=maxcost)
+
+        # We copy entries by iterating in oldest-to-newest order so the copy
+        # has the correct ordering.
+
+        # Find the first non-empty entry.
         n = self._head.prev
-        # Iterate in oldest-to-newest order, so the copy has the right ordering
+        while n.key is _notset and n is not self._head:
+            n = n.prev
+
+        # We could potentially skip the first N items when decreasing capacity.
+        # But let's keep it simple unless it is a performance problem.
         for i in range(len(self._cache)):
-            result[n.key] = n.value
+            result.insert(n.key, n.value, cost=n.cost)
             n = n.prev
+
         return result
 
+    def popoldest(self):
+        """Remove the oldest item from the cache.
+
+        Returns the (key, value) describing the removed cache entry.
+        """
+        if not self._cache:
+            return
+
+        # Walk the linked list backwards starting at tail node until we hit
+        # a non-empty node.
+        n = self._head.prev
+        while n.key is _notset:
+            n = n.prev
+
+        key, value = n.key, n.value
+
+        # And remove it from the cache and mark it as empty.
+        del self._cache[n.key]
+        self.totalcost -= n.cost
+        n.markempty()
+
+        return key, value
+
     def _movetohead(self, node):
         """Mark a node as the newest, making it the new head.
 
@@ -1377,6 +1460,38 @@
         self._size += 1
         return node
 
+    def _enforcecostlimit(self):
+        # This should run after an insertion. It should only be called if total
+        # cost limits are being enforced.
+        # The most recently inserted node is never evicted.
+        if len(self) <= 1 or self.totalcost <= self.maxcost:
+            return
+
+        # This is logically equivalent to calling popoldest() until we
+        # free up enough cost. We don't do that since popoldest() needs
+        # to walk the linked list and doing this in a loop would be
+        # quadratic. So we find the first non-empty node and then
+        # walk nodes until we free up enough capacity.
+        #
+        # If we only removed the minimum number of nodes to free enough
+        # cost at insert time, chances are high that the next insert would
+        # also require pruning. This would effectively constitute quadratic
+        # behavior for insert-heavy workloads. To mitigate this, we set a
+        # target cost that is a percentage of the max cost. This will tend
+        # to free more nodes when the high water mark is reached, which
+        # lowers the chances of needing to prune on the subsequent insert.
+        targetcost = int(self.maxcost * 0.75)
+
+        n = self._head.prev
+        while n.key is _notset:
+            n = n.prev
+
+        while len(self) > 1 and self.totalcost > targetcost:
+            del self._cache[n.key]
+            self.totalcost -= n.cost
+            n.markempty()
+            n = n.prev
+
 def lrucachefunc(func):
     '''cache most recent results of function calls'''
     cache = {}
@@ -2874,7 +2989,44 @@
     (1, 0.000000001, _('%.3f ns')),
     )
 
-_timenesting = [0]
+@attr.s
+class timedcmstats(object):
+    """Stats information produced by the timedcm context manager on entering."""
+
+    # the starting value of the timer as a float (meaning and resulution is
+    # platform dependent, see util.timer)
+    start = attr.ib(default=attr.Factory(lambda: timer()))
+    # the number of seconds as a floating point value; starts at 0, updated when
+    # the context is exited.
+    elapsed = attr.ib(default=0)
+    # the number of nested timedcm context managers.
+    level = attr.ib(default=1)
+
+    def __bytes__(self):
+        return timecount(self.elapsed) if self.elapsed else '<unknown>'
+
+    __str__ = encoding.strmethod(__bytes__)
+
+@contextlib.contextmanager
+def timedcm(whencefmt, *whenceargs):
+    """A context manager that produces timing information for a given context.
+
+    On entering a timedcmstats instance is produced.
+
+    This context manager is reentrant.
+
+    """
+    # track nested context managers
+    timedcm._nested += 1
+    timing_stats = timedcmstats(level=timedcm._nested)
+    try:
+        with tracing.log(whencefmt, *whenceargs):
+            yield timing_stats
+    finally:
+        timing_stats.elapsed = timer() - timing_stats.start
+        timedcm._nested -= 1
+
+timedcm._nested = 0
 
 def timed(func):
     '''Report the execution time of a function call to stderr.
@@ -2888,18 +3040,13 @@
     '''
 
     def wrapper(*args, **kwargs):
-        start = timer()
-        indent = 2
-        _timenesting[0] += indent
-        try:
-            return func(*args, **kwargs)
-        finally:
-            elapsed = timer() - start
-            _timenesting[0] -= indent
-            stderr = procutil.stderr
-            stderr.write('%s%s: %s\n' %
-                         (' ' * _timenesting[0], func.__name__,
-                          timecount(elapsed)))
+        with timedcm(pycompat.bytestr(func.__name__)) as time_stats:
+            result = func(*args, **kwargs)
+        stderr = procutil.stderr
+        stderr.write('%s%s: %s\n' % (
+            ' ' * time_stats.level * 2, pycompat.bytestr(func.__name__),
+            time_stats))
+        return result
     return wrapper
 
 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
@@ -3301,7 +3448,7 @@
         The object has a ``decompress(data)`` method that decompresses
         data. The method will only be called if ``data`` begins with
         ``revlogheader()``. The method should return the raw, uncompressed
-        data or raise a ``RevlogError``.
+        data or raise a ``StorageError``.
 
         The object is reusable but is not thread safe.
         """
@@ -3343,6 +3490,9 @@
                 return ''.join(buf)
             chunk = self._reader(65536)
             self._decompress(chunk)
+            if not chunk and not self._pending and not self._eof:
+                # No progress and no new data, bail out
+                return ''.join(buf)
 
 class _GzipCompressedStreamReader(_CompressedStreamReader):
     def __init__(self, fh):
@@ -3476,8 +3626,8 @@
             try:
                 return zlib.decompress(data)
             except zlib.error as e:
-                raise error.RevlogError(_('revlog decompress error: %s') %
-                                        stringutil.forcebytestr(e))
+                raise error.StorageError(_('revlog decompress error: %s') %
+                                         stringutil.forcebytestr(e))
 
     def revlogcompressor(self, opts=None):
         return self.zlibrevlogcompressor()
@@ -3688,8 +3838,8 @@
 
                 return ''.join(chunks)
             except Exception as e:
-                raise error.RevlogError(_('revlog decompress error: %s') %
-                                        stringutil.forcebytestr(e))
+                raise error.StorageError(_('revlog decompress error: %s') %
+                                         stringutil.forcebytestr(e))
 
     def revlogcompressor(self, opts=None):
         opts = opts or {}
--- a/mercurial/utils/cborutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/utils/cborutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -8,10 +8,9 @@
 from __future__ import absolute_import
 
 import struct
+import sys
 
-from ..thirdparty.cbor.cbor2 import (
-    decoder as decodermod,
-)
+from .. import pycompat
 
 # Very short very of RFC 7049...
 #
@@ -35,11 +34,16 @@
 
 SUBTYPE_MASK = 0b00011111
 
+SUBTYPE_FALSE = 20
+SUBTYPE_TRUE = 21
+SUBTYPE_NULL = 22
 SUBTYPE_HALF_FLOAT = 25
 SUBTYPE_SINGLE_FLOAT = 26
 SUBTYPE_DOUBLE_FLOAT = 27
 SUBTYPE_INDEFINITE = 31
 
+SEMANTIC_TAG_FINITE_SET = 258
+
 # Indefinite types begin with their major type ORd with information value 31.
 BEGIN_INDEFINITE_BYTESTRING = struct.pack(
     r'>B', MAJOR_TYPE_BYTESTRING << 5 | SUBTYPE_INDEFINITE)
@@ -146,7 +150,7 @@
 def streamencodeset(s):
     # https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml defines
     # semantic tag 258 for finite sets.
-    yield encodelength(MAJOR_TYPE_SEMANTIC, 258)
+    yield encodelength(MAJOR_TYPE_SEMANTIC, SEMANTIC_TAG_FINITE_SET)
 
     for chunk in streamencodearray(sorted(s, key=_mixedtypesortkey)):
         yield chunk
@@ -188,6 +192,7 @@
 STREAM_ENCODERS = {
     bytes: streamencodebytestring,
     int: streamencodeint,
+    pycompat.long: streamencodeint,
     list: streamencodearray,
     tuple: streamencodearray,
     dict: streamencodemap,
@@ -213,50 +218,751 @@
 
     return fn(v)
 
-def readindefinitebytestringtoiter(fh, expectheader=True):
-    """Read an indefinite bytestring to a generator.
+class CBORDecodeError(Exception):
+    """Represents an error decoding CBOR."""
+
+if sys.version_info.major >= 3:
+    def _elementtointeger(b, i):
+        return b[i]
+else:
+    def _elementtointeger(b, i):
+        return ord(b[i])
+
+STRUCT_BIG_UBYTE = struct.Struct(r'>B')
+STRUCT_BIG_USHORT = struct.Struct('>H')
+STRUCT_BIG_ULONG = struct.Struct('>L')
+STRUCT_BIG_ULONGLONG = struct.Struct('>Q')
+
+SPECIAL_NONE = 0
+SPECIAL_START_INDEFINITE_BYTESTRING = 1
+SPECIAL_START_ARRAY = 2
+SPECIAL_START_MAP = 3
+SPECIAL_START_SET = 4
+SPECIAL_INDEFINITE_BREAK = 5
 
-    Receives an object with a ``read(X)`` method to read N bytes.
+def decodeitem(b, offset=0):
+    """Decode a new CBOR value from a buffer at offset.
+
+    This function attempts to decode up to one complete CBOR value
+    from ``b`` starting at offset ``offset``.
+
+    The beginning of a collection (such as an array, map, set, or
+    indefinite length bytestring) counts as a single value. For these
+    special cases, a state flag will indicate that a special value was seen.
 
-    If ``expectheader`` is True, it is expected that the first byte read
-    will represent an indefinite length bytestring. Otherwise, we
-    expect the first byte to be part of the first bytestring chunk.
+    When called, the function either returns a decoded value or gives
+    a hint as to how many more bytes are needed to do so. By calling
+    the function repeatedly given a stream of bytes, the caller can
+    build up the original values.
+
+    Returns a tuple with the following elements:
+
+    * Bool indicating whether a complete value was decoded.
+    * A decoded value if first value is True otherwise None
+    * Integer number of bytes. If positive, the number of bytes
+      read. If negative, the number of bytes we need to read to
+      decode this value or the next chunk in this value.
+    * One of the ``SPECIAL_*`` constants indicating special treatment
+      for this value. ``SPECIAL_NONE`` means this is a fully decoded
+      simple value (such as an integer or bool).
     """
-    read = fh.read
-    decodeuint = decodermod.decode_uint
-    byteasinteger = decodermod.byte_as_integer
+
+    initial = _elementtointeger(b, offset)
+    offset += 1
+
+    majortype = initial >> 5
+    subtype = initial & SUBTYPE_MASK
+
+    if majortype == MAJOR_TYPE_UINT:
+        complete, value, readcount = decodeuint(subtype, b, offset)
+
+        if complete:
+            return True, value, readcount + 1, SPECIAL_NONE
+        else:
+            return False, None, readcount, SPECIAL_NONE
+
+    elif majortype == MAJOR_TYPE_NEGINT:
+        # Negative integers are the same as UINT except inverted minus 1.
+        complete, value, readcount = decodeuint(subtype, b, offset)
+
+        if complete:
+            return True, -value - 1, readcount + 1, SPECIAL_NONE
+        else:
+            return False, None, readcount, SPECIAL_NONE
+
+    elif majortype == MAJOR_TYPE_BYTESTRING:
+        # Beginning of bytestrings are treated as uints in order to
+        # decode their length, which may be indefinite.
+        complete, size, readcount = decodeuint(subtype, b, offset,
+                                               allowindefinite=True)
+
+        # We don't know the size of the bytestring. It must be a definitive
+        # length since the indefinite subtype would be encoded in the initial
+        # byte.
+        if not complete:
+            return False, None, readcount, SPECIAL_NONE
+
+        # We know the length of the bytestring.
+        if size is not None:
+            # And the data is available in the buffer.
+            if offset + readcount + size <= len(b):
+                value = b[offset + readcount:offset + readcount + size]
+                return True, value, readcount + size + 1, SPECIAL_NONE
+
+            # And we need more data in order to return the bytestring.
+            else:
+                wanted = len(b) - offset - readcount - size
+                return False, None, wanted, SPECIAL_NONE
+
+        # It is an indefinite length bytestring.
+        else:
+            return True, None, 1, SPECIAL_START_INDEFINITE_BYTESTRING
+
+    elif majortype == MAJOR_TYPE_STRING:
+        raise CBORDecodeError('string major type not supported')
+
+    elif majortype == MAJOR_TYPE_ARRAY:
+        # Beginning of arrays are treated as uints in order to decode their
+        # length. We don't allow indefinite length arrays.
+        complete, size, readcount = decodeuint(subtype, b, offset)
+
+        if complete:
+            return True, size, readcount + 1, SPECIAL_START_ARRAY
+        else:
+            return False, None, readcount, SPECIAL_NONE
+
+    elif majortype == MAJOR_TYPE_MAP:
+        # Beginning of maps are treated as uints in order to decode their
+        # number of elements. We don't allow indefinite length arrays.
+        complete, size, readcount = decodeuint(subtype, b, offset)
+
+        if complete:
+            return True, size, readcount + 1, SPECIAL_START_MAP
+        else:
+            return False, None, readcount, SPECIAL_NONE
 
-    if expectheader:
-        initial = decodermod.byte_as_integer(read(1))
+    elif majortype == MAJOR_TYPE_SEMANTIC:
+        # Semantic tag value is read the same as a uint.
+        complete, tagvalue, readcount = decodeuint(subtype, b, offset)
+
+        if not complete:
+            return False, None, readcount, SPECIAL_NONE
+
+        # This behavior here is a little wonky. The main type being "decorated"
+        # by this semantic tag follows. A more robust parser would probably emit
+        # a special flag indicating this as a semantic tag and let the caller
+        # deal with the types that follow. But since we don't support many
+        # semantic tags, it is easier to deal with the special cases here and
+        # hide complexity from the caller. If we add support for more semantic
+        # tags, we should probably move semantic tag handling into the caller.
+        if tagvalue == SEMANTIC_TAG_FINITE_SET:
+            if offset + readcount >= len(b):
+                return False, None, -1, SPECIAL_NONE
+
+            complete, size, readcount2, special = decodeitem(b,
+                                                             offset + readcount)
+
+            if not complete:
+                return False, None, readcount2, SPECIAL_NONE
+
+            if special != SPECIAL_START_ARRAY:
+                raise CBORDecodeError('expected array after finite set '
+                                      'semantic tag')
+
+            return True, size, readcount + readcount2 + 1, SPECIAL_START_SET
+
+        else:
+            raise CBORDecodeError('semantic tag %d not allowed' % tagvalue)
+
+    elif majortype == MAJOR_TYPE_SPECIAL:
+        # Only specific values for the information field are allowed.
+        if subtype == SUBTYPE_FALSE:
+            return True, False, 1, SPECIAL_NONE
+        elif subtype == SUBTYPE_TRUE:
+            return True, True, 1, SPECIAL_NONE
+        elif subtype == SUBTYPE_NULL:
+            return True, None, 1, SPECIAL_NONE
+        elif subtype == SUBTYPE_INDEFINITE:
+            return True, None, 1, SPECIAL_INDEFINITE_BREAK
+        # If value is 24, subtype is in next byte.
+        else:
+            raise CBORDecodeError('special type %d not allowed' % subtype)
+    else:
+        assert False
+
+def decodeuint(subtype, b, offset=0, allowindefinite=False):
+    """Decode an unsigned integer.
+
+    ``subtype`` is the lower 5 bits from the initial byte CBOR item
+    "header." ``b`` is a buffer containing bytes. ``offset`` points to
+    the index of the first byte after the byte that ``subtype`` was
+    derived from.
+
+    ``allowindefinite`` allows the special indefinite length value
+    indicator.
+
+    Returns a 3-tuple of (successful, value, count).
+
+    The first element is a bool indicating if decoding completed. The 2nd
+    is the decoded integer value or None if not fully decoded or the subtype
+    is 31 and ``allowindefinite`` is True. The 3rd value is the count of bytes.
+    If positive, it is the number of additional bytes decoded. If negative,
+    it is the number of additional bytes needed to decode this value.
+    """
+
+    # Small values are inline.
+    if subtype < 24:
+        return True, subtype, 0
+    # Indefinite length specifier.
+    elif subtype == 31:
+        if allowindefinite:
+            return True, None, 0
+        else:
+            raise CBORDecodeError('indefinite length uint not allowed here')
+    elif subtype >= 28:
+        raise CBORDecodeError('unsupported subtype on integer type: %d' %
+                              subtype)
 
-        majortype = initial >> 5
-        subtype = initial & SUBTYPE_MASK
+    if subtype == 24:
+        s = STRUCT_BIG_UBYTE
+    elif subtype == 25:
+        s = STRUCT_BIG_USHORT
+    elif subtype == 26:
+        s = STRUCT_BIG_ULONG
+    elif subtype == 27:
+        s = STRUCT_BIG_ULONGLONG
+    else:
+        raise CBORDecodeError('bounds condition checking violation')
+
+    if len(b) - offset >= s.size:
+        return True, s.unpack_from(b, offset)[0], s.size
+    else:
+        return False, None, len(b) - offset - s.size
+
+class bytestringchunk(bytes):
+    """Represents a chunk/segment in an indefinite length bytestring.
+
+    This behaves like a ``bytes`` but in addition has the ``isfirst``
+    and ``islast`` attributes indicating whether this chunk is the first
+    or last in an indefinite length bytestring.
+    """
+
+    def __new__(cls, v, first=False, last=False):
+        self = bytes.__new__(cls, v)
+        self.isfirst = first
+        self.islast = last
+
+        return self
+
+class sansiodecoder(object):
+    """A CBOR decoder that doesn't perform its own I/O.
+
+    To use, construct an instance and feed it segments containing
+    CBOR-encoded bytes via ``decode()``. The return value from ``decode()``
+    indicates whether a fully-decoded value is available, how many bytes
+    were consumed, and offers a hint as to how many bytes should be fed
+    in next time to decode the next value.
+
+    The decoder assumes it will decode N discrete CBOR values, not just
+    a single value. i.e. if the bytestream contains uints packed one after
+    the other, the decoder will decode them all, rather than just the initial
+    one.
+
+    When ``decode()`` indicates a value is available, call ``getavailable()``
+    to return all fully decoded values.
+
+    ``decode()`` can partially decode input. It is up to the caller to keep
+    track of what data was consumed and to pass unconsumed data in on the
+    next invocation.
+
+    The decoder decodes atomically at the *item* level. See ``decodeitem()``.
+    If an *item* cannot be fully decoded, the decoder won't record it as
+    partially consumed. Instead, the caller will be instructed to pass in
+    the initial bytes of this item on the next invocation. This does result
+    in some redundant parsing. But the overhead should be minimal.
+
+    This decoder only supports a subset of CBOR as required by Mercurial.
+    It lacks support for:
+
+    * Indefinite length arrays
+    * Indefinite length maps
+    * Use of indefinite length bytestrings as keys or values within
+      arrays, maps, or sets.
+    * Nested arrays, maps, or sets within sets
+    * Any semantic tag that isn't a mathematical finite set
+    * Floating point numbers
+    * Undefined special value
+
+    CBOR types are decoded to Python types as follows:
+
+    uint -> int
+    negint -> int
+    bytestring -> bytes
+    map -> dict
+    array -> list
+    True -> bool
+    False -> bool
+    null -> None
+    indefinite length bytestring chunk -> [bytestringchunk]
 
-        if majortype != MAJOR_TYPE_BYTESTRING:
-            raise decodermod.CBORDecodeError(
-                'expected major type %d; got %d' % (MAJOR_TYPE_BYTESTRING,
-                                                    majortype))
+    The only non-obvious mapping here is an indefinite length bytestring
+    to the ``bytestringchunk`` type. This is to facilitate streaming
+    indefinite length bytestrings out of the decoder and to differentiate
+    a regular bytestring from an indefinite length bytestring.
+    """
+
+    _STATE_NONE = 0
+    _STATE_WANT_MAP_KEY = 1
+    _STATE_WANT_MAP_VALUE = 2
+    _STATE_WANT_ARRAY_VALUE = 3
+    _STATE_WANT_SET_VALUE = 4
+    _STATE_WANT_BYTESTRING_CHUNK_FIRST = 5
+    _STATE_WANT_BYTESTRING_CHUNK_SUBSEQUENT = 6
+
+    def __init__(self):
+        # TODO add support for limiting size of bytestrings
+        # TODO add support for limiting number of keys / values in collections
+        # TODO add support for limiting size of buffered partial values
+
+        self.decodedbytecount = 0
+
+        self._state = self._STATE_NONE
+
+        # Stack of active nested collections. Each entry is a dict describing
+        # the collection.
+        self._collectionstack = []
+
+        # Fully decoded key to use for the current map.
+        self._currentmapkey = None
+
+        # Fully decoded values available for retrieval.
+        self._decodedvalues = []
+
+    @property
+    def inprogress(self):
+        """Whether the decoder has partially decoded a value."""
+        return self._state != self._STATE_NONE
+
+    def decode(self, b, offset=0):
+        """Attempt to decode bytes from an input buffer.
+
+        ``b`` is a collection of bytes and ``offset`` is the byte
+        offset within that buffer from which to begin reading data.
+
+        ``b`` must support ``len()`` and accessing bytes slices via
+        ``__slice__``. Typically ``bytes`` instances are used.
+
+        Returns a tuple with the following fields:
+
+        * Bool indicating whether values are available for retrieval.
+        * Integer indicating the number of bytes that were fully consumed,
+          starting from ``offset``.
+        * Integer indicating the number of bytes that are desired for the
+          next call in order to decode an item.
+        """
+        if not b:
+            return bool(self._decodedvalues), 0, 0
+
+        initialoffset = offset
+
+        # We could easily split the body of this loop into a function. But
+        # Python performance is sensitive to function calls and collections
+        # are composed of many items. So leaving as a while loop could help
+        # with performance. One thing that may not help is the use of
+        # if..elif versus a lookup/dispatch table. There may be value
+        # in switching that.
+        while offset < len(b):
+            # Attempt to decode an item. This could be a whole value or a
+            # special value indicating an event, such as start or end of a
+            # collection or indefinite length type.
+            complete, value, readcount, special = decodeitem(b, offset)
+
+            if readcount > 0:
+                self.decodedbytecount += readcount
+
+            if not complete:
+                assert readcount < 0
+                return (
+                    bool(self._decodedvalues),
+                    offset - initialoffset,
+                    -readcount,
+                )
+
+            offset += readcount
 
-        if subtype != SUBTYPE_INDEFINITE:
-            raise decodermod.CBORDecodeError(
-                'expected indefinite subtype; got %d' % subtype)
+            # No nested state. We either have a full value or beginning of a
+            # complex value to deal with.
+            if self._state == self._STATE_NONE:
+                # A normal value.
+                if special == SPECIAL_NONE:
+                    self._decodedvalues.append(value)
+
+                elif special == SPECIAL_START_ARRAY:
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': [],
+                    })
+                    self._state = self._STATE_WANT_ARRAY_VALUE
+
+                elif special == SPECIAL_START_MAP:
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': {},
+                    })
+                    self._state = self._STATE_WANT_MAP_KEY
+
+                elif special == SPECIAL_START_SET:
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': set(),
+                    })
+                    self._state = self._STATE_WANT_SET_VALUE
+
+                elif special == SPECIAL_START_INDEFINITE_BYTESTRING:
+                    self._state = self._STATE_WANT_BYTESTRING_CHUNK_FIRST
+
+                else:
+                    raise CBORDecodeError('unhandled special state: %d' %
+                                          special)
+
+            # This value becomes an element of the current array.
+            elif self._state == self._STATE_WANT_ARRAY_VALUE:
+                # Simple values get appended.
+                if special == SPECIAL_NONE:
+                    c = self._collectionstack[-1]
+                    c['v'].append(value)
+                    c['remaining'] -= 1
+
+                    # self._state doesn't need changed.
+
+                # An array nested within an array.
+                elif special == SPECIAL_START_ARRAY:
+                    lastc = self._collectionstack[-1]
+                    newvalue = []
+
+                    lastc['v'].append(newvalue)
+                    lastc['remaining'] -= 1
+
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': newvalue,
+                    })
+
+                    # self._state doesn't need changed.
+
+                # A map nested within an array.
+                elif special == SPECIAL_START_MAP:
+                    lastc = self._collectionstack[-1]
+                    newvalue = {}
+
+                    lastc['v'].append(newvalue)
+                    lastc['remaining'] -= 1
+
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': newvalue
+                    })
+
+                    self._state = self._STATE_WANT_MAP_KEY
+
+                elif special == SPECIAL_START_SET:
+                    lastc = self._collectionstack[-1]
+                    newvalue = set()
+
+                    lastc['v'].append(newvalue)
+                    lastc['remaining'] -= 1
+
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': newvalue,
+                    })
+
+                    self._state = self._STATE_WANT_SET_VALUE
 
-    # The indefinite bytestring is composed of chunks of normal bytestrings.
-    # Read chunks until we hit a BREAK byte.
+                elif special == SPECIAL_START_INDEFINITE_BYTESTRING:
+                    raise CBORDecodeError('indefinite length bytestrings '
+                                          'not allowed as array values')
+
+                else:
+                    raise CBORDecodeError('unhandled special item when '
+                                          'expecting array value: %d' % special)
+
+            # This value becomes the key of the current map instance.
+            elif self._state == self._STATE_WANT_MAP_KEY:
+                if special == SPECIAL_NONE:
+                    self._currentmapkey = value
+                    self._state = self._STATE_WANT_MAP_VALUE
+
+                elif special == SPECIAL_START_INDEFINITE_BYTESTRING:
+                    raise CBORDecodeError('indefinite length bytestrings '
+                                          'not allowed as map keys')
+
+                elif special in (SPECIAL_START_ARRAY, SPECIAL_START_MAP,
+                                 SPECIAL_START_SET):
+                    raise CBORDecodeError('collections not supported as map '
+                                          'keys')
+
+                # We do not allow special values to be used as map keys.
+                else:
+                    raise CBORDecodeError('unhandled special item when '
+                                          'expecting map key: %d' % special)
+
+            # This value becomes the value of the current map key.
+            elif self._state == self._STATE_WANT_MAP_VALUE:
+                # Simple values simply get inserted into the map.
+                if special == SPECIAL_NONE:
+                    lastc = self._collectionstack[-1]
+                    lastc['v'][self._currentmapkey] = value
+                    lastc['remaining'] -= 1
+
+                    self._state = self._STATE_WANT_MAP_KEY
+
+                # A new array is used as the map value.
+                elif special == SPECIAL_START_ARRAY:
+                    lastc = self._collectionstack[-1]
+                    newvalue = []
+
+                    lastc['v'][self._currentmapkey] = newvalue
+                    lastc['remaining'] -= 1
 
-    while True:
-        # We need to sniff for the BREAK byte.
-        initial = byteasinteger(read(1))
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': newvalue,
+                    })
+
+                    self._state = self._STATE_WANT_ARRAY_VALUE
+
+                # A new map is used as the map value.
+                elif special == SPECIAL_START_MAP:
+                    lastc = self._collectionstack[-1]
+                    newvalue = {}
+
+                    lastc['v'][self._currentmapkey] = newvalue
+                    lastc['remaining'] -= 1
+
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': newvalue,
+                    })
+
+                    self._state = self._STATE_WANT_MAP_KEY
+
+                # A new set is used as the map value.
+                elif special == SPECIAL_START_SET:
+                    lastc = self._collectionstack[-1]
+                    newvalue = set()
+
+                    lastc['v'][self._currentmapkey] = newvalue
+                    lastc['remaining'] -= 1
+
+                    self._collectionstack.append({
+                        'remaining': value,
+                        'v': newvalue,
+                    })
+
+                    self._state = self._STATE_WANT_SET_VALUE
+
+                elif special == SPECIAL_START_INDEFINITE_BYTESTRING:
+                    raise CBORDecodeError('indefinite length bytestrings not '
+                                          'allowed as map values')
+
+                else:
+                    raise CBORDecodeError('unhandled special item when '
+                                          'expecting map value: %d' % special)
+
+                self._currentmapkey = None
 
-        if initial == BREAK_INT:
-            break
+            # This value is added to the current set.
+            elif self._state == self._STATE_WANT_SET_VALUE:
+                if special == SPECIAL_NONE:
+                    lastc = self._collectionstack[-1]
+                    lastc['v'].add(value)
+                    lastc['remaining'] -= 1
+
+                elif special == SPECIAL_START_INDEFINITE_BYTESTRING:
+                    raise CBORDecodeError('indefinite length bytestrings not '
+                                          'allowed as set values')
+
+                elif special in (SPECIAL_START_ARRAY,
+                                 SPECIAL_START_MAP,
+                                 SPECIAL_START_SET):
+                    raise CBORDecodeError('collections not allowed as set '
+                                          'values')
+
+                # We don't allow non-trivial types to exist as set values.
+                else:
+                    raise CBORDecodeError('unhandled special item when '
+                                          'expecting set value: %d' % special)
+
+            # This value represents the first chunk in an indefinite length
+            # bytestring.
+            elif self._state == self._STATE_WANT_BYTESTRING_CHUNK_FIRST:
+                # We received a full chunk.
+                if special == SPECIAL_NONE:
+                    self._decodedvalues.append(bytestringchunk(value,
+                                                               first=True))
+
+                    self._state = self._STATE_WANT_BYTESTRING_CHUNK_SUBSEQUENT
+
+                # The end of stream marker. This means it is an empty
+                # indefinite length bytestring.
+                elif special == SPECIAL_INDEFINITE_BREAK:
+                    # We /could/ convert this to a b''. But we want to preserve
+                    # the nature of the underlying data so consumers expecting
+                    # an indefinite length bytestring get one.
+                    self._decodedvalues.append(bytestringchunk(b'',
+                                                               first=True,
+                                                               last=True))
+
+                    # Since indefinite length bytestrings can't be used in
+                    # collections, we must be at the root level.
+                    assert not self._collectionstack
+                    self._state = self._STATE_NONE
 
-        length = decodeuint(fh, initial & SUBTYPE_MASK)
-        chunk = read(length)
+                else:
+                    raise CBORDecodeError('unexpected special value when '
+                                          'expecting bytestring chunk: %d' %
+                                          special)
+
+            # This value represents the non-initial chunk in an indefinite
+            # length bytestring.
+            elif self._state == self._STATE_WANT_BYTESTRING_CHUNK_SUBSEQUENT:
+                # We received a full chunk.
+                if special == SPECIAL_NONE:
+                    self._decodedvalues.append(bytestringchunk(value))
+
+                # The end of stream marker.
+                elif special == SPECIAL_INDEFINITE_BREAK:
+                    self._decodedvalues.append(bytestringchunk(b'', last=True))
+
+                    # Since indefinite length bytestrings can't be used in
+                    # collections, we must be at the root level.
+                    assert not self._collectionstack
+                    self._state = self._STATE_NONE
+
+                else:
+                    raise CBORDecodeError('unexpected special value when '
+                                          'expecting bytestring chunk: %d' %
+                                          special)
+
+            else:
+                raise CBORDecodeError('unhandled decoder state: %d' %
+                                      self._state)
+
+            # We could have just added the final value in a collection. End
+            # all complete collections at the top of the stack.
+            while True:
+                # Bail if we're not waiting on a new collection item.
+                if self._state not in (self._STATE_WANT_ARRAY_VALUE,
+                                       self._STATE_WANT_MAP_KEY,
+                                       self._STATE_WANT_SET_VALUE):
+                    break
+
+                # Or we are expecting more items for this collection.
+                lastc = self._collectionstack[-1]
+
+                if lastc['remaining']:
+                    break
+
+                # The collection at the top of the stack is complete.
+
+                # Discard it, as it isn't needed for future items.
+                self._collectionstack.pop()
 
-        if len(chunk) != length:
-            raise decodermod.CBORDecodeError(
-                'failed to read bytestring chunk: got %d bytes; expected %d' % (
-                    len(chunk), length))
+                # If this is a nested collection, we don't emit it, since it
+                # will be emitted by its parent collection. But we do need to
+                # update state to reflect what the new top-most collection
+                # on the stack is.
+                if self._collectionstack:
+                    self._state = {
+                        list: self._STATE_WANT_ARRAY_VALUE,
+                        dict: self._STATE_WANT_MAP_KEY,
+                        set: self._STATE_WANT_SET_VALUE,
+                    }[type(self._collectionstack[-1]['v'])]
+
+                # If this is the root collection, emit it.
+                else:
+                    self._decodedvalues.append(lastc['v'])
+                    self._state = self._STATE_NONE
+
+        return (
+            bool(self._decodedvalues),
+            offset - initialoffset,
+            0,
+        )
+
+    def getavailable(self):
+        """Returns an iterator over fully decoded values.
+
+        Once values are retrieved, they won't be available on the next call.
+        """
+
+        l = list(self._decodedvalues)
+        self._decodedvalues = []
+        return l
+
+class bufferingdecoder(object):
+    """A CBOR decoder that buffers undecoded input.
+
+    This is a glorified wrapper around ``sansiodecoder`` that adds a buffering
+    layer. All input that isn't consumed by ``sansiodecoder`` will be buffered
+    and concatenated with any new input that arrives later.
+
+    TODO consider adding limits as to the maximum amount of data that can
+    be buffered.
+    """
+    def __init__(self):
+        self._decoder = sansiodecoder()
+        self._leftover = None
+
+    def decode(self, b):
+        """Attempt to decode bytes to CBOR values.
 
-        yield chunk
+        Returns a tuple with the following fields:
+
+        * Bool indicating whether new values are available for retrieval.
+        * Integer number of bytes decoded from the new input.
+        * Integer number of bytes wanted to decode the next value.
+        """
+
+        if self._leftover:
+            oldlen = len(self._leftover)
+            b = self._leftover + b
+            self._leftover = None
+        else:
+            b = b
+            oldlen = 0
+
+        available, readcount, wanted = self._decoder.decode(b)
+
+        if readcount < len(b):
+            self._leftover = b[readcount:]
+
+        return available, readcount - oldlen, wanted
+
+    def getavailable(self):
+        return self._decoder.getavailable()
+
+def decodeall(b):
+    """Decode all CBOR items present in an iterable of bytes.
+
+    In addition to regular decode errors, raises CBORDecodeError if the
+    entirety of the passed buffer does not fully decode to complete CBOR
+    values. This includes failure to decode any value, incomplete collection
+    types, incomplete indefinite length items, and extra data at the end of
+    the buffer.
+    """
+    if not b:
+        return []
+
+    decoder = sansiodecoder()
+
+    havevalues, readcount, wantbytes = decoder.decode(b)
+
+    if readcount != len(b):
+        raise CBORDecodeError('input data not fully consumed')
+
+    if decoder.inprogress:
+        raise CBORDecodeError('input data not complete')
+
+    return decoder.getavailable()
--- a/mercurial/utils/procutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/utils/procutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -120,13 +120,15 @@
     raise error.ProgrammingError('unsupported mode: %r' % mode)
 
 def _popenreader(cmd, bufsize):
-    p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
+    p = subprocess.Popen(tonativestr(quotecommand(cmd)),
+                         shell=True, bufsize=bufsize,
                          close_fds=closefds,
                          stdout=subprocess.PIPE)
     return _pfile(p, p.stdout)
 
 def _popenwriter(cmd, bufsize):
-    p = subprocess.Popen(quotecommand(cmd), shell=True, bufsize=bufsize,
+    p = subprocess.Popen(tonativestr(quotecommand(cmd)),
+                         shell=True, bufsize=bufsize,
                          close_fds=closefds,
                          stdin=subprocess.PIPE)
     return _pfile(p, p.stdin)
@@ -135,10 +137,11 @@
     # Setting bufsize to -1 lets the system decide the buffer size.
     # The default for bufsize is 0, meaning unbuffered. This leads to
     # poor performance on Mac OS X: http://bugs.python.org/issue4194
-    p = subprocess.Popen(cmd, shell=True, bufsize=-1,
+    p = subprocess.Popen(pycompat.rapply(tonativestr, cmd),
+                         shell=True, bufsize=-1,
                          close_fds=closefds,
                          stdin=subprocess.PIPE, stdout=subprocess.PIPE,
-                         env=env)
+                         env=tonativeenv(env))
     return p.stdin, p.stdout
 
 def popen3(cmd, env=None):
@@ -146,16 +149,18 @@
     return stdin, stdout, stderr
 
 def popen4(cmd, env=None, bufsize=-1):
-    p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
+    p = subprocess.Popen(pycompat.rapply(tonativestr, cmd),
+                         shell=True, bufsize=bufsize,
                          close_fds=closefds,
                          stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                          stderr=subprocess.PIPE,
-                         env=env)
+                         env=tonativeenv(env))
     return p.stdin, p.stdout, p.stderr, p
 
 def pipefilter(s, cmd):
     '''filter string S through command CMD, returning its output'''
-    p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
+    p = subprocess.Popen(pycompat.rapply(tonativestr, cmd),
+                         shell=True, close_fds=closefds,
                          stdin=subprocess.PIPE, stdout=subprocess.PIPE)
     pout, perr = p.communicate(s)
     return pout
@@ -320,10 +325,19 @@
 if pycompat.iswindows:
     def shelltonative(cmd, env):
         return platform.shelltocmdexe(cmd, shellenviron(env))
+
+    tonativestr = encoding.strfromlocal
 else:
     def shelltonative(cmd, env):
         return cmd
 
+    tonativestr = pycompat.identity
+
+def tonativeenv(env):
+    '''convert the environment from bytes to strings suitable for Popen(), etc.
+    '''
+    return pycompat.rapply(tonativestr, env)
+
 def system(cmd, environ=None, cwd=None, out=None):
     '''enhanced shell command execution.
     run with environment maybe modified, maybe in different dir.
@@ -337,11 +351,16 @@
     cmd = quotecommand(cmd)
     env = shellenviron(environ)
     if out is None or isstdout(out):
-        rc = subprocess.call(cmd, shell=True, close_fds=closefds,
-                             env=env, cwd=cwd)
+        rc = subprocess.call(pycompat.rapply(tonativestr, cmd),
+                             shell=True, close_fds=closefds,
+                             env=tonativeenv(env),
+                             cwd=pycompat.rapply(tonativestr, cwd))
     else:
-        proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
-                                env=env, cwd=cwd, stdout=subprocess.PIPE,
+        proc = subprocess.Popen(pycompat.rapply(tonativestr, cmd),
+                                shell=True, close_fds=closefds,
+                                env=tonativeenv(env),
+                                cwd=pycompat.rapply(tonativestr, cwd),
+                                stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT)
         for line in iter(proc.stdout.readline, ''):
             out.write(line)
--- a/mercurial/utils/stringutil.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/utils/stringutil.py	Wed Sep 26 20:33:09 2018 +0900
@@ -13,6 +13,7 @@
 import codecs
 import re as remod
 import textwrap
+import types
 
 from ..i18n import _
 from ..thirdparty import attr
@@ -42,27 +43,190 @@
         return pat
     return pat.encode('latin1')
 
-def pprint(o, bprefix=False):
+def pprint(o, bprefix=False, indent=0):
     """Pretty print an object."""
+    return b''.join(pprintgen(o, bprefix=bprefix, indent=indent))
+
+def pprintgen(o, bprefix=False, indent=0, _level=1):
+    """Pretty print an object to a generator of atoms.
+
+    ``bprefix`` is a flag influencing whether bytestrings are preferred with
+    a ``b''`` prefix.
+
+    ``indent`` controls whether collections and nested data structures
+    span multiple lines via the indentation amount in spaces. By default,
+    no newlines are emitted.
+    """
+
     if isinstance(o, bytes):
         if bprefix:
-            return "b'%s'" % escapestr(o)
-        return "'%s'" % escapestr(o)
+            yield "b'%s'" % escapestr(o)
+        else:
+            yield "'%s'" % escapestr(o)
     elif isinstance(o, bytearray):
         # codecs.escape_encode() can't handle bytearray, so escapestr fails
         # without coercion.
-        return "bytearray['%s']" % escapestr(bytes(o))
+        yield "bytearray['%s']" % escapestr(bytes(o))
     elif isinstance(o, list):
-        return '[%s]' % (b', '.join(pprint(a, bprefix=bprefix) for a in o))
+        if not o:
+            yield '[]'
+            return
+
+        yield '['
+
+        if indent:
+            yield '\n'
+            yield ' ' * (_level * indent)
+
+        for i, a in enumerate(o):
+            for chunk in pprintgen(a, bprefix=bprefix, indent=indent,
+                                   _level=_level + 1):
+                yield chunk
+
+            if i + 1 < len(o):
+                if indent:
+                    yield ',\n'
+                    yield ' ' * (_level * indent)
+                else:
+                    yield ', '
+
+        if indent:
+            yield '\n'
+            yield ' ' * ((_level - 1) * indent)
+
+        yield ']'
     elif isinstance(o, dict):
-        return '{%s}' % (b', '.join(
-            '%s: %s' % (pprint(k, bprefix=bprefix),
-                        pprint(v, bprefix=bprefix))
-            for k, v in sorted(o.items())))
+        if not o:
+            yield '{}'
+            return
+
+        yield '{'
+
+        if indent:
+            yield '\n'
+            yield ' ' * (_level * indent)
+
+        for i, (k, v) in enumerate(sorted(o.items())):
+            for chunk in pprintgen(k, bprefix=bprefix, indent=indent,
+                                   _level=_level + 1):
+                yield chunk
+
+            yield ': '
+
+            for chunk in pprintgen(v, bprefix=bprefix, indent=indent,
+                                   _level=_level + 1):
+                yield chunk
+
+            if i + 1 < len(o):
+                if indent:
+                    yield ',\n'
+                    yield ' ' * (_level * indent)
+                else:
+                    yield ', '
+
+        if indent:
+            yield '\n'
+            yield ' ' * ((_level - 1) * indent)
+
+        yield '}'
+    elif isinstance(o, set):
+        if not o:
+            yield 'set([])'
+            return
+
+        yield 'set(['
+
+        if indent:
+            yield '\n'
+            yield ' ' * (_level * indent)
+
+        for i, k in enumerate(sorted(o)):
+            for chunk in pprintgen(k, bprefix=bprefix, indent=indent,
+                                   _level=_level + 1):
+                yield chunk
+
+            if i + 1 < len(o):
+                if indent:
+                    yield ',\n'
+                    yield ' ' * (_level * indent)
+                else:
+                    yield ', '
+
+        if indent:
+            yield '\n'
+            yield ' ' * ((_level - 1) * indent)
+
+        yield '])'
     elif isinstance(o, tuple):
-        return '(%s)' % (b', '.join(pprint(a, bprefix=bprefix) for a in o))
+        if not o:
+            yield '()'
+            return
+
+        yield '('
+
+        if indent:
+            yield '\n'
+            yield ' ' * (_level * indent)
+
+        for i, a in enumerate(o):
+            for chunk in pprintgen(a, bprefix=bprefix, indent=indent,
+                                   _level=_level + 1):
+                yield chunk
+
+            if i + 1 < len(o):
+                if indent:
+                    yield ',\n'
+                    yield ' ' * (_level * indent)
+                else:
+                    yield ', '
+
+        if indent:
+            yield '\n'
+            yield ' ' * ((_level - 1) * indent)
+
+        yield ')'
+    elif isinstance(o, types.GeneratorType):
+        # Special case of empty generator.
+        try:
+            nextitem = next(o)
+        except StopIteration:
+            yield 'gen[]'
+            return
+
+        yield 'gen['
+
+        if indent:
+            yield '\n'
+            yield ' ' * (_level * indent)
+
+        last = False
+
+        while not last:
+            current = nextitem
+
+            try:
+                nextitem = next(o)
+            except StopIteration:
+                last = True
+
+            for chunk in pprintgen(current, bprefix=bprefix, indent=indent,
+                                   _level=_level + 1):
+                yield chunk
+
+            if not last:
+                if indent:
+                    yield ',\n'
+                    yield ' ' * (_level * indent)
+                else:
+                    yield ', '
+
+        if indent:
+            yield '\n'
+            yield ' ' * ((_level -1) * indent)
+
+        yield ']'
     else:
-        return pycompat.byterepr(o)
+        yield pycompat.byterepr(o)
 
 def prettyrepr(o):
     """Pretty print a representation of a possibly-nested object"""
@@ -111,7 +275,7 @@
     elif callable(r):
         return r()
     else:
-        return pycompat.byterepr(r)
+        return pprint(r)
 
 def binary(s):
     """return true if a string is binary data"""
@@ -424,6 +588,8 @@
     return encoding.trim(text, maxlength, ellipsis='...')
 
 def escapestr(s):
+    if isinstance(s, memoryview):
+        s = bytes(s)
     # call underlying function of s.encode('string_escape') directly for
     # Python 3 compatibility
     return codecs.escape_encode(s)[0]
@@ -464,7 +630,7 @@
         def _cutdown(self, ucstr, space_left):
             l = 0
             colwidth = encoding.ucolwidth
-            for i in xrange(len(ucstr)):
+            for i in pycompat.xrange(len(ucstr)):
                 l += colwidth(ucstr[i])
                 if space_left < l:
                     return (ucstr[:i], ucstr[i:])
--- a/mercurial/verify.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/verify.py	Wed Sep 26 20:33:09 2018 +0900
@@ -45,7 +45,7 @@
         self.errors = 0
         self.warnings = 0
         self.havecl = len(repo.changelog) > 0
-        self.havemf = len(repo.manifestlog._revlog) > 0
+        self.havemf = len(repo.manifestlog.getstorage(b'')) > 0
         self.revlogv1 = repo.changelog.version != revlog.REVLOGV0
         self.lrugetctx = util.lrucachefunc(repo.__getitem__)
         self.refersmf = False
@@ -153,8 +153,8 @@
 
         totalfiles, filerevisions = self._verifyfiles(filenodes, filelinkrevs)
 
-        ui.status(_("%d files, %d changesets, %d total revisions\n") %
-                       (totalfiles, len(repo.changelog), filerevisions))
+        ui.status(_("checked %d changesets with %d changes to %d files\n") %
+                       (len(repo.changelog), filerevisions, totalfiles))
         if self.warnings:
             ui.warn(_("%d warnings encountered!\n") % self.warnings)
         if self.fncachewarned:
@@ -205,7 +205,7 @@
         ui = self.ui
         match = self.match
         mfl = self.repo.manifestlog
-        mf = mfl._revlog.dirlog(dir)
+        mf = mfl.getstorage(dir)
 
         if not dir:
             self.ui.status(_("checking manifests\n"))
@@ -360,7 +360,7 @@
 
             try:
                 fl = repo.file(f)
-            except error.RevlogError as e:
+            except error.StorageError as e:
                 self.err(lr, _("broken revlog! (%s)") % e, f)
                 continue
 
--- a/mercurial/vfs.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/vfs.py	Wed Sep 26 20:33:09 2018 +0900
@@ -213,6 +213,10 @@
         """
         return util.removedirs(self.join(path))
 
+    def rmdir(self, path=None):
+        """Remove an empty directory."""
+        return os.rmdir(self.join(path))
+
     def rmtree(self, path=None, ignore_errors=False, forcibly=False):
         """Remove a directory tree recursively
 
--- a/mercurial/win32.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/win32.py	Wed Sep 26 20:33:09 2018 +0900
@@ -56,20 +56,20 @@
     _LPARAM = ctypes.c_longlong
 
 class _FILETIME(ctypes.Structure):
-    _fields_ = [('dwLowDateTime', _DWORD),
-                ('dwHighDateTime', _DWORD)]
+    _fields_ = [(r'dwLowDateTime', _DWORD),
+                (r'dwHighDateTime', _DWORD)]
 
 class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure):
-    _fields_ = [('dwFileAttributes', _DWORD),
-                ('ftCreationTime', _FILETIME),
-                ('ftLastAccessTime', _FILETIME),
-                ('ftLastWriteTime', _FILETIME),
-                ('dwVolumeSerialNumber', _DWORD),
-                ('nFileSizeHigh', _DWORD),
-                ('nFileSizeLow', _DWORD),
-                ('nNumberOfLinks', _DWORD),
-                ('nFileIndexHigh', _DWORD),
-                ('nFileIndexLow', _DWORD)]
+    _fields_ = [(r'dwFileAttributes', _DWORD),
+                (r'ftCreationTime', _FILETIME),
+                (r'ftLastAccessTime', _FILETIME),
+                (r'ftLastWriteTime', _FILETIME),
+                (r'dwVolumeSerialNumber', _DWORD),
+                (r'nFileSizeHigh', _DWORD),
+                (r'nFileSizeLow', _DWORD),
+                (r'nNumberOfLinks', _DWORD),
+                (r'nFileIndexHigh', _DWORD),
+                (r'nFileIndexLow', _DWORD)]
 
 # CreateFile
 _FILE_SHARE_READ = 0x00000001
@@ -91,50 +91,50 @@
 _STILL_ACTIVE = 259
 
 class _STARTUPINFO(ctypes.Structure):
-    _fields_ = [('cb', _DWORD),
-                ('lpReserved', _LPSTR),
-                ('lpDesktop', _LPSTR),
-                ('lpTitle', _LPSTR),
-                ('dwX', _DWORD),
-                ('dwY', _DWORD),
-                ('dwXSize', _DWORD),
-                ('dwYSize', _DWORD),
-                ('dwXCountChars', _DWORD),
-                ('dwYCountChars', _DWORD),
-                ('dwFillAttribute', _DWORD),
-                ('dwFlags', _DWORD),
-                ('wShowWindow', _WORD),
-                ('cbReserved2', _WORD),
-                ('lpReserved2', ctypes.c_char_p),
-                ('hStdInput', _HANDLE),
-                ('hStdOutput', _HANDLE),
-                ('hStdError', _HANDLE)]
+    _fields_ = [(r'cb', _DWORD),
+                (r'lpReserved', _LPSTR),
+                (r'lpDesktop', _LPSTR),
+                (r'lpTitle', _LPSTR),
+                (r'dwX', _DWORD),
+                (r'dwY', _DWORD),
+                (r'dwXSize', _DWORD),
+                (r'dwYSize', _DWORD),
+                (r'dwXCountChars', _DWORD),
+                (r'dwYCountChars', _DWORD),
+                (r'dwFillAttribute', _DWORD),
+                (r'dwFlags', _DWORD),
+                (r'wShowWindow', _WORD),
+                (r'cbReserved2', _WORD),
+                (r'lpReserved2', ctypes.c_char_p),
+                (r'hStdInput', _HANDLE),
+                (r'hStdOutput', _HANDLE),
+                (r'hStdError', _HANDLE)]
 
 class _PROCESS_INFORMATION(ctypes.Structure):
-    _fields_ = [('hProcess', _HANDLE),
-                ('hThread', _HANDLE),
-                ('dwProcessId', _DWORD),
-                ('dwThreadId', _DWORD)]
+    _fields_ = [(r'hProcess', _HANDLE),
+                (r'hThread', _HANDLE),
+                (r'dwProcessId', _DWORD),
+                (r'dwThreadId', _DWORD)]
 
 _CREATE_NO_WINDOW = 0x08000000
 _SW_HIDE = 0
 
 class _COORD(ctypes.Structure):
-    _fields_ = [('X', ctypes.c_short),
-                ('Y', ctypes.c_short)]
+    _fields_ = [(r'X', ctypes.c_short),
+                (r'Y', ctypes.c_short)]
 
 class _SMALL_RECT(ctypes.Structure):
-    _fields_ = [('Left', ctypes.c_short),
-                ('Top', ctypes.c_short),
-                ('Right', ctypes.c_short),
-                ('Bottom', ctypes.c_short)]
+    _fields_ = [(r'Left', ctypes.c_short),
+                (r'Top', ctypes.c_short),
+                (r'Right', ctypes.c_short),
+                (r'Bottom', ctypes.c_short)]
 
 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
-    _fields_ = [('dwSize', _COORD),
-                ('dwCursorPosition', _COORD),
-                ('wAttributes', _WORD),
-                ('srWindow', _SMALL_RECT),
-                ('dwMaximumWindowSize', _COORD)]
+    _fields_ = [(r'dwSize', _COORD),
+                (r'dwCursorPosition', _COORD),
+                (r'wAttributes', _WORD),
+                (r'srWindow', _SMALL_RECT),
+                (r'dwMaximumWindowSize', _COORD)]
 
 _STD_OUTPUT_HANDLE = _DWORD(-11).value
 _STD_ERROR_HANDLE = _DWORD(-12).value
@@ -149,40 +149,40 @@
 # These structs are only complete enough to achieve what we need.
 class CERT_CHAIN_CONTEXT(ctypes.Structure):
     _fields_ = (
-        ("cbSize", _DWORD),
+        (r"cbSize", _DWORD),
 
         # CERT_TRUST_STATUS struct
-        ("dwErrorStatus", _DWORD),
-        ("dwInfoStatus", _DWORD),
+        (r"dwErrorStatus", _DWORD),
+        (r"dwInfoStatus", _DWORD),
 
-        ("cChain", _DWORD),
-        ("rgpChain", ctypes.c_void_p),
-        ("cLowerQualityChainContext", _DWORD),
-        ("rgpLowerQualityChainContext", ctypes.c_void_p),
-        ("fHasRevocationFreshnessTime", _BOOL),
-        ("dwRevocationFreshnessTime", _DWORD),
+        (r"cChain", _DWORD),
+        (r"rgpChain", ctypes.c_void_p),
+        (r"cLowerQualityChainContext", _DWORD),
+        (r"rgpLowerQualityChainContext", ctypes.c_void_p),
+        (r"fHasRevocationFreshnessTime", _BOOL),
+        (r"dwRevocationFreshnessTime", _DWORD),
     )
 
 class CERT_USAGE_MATCH(ctypes.Structure):
     _fields_ = (
-        ("dwType", _DWORD),
+        (r"dwType", _DWORD),
 
          # CERT_ENHKEY_USAGE struct
-        ("cUsageIdentifier", _DWORD),
-        ("rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
+        (r"cUsageIdentifier", _DWORD),
+        (r"rgpszUsageIdentifier", ctypes.c_void_p), # LPSTR *
     )
 
 class CERT_CHAIN_PARA(ctypes.Structure):
     _fields_ = (
-        ("cbSize", _DWORD),
-        ("RequestedUsage", CERT_USAGE_MATCH),
-        ("RequestedIssuancePolicy", CERT_USAGE_MATCH),
-        ("dwUrlRetrievalTimeout", _DWORD),
-        ("fCheckRevocationFreshnessTime", _BOOL),
-        ("dwRevocationFreshnessTime", _DWORD),
-        ("pftCacheResync", ctypes.c_void_p), # LPFILETIME
-        ("pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
-        ("dwStrongSignFlags", _DWORD),
+        (r"cbSize", _DWORD),
+        (r"RequestedUsage", CERT_USAGE_MATCH),
+        (r"RequestedIssuancePolicy", CERT_USAGE_MATCH),
+        (r"dwUrlRetrievalTimeout", _DWORD),
+        (r"fCheckRevocationFreshnessTime", _BOOL),
+        (r"dwRevocationFreshnessTime", _DWORD),
+        (r"pftCacheResync", ctypes.c_void_p), # LPFILETIME
+        (r"pStrongSignPara", ctypes.c_void_p), # PCCERT_STRONG_SIGN_PARA
+        (r"dwStrongSignFlags", _DWORD),
     )
 
 # types of parameters of C functions used (required by pypy)
@@ -307,8 +307,8 @@
     if code > 0x7fffffff:
         code -= 2**32
     err = ctypes.WinError(code=code)
-    raise OSError(err.errno, '%s: %s' % (name,
-                                         encoding.strtolocal(err.strerror)))
+    raise OSError(err.errno, r'%s: %s' % (encoding.strfromlocal(name),
+                                          err.strerror))
 
 def _getfileinfo(name):
     fh = _kernel32.CreateFileA(name, 0,
@@ -579,11 +579,12 @@
         env = '\0'
     env += '\0'
 
-    args = subprocess.list2cmdline(args)
+    args = subprocess.list2cmdline(pycompat.rapply(encoding.strfromlocal, args))
 
+    # TODO: CreateProcessW on py3?
     res = _kernel32.CreateProcessA(
-        None, args, None, None, False, _CREATE_NO_WINDOW,
-        env, pycompat.getcwd(), ctypes.byref(si), ctypes.byref(pi))
+        None, encoding.strtolocal(args), None, None, False, _CREATE_NO_WINDOW,
+        env, encoding.getcwd(), ctypes.byref(si), ctypes.byref(pi))
     if not res:
         raise ctypes.WinError()
 
@@ -596,7 +597,8 @@
         # use EPERM because it is POSIX prescribed value, even though
         # unlink(2) on directories returns EISDIR on Linux
         raise IOError(errno.EPERM,
-                      "Unlinking directory not permitted: '%s'" % f)
+                      r"Unlinking directory not permitted: '%s'"
+                      % encoding.strfromlocal(f))
 
     # POSIX allows to unlink and rename open files. Windows has serious
     # problems with doing that:
@@ -615,7 +617,7 @@
     # callers to recreate f immediately while having other readers do their
     # implicit zombie filename blocking on a temporary name.
 
-    for tries in xrange(10):
+    for tries in pycompat.xrange(10):
         temp = '%s-%08x' % (f, random.randint(0, 0xffffffff))
         try:
             os.rename(f, temp)  # raises OSError EEXIST if temp exists
@@ -624,7 +626,7 @@
             if e.errno != errno.EEXIST:
                 raise
     else:
-        raise IOError(errno.EEXIST, "No usable temporary filename found")
+        raise IOError(errno.EEXIST, r"No usable temporary filename found")
 
     try:
         os.unlink(temp)
--- a/mercurial/windows.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/windows.py	Wed Sep 26 20:33:09 2018 +0900
@@ -123,11 +123,36 @@
         object.__setattr__(self, r'_lastop', self.OPREAD)
         return self._fp.readlines(*args, **kwargs)
 
+class fdproxy(object):
+    """Wraps osutil.posixfile() to override the name attribute to reflect the
+    underlying file name.
+    """
+    def __init__(self, name, fp):
+        self.name = name
+        self._fp = fp
+
+    def __enter__(self):
+        return self._fp.__enter__()
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self._fp.__exit__(exc_type, exc_value, traceback)
+
+    def __iter__(self):
+        return iter(self._fp)
+
+    def __getattr__(self, name):
+        return getattr(self._fp, name)
+
 def posixfile(name, mode='r', buffering=-1):
     '''Open a file with even more POSIX-like semantics'''
     try:
         fp = osutil.posixfile(name, mode, buffering) # may raise WindowsError
 
+        # PyFile_FromFd() ignores the name, and seems to report fp.name as the
+        # underlying file descriptor.
+        if pycompat.ispy3:
+            fp = fdproxy(name, fp)
+
         # The position when opening in append mode is implementation defined, so
         # make it consistent with other platforms, which position at EOF.
         if 'a' in mode:
@@ -389,7 +414,7 @@
     """
     global _quotere
     if _quotere is None:
-        _quotere = re.compile(r'(\\*)("|\\$)')
+        _quotere = re.compile(br'(\\*)("|\\$)')
     global _needsshellquote
     if _needsshellquote is None:
         # ":" is also treated as "safe character", because it is used as a part
@@ -397,11 +422,11 @@
         # safe because shlex.split() (kind of) treats it as an escape char and
         # drops it.  It will leave the next character, even if it is another
         # "\".
-        _needsshellquote = re.compile(r'[^a-zA-Z0-9._:/-]').search
+        _needsshellquote = re.compile(br'[^a-zA-Z0-9._:/-]').search
     if s and not _needsshellquote(s) and not _quotere.search(s):
         # "s" shouldn't have to be quoted
         return s
-    return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
+    return b'"%s"' % _quotere.sub(br'\1\1\\\2', s)
 
 def _unquote(s):
     if s.startswith(b'"') and s.endswith(b'"'):
@@ -523,7 +548,7 @@
         os.rename(src, dst)
 
 def gethgcmd():
-    return [sys.executable] + sys.argv[:1]
+    return [encoding.strtolocal(arg) for arg in [sys.executable] + sys.argv[:1]]
 
 def groupmembers(name):
     # Don't support groups on Windows for now
@@ -554,9 +579,10 @@
         scope = (scope,)
     for s in scope:
         try:
-            val = winreg.QueryValueEx(winreg.OpenKey(s, key), valname)[0]
-            # never let a Unicode string escape into the wild
-            return encoding.unitolocal(val)
+            with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
+                val = winreg.QueryValueEx(hkey, valname)[0]
+                # never let a Unicode string escape into the wild
+                return encoding.unitolocal(val)
         except EnvironmentError:
             pass
 
--- a/mercurial/wireprotoframing.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/wireprotoframing.py	Wed Sep 26 20:33:09 2018 +0900
@@ -17,7 +17,6 @@
 from .i18n import _
 from .thirdparty import (
     attr,
-    cbor,
 )
 from . import (
     encoding,
@@ -25,6 +24,7 @@
     util,
 )
 from .utils import (
+    cborutil,
     stringutil,
 )
 
@@ -217,8 +217,8 @@
             finalflags |= int(flag)
 
     if payload.startswith(b'cbor:'):
-        payload = cbor.dumps(stringutil.evalpythonliteral(payload[5:]),
-                             canonical=True)
+        payload = b''.join(cborutil.streamencode(
+            stringutil.evalpythonliteral(payload[5:])))
 
     else:
         payload = stringutil.unescapestr(payload)
@@ -289,7 +289,7 @@
     if args:
         data[b'args'] = args
 
-    data = cbor.dumps(data, canonical=True)
+    data = b''.join(cborutil.streamencode(data))
 
     offset = 0
 
@@ -347,7 +347,7 @@
     Returns a generator of bytearrays.
     """
     # Automatically send the overall CBOR response map.
-    overall = cbor.dumps({b'status': b'ok'}, canonical=True)
+    overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
     if len(overall) > maxframesize:
         raise error.ProgrammingError('not yet implemented')
 
@@ -388,16 +388,14 @@
 
 def createbytesresponseframesfromgen(stream, requestid, gen,
                                      maxframesize=DEFAULT_MAX_FRAME_SIZE):
-    overall = cbor.dumps({b'status': b'ok'}, canonical=True)
+    """Generator of frames from a generator of byte chunks.
 
-    yield stream.makeframe(requestid=requestid,
-                           typeid=FRAME_TYPE_COMMAND_RESPONSE,
-                           flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
-                           payload=overall)
-
+    This assumes that another frame will follow whatever this emits. i.e.
+    this always emits the continuation flag and never emits the end-of-stream
+    flag.
+    """
     cb = util.chunkbuffer(gen)
-
-    flags = 0
+    flags = FLAG_COMMAND_RESPONSE_CONTINUATION
 
     while True:
         chunk = cb.read(maxframesize)
@@ -411,14 +409,24 @@
 
         flags |= FLAG_COMMAND_RESPONSE_CONTINUATION
 
-    flags ^= FLAG_COMMAND_RESPONSE_CONTINUATION
-    flags |= FLAG_COMMAND_RESPONSE_EOS
-    yield stream.makeframe(requestid=requestid,
-                           typeid=FRAME_TYPE_COMMAND_RESPONSE,
-                           flags=flags,
-                           payload=b'')
+def createcommandresponseokframe(stream, requestid):
+    overall = b''.join(cborutil.streamencode({b'status': b'ok'}))
+
+    return stream.makeframe(requestid=requestid,
+                            typeid=FRAME_TYPE_COMMAND_RESPONSE,
+                            flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
+                            payload=overall)
+
+def createcommandresponseeosframe(stream, requestid):
+    """Create an empty payload frame representing command end-of-stream."""
+    return stream.makeframe(requestid=requestid,
+                            typeid=FRAME_TYPE_COMMAND_RESPONSE,
+                            flags=FLAG_COMMAND_RESPONSE_EOS,
+                            payload=b'')
 
 def createcommanderrorresponse(stream, requestid, message, args=None):
+    # TODO should this be using a list of {'msg': ..., 'args': {}} so atom
+    # formatting works consistently?
     m = {
         b'status': b'error',
         b'error': {
@@ -429,7 +437,7 @@
     if args:
         m[b'error'][b'args'] = args
 
-    overall = cbor.dumps(m, canonical=True)
+    overall = b''.join(cborutil.streamencode(m))
 
     yield stream.makeframe(requestid=requestid,
                            typeid=FRAME_TYPE_COMMAND_RESPONSE,
@@ -440,10 +448,10 @@
     # TODO properly handle frame size limits.
     assert len(msg) <= DEFAULT_MAX_FRAME_SIZE
 
-    payload = cbor.dumps({
+    payload = b''.join(cborutil.streamencode({
         b'type': errtype,
         b'message': [{b'msg': msg}],
-    }, canonical=True)
+    }))
 
     yield stream.makeframe(requestid=requestid,
                            typeid=FRAME_TYPE_ERROR_RESPONSE,
@@ -493,7 +501,7 @@
 
         atomdicts.append(atom)
 
-    payload = cbor.dumps(atomdicts, canonical=True)
+    payload = b''.join(cborutil.streamencode(atomdicts))
 
     if len(payload) > maxframesize:
         raise ValueError('cannot encode data in a single frame')
@@ -503,6 +511,98 @@
                            flags=0,
                            payload=payload)
 
+class bufferingcommandresponseemitter(object):
+    """Helper object to emit command response frames intelligently.
+
+    Raw command response data is likely emitted in chunks much smaller
+    than what can fit in a single frame. This class exists to buffer
+    chunks until enough data is available to fit in a single frame.
+
+    TODO we'll need something like this when compression is supported.
+    So it might make sense to implement this functionality at the stream
+    level.
+    """
+    def __init__(self, stream, requestid, maxframesize=DEFAULT_MAX_FRAME_SIZE):
+        self._stream = stream
+        self._requestid = requestid
+        self._maxsize = maxframesize
+        self._chunks = []
+        self._chunkssize = 0
+
+    def send(self, data):
+        """Send new data for emission.
+
+        Is a generator of new frames that were derived from the new input.
+
+        If the special input ``None`` is received, flushes all buffered
+        data to frames.
+        """
+
+        if data is None:
+            for frame in self._flush():
+                yield frame
+            return
+
+        # There is a ton of potential to do more complicated things here.
+        # Our immediate goal is to coalesce small chunks into big frames,
+        # not achieve the fewest number of frames possible. So we go with
+        # a simple implementation:
+        #
+        # * If a chunk is too large for a frame, we flush and emit frames
+        #   for the new chunk.
+        # * If a chunk can be buffered without total buffered size limits
+        #   being exceeded, we do that.
+        # * If a chunk causes us to go over our buffering limit, we flush
+        #   and then buffer the new chunk.
+
+        if len(data) > self._maxsize:
+            for frame in self._flush():
+                yield frame
+
+            # Now emit frames for the big chunk.
+            offset = 0
+            while True:
+                chunk = data[offset:offset + self._maxsize]
+                offset += len(chunk)
+
+                yield self._stream.makeframe(
+                    self._requestid,
+                    typeid=FRAME_TYPE_COMMAND_RESPONSE,
+                    flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
+                    payload=chunk)
+
+                if offset == len(data):
+                    return
+
+        # If we don't have enough to constitute a full frame, buffer and
+        # return.
+        if len(data) + self._chunkssize < self._maxsize:
+            self._chunks.append(data)
+            self._chunkssize += len(data)
+            return
+
+        # Else flush what we have and buffer the new chunk. We could do
+        # something more intelligent here, like break the chunk. Let's
+        # keep things simple for now.
+        for frame in self._flush():
+            yield frame
+
+        self._chunks.append(data)
+        self._chunkssize = len(data)
+
+    def _flush(self):
+        payload = b''.join(self._chunks)
+        assert len(payload) <= self._maxsize
+
+        self._chunks[:] = []
+        self._chunkssize = 0
+
+        yield self._stream.makeframe(
+            self._requestid,
+            typeid=FRAME_TYPE_COMMAND_RESPONSE,
+            flags=FLAG_COMMAND_RESPONSE_CONTINUATION,
+            payload=payload)
+
 class stream(object):
     """Represents a logical unidirectional series of frames."""
 
@@ -684,14 +784,72 @@
                 'framegen': result,
             }
 
-    def oncommandresponsereadygen(self, stream, requestid, gen):
-        """Signal that a bytes response is ready, with data as a generator."""
+    def oncommandresponsereadyobjects(self, stream, requestid, objs):
+        """Signal that objects are ready to be sent to the client.
+
+        ``objs`` is an iterable of objects (typically a generator) that will
+        be encoded via CBOR and added to frames, which will be sent to the
+        client.
+        """
         ensureserverstream(stream)
 
+        # We need to take care over exception handling. Uncaught exceptions
+        # when generating frames could lead to premature end of the frame
+        # stream and the possibility of the server or client process getting
+        # in a bad state.
+        #
+        # Keep in mind that if ``objs`` is a generator, advancing it could
+        # raise exceptions that originated in e.g. wire protocol command
+        # functions. That is why we differentiate between exceptions raised
+        # when iterating versus other exceptions that occur.
+        #
+        # In all cases, when the function finishes, the request is fully
+        # handled and no new frames for it should be seen.
+
         def sendframes():
-            for frame in createbytesresponseframesfromgen(stream, requestid,
-                                                          gen):
-                yield frame
+            emitted = False
+            emitter = bufferingcommandresponseemitter(stream, requestid)
+            while True:
+                try:
+                    o = next(objs)
+                except StopIteration:
+                    for frame in emitter.send(None):
+                        yield frame
+
+                    if emitted:
+                        yield createcommandresponseeosframe(stream, requestid)
+                    break
+
+                except error.WireprotoCommandError as e:
+                    for frame in createcommanderrorresponse(
+                        stream, requestid, e.message, e.messageargs):
+                        yield frame
+                    break
+
+                except Exception as e:
+                    for frame in createerrorframe(stream, requestid,
+                                                  '%s' % e,
+                                                  errtype='server'):
+                        yield frame
+
+                    break
+
+                try:
+                    if not emitted:
+                        yield createcommandresponseokframe(stream, requestid)
+                        emitted = True
+
+                    for chunk in cborutil.streamencode(o):
+                        for frame in emitter.send(chunk):
+                            yield frame
+
+                except Exception as e:
+                    for frame in createerrorframe(stream, requestid,
+                                                  '%s' % e,
+                                                  errtype='server'):
+                        yield frame
+
+                    break
 
             self._activecommands.remove(requestid)
 
@@ -784,7 +942,7 @@
 
         # Decode the payloads as CBOR.
         entry['payload'].seek(0)
-        request = cbor.load(entry['payload'])
+        request = cborutil.decodeall(entry['payload'].getvalue())[0]
 
         if b'name' not in request:
             self._state = 'errored'
@@ -1158,7 +1316,7 @@
         del self._activerequests[request.requestid]
 
         # The payload should be a CBOR map.
-        m = cbor.loads(frame.payload)
+        m = cborutil.decodeall(frame.payload)[0]
 
         return 'error', {
             'request': request,
--- a/mercurial/wireprotoserver.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/wireprotoserver.py	Wed Sep 26 20:33:09 2018 +0900
@@ -12,9 +12,6 @@
 import threading
 
 from .i18n import _
-from .thirdparty import (
-    cbor,
-)
 from . import (
     encoding,
     error,
@@ -25,6 +22,7 @@
     wireprotov2server,
 )
 from .utils import (
+    cborutil,
     interfaceutil,
     procutil,
 )
@@ -389,7 +387,7 @@
 
     res.status = b'200 OK'
     res.headers[b'Content-Type'] = b'application/mercurial-cbor'
-    res.setbodybytes(cbor.dumps(m, canonical=True))
+    res.setbodybytes(b''.join(cborutil.streamencode(m)))
 
     return True
 
@@ -502,14 +500,14 @@
     def getargs(self, args):
         data = {}
         keys = args.split()
-        for n in xrange(len(keys)):
+        for n in pycompat.xrange(len(keys)):
             argline = self._fin.readline()[:-1]
             arg, l = argline.split()
             if arg not in keys:
                 raise error.Abort(_("unexpected parameter %r") % arg)
             if arg == '*':
                 star = {}
-                for k in xrange(int(l)):
+                for k in pycompat.xrange(int(l)):
                     argline = self._fin.readline()[:-1]
                     arg, l = argline.split()
                     val = self._fin.read(int(l))
--- a/mercurial/wireprototypes.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/wireprototypes.py	Wed Sep 26 20:33:09 2018 +0900
@@ -22,8 +22,8 @@
 SSHV1 = 'ssh-v1'
 # These are advertised over the wire. Increment the counters at the end
 # to reflect BC breakages.
-SSHV2 = 'exp-ssh-v2-0001'
-HTTP_WIREPROTO_V2 = 'exp-http-v2-0001'
+SSHV2 = 'exp-ssh-v2-0002'
+HTTP_WIREPROTO_V2 = 'exp-http-v2-0002'
 
 # All available wire protocol transports.
 TRANSPORTS = {
@@ -106,27 +106,6 @@
     def __init__(self, gen=None):
         self.gen = gen
 
-class cborresponse(object):
-    """Encode the response value as CBOR."""
-    def __init__(self, v):
-        self.value = v
-
-class v2errorresponse(object):
-    """Represents a command error for version 2 transports."""
-    def __init__(self, message, args=None):
-        self.message = message
-        self.args = args
-
-class v2streamingresponse(object):
-    """A response whose data is supplied by a generator.
-
-    The generator can either consist of data structures to CBOR
-    encode or a stream of already-encoded bytes.
-    """
-    def __init__(self, gen, compressible=True):
-        self.gen = gen
-        self.compressible = compressible
-
 # list of nodes encoding / decoding
 def decodelist(l, sep=' '):
     if l:
--- a/mercurial/wireprotov1peer.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/wireprotov1peer.py	Wed Sep 26 20:33:09 2018 +0900
@@ -64,6 +64,7 @@
         encresref.set(self._submitone(cmd, encargsorres))
         return next(batchable)
     setattr(plain, 'batchable', f)
+    setattr(plain, '__name__', f.__name__)
     return plain
 
 class future(object):
@@ -497,7 +498,7 @@
     def between(self, pairs):
         batch = 8 # avoid giant requests
         r = []
-        for i in xrange(0, len(pairs), batch):
+        for i in pycompat.xrange(0, len(pairs), batch):
             n = " ".join([wireprototypes.encodelist(p, '-')
                           for p in pairs[i:i + batch]])
             d = self._call("between", pairs=n)
--- a/mercurial/wireprotov2peer.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/wireprotov2peer.py	Wed Sep 26 20:33:09 2018 +0900
@@ -7,16 +7,17 @@
 
 from __future__ import absolute_import
 
+import threading
+
 from .i18n import _
-from .thirdparty import (
-    cbor,
-)
 from . import (
     encoding,
     error,
-    util,
     wireprotoframing,
 )
+from .utils import (
+    cborutil,
+)
 
 def formatrichmessage(atoms):
     """Format an encoded message from the framing protocol."""
@@ -27,30 +28,108 @@
         msg = _(atom[b'msg'])
 
         if b'args' in atom:
-            msg = msg % atom[b'args']
+            msg = msg % tuple(atom[b'args'])
 
         chunks.append(msg)
 
     return b''.join(chunks)
 
 class commandresponse(object):
-    """Represents the response to a command request."""
+    """Represents the response to a command request.
+
+    Instances track the state of the command and hold its results.
+
+    An external entity is required to update the state of the object when
+    events occur.
+    """
 
     def __init__(self, requestid, command):
         self.requestid = requestid
         self.command = command
 
-        self.b = util.bytesio()
+        # Whether all remote input related to this command has been
+        # received.
+        self._inputcomplete = False
+
+        # We have a lock that is acquired when important object state is
+        # mutated. This is to prevent race conditions between 1 thread
+        # sending us new data and another consuming it.
+        self._lock = threading.RLock()
+
+        # An event is set when state of the object changes. This event
+        # is waited on by the generator emitting objects.
+        self._serviceable = threading.Event()
+
+        self._pendingevents = []
+        self._decoder = cborutil.bufferingdecoder()
+        self._seeninitial = False
+
+    def _oninputcomplete(self):
+        with self._lock:
+            self._inputcomplete = True
+            self._serviceable.set()
+
+    def _onresponsedata(self, data):
+        available, readcount, wanted = self._decoder.decode(data)
+
+        if not available:
+            return
+
+        with self._lock:
+            for o in self._decoder.getavailable():
+                if not self._seeninitial:
+                    self._handleinitial(o)
+                    continue
+
+                self._pendingevents.append(o)
+
+            self._serviceable.set()
 
-    def cborobjects(self):
-        """Obtain decoded CBOR objects from this response."""
-        size = self.b.tell()
-        self.b.seek(0)
+    def _handleinitial(self, o):
+        self._seeninitial = True
+        if o[b'status'] == 'ok':
+            return
+
+        atoms = [{'msg': o[b'error'][b'message']}]
+        if b'args' in o[b'error']:
+            atoms[0]['args'] = o[b'error'][b'args']
+
+        raise error.RepoError(formatrichmessage(atoms))
+
+    def objects(self):
+        """Obtained decoded objects from this response.
+
+        This is a generator of data structures that were decoded from the
+        command response.
+
+        Obtaining the next member of the generator may block due to waiting
+        on external data to become available.
 
-        decoder = cbor.CBORDecoder(self.b)
+        If the server encountered an error in the middle of serving the data
+        or if another error occurred, an exception may be raised when
+        advancing the generator.
+        """
+        while True:
+            # TODO this can infinite loop if self._inputcomplete is never
+            # set. We likely want to tie the lifetime of this object/state
+            # to that of the background thread receiving frames and updating
+            # our state.
+            self._serviceable.wait(1.0)
 
-        while self.b.tell() < size:
-            yield decoder.decode()
+            with self._lock:
+                self._serviceable.clear()
+
+                # Make copies because objects could be mutated during
+                # iteration.
+                stop = self._inputcomplete
+                pending = list(self._pendingevents)
+                self._pendingevents[:] = []
+
+            for o in pending:
+                yield o
+
+            if stop:
+                break
 
 class clienthandler(object):
     """Object to handle higher-level client activities.
@@ -83,6 +162,8 @@
         rid = request.requestid
         self._requests[rid] = request
         self._futures[rid] = f
+        # TODO we need some kind of lifetime on response instances otherwise
+        # objects() may deadlock.
         self._responses[rid] = commandresponse(rid, command)
 
         return iter(())
@@ -122,11 +203,17 @@
         if action == 'error':
             e = error.RepoError(meta['message'])
 
+            if frame.requestid in self._responses:
+                self._responses[frame.requestid]._oninputcomplete()
+
             if frame.requestid in self._futures:
                 self._futures[frame.requestid].set_exception(e)
+                del self._futures[frame.requestid]
             else:
                 raise e
 
+            return
+
         if frame.requestid not in self._requests:
             raise error.ProgrammingError(
                 'received frame for unknown request; this is either a bug in '
@@ -136,35 +223,40 @@
         response = self._responses[frame.requestid]
 
         if action == 'responsedata':
-            response.b.write(meta['data'])
-
-            if meta['eos']:
-                # If the command has a decoder, resolve the future to the
-                # decoded value. Otherwise resolve to the rich response object.
-                decoder = COMMAND_DECODERS.get(response.command)
-
-                # TODO consider always resolving the overall status map.
-                if decoder:
-                    objs = response.cborobjects()
-
-                    overall = next(objs)
-
-                    if overall['status'] == 'ok':
-                        self._futures[frame.requestid].set_result(decoder(objs))
-                    else:
-                        e = error.RepoError(
-                            formatrichmessage(overall['error']['message']))
-                        self._futures[frame.requestid].set_exception(e)
-                else:
-                    self._futures[frame.requestid].set_result(response)
-
-                del self._requests[frame.requestid]
+            # Any failures processing this frame should bubble up to the
+            # future tracking the request.
+            try:
+                self._processresponsedata(frame, meta, response)
+            except BaseException as e:
+                self._futures[frame.requestid].set_exception(e)
                 del self._futures[frame.requestid]
-
+                response._oninputcomplete()
         else:
             raise error.ProgrammingError(
                 'unhandled action from clientreactor: %s' % action)
 
+    def _processresponsedata(self, frame, meta, response):
+        # This can raise. The caller can handle it.
+        response._onresponsedata(meta['data'])
+
+        if meta['eos']:
+            response._oninputcomplete()
+            del self._requests[frame.requestid]
+
+        # If the command has a decoder, we wait until all input has been
+        # received before resolving the future. Otherwise we resolve the
+        # future immediately.
+        if frame.requestid not in self._futures:
+            return
+
+        if response.command not in COMMAND_DECODERS:
+            self._futures[frame.requestid].set_result(response.objects())
+            del self._futures[frame.requestid]
+        elif response._inputcomplete:
+            decoded = COMMAND_DECODERS[response.command](response.objects())
+            self._futures[frame.requestid].set_result(decoded)
+            del self._futures[frame.requestid]
+
 def decodebranchmap(objs):
     # Response should be a single CBOR map of branch name to array of nodes.
     bm = next(objs)
--- a/mercurial/wireprotov2server.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/mercurial/wireprotov2server.py	Wed Sep 26 20:33:09 2018 +0900
@@ -9,12 +9,18 @@
 import contextlib
 
 from .i18n import _
-from .thirdparty import (
-    cbor,
+from .node import (
+    hex,
+    nullid,
+    nullrev,
 )
 from . import (
+    changegroup,
+    dagop,
+    discovery,
     encoding,
     error,
+    narrowspec,
     pycompat,
     streamclone,
     util,
@@ -297,28 +303,23 @@
             res.setbodybytes(_('command in frame must match command in URL'))
             return True
 
-    rsp = dispatch(repo, proto, command['command'])
-
     res.status = b'200 OK'
     res.headers[b'Content-Type'] = FRAMINGTYPE
 
-    if isinstance(rsp, wireprototypes.cborresponse):
-        encoded = cbor.dumps(rsp.value, canonical=True)
-        action, meta = reactor.oncommandresponseready(outstream,
-                                                      command['requestid'],
-                                                      encoded)
-    elif isinstance(rsp, wireprototypes.v2streamingresponse):
-        action, meta = reactor.oncommandresponsereadygen(outstream,
-                                                         command['requestid'],
-                                                         rsp.gen)
-    elif isinstance(rsp, wireprototypes.v2errorresponse):
-        action, meta = reactor.oncommanderror(outstream,
-                                              command['requestid'],
-                                              rsp.message,
-                                              rsp.args)
-    else:
+    try:
+        objs = dispatch(repo, proto, command['command'])
+
+        action, meta = reactor.oncommandresponsereadyobjects(
+            outstream, command['requestid'], objs)
+
+    except error.WireprotoCommandError as e:
+        action, meta = reactor.oncommanderror(
+            outstream, command['requestid'], e.message, e.messageargs)
+
+    except Exception as e:
         action, meta = reactor.onservererror(
-            _('unhandled response type from wire proto command'))
+            outstream, command['requestid'],
+            _('exception when invoking command: %s') % e)
 
     if action == 'sendframes':
         res.setbodygen(meta['framegen'])
@@ -338,7 +339,7 @@
     func, spec = COMMANDS[command]
     args = proto.getargs(spec)
 
-    return func(repo, proto, **args)
+    return func(repo, proto, **pycompat.strkwargs(args))
 
 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
 class httpv2protocolhandler(object):
@@ -352,13 +353,39 @@
         return HTTP_WIREPROTO_V2
 
     def getargs(self, args):
+        # First look for args that were passed but aren't registered on this
+        # command.
+        extra = set(self._args) - set(args)
+        if extra:
+            raise error.WireprotoCommandError(
+                'unsupported argument to command: %s' %
+                ', '.join(sorted(extra)))
+
+        # And look for required arguments that are missing.
+        missing = {a for a in args if args[a]['required']} - set(self._args)
+
+        if missing:
+            raise error.WireprotoCommandError(
+                'missing required arguments: %s' % ', '.join(sorted(missing)))
+
+        # Now derive the arguments to pass to the command, taking into
+        # account the arguments specified by the client.
         data = {}
-        for k, typ in args.items():
-            if k == '*':
-                raise NotImplementedError('do not support * args')
-            elif k in self._args:
-                # TODO consider validating value types.
-                data[k] = self._args[k]
+        for k, meta in sorted(args.items()):
+            # This argument wasn't passed by the client.
+            if k not in self._args:
+                data[k] = meta['default']()
+                continue
+
+            v = self._args[k]
+
+            # Sets may be expressed as lists. Silently normalize.
+            if meta['type'] == 'set' and isinstance(v, list):
+                v = set(v)
+
+            # TODO consider more/stronger type validation.
+
+            data[k] = v
 
         return data
 
@@ -403,11 +430,28 @@
         'commands': {},
         'compression': compression,
         'framingmediatypes': [FRAMINGTYPE],
+        'pathfilterprefixes': set(narrowspec.VALID_PREFIXES),
     }
 
     for command, entry in COMMANDS.items():
+        args = {}
+
+        for arg, meta in entry.args.items():
+            args[arg] = {
+                # TODO should this be a normalized type using CBOR's
+                # terminology?
+                b'type': meta['type'],
+                b'required': meta['required'],
+            }
+
+            if not meta['required']:
+                args[arg][b'default'] = meta['default']()
+
+            if meta['validvalues']:
+                args[arg][b'validvalues'] = meta['validvalues']
+
         caps['commands'][command] = {
-            'args': entry.args,
+            'args': args,
             'permissions': [entry.permission],
         }
 
@@ -417,18 +461,123 @@
 
     return proto.addcapabilities(repo, caps)
 
+def builddeltarequests(store, nodes, haveparents):
+    """Build a series of revision delta requests against a backend store.
+
+    Returns a list of revision numbers in the order they should be sent
+    and a list of ``irevisiondeltarequest`` instances to be made against
+    the backend store.
+    """
+    # We sort and send nodes in DAG order because this is optimal for
+    # storage emission.
+    # TODO we may want a better storage API here - one where we can throw
+    # a list of nodes and delta preconditions over a figurative wall and
+    # have the storage backend figure it out for us.
+    revs = dagop.linearize({store.rev(n) for n in nodes}, store.parentrevs)
+
+    requests = []
+    seenrevs = set()
+
+    for rev in revs:
+        node = store.node(rev)
+        parentnodes = store.parents(node)
+        parentrevs = [store.rev(n) for n in parentnodes]
+        deltabaserev = store.deltaparent(rev)
+        deltabasenode = store.node(deltabaserev)
+
+        # The choice of whether to send a fulltext revision or a delta and
+        # what delta to send is governed by a few factors.
+        #
+        # To send a delta, we need to ensure the receiver is capable of
+        # decoding it. And that requires the receiver to have the base
+        # revision the delta is against.
+        #
+        # We can only guarantee the receiver has the base revision if
+        # a) we've already sent the revision as part of this group
+        # b) the receiver has indicated they already have the revision.
+        # And the mechanism for "b" is the client indicating they have
+        # parent revisions. So this means we can only send the delta if
+        # it is sent before or it is against a delta and the receiver says
+        # they have a parent.
+
+        # We can send storage delta if it is against a revision we've sent
+        # in this group.
+        if deltabaserev != nullrev and deltabaserev in seenrevs:
+            basenode = deltabasenode
+
+        # We can send storage delta if it is against a parent revision and
+        # the receiver indicates they have the parents.
+        elif (deltabaserev != nullrev and deltabaserev in parentrevs
+              and haveparents):
+            basenode = deltabasenode
+
+        # Otherwise the storage delta isn't appropriate. Fall back to
+        # using another delta, if possible.
+
+        # Use p1 if we've emitted it or receiver says they have it.
+        elif parentrevs[0] != nullrev and (
+            parentrevs[0] in seenrevs or haveparents):
+            basenode = parentnodes[0]
+
+        # Use p2 if we've emitted it or receiver says they have it.
+        elif parentrevs[1] != nullrev and (
+            parentrevs[1] in seenrevs or haveparents):
+            basenode = parentnodes[1]
+
+        # Nothing appropriate to delta against. Send the full revision.
+        else:
+            basenode = nullid
+
+        requests.append(changegroup.revisiondeltarequest(
+            node=node,
+            p1node=parentnodes[0],
+            p2node=parentnodes[1],
+            # Receiver deals with linknode resolution.
+            linknode=nullid,
+            basenode=basenode,
+        ))
+
+        seenrevs.add(rev)
+
+    return revs, requests
+
 def wireprotocommand(name, args=None, permission='push'):
     """Decorator to declare a wire protocol command.
 
     ``name`` is the name of the wire protocol command being provided.
 
-    ``args`` is a dict of argument names to example values.
+    ``args`` is a dict defining arguments accepted by the command. Keys are
+    the argument name. Values are dicts with the following keys:
+
+       ``type``
+          The argument data type. Must be one of the following string
+          literals: ``bytes``, ``int``, ``list``, ``dict``, ``set``,
+          or ``bool``.
+
+       ``default``
+          A callable returning the default value for this argument. If not
+          specified, ``None`` will be the default value.
+
+       ``required``
+          Bool indicating whether the argument is required.
+
+       ``example``
+          An example value for this argument.
+
+       ``validvalues``
+          Set of recognized values for this argument.
 
     ``permission`` defines the permission type needed to run this command.
     Can be ``push`` or ``pull``. These roughly map to read-write and read-only,
     respectively. Default is to assume command requires ``push`` permissions
     because otherwise commands not declaring their permissions could modify
     a repository that is supposed to be read-only.
+
+    Wire protocol commands are generators of objects to be serialized and
+    sent to the client.
+
+    If a command raises an uncaught exception, this will be translated into
+    a command error.
     """
     transports = {k for k, v in wireprototypes.TRANSPORTS.items()
                   if v['version'] == 2}
@@ -445,6 +594,37 @@
         raise error.ProgrammingError('arguments for version 2 commands '
                                      'must be declared as dicts')
 
+    for arg, meta in args.items():
+        if arg == '*':
+            raise error.ProgrammingError('* argument name not allowed on '
+                                         'version 2 commands')
+
+        if not isinstance(meta, dict):
+            raise error.ProgrammingError('arguments for version 2 commands '
+                                         'must declare metadata as a dict')
+
+        if 'type' not in meta:
+            raise error.ProgrammingError('%s argument for command %s does not '
+                                         'declare type field' % (arg, name))
+
+        if meta['type'] not in ('bytes', 'int', 'list', 'dict', 'set', 'bool'):
+            raise error.ProgrammingError('%s argument for command %s has '
+                                         'illegal type: %s' % (arg, name,
+                                                               meta['type']))
+
+        if 'example' not in meta:
+            raise error.ProgrammingError('%s argument for command %s does not '
+                                         'declare example field' % (arg, name))
+
+        if 'default' in meta and meta.get('required'):
+            raise error.ProgrammingError('%s argument for command %s is marked '
+                                         'as required but has a default value' %
+                                         (arg, name))
+
+        meta.setdefault('default', lambda: None)
+        meta.setdefault('required', False)
+        meta.setdefault('validvalues', None)
+
     def register(func):
         if name in COMMANDS:
             raise error.ProgrammingError('%s command already registered '
@@ -459,76 +639,469 @@
 
 @wireprotocommand('branchmap', permission='pull')
 def branchmapv2(repo, proto):
-    branchmap = {encoding.fromlocal(k): v
-                 for k, v in repo.branchmap().iteritems()}
-
-    return wireprototypes.cborresponse(branchmap)
+    yield {encoding.fromlocal(k): v
+           for k, v in repo.branchmap().iteritems()}
 
 @wireprotocommand('capabilities', permission='pull')
 def capabilitiesv2(repo, proto):
-    caps = _capabilitiesv2(repo, proto)
+    yield _capabilitiesv2(repo, proto)
+
+@wireprotocommand(
+    'changesetdata',
+    args={
+        'noderange': {
+            'type': 'list',
+            'example': [[b'0123456...'], [b'abcdef...']],
+        },
+        'nodes': {
+            'type': 'list',
+            'example': [b'0123456...'],
+        },
+        'nodesdepth': {
+            'type': 'int',
+            'example': 10,
+        },
+        'fields': {
+            'type': 'set',
+            'default': set,
+            'example': {b'parents', b'revision'},
+            'validvalues': {b'bookmarks', b'parents', b'phase', b'revision'},
+        },
+    },
+    permission='pull')
+def changesetdata(repo, proto, noderange, nodes, nodesdepth, fields):
+    # TODO look for unknown fields and abort when they can't be serviced.
+    # This could probably be validated by dispatcher using validvalues.
+
+    if noderange is None and nodes is None:
+        raise error.WireprotoCommandError(
+            'noderange or nodes must be defined')
+
+    if nodesdepth is not None and nodes is None:
+        raise error.WireprotoCommandError(
+            'nodesdepth requires the nodes argument')
+
+    if noderange is not None:
+        if len(noderange) != 2:
+            raise error.WireprotoCommandError(
+                'noderange must consist of 2 elements')
+
+        if not noderange[1]:
+            raise error.WireprotoCommandError(
+                'heads in noderange request cannot be empty')
+
+    cl = repo.changelog
+    hasnode = cl.hasnode
+
+    seen = set()
+    outgoing = []
+
+    if nodes is not None:
+        outgoing = [n for n in nodes if hasnode(n)]
+
+        if nodesdepth:
+            outgoing = [cl.node(r) for r in
+                        repo.revs(b'ancestors(%ln, %d)', outgoing,
+                                  nodesdepth - 1)]
+
+        seen |= set(outgoing)
+
+    if noderange is not None:
+        if noderange[0]:
+            common = [n for n in noderange[0] if hasnode(n)]
+        else:
+            common = [nullid]
 
-    return wireprototypes.cborresponse(caps)
+        for n in discovery.outgoing(repo, common, noderange[1]).missing:
+            if n not in seen:
+                outgoing.append(n)
+            # Don't need to add to seen here because this is the final
+            # source of nodes and there should be no duplicates in this
+            # list.
+
+    seen.clear()
+    publishing = repo.publishing()
+
+    if outgoing:
+        repo.hook('preoutgoing', throw=True, source='serve')
+
+    yield {
+        b'totalitems': len(outgoing),
+    }
+
+    # The phases of nodes already transferred to the client may have changed
+    # since the client last requested data. We send phase-only records
+    # for these revisions, if requested.
+    if b'phase' in fields and noderange is not None:
+        # TODO skip nodes whose phase will be reflected by a node in the
+        # outgoing set. This is purely an optimization to reduce data
+        # size.
+        for node in noderange[0]:
+            yield {
+                b'node': node,
+                b'phase': b'public' if publishing else repo[node].phasestr()
+            }
+
+    nodebookmarks = {}
+    for mark, node in repo._bookmarks.items():
+        nodebookmarks.setdefault(node, set()).add(mark)
+
+    # It is already topologically sorted by revision number.
+    for node in outgoing:
+        d = {
+            b'node': node,
+        }
+
+        if b'parents' in fields:
+            d[b'parents'] = cl.parents(node)
+
+        if b'phase' in fields:
+            if publishing:
+                d[b'phase'] = b'public'
+            else:
+                ctx = repo[node]
+                d[b'phase'] = ctx.phasestr()
+
+        if b'bookmarks' in fields and node in nodebookmarks:
+            d[b'bookmarks'] = sorted(nodebookmarks[node])
+            del nodebookmarks[node]
+
+        followingmeta = []
+        followingdata = []
+
+        if b'revision' in fields:
+            revisiondata = cl.revision(node, raw=True)
+            followingmeta.append((b'revision', len(revisiondata)))
+            followingdata.append(revisiondata)
+
+        # TODO make it possible for extensions to wrap a function or register
+        # a handler to service custom fields.
+
+        if followingmeta:
+            d[b'fieldsfollowing'] = followingmeta
+
+        yield d
+
+        for extra in followingdata:
+            yield extra
 
-@wireprotocommand('heads',
-                  args={
-                      'publiconly': False,
-                  },
-                  permission='pull')
-def headsv2(repo, proto, publiconly=False):
+    # If requested, send bookmarks from nodes that didn't have revision
+    # data sent so receiver is aware of any bookmark updates.
+    if b'bookmarks' in fields:
+        for node, marks in sorted(nodebookmarks.iteritems()):
+            yield {
+                b'node': node,
+                b'bookmarks': sorted(marks),
+            }
+
+class FileAccessError(Exception):
+    """Represents an error accessing a specific file."""
+
+    def __init__(self, path, msg, args):
+        self.path = path
+        self.msg = msg
+        self.args = args
+
+def getfilestore(repo, proto, path):
+    """Obtain a file storage object for use with wire protocol.
+
+    Exists as a standalone function so extensions can monkeypatch to add
+    access control.
+    """
+    # This seems to work even if the file doesn't exist. So catch
+    # "empty" files and return an error.
+    fl = repo.file(path)
+
+    if not len(fl):
+        raise FileAccessError(path, 'unknown file: %s', (path,))
+
+    return fl
+
+@wireprotocommand(
+    'filedata',
+    args={
+        'haveparents': {
+            'type': 'bool',
+            'default': lambda: False,
+            'example': True,
+        },
+        'nodes': {
+            'type': 'list',
+            'required': True,
+            'example': [b'0123456...'],
+        },
+        'fields': {
+            'type': 'set',
+            'default': set,
+            'example': {b'parents', b'revision'},
+            'validvalues': {b'parents', b'revision'},
+        },
+        'path': {
+            'type': 'bytes',
+            'required': True,
+            'example': b'foo.txt',
+        }
+    },
+    permission='pull')
+def filedata(repo, proto, haveparents, nodes, fields, path):
+    try:
+        # Extensions may wish to access the protocol handler.
+        store = getfilestore(repo, proto, path)
+    except FileAccessError as e:
+        raise error.WireprotoCommandError(e.msg, e.args)
+
+    # Validate requested nodes.
+    for node in nodes:
+        try:
+            store.rev(node)
+        except error.LookupError:
+            raise error.WireprotoCommandError('unknown file node: %s',
+                                              (hex(node),))
+
+    revs, requests = builddeltarequests(store, nodes, haveparents)
+
+    yield {
+        b'totalitems': len(revs),
+    }
+
+    if b'revision' in fields:
+        deltas = store.emitrevisiondeltas(requests)
+    else:
+        deltas = None
+
+    for rev in revs:
+        node = store.node(rev)
+
+        if deltas is not None:
+            delta = next(deltas)
+        else:
+            delta = None
+
+        d = {
+            b'node': node,
+        }
+
+        if b'parents' in fields:
+            d[b'parents'] = store.parents(node)
+
+        followingmeta = []
+        followingdata = []
+
+        if b'revision' in fields:
+            assert delta is not None
+            assert delta.flags == 0
+            assert d[b'node'] == delta.node
+
+            if delta.revision is not None:
+                followingmeta.append((b'revision', len(delta.revision)))
+                followingdata.append(delta.revision)
+            else:
+                d[b'deltabasenode'] = delta.basenode
+                followingmeta.append((b'delta', len(delta.delta)))
+                followingdata.append(delta.delta)
+
+        if followingmeta:
+            d[b'fieldsfollowing'] = followingmeta
+
+        yield d
+
+        for extra in followingdata:
+            yield extra
+
+    if deltas is not None:
+        try:
+            next(deltas)
+            raise error.ProgrammingError('should not have more deltas')
+        except GeneratorExit:
+            pass
+
+@wireprotocommand(
+    'heads',
+    args={
+        'publiconly': {
+            'type': 'bool',
+            'default': lambda: False,
+            'example': False,
+        },
+    },
+    permission='pull')
+def headsv2(repo, proto, publiconly):
     if publiconly:
         repo = repo.filtered('immutable')
 
-    return wireprototypes.cborresponse(repo.heads())
+    yield repo.heads()
 
-@wireprotocommand('known',
-                  args={
-                      'nodes': [b'deadbeef'],
-                  },
-                  permission='pull')
-def knownv2(repo, proto, nodes=None):
-    nodes = nodes or []
+@wireprotocommand(
+    'known',
+    args={
+        'nodes': {
+            'type': 'list',
+            'default': list,
+            'example': [b'deadbeef'],
+        },
+    },
+    permission='pull')
+def knownv2(repo, proto, nodes):
     result = b''.join(b'1' if n else b'0' for n in repo.known(nodes))
-    return wireprototypes.cborresponse(result)
+    yield result
 
-@wireprotocommand('listkeys',
-                  args={
-                      'namespace': b'ns',
-                  },
-                  permission='pull')
-def listkeysv2(repo, proto, namespace=None):
+@wireprotocommand(
+    'listkeys',
+    args={
+        'namespace': {
+            'type': 'bytes',
+            'required': True,
+            'example': b'ns',
+        },
+    },
+    permission='pull')
+def listkeysv2(repo, proto, namespace):
     keys = repo.listkeys(encoding.tolocal(namespace))
     keys = {encoding.fromlocal(k): encoding.fromlocal(v)
             for k, v in keys.iteritems()}
 
-    return wireprototypes.cborresponse(keys)
+    yield keys
 
-@wireprotocommand('lookup',
-                  args={
-                      'key': b'foo',
-                  },
-                  permission='pull')
+@wireprotocommand(
+    'lookup',
+    args={
+        'key': {
+            'type': 'bytes',
+            'required': True,
+            'example': b'foo',
+        },
+    },
+    permission='pull')
 def lookupv2(repo, proto, key):
     key = encoding.tolocal(key)
 
     # TODO handle exception.
     node = repo.lookup(key)
 
-    return wireprototypes.cborresponse(node)
+    yield node
+
+@wireprotocommand(
+    'manifestdata',
+    args={
+        'nodes': {
+            'type': 'list',
+            'required': True,
+            'example': [b'0123456...'],
+        },
+        'haveparents': {
+            'type': 'bool',
+            'default': lambda: False,
+            'example': True,
+        },
+        'fields': {
+            'type': 'set',
+            'default': set,
+            'example': {b'parents', b'revision'},
+            'validvalues': {b'parents', b'revision'},
+        },
+        'tree': {
+            'type': 'bytes',
+            'required': True,
+            'example': b'',
+        },
+    },
+    permission='pull')
+def manifestdata(repo, proto, haveparents, nodes, fields, tree):
+    store = repo.manifestlog.getstorage(tree)
+
+    # Validate the node is known and abort on unknown revisions.
+    for node in nodes:
+        try:
+            store.rev(node)
+        except error.LookupError:
+            raise error.WireprotoCommandError(
+                'unknown node: %s', (node,))
+
+    revs, requests = builddeltarequests(store, nodes, haveparents)
+
+    yield {
+        b'totalitems': len(revs),
+    }
+
+    if b'revision' in fields:
+        deltas = store.emitrevisiondeltas(requests)
+    else:
+        deltas = None
+
+    for rev in revs:
+        node = store.node(rev)
+
+        if deltas is not None:
+            delta = next(deltas)
+        else:
+            delta = None
 
-@wireprotocommand('pushkey',
-                  args={
-                      'namespace': b'ns',
-                      'key': b'key',
-                      'old': b'old',
-                      'new': b'new',
-                  },
-                  permission='push')
+        d = {
+            b'node': node,
+        }
+
+        if b'parents' in fields:
+            d[b'parents'] = store.parents(node)
+
+        followingmeta = []
+        followingdata = []
+
+        if b'revision' in fields:
+            assert delta is not None
+            assert delta.flags == 0
+            assert d[b'node'] == delta.node
+
+            if delta.revision is not None:
+                followingmeta.append((b'revision', len(delta.revision)))
+                followingdata.append(delta.revision)
+            else:
+                d[b'deltabasenode'] = delta.basenode
+                followingmeta.append((b'delta', len(delta.delta)))
+                followingdata.append(delta.delta)
+
+        if followingmeta:
+            d[b'fieldsfollowing'] = followingmeta
+
+        yield d
+
+        for extra in followingdata:
+            yield extra
+
+    if deltas is not None:
+        try:
+            next(deltas)
+            raise error.ProgrammingError('should not have more deltas')
+        except GeneratorExit:
+            pass
+
+@wireprotocommand(
+    'pushkey',
+    args={
+        'namespace': {
+            'type': 'bytes',
+            'required': True,
+            'example': b'ns',
+        },
+        'key': {
+            'type': 'bytes',
+            'required': True,
+            'example': b'key',
+        },
+        'old': {
+            'type': 'bytes',
+            'required': True,
+            'example': b'old',
+        },
+        'new': {
+            'type': 'bytes',
+            'required': True,
+            'example': 'new',
+        },
+    },
+    permission='push')
 def pushkeyv2(repo, proto, namespace, key, old, new):
     # TODO handle ui output redirection
-    r = repo.pushkey(encoding.tolocal(namespace),
-                     encoding.tolocal(key),
-                     encoding.tolocal(old),
-                     encoding.tolocal(new))
-
-    return wireprototypes.cborresponse(r)
+    yield repo.pushkey(encoding.tolocal(namespace),
+                       encoding.tolocal(key),
+                       encoding.tolocal(old),
+                       encoding.tolocal(new))
--- a/setup.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/setup.py	Wed Sep 26 20:33:09 2018 +0900
@@ -11,6 +11,9 @@
     # Mercurial will never work on Python 3 before 3.5 due to a lack
     # of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
     # due to a bug in % formatting in bytestrings.
+    # We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
+    # codecs.escape_encode() where it raises SystemError on empty bytestring
+    # bug link: https://bugs.python.org/issue25270
     #
     # TODO: when we actually work on Python 3, use this string as the
     # actual supportedpy string.
@@ -21,6 +24,9 @@
         '!=3.2.*',
         '!=3.3.*',
         '!=3.4.*',
+        '!=3.5.0',
+        '!=3.5.1',
+        '!=3.5.2',
         '!=3.6.0',
         '!=3.6.1',
     ])
@@ -604,7 +610,7 @@
 
         if filelen > 0 and filelen != size:
             dllbasename = os.path.basename(buf.value)
-            if not dllbasename.lower().endswith('.dll'):
+            if not dllbasename.lower().endswith(b'.dll'):
                 raise SystemExit('Python DLL does not end with .dll: %s' %
                                  dllbasename)
             pythonlib = dllbasename[:-4]
@@ -617,8 +623,8 @@
 
         log.info('using %s as Python library name' % pythonlib)
         with open('mercurial/hgpythonlib.h', 'wb') as f:
-            f.write('/* this file is autogenerated by setup.py */\n')
-            f.write('#define HGPYTHONLIB "%s"\n' % pythonlib)
+            f.write(b'/* this file is autogenerated by setup.py */\n')
+            f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
         objects = self.compiler.compile(['mercurial/exewrapper.c'],
                                          output_dir=self.build_temp)
         dir = os.path.dirname(self.get_ext_fullpath('dummy'))
@@ -812,18 +818,22 @@
             'mercurial.thirdparty.attr',
             'mercurial.thirdparty.cbor',
             'mercurial.thirdparty.cbor.cbor2',
-            'mercurial.thirdparty.concurrent',
-            'mercurial.thirdparty.concurrent.futures',
             'mercurial.thirdparty.zope',
             'mercurial.thirdparty.zope.interface',
             'mercurial.utils',
+            'mercurial.revlogutils',
+            'mercurial.testing',
             'hgext', 'hgext.convert', 'hgext.fsmonitor',
+            'hgext.fastannotate',
             'hgext.fsmonitor.pywatchman',
             'hgext.infinitepush',
             'hgext.highlight',
             'hgext.largefiles', 'hgext.lfs', 'hgext.narrow',
             'hgext.zeroconf', 'hgext3rd',
             'hgdemandimport']
+if sys.version_info[0] == 2:
+    packages.extend(['mercurial.thirdparty.concurrent',
+                     'mercurial.thirdparty.concurrent.futures'])
 
 common_depends = ['mercurial/bitmanipulation.h',
                   'mercurial/compat.h',
@@ -992,7 +1002,7 @@
 if os.name == 'nt':
     # Windows binary file versions for exe/dll files must have the
     # form W.X.Y.Z, where W,X,Y,Z are numbers in the range 0..65535
-    setupversion = version.split('+', 1)[0]
+    setupversion = version.split(b'+', 1)[0]
 
 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
     version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/artifacts/PURPOSE	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,9 @@
+This directory is meant to cache artifacts useful for tests (such as bundle).
+
+Those artifacts need to be cached because they are slow to regenerate on each
+test but too large to be tracked within the repository. They are not expected
+to change between each run and can be cached.
+
+The `./scripts/` contains code to generate the artifact while the `cache`
+directory contains resulting artifact.
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/artifacts/cache/big-file-churn.hg.md5	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,1 @@
+fe0d0bb5979de50f4fed71bb9437764d
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/artifacts/scripts/generate-churning-bundle.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+#
+# generate-branchy-bundle - generate a branch for a "large" branchy repository
+#
+# Copyright 2018 Octobus, contact@octobus.net
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+#
+# This script generates a repository suitable for testing delta computation
+# strategies.
+#
+# The repository update a single "large" file with many updates. One fixed part
+# of the files always get updated while the rest of the lines get updated over
+# time. This update happens over many topological branches, some getting merged
+# back.
+#
+# Running with `chg` in your path and `CHGHG` set is recommended for speed.
+
+from __future__ import absolute_import, print_function
+
+import hashlib
+import os
+import shutil
+import subprocess
+import sys
+import tempfile
+
+BUNDLE_NAME = 'big-file-churn.hg'
+
+# constants for generating the repository
+NB_CHANGESET = 5000
+PERIOD_MERGING = 8
+PERIOD_BRANCHING = 7
+MOVE_BACK_MIN = 3
+MOVE_BACK_RANGE = 5
+
+# constants for generating the large file we keep updating
+#
+# At each revision, the beginning on the file change,
+# and set of other lines changes too.
+FILENAME='SPARSE-REVLOG-TEST-FILE'
+NB_LINES = 10500
+ALWAYS_CHANGE_LINES = 500
+FILENAME = 'SPARSE-REVLOG-TEST-FILE'
+OTHER_CHANGES = 300
+
+def nextcontent(previous_content):
+    """utility to produce a new file content from the previous one"""
+    return hashlib.md5(previous_content).hexdigest()
+
+def filecontent(iteridx, oldcontent):
+    """generate a new file content
+
+    The content is generated according the iteration index and previous
+    content"""
+
+    # initial call
+    if iteridx is None:
+        current = ''
+    else:
+        current = str(iteridx)
+
+    for idx in xrange(NB_LINES):
+        do_change_line = True
+        if oldcontent is not None and ALWAYS_CHANGE_LINES < idx:
+            do_change_line = not ((idx - iteridx) % OTHER_CHANGES)
+
+        if do_change_line:
+            to_write = current + '\n'
+            current = nextcontent(current)
+        else:
+            to_write = oldcontent[idx]
+        yield to_write
+
+def updatefile(filename, idx):
+    """update <filename> to be at appropriate content for iteration <idx>"""
+    existing = None
+    if idx is not None:
+        with open(filename, 'rb') as old:
+            existing = old.readlines()
+    with open(filename, 'wb') as target:
+        for line in filecontent(idx, existing):
+            target.write(line)
+
+def hg(command, *args):
+    """call a mercurial command with appropriate config and argument"""
+    env = os.environ.copy()
+    if 'CHGHG' in env:
+        full_cmd = ['chg']
+    else:
+        full_cmd = ['hg']
+    full_cmd.append('--quiet')
+    full_cmd.append(command)
+    if command == 'commit':
+        # reproducible commit metadata
+        full_cmd.extend(['--date', '0 0', '--user', 'test'])
+    elif command == 'merge':
+        # avoid conflicts by picking the local variant
+        full_cmd.extend(['--tool', ':merge-local'])
+    full_cmd.extend(args)
+    env['HGRCPATH'] = ''
+    return subprocess.check_call(full_cmd, env=env)
+
+def run(target):
+    tmpdir = tempfile.mkdtemp(prefix='tmp-hg-test-big-file-bundle-')
+    try:
+        os.chdir(tmpdir)
+        hg('init')
+        updatefile(FILENAME, None)
+        hg('commit', '--addremove', '--message', 'initial commit')
+        for idx in xrange(1, NB_CHANGESET + 1):
+            if sys.stdout.isatty():
+                print("generating commit #%d/%d" % (idx, NB_CHANGESET))
+            if (idx % PERIOD_BRANCHING) == 0:
+                move_back = MOVE_BACK_MIN + (idx % MOVE_BACK_RANGE)
+                hg('update', ".~%d" % move_back)
+            if (idx % PERIOD_MERGING) == 0:
+                hg('merge', 'min(head())')
+            updatefile(FILENAME, idx)
+            hg('commit', '--message', 'commit #%d' % idx)
+        hg('bundle', '--all', target)
+        with open(target, 'rb') as bundle:
+            data = bundle.read()
+            digest = hashlib.md5(data).hexdigest()
+        with open(target + '.md5', 'wb') as md5file:
+            md5file.write(digest + '\n')
+        if sys.stdout.isatty():
+            print('bundle generated at "%s" md5: %s' % (target, digest))
+
+    finally:
+        shutil.rmtree(tmpdir)
+    return 0
+
+if __name__ == '__main__':
+    orig = os.path.realpath(os.path.dirname(sys.argv[0]))
+    target = os.path.join(orig, os.pardir, 'cache', BUNDLE_NAME)
+    sys.exit(run(target))
+
--- a/tests/badserverext.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/badserverext.py	Wed Sep 26 20:33:09 2018 +0900
@@ -238,10 +238,10 @@
             self._ui = ui
             super(badserver, self).__init__(ui, *args, **kwargs)
 
-            recvbytes = self._ui.config('badserver', 'closeafterrecvbytes')
+            recvbytes = self._ui.config(b'badserver', b'closeafterrecvbytes')
             recvbytes = recvbytes.split(',')
             self.closeafterrecvbytes = [int(v) for v in recvbytes if v]
-            sendbytes = self._ui.config('badserver', 'closeaftersendbytes')
+            sendbytes = self._ui.config(b'badserver', b'closeaftersendbytes')
             sendbytes = sendbytes.split(',')
             self.closeaftersendbytes = [int(v) for v in sendbytes if v]
 
@@ -261,7 +261,7 @@
 
         # Called to accept() a pending socket.
         def get_request(self):
-            if self._ui.configbool('badserver', 'closebeforeaccept'):
+            if self._ui.configbool(b'badserver', b'closebeforeaccept'):
                 self.socket.close()
 
                 # Tells the server to stop processing more requests.
--- a/tests/bruterebase.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/bruterebase.py	Wed Sep 26 20:33:09 2018 +0900
@@ -45,7 +45,7 @@
             subset = [rev for j, rev in enumerate(srevs) if i & (1 << j) != 0]
             spec = revsetlang.formatspec(b'%ld', subset)
             tr = repo.transaction(b'rebase')
-            tr.report = lambda x: 0 # hide "transaction abort"
+            tr._report = lambda x: 0 # hide "transaction abort"
 
             ui.pushbuffer()
             try:
--- a/tests/bzr-definitions	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/bzr-definitions	Wed Sep 26 20:33:09 2018 +0900
@@ -6,7 +6,7 @@
 
 glog()
 {
-    hg log -G --template '{rev}@{branch} "{desc|firstline}" files: {files}\n' "$@"
+    hg log -G --template '{rev}@{branch} "{desc|firstline}" files+: [{file_adds}], files-: [{file_dels}], files: [{file_mods}]\n' "$@"
 }
 
 manifest()
--- a/tests/common-pattern.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/common-pattern.py	Wed Sep 26 20:33:09 2018 +0900
@@ -81,7 +81,8 @@
      br'listkeys%0A'
      br'pushkey%0A'
      br'remote-changegroup%3Dhttp%2Chttps%0A'
-     br'rev-branch-cache',
+     br'rev-branch-cache%0A'
+     br'stream%3Dv2',
      # (replacement patterns)
      br'$USUAL_BUNDLE2_CAPS_NO_PHASES$'
     ),
--- a/tests/drawdag.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/drawdag.py	Wed Sep 26 20:33:09 2018 +0900
@@ -288,8 +288,7 @@
             'date': b'0 0',
             'extra': {b'branch': b'default'},
         }
-        super(simplecommitctx, self).__init__(self, name, **opts)
-        self._repo = repo
+        super(simplecommitctx, self).__init__(repo, name, **opts)
         self._added = added
         self._parents = parentctxs
         while len(self._parents) < 2:
--- a/tests/dummysmtpd.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/dummysmtpd.py	Wed Sep 26 20:33:09 2018 +0900
@@ -26,7 +26,7 @@
     def __init__(self, localaddr):
         smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None)
 
-    def process_message(self, peer, mailfrom, rcpttos, data):
+    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
         log('%s from=%s to=%s\n' % (peer[0], mailfrom, ', '.join(rcpttos)))
 
     def handle_error(self):
--- a/tests/hghave.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/hghave.py	Wed Sep 26 20:33:09 2018 +0900
@@ -16,6 +16,22 @@
     "false": (lambda: False, "nail clipper"),
 }
 
+if sys.version_info[0] >= 3:
+    def _bytespath(p):
+        if p is None:
+            return p
+        return p.encode('utf-8')
+
+    def _strpath(p):
+        if p is None:
+            return p
+        return p.decode('utf-8')
+else:
+    def _bytespath(p):
+        return p
+
+    _strpath = _bytespath
+
 def check(name, desc):
     """Registers a check function for a feature."""
     def decorator(func):
@@ -360,7 +376,7 @@
     os.close(fh)
     name = tempfile.mktemp(dir='.', prefix=tempprefix)
     try:
-        util.oslink(fn, name)
+        util.oslink(_bytespath(fn), _bytespath(name))
         os.unlink(name)
         return True
     except OSError:
@@ -631,17 +647,7 @@
 
 @check("py3exe", "a Python 3.x interpreter is available")
 def has_python3exe():
-    return 'PYTHON3' in os.environ
-
-@check("py3pygments", "Pygments available on Python 3.x")
-def has_py3pygments():
-    if has_py3k():
-        return has_pygments()
-    elif has_python3exe():
-        # just check exit status (ignoring output)
-        py3 = os.environ['PYTHON3']
-        return matchoutput('%s -c "import pygments"' % py3, br'')
-    return False
+    return matchoutput('python3 -V', br'^Python 3.(5|6|7|8|9)')
 
 @check("pure", "running with pure Python code")
 def has_pure():
@@ -780,3 +786,13 @@
 @check('repofncache', 'repository has an fncache')
 def has_repofncache():
     return 'fncache' in getrepofeatures()
+
+@check('vcr', 'vcr http mocking library')
+def has_vcr():
+    try:
+        import vcr
+        vcr.VCR
+        return True
+    except (ImportError, AttributeError):
+        pass
+    return False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/phabricator/accept-4564.json	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,138 @@
+{
+    "version": 1, 
+    "interactions": [
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&ids%5B0%5D=4564", 
+                "headers": {
+                    "content-length": [
+                        "58"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+861-aa7e312375cf)"
+                    ]
+                }, 
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.query"
+            }, 
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "headers": {
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F24j2baem5tmap4tvfdz7ufmca2lhm3wx4agyqv4w; expires=Thu, 14-Sep-2023 04:24:35 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "connection": [
+                        "close"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:24:35 GMT"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":[{\"id\":\"4564\",\"phid\":\"PHID-DREV-6cgnf5fyeeqhntbxgfb7\",\"title\":\"localrepo: move some vfs initialization out of __init__\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D4564\",\"dateCreated\":\"1536856174\",\"dateModified\":\"1536856175\",\"authorPHID\":\"PHID-USER-p54bpwbifxx7sbgpx47d\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":[],\"branch\":null,\"summary\":\"In order to make repository types more dynamic, we'll need to move the\\nlogic for determining repository behavior out of\\nlocalrepository.__init__ so we can influence behavior before the type\\nis instantiated.\\n\\nThis commit starts that process by moving working directory and .hg\\/\\nvfs initialization to our new standalone function for instantiating\\nlocal repositories.\\n\\nAside from API changes, behavior should be fully backwards compatible.\\n\\n.. api::\\n\\n   localrepository.__init__ now does less work and accepts new args\\n\\n   Use ``hg.repository()``, ``localrepo.instance()``, or\\n   ``localrepo.makelocalrepository()`` to obtain a new local repository\\n   instance instead of calling the ``localrepository`` constructor\\n   directly.\",\"testPlan\":\"\",\"lineCount\":\"64\",\"activeDiffPHID\":\"PHID-DIFF-ir6bizkdou7fm7xhuo6v\",\"diffs\":[\"11002\"],\"commits\":[],\"reviewers\":{\"PHID-PROJ-3dvcxzznrjru2xmmses3\":\"PHID-PROJ-3dvcxzznrjru2xmmses3\"},\"ccs\":[\"PHID-USER-q42dn7cc3donqriafhjx\"],\"hashes\":[],\"auxiliary\":{\"phabricator:projects\":[],\"phabricator:depends-on\":[\"PHID-DREV-gqp33hnxg65vkl3xioka\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\"}],\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&objectIdentifier=PHID-DREV-6cgnf5fyeeqhntbxgfb7&transactions%5B0%5D%5Btype%5D=accept&transactions%5B0%5D%5Bvalue%5D=true&transactions%5B1%5D%5Btype%5D=comment&transactions%5B1%5D%5Bvalue%5D=I+think+I+like+where+this+is+headed.+Will+read+rest+of+series+later.", 
+                "headers": {
+                    "content-length": [
+                        "301"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+861-aa7e312375cf)"
+                    ]
+                }, 
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit"
+            }, 
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "headers": {
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fcna7xx3xon5xxyoasbveqlfz4fswd2risihw7dff; expires=Thu, 14-Sep-2023 04:24:36 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:24:36 GMT"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"object\":{\"id\":\"4564\",\"phid\":\"PHID-DREV-6cgnf5fyeeqhntbxgfb7\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-nfqswjwwfuzdrhb\"},{\"phid\":\"PHID-XACT-DREV-oqb5pkqsdify6nm\"},{\"phid\":\"PHID-XACT-DREV-i6epvc7avyv3ve7\"},{\"phid\":\"PHID-XACT-DREV-du5hbg5rege3i5w\"}]},\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }
+    ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/phabricator/phabread-4480.json	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,200 @@
+{
+    "version": 1, 
+    "interactions": [
+        {
+            "response": {
+                "headers": {
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F6ypywsajmaqsclzrydncbnegfczzct2m5c4wovqw; expires=Thu, 14-Sep-2023 04:15:56 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:15:56 GMT"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ]
+                }, 
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "body": {
+                    "string": "{\"result\":[{\"id\":\"4480\",\"phid\":\"PHID-DREV-gsa7dkuimmam7nafw7h3\",\"title\":\"exchangev2: start to implement pull with wire protocol v2\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D4480\",\"dateCreated\":\"1536164431\",\"dateModified\":\"1536981352\",\"authorPHID\":\"PHID-USER-p54bpwbifxx7sbgpx47d\",\"status\":\"3\",\"statusName\":\"Closed\",\"properties\":{\"wasAcceptedBeforeClose\":false},\"branch\":null,\"summary\":\"Wire protocol version 2 will take a substantially different\\napproach to exchange than version 1 (at least as far as pulling\\nis concerned).\\n\\nThis commit establishes a new exchangev2 module for holding\\ncode related to exchange using wire protocol v2. I could have\\nadded things to the existing exchange module. But it is already\\nquite big. And doing things inline isn't in question because\\nthe existing code is already littered with conditional code\\nfor various states of support for the existing wire protocol\\nas it evolved over 10+ years. A new module gives us a chance\\nto make a clean break.\\n\\nThis approach does mean we'll end up writing some duplicate\\ncode. And there's a significant chance we'll miss functionality\\nas code is ported. The plan is to eventually add #testcase's\\nto existing tests so the new wire protocol is tested side-by-side\\nwith the existing one. This will hopefully tease out any\\nfeatures that weren't ported properly. But before we get there,\\nwe need to build up support for the new exchange methods.\\n\\nOur journey towards implementing a new exchange begins with pulling.\\nAnd pulling begins with discovery.\\n\\nThe discovery code added to exchangev2 is heavily drawn from\\nthe following functions:\\n\\n* exchange._pulldiscoverychangegroup\\n* discovery.findcommonincoming\\n\\nFor now, we build on top of existing discovery mechanisms. The\\nnew wire protocol should be capable of doing things more efficiently.\\nBut I'd rather defer on this problem.\\n\\nTo foster the transition, we invent a fake capability on the HTTPv2\\npeer and have the main pull code in exchange.py call into exchangev2\\nwhen the new wire protocol is being used.\",\"testPlan\":\"\",\"lineCount\":\"145\",\"activeDiffPHID\":\"PHID-DIFF-kg2rt6kiekgo5rgyeu5n\",\"diffs\":[\"11058\",\"10961\",\"10793\"],\"commits\":[\"PHID-CMIT-kvz2f3rczvi6exmvtyaq\"],\"reviewers\":{\"PHID-PROJ-3dvcxzznrjru2xmmses3\":\"PHID-PROJ-3dvcxzznrjru2xmmses3\"},\"ccs\":[\"PHID-USER-cgcdlc6c3gpxapbmkwa2\",\"PHID-USER-q42dn7cc3donqriafhjx\"],\"hashes\":[],\"auxiliary\":{\"phabricator:projects\":[],\"phabricator:depends-on\":[\"PHID-DREV-a77jfv32jtxfwxngd6bd\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\"}],\"error_code\":null,\"error_info\":null}"
+                }
+            }, 
+            "request": {
+                "headers": {
+                    "content-length": [
+                        "58"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+861-aa7e312375cf)"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ]
+                }, 
+                "uri": "https://phab.mercurial-scm.org//api/differential.query", 
+                "method": "POST", 
+                "body": "ids%5B0%5D=4480&api.token=cli-hahayouwish"
+            }
+        }, 
+        {
+            "response": {
+                "headers": {
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Floppdxhbjv46vg5mwnf2squrj4vgegsce5fwhhb6; expires=Thu, 14-Sep-2023 04:15:57 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:15:57 GMT"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ]
+                }, 
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"11058\":{\"id\":\"11058\",\"revisionID\":\"4480\",\"dateCreated\":\"1536771503\",\"dateModified\":\"1536981352\",\"sourceControlBaseRevision\":\"a5de21c9e3703f8e8eb064bd7d893ff2f703c66a\",\"sourceControlPath\":null,\"sourceControlSystem\":\"hg\",\"branch\":null,\"bookmark\":null,\"creationMethod\":\"commit\",\"description\":\"rHGa86d21e70b2b79d5e7e1085e5e755b4b26b8676d\",\"unitStatus\":\"6\",\"lintStatus\":\"6\",\"changes\":[{\"id\":\"24371\",\"metadata\":{\"line:first\":59},\"oldPath\":\"tests\\/wireprotohelpers.sh\",\"currentPath\":\"tests\\/wireprotohelpers.sh\",\"awayPaths\":[],\"oldProperties\":[],\"newProperties\":[],\"type\":\"2\",\"fileType\":\"1\",\"commitHash\":null,\"addLines\":\"7\",\"delLines\":\"0\",\"hunks\":[{\"oldOffset\":\"1\",\"newOffset\":\"1\",\"oldLength\":\"58\",\"newLength\":\"65\",\"addLines\":null,\"delLines\":null,\"isMissingOldNewline\":null,\"isMissingNewNewline\":null,\"corpus\":\" HTTPV2=exp-http-v2-0001\\n MEDIATYPE=application\\/mercurial-exp-framing-0005\\n \\n sendhttpraw() {\\n   hg --verbose debugwireproto --peer raw http:\\/\\/$LOCALIP:$HGPORT\\/\\n }\\n \\n sendhttpv2peer() {\\n   hg --verbose debugwireproto --nologhandshake --peer http2 http:\\/\\/$LOCALIP:$HGPORT\\/\\n }\\n \\n sendhttpv2peerhandshake() {\\n   hg --verbose debugwireproto --peer http2 http:\\/\\/$LOCALIP:$HGPORT\\/\\n }\\n \\n cat \\u003e dummycommands.py \\u003c\\u003c EOF\\n from mercurial import (\\n     wireprototypes,\\n     wireprotov1server,\\n     wireprotov2server,\\n )\\n \\n @wireprotov1server.wireprotocommand(b'customreadonly', permission=b'pull')\\n def customreadonlyv1(repo, proto):\\n     return wireprototypes.bytesresponse(b'customreadonly bytes response')\\n \\n @wireprotov2server.wireprotocommand(b'customreadonly', permission=b'pull')\\n def customreadonlyv2(repo, proto):\\n     yield b'customreadonly bytes response'\\n \\n @wireprotov1server.wireprotocommand(b'customreadwrite', permission=b'push')\\n def customreadwrite(repo, proto):\\n     return wireprototypes.bytesresponse(b'customreadwrite bytes response')\\n \\n @wireprotov2server.wireprotocommand(b'customreadwrite', permission=b'push')\\n def customreadwritev2(repo, proto):\\n     yield b'customreadwrite bytes response'\\n EOF\\n \\n cat \\u003e\\u003e $HGRCPATH \\u003c\\u003c EOF\\n [extensions]\\n drawdag = $TESTDIR\\/drawdag.py\\n EOF\\n \\n enabledummycommands() {\\n   cat \\u003e\\u003e $HGRCPATH \\u003c\\u003c EOF\\n [extensions]\\n dummycommands = $TESTTMP\\/dummycommands.py\\n EOF\\n }\\n \\n enablehttpv2() {\\n   cat \\u003e\\u003e $1\\/.hg\\/hgrc \\u003c\\u003c EOF\\n [experimental]\\n web.apiserver = true\\n web.api.http-v2 = true\\n EOF\\n }\\n+\\n+enablehttpv2client() {\\n+  cat \\u003e\\u003e $HGRCPATH \\u003c\\u003c EOF\\n+[experimental]\\n+httppeer.advertise-v2 = true\\n+EOF\\n+}\\n\"}]},{\"id\":\"24370\",\"metadata\":{\"line:first\":1},\"oldPath\":null,\"currentPath\":\"tests\\/test-wireproto-exchangev2.t\",\"awayPaths\":[],\"oldProperties\":[],\"newProperties\":{\"unix:filemode\":\"100644\"},\"type\":\"1\",\"fileType\":\"1\",\"commitHash\":null,\"addLines\":\"53\",\"delLines\":\"0\",\"hunks\":[{\"oldOffset\":\"0\",\"newOffset\":\"1\",\"oldLength\":\"0\",\"newLength\":\"53\",\"addLines\":null,\"delLines\":null,\"isMissingOldNewline\":null,\"isMissingNewNewline\":null,\"corpus\":\"+Tests for wire protocol version 2 exchange.\\n+Tests in this file should be folded into existing tests once protocol\\n+v2 has enough features that it can be enabled via #testcase in existing\\n+tests.\\n+\\n+  $ . $TESTDIR\\/wireprotohelpers.sh\\n+  $ enablehttpv2client\\n+\\n+  $ hg init server-simple\\n+  $ enablehttpv2 server-simple\\n+  $ cd server-simple\\n+  $ cat \\u003e\\u003e .hg\\/hgrc \\u003c\\u003c EOF\\n+  \\u003e [phases]\\n+  \\u003e publish = false\\n+  \\u003e EOF\\n+  $ echo a0 \\u003e a\\n+  $ echo b0 \\u003e b\\n+  $ hg -q commit -A -m 'commit 0'\\n+\\n+  $ echo a1 \\u003e a\\n+  $ hg commit -m 'commit 1'\\n+  $ hg phase --public -r .\\n+  $ echo a2 \\u003e a\\n+  $ hg commit -m 'commit 2'\\n+\\n+  $ hg -q up -r 0\\n+  $ echo b1 \\u003e b\\n+  $ hg -q commit -m 'head 2 commit 1'\\n+  $ echo b2 \\u003e b\\n+  $ hg -q commit -m 'head 2 commit 2'\\n+\\n+  $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log\\n+  $ cat hg.pid \\u003e $DAEMON_PIDS\\n+\\n+  $ cd ..\\n+\\n+Test basic clone\\n+\\n+  $ hg --debug clone -U http:\\/\\/localhost:$HGPORT client-simple\\n+  using http:\\/\\/localhost:$HGPORT\\/\\n+  sending capabilities command\\n+  query 1; heads\\n+  sending 2 commands\\n+  sending command heads: {}\\n+  sending command known: {\\n+    'nodes': []\\n+  }\\n+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)\\n+  received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)\\n+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)\\n+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)\\n+  received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)\\n+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)\\n\"}]},{\"id\":\"24369\",\"metadata\":{\"line:first\":805},\"oldPath\":\"mercurial\\/httppeer.py\",\"currentPath\":\"mercurial\\/httppeer.py\",\"awayPaths\":[],\"oldProperties\":[],\"newProperties\":[],\"type\":\"2\",\"fileType\":\"1\",\"commitHash\":null,\"addLines\":\"2\",\"delLines\":\"1\",\"hunks\":[{\"oldOffset\":\"1\",\"newOffset\":\"1\",\"oldLength\":\"1006\",\"newLength\":\"1007\",\"addLines\":null,\"delLines\":null,\"isMissingOldNewline\":null,\"isMissingNewNewline\":null,\"corpus\":\" # httppeer.py - HTTP repository proxy classes for mercurial\\n #\\n # Copyright 2005, 2006 Matt Mackall \\u003cmpm@selenic.com\\u003e\\n # Copyright 2006 Vadim Gelfer \\u003cvadim.gelfer@gmail.com\\u003e\\n #\\n # This software may be used and distributed according to the terms of the\\n # GNU General Public License version 2 or any later version.\\n \\n from __future__ import absolute_import\\n \\n import errno\\n import io\\n import os\\n import socket\\n import struct\\n import weakref\\n \\n from .i18n import _\\n from . import (\\n     bundle2,\\n     error,\\n     httpconnection,\\n     pycompat,\\n     repository,\\n     statichttprepo,\\n     url as urlmod,\\n     util,\\n     wireprotoframing,\\n     wireprototypes,\\n     wireprotov1peer,\\n     wireprotov2peer,\\n     wireprotov2server,\\n )\\n from .utils import (\\n     cborutil,\\n     interfaceutil,\\n     stringutil,\\n )\\n \\n httplib = util.httplib\\n urlerr = util.urlerr\\n urlreq = util.urlreq\\n \\n def encodevalueinheaders(value, header, limit):\\n     \\\"\\\"\\\"Encode a string value into multiple HTTP headers.\\n \\n     ``value`` will be encoded into 1 or more HTTP headers with the names\\n     ``header-\\u003cN\\u003e`` where ``\\u003cN\\u003e`` is an integer starting at 1. Each header\\n     name + value will be at most ``limit`` bytes long.\\n \\n     Returns an iterable of 2-tuples consisting of header names and\\n     values as native strings.\\n     \\\"\\\"\\\"\\n     # HTTP Headers are ASCII. Python 3 requires them to be unicodes,\\n     # not bytes. This function always takes bytes in as arguments.\\n     fmt = pycompat.strurl(header) + r'-%s'\\n     # Note: it is *NOT* a bug that the last bit here is a bytestring\\n     # and not a unicode: we're just getting the encoded length anyway,\\n     # and using an r-string to make it portable between Python 2 and 3\\n     # doesn't work because then the \\\\r is a literal backslash-r\\n     # instead of a carriage return.\\n     valuelen = limit - len(fmt % r'000') - len(': \\\\r\\\\n')\\n     result = []\\n \\n     n = 0\\n     for i in pycompat.xrange(0, len(value), valuelen):\\n         n += 1\\n         result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))\\n \\n     return result\\n \\n def _wraphttpresponse(resp):\\n     \\\"\\\"\\\"Wrap an HTTPResponse with common error handlers.\\n \\n     This ensures that any I\\/O from any consumer raises the appropriate\\n     error and messaging.\\n     \\\"\\\"\\\"\\n     origread = resp.read\\n \\n     class readerproxy(resp.__class__):\\n         def read(self, size=None):\\n             try:\\n                 return origread(size)\\n             except httplib.IncompleteRead as e:\\n                 # e.expected is an integer if length known or None otherwise.\\n                 if e.expected:\\n                     got = len(e.partial)\\n                     total = e.expected + got\\n                     msg = _('HTTP request error (incomplete response; '\\n                             'expected %d bytes got %d)') % (total, got)\\n                 else:\\n                     msg = _('HTTP request error (incomplete response)')\\n \\n                 raise error.PeerTransportError(\\n                     msg,\\n                     hint=_('this may be an intermittent network failure; '\\n                            'if the error persists, consider contacting the '\\n                            'network or server operator'))\\n             except httplib.HTTPException as e:\\n                 raise error.PeerTransportError(\\n                     _('HTTP request error (%s)') % e,\\n                     hint=_('this may be an intermittent network failure; '\\n                            'if the error persists, consider contacting the '\\n                            'network or server operator'))\\n \\n     resp.__class__ = readerproxy\\n \\n class _multifile(object):\\n     def __init__(self, *fileobjs):\\n         for f in fileobjs:\\n             if not util.safehasattr(f, 'length'):\\n                 raise ValueError(\\n                     '_multifile only supports file objects that '\\n                     'have a length but this one does not:', type(f), f)\\n         self._fileobjs = fileobjs\\n         self._index = 0\\n \\n     @property\\n     def length(self):\\n         return sum(f.length for f in self._fileobjs)\\n \\n     def read(self, amt=None):\\n         if amt \\u003c= 0:\\n             return ''.join(f.read() for f in self._fileobjs)\\n         parts = []\\n         while amt and self._index \\u003c len(self._fileobjs):\\n             parts.append(self._fileobjs[self._index].read(amt))\\n             got = len(parts[-1])\\n             if got \\u003c amt:\\n                 self._index += 1\\n             amt -= got\\n         return ''.join(parts)\\n \\n     def seek(self, offset, whence=os.SEEK_SET):\\n         if whence != os.SEEK_SET:\\n             raise NotImplementedError(\\n                 '_multifile does not support anything other'\\n                 ' than os.SEEK_SET for whence on seek()')\\n         if offset != 0:\\n             raise NotImplementedError(\\n                 '_multifile only supports seeking to start, but that '\\n                 'could be fixed if you need it')\\n         for f in self._fileobjs:\\n             f.seek(0)\\n         self._index = 0\\n \\n def makev1commandrequest(ui, requestbuilder, caps, capablefn,\\n                          repobaseurl, cmd, args):\\n     \\\"\\\"\\\"Make an HTTP request to run a command for a version 1 client.\\n \\n     ``caps`` is a set of known server capabilities. The value may be\\n     None if capabilities are not yet known.\\n \\n     ``capablefn`` is a function to evaluate a capability.\\n \\n     ``cmd``, ``args``, and ``data`` define the command, its arguments, and\\n     raw data to pass to it.\\n     \\\"\\\"\\\"\\n     if cmd == 'pushkey':\\n         args['data'] = ''\\n     data = args.pop('data', None)\\n     headers = args.pop('headers', {})\\n \\n     ui.debug(\\\"sending %s command\\\\n\\\" % cmd)\\n     q = [('cmd', cmd)]\\n     headersize = 0\\n     # Important: don't use self.capable() here or else you end up\\n     # with infinite recursion when trying to look up capabilities\\n     # for the first time.\\n     postargsok = caps is not None and 'httppostargs' in caps\\n \\n     # Send arguments via POST.\\n     if postargsok and args:\\n         strargs = urlreq.urlencode(sorted(args.items()))\\n         if not data:\\n             data = strargs\\n         else:\\n             if isinstance(data, bytes):\\n                 i = io.BytesIO(data)\\n                 i.length = len(data)\\n                 data = i\\n             argsio = io.BytesIO(strargs)\\n             argsio.length = len(strargs)\\n             data = _multifile(argsio, data)\\n         headers[r'X-HgArgs-Post'] = len(strargs)\\n     elif args:\\n         # Calling self.capable() can infinite loop if we are calling\\n         # \\\"capabilities\\\". But that command should never accept wire\\n         # protocol arguments. So this should never happen.\\n         assert cmd != 'capabilities'\\n         httpheader = capablefn('httpheader')\\n         if httpheader:\\n             headersize = int(httpheader.split(',', 1)[0])\\n \\n         # Send arguments via HTTP headers.\\n         if headersize \\u003e 0:\\n             # The headers can typically carry more data than the URL.\\n             encargs = urlreq.urlencode(sorted(args.items()))\\n             for header, value in encodevalueinheaders(encargs, 'X-HgArg',\\n                                                       headersize):\\n                 headers[header] = value\\n         # Send arguments via query string (Mercurial \\u003c1.9).\\n         else:\\n             q += sorted(args.items())\\n \\n     qs = '?%s' % urlreq.urlencode(q)\\n     cu = \\\"%s%s\\\" % (repobaseurl, qs)\\n     size = 0\\n     if util.safehasattr(data, 'length'):\\n         size = data.length\\n     elif data is not None:\\n         size = len(data)\\n     if data is not None and r'Content-Type' not in headers:\\n         headers[r'Content-Type'] = r'application\\/mercurial-0.1'\\n \\n     # Tell the server we accept application\\/mercurial-0.2 and multiple\\n     # compression formats if the server is capable of emitting those\\n     # payloads.\\n     # Note: Keep this set empty by default, as client advertisement of\\n     # protocol parameters should only occur after the handshake.\\n     protoparams = set()\\n \\n     mediatypes = set()\\n     if caps is not None:\\n         mt = capablefn('httpmediatype')\\n         if mt:\\n             protoparams.add('0.1')\\n             mediatypes = set(mt.split(','))\\n \\n         protoparams.add('partial-pull')\\n \\n     if '0.2tx' in mediatypes:\\n         protoparams.add('0.2')\\n \\n     if '0.2tx' in mediatypes and capablefn('compression'):\\n         # We \\/could\\/ compare supported compression formats and prune\\n         # non-mutually supported or error if nothing is mutually supported.\\n         # For now, send the full list to the server and have it error.\\n         comps = [e.wireprotosupport().name for e in\\n                  util.compengines.supportedwireengines(util.CLIENTROLE)]\\n         protoparams.add('comp=%s' % ','.join(comps))\\n \\n     if protoparams:\\n         protoheaders = encodevalueinheaders(' '.join(sorted(protoparams)),\\n                                             'X-HgProto',\\n                                             headersize or 1024)\\n         for header, value in protoheaders:\\n             headers[header] = value\\n \\n     varyheaders = []\\n     for header in headers:\\n         if header.lower().startswith(r'x-hg'):\\n             varyheaders.append(header)\\n \\n     if varyheaders:\\n         headers[r'Vary'] = r','.join(sorted(varyheaders))\\n \\n     req = requestbuilder(pycompat.strurl(cu), data, headers)\\n \\n     if data is not None:\\n         ui.debug(\\\"sending %d bytes\\\\n\\\" % size)\\n         req.add_unredirected_header(r'Content-Length', r'%d' % size)\\n \\n     return req, cu, qs\\n \\n def _reqdata(req):\\n     \\\"\\\"\\\"Get request data, if any. If no data, returns None.\\\"\\\"\\\"\\n     if pycompat.ispy3:\\n         return req.data\\n     if not req.has_data():\\n         return None\\n     return req.get_data()\\n \\n def sendrequest(ui, opener, req):\\n     \\\"\\\"\\\"Send a prepared HTTP request.\\n \\n     Returns the response object.\\n     \\\"\\\"\\\"\\n     dbg = ui.debug\\n     if (ui.debugflag\\n         and ui.configbool('devel', 'debug.peer-request')):\\n         line = 'devel-peer-request: %s\\\\n'\\n         dbg(line % '%s %s' % (pycompat.bytesurl(req.get_method()),\\n                               pycompat.bytesurl(req.get_full_url())))\\n         hgargssize = None\\n \\n         for header, value in sorted(req.header_items()):\\n             header = pycompat.bytesurl(header)\\n             value = pycompat.bytesurl(value)\\n             if header.startswith('X-hgarg-'):\\n                 if hgargssize is None:\\n                     hgargssize = 0\\n                 hgargssize += len(value)\\n             else:\\n                 dbg(line % '  %s %s' % (header, value))\\n \\n         if hgargssize is not None:\\n             dbg(line % '  %d bytes of commands arguments in headers'\\n                 % hgargssize)\\n         data = _reqdata(req)\\n         if data is not None:\\n             length = getattr(data, 'length', None)\\n             if length is None:\\n                 length = len(data)\\n             dbg(line % '  %d bytes of data' % length)\\n \\n         start = util.timer()\\n \\n     res = None\\n     try:\\n         res = opener.open(req)\\n     except urlerr.httperror as inst:\\n         if inst.code == 401:\\n             raise error.Abort(_('authorization failed'))\\n         raise\\n     except httplib.HTTPException as inst:\\n         ui.debug('http error requesting %s\\\\n' %\\n                  util.hidepassword(req.get_full_url()))\\n         ui.traceback()\\n         raise IOError(None, inst)\\n     finally:\\n         if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):\\n             code = res.code if res else -1\\n             dbg(line % '  finished in %.4f seconds (%d)'\\n                 % (util.timer() - start, code))\\n \\n     # Insert error handlers for common I\\/O failures.\\n     _wraphttpresponse(res)\\n \\n     return res\\n \\n class RedirectedRepoError(error.RepoError):\\n     def __init__(self, msg, respurl):\\n         super(RedirectedRepoError, self).__init__(msg)\\n         self.respurl = respurl\\n \\n def parsev1commandresponse(ui, baseurl, requrl, qs, resp, compressible,\\n                            allowcbor=False):\\n     # record the url we got redirected to\\n     redirected = False\\n     respurl = pycompat.bytesurl(resp.geturl())\\n     if respurl.endswith(qs):\\n         respurl = respurl[:-len(qs)]\\n         qsdropped = False\\n     else:\\n         qsdropped = True\\n \\n     if baseurl.rstrip('\\/') != respurl.rstrip('\\/'):\\n         redirected = True\\n         if not ui.quiet:\\n             ui.warn(_('real URL is %s\\\\n') % respurl)\\n \\n     try:\\n         proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))\\n     except AttributeError:\\n         proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))\\n \\n     safeurl = util.hidepassword(baseurl)\\n     if proto.startswith('application\\/hg-error'):\\n         raise error.OutOfBandError(resp.read())\\n \\n     # Pre 1.0 versions of Mercurial used text\\/plain and\\n     # application\\/hg-changegroup. We don't support such old servers.\\n     if not proto.startswith('application\\/mercurial-'):\\n         ui.debug(\\\"requested URL: '%s'\\\\n\\\" % util.hidepassword(requrl))\\n         msg = _(\\\"'%s' does not appear to be an hg repository:\\\\n\\\"\\n                 \\\"---%%\\u003c--- (%s)\\\\n%s\\\\n---%%\\u003c---\\\\n\\\") % (\\n             safeurl, proto or 'no content-type', resp.read(1024))\\n \\n         # Some servers may strip the query string from the redirect. We\\n         # raise a special error type so callers can react to this specially.\\n         if redirected and qsdropped:\\n             raise RedirectedRepoError(msg, respurl)\\n         else:\\n             raise error.RepoError(msg)\\n \\n     try:\\n         subtype = proto.split('-', 1)[1]\\n \\n         # Unless we end up supporting CBOR in the legacy wire protocol,\\n         # this should ONLY be encountered for the initial capabilities\\n         # request during handshake.\\n         if subtype == 'cbor':\\n             if allowcbor:\\n                 return respurl, proto, resp\\n             else:\\n                 raise error.RepoError(_('unexpected CBOR response from '\\n                                         'server'))\\n \\n         version_info = tuple([int(n) for n in subtype.split('.')])\\n     except ValueError:\\n         raise error.RepoError(_(\\\"'%s' sent a broken Content-Type \\\"\\n                                 \\\"header (%s)\\\") % (safeurl, proto))\\n \\n     # TODO consider switching to a decompression reader that uses\\n     # generators.\\n     if version_info == (0, 1):\\n         if compressible:\\n             resp = util.compengines['zlib'].decompressorreader(resp)\\n \\n     elif version_info == (0, 2):\\n         # application\\/mercurial-0.2 always identifies the compression\\n         # engine in the payload header.\\n         elen = struct.unpack('B', util.readexactly(resp, 1))[0]\\n         ename = util.readexactly(resp, elen)\\n         engine = util.compengines.forwiretype(ename)\\n \\n         resp = engine.decompressorreader(resp)\\n     else:\\n         raise error.RepoError(_(\\\"'%s' uses newer protocol %s\\\") %\\n                               (safeurl, subtype))\\n \\n     return respurl, proto, resp\\n \\n class httppeer(wireprotov1peer.wirepeer):\\n     def __init__(self, ui, path, url, opener, requestbuilder, caps):\\n         self.ui = ui\\n         self._path = path\\n         self._url = url\\n         self._caps = caps\\n         self._urlopener = opener\\n         self._requestbuilder = requestbuilder\\n \\n     def __del__(self):\\n         for h in self._urlopener.handlers:\\n             h.close()\\n             getattr(h, \\\"close_all\\\", lambda: None)()\\n \\n     # Begin of ipeerconnection interface.\\n \\n     def url(self):\\n         return self._path\\n \\n     def local(self):\\n         return None\\n \\n     def peer(self):\\n         return self\\n \\n     def canpush(self):\\n         return True\\n \\n     def close(self):\\n         pass\\n \\n     # End of ipeerconnection interface.\\n \\n     # Begin of ipeercommands interface.\\n \\n     def capabilities(self):\\n         return self._caps\\n \\n     # End of ipeercommands interface.\\n \\n     def _callstream(self, cmd, _compressible=False, **args):\\n         args = pycompat.byteskwargs(args)\\n \\n         req, cu, qs = makev1commandrequest(self.ui, self._requestbuilder,\\n                                            self._caps, self.capable,\\n                                            self._url, cmd, args)\\n \\n         resp = sendrequest(self.ui, self._urlopener, req)\\n \\n         self._url, ct, resp = parsev1commandresponse(self.ui, self._url, cu, qs,\\n                                                      resp, _compressible)\\n \\n         return resp\\n \\n     def _call(self, cmd, **args):\\n         fp = self._callstream(cmd, **args)\\n         try:\\n             return fp.read()\\n         finally:\\n             # if using keepalive, allow connection to be reused\\n             fp.close()\\n \\n     def _callpush(self, cmd, cg, **args):\\n         # have to stream bundle to a temp file because we do not have\\n         # http 1.1 chunked transfer.\\n \\n         types = self.capable('unbundle')\\n         try:\\n             types = types.split(',')\\n         except AttributeError:\\n             # servers older than d1b16a746db6 will send 'unbundle' as a\\n             # boolean capability. They only support headerless\\/uncompressed\\n             # bundles.\\n             types = [\\\"\\\"]\\n         for x in types:\\n             if x in bundle2.bundletypes:\\n                 type = x\\n                 break\\n \\n         tempname = bundle2.writebundle(self.ui, cg, None, type)\\n         fp = httpconnection.httpsendfile(self.ui, tempname, \\\"rb\\\")\\n         headers = {r'Content-Type': r'application\\/mercurial-0.1'}\\n \\n         try:\\n             r = self._call(cmd, data=fp, headers=headers, **args)\\n             vals = r.split('\\\\n', 1)\\n             if len(vals) \\u003c 2:\\n                 raise error.ResponseError(_(\\\"unexpected response:\\\"), r)\\n             return vals\\n         except urlerr.httperror:\\n             # Catch and re-raise these so we don't try and treat them\\n             # like generic socket errors. They lack any values in\\n             # .args on Python 3 which breaks our socket.error block.\\n             raise\\n         except socket.error as err:\\n             if err.args[0] in (errno.ECONNRESET, errno.EPIPE):\\n                 raise error.Abort(_('push failed: %s') % err.args[1])\\n             raise error.Abort(err.args[1])\\n         finally:\\n             fp.close()\\n             os.unlink(tempname)\\n \\n     def _calltwowaystream(self, cmd, fp, **args):\\n         fh = None\\n         fp_ = None\\n         filename = None\\n         try:\\n             # dump bundle to disk\\n             fd, filename = pycompat.mkstemp(prefix=\\\"hg-bundle-\\\", suffix=\\\".hg\\\")\\n             fh = os.fdopen(fd, r\\\"wb\\\")\\n             d = fp.read(4096)\\n             while d:\\n                 fh.write(d)\\n                 d = fp.read(4096)\\n             fh.close()\\n             # start http push\\n             fp_ = httpconnection.httpsendfile(self.ui, filename, \\\"rb\\\")\\n             headers = {r'Content-Type': r'application\\/mercurial-0.1'}\\n             return self._callstream(cmd, data=fp_, headers=headers, **args)\\n         finally:\\n             if fp_ is not None:\\n                 fp_.close()\\n             if fh is not None:\\n                 fh.close()\\n                 os.unlink(filename)\\n \\n     def _callcompressable(self, cmd, **args):\\n         return self._callstream(cmd, _compressible=True, **args)\\n \\n     def _abort(self, exception):\\n         raise exception\\n \\n def sendv2request(ui, opener, requestbuilder, apiurl, permission, requests):\\n     reactor = wireprotoframing.clientreactor(hasmultiplesend=False,\\n                                              buffersends=True)\\n \\n     handler = wireprotov2peer.clienthandler(ui, reactor)\\n \\n     url = '%s\\/%s' % (apiurl, permission)\\n \\n     if len(requests) \\u003e 1:\\n         url += '\\/multirequest'\\n     else:\\n         url += '\\/%s' % requests[0][0]\\n \\n     ui.debug('sending %d commands\\\\n' % len(requests))\\n     for command, args, f in requests:\\n         ui.debug('sending command %s: %s\\\\n' % (\\n             command, stringutil.pprint(args, indent=2)))\\n         assert not list(handler.callcommand(command, args, f))\\n \\n     # TODO stream this.\\n     body = b''.join(map(bytes, handler.flushcommands()))\\n \\n     # TODO modify user-agent to reflect v2\\n     headers = {\\n         r'Accept': wireprotov2server.FRAMINGTYPE,\\n         r'Content-Type': wireprotov2server.FRAMINGTYPE,\\n     }\\n \\n     req = requestbuilder(pycompat.strurl(url), body, headers)\\n     req.add_unredirected_header(r'Content-Length', r'%d' % len(body))\\n \\n     try:\\n         res = opener.open(req)\\n     except urlerr.httperror as e:\\n         if e.code == 401:\\n             raise error.Abort(_('authorization failed'))\\n \\n         raise\\n     except httplib.HTTPException as e:\\n         ui.traceback()\\n         raise IOError(None, e)\\n \\n     return handler, res\\n \\n class queuedcommandfuture(pycompat.futures.Future):\\n     \\\"\\\"\\\"Wraps result() on command futures to trigger submission on call.\\\"\\\"\\\"\\n \\n     def result(self, timeout=None):\\n         if self.done():\\n             return pycompat.futures.Future.result(self, timeout)\\n \\n         self._peerexecutor.sendcommands()\\n \\n         # sendcommands() will restore the original __class__ and self.result\\n         # will resolve to Future.result.\\n         return self.result(timeout)\\n \\n @interfaceutil.implementer(repository.ipeercommandexecutor)\\n class httpv2executor(object):\\n     def __init__(self, ui, opener, requestbuilder, apiurl, descriptor):\\n         self._ui = ui\\n         self._opener = opener\\n         self._requestbuilder = requestbuilder\\n         self._apiurl = apiurl\\n         self._descriptor = descriptor\\n         self._sent = False\\n         self._closed = False\\n         self._neededpermissions = set()\\n         self._calls = []\\n         self._futures = weakref.WeakSet()\\n         self._responseexecutor = None\\n         self._responsef = None\\n \\n     def __enter__(self):\\n         return self\\n \\n     def __exit__(self, exctype, excvalue, exctb):\\n         self.close()\\n \\n     def callcommand(self, command, args):\\n         if self._sent:\\n             raise error.ProgrammingError('callcommand() cannot be used after '\\n                                          'commands are sent')\\n \\n         if self._closed:\\n             raise error.ProgrammingError('callcommand() cannot be used after '\\n                                          'close()')\\n \\n         # The service advertises which commands are available. So if we attempt\\n         # to call an unknown command or pass an unknown argument, we can screen\\n         # for this.\\n         if command not in self._descriptor['commands']:\\n             raise error.ProgrammingError(\\n                 'wire protocol command %s is not available' % command)\\n \\n         cmdinfo = self._descriptor['commands'][command]\\n         unknownargs = set(args.keys()) - set(cmdinfo.get('args', {}))\\n \\n         if unknownargs:\\n             raise error.ProgrammingError(\\n                 'wire protocol command %s does not accept argument: %s' % (\\n                     command, ', '.join(sorted(unknownargs))))\\n \\n         self._neededpermissions |= set(cmdinfo['permissions'])\\n \\n         # TODO we \\/could\\/ also validate types here, since the API descriptor\\n         # includes types...\\n \\n         f = pycompat.futures.Future()\\n \\n         # Monkeypatch it so result() triggers sendcommands(), otherwise result()\\n         # could deadlock.\\n         f.__class__ = queuedcommandfuture\\n         f._peerexecutor = self\\n \\n         self._futures.add(f)\\n         self._calls.append((command, args, f))\\n \\n         return f\\n \\n     def sendcommands(self):\\n         if self._sent:\\n             return\\n \\n         if not self._calls:\\n             return\\n \\n         self._sent = True\\n \\n         # Unhack any future types so caller sees a clean type and so we\\n         # break reference cycle.\\n         for f in self._futures:\\n             if isinstance(f, queuedcommandfuture):\\n                 f.__class__ = pycompat.futures.Future\\n                 f._peerexecutor = None\\n \\n         # Mark the future as running and filter out cancelled futures.\\n         calls = [(command, args, f)\\n                  for command, args, f in self._calls\\n                  if f.set_running_or_notify_cancel()]\\n \\n         # Clear out references, prevent improper object usage.\\n         self._calls = None\\n \\n         if not calls:\\n             return\\n \\n         permissions = set(self._neededpermissions)\\n \\n         if 'push' in permissions and 'pull' in permissions:\\n             permissions.remove('pull')\\n \\n         if len(permissions) \\u003e 1:\\n             raise error.RepoError(_('cannot make request requiring multiple '\\n                                     'permissions: %s') %\\n                                   _(', ').join(sorted(permissions)))\\n \\n         permission = {\\n             'push': 'rw',\\n             'pull': 'ro',\\n         }[permissions.pop()]\\n \\n         handler, resp = sendv2request(\\n             self._ui, self._opener, self._requestbuilder, self._apiurl,\\n             permission, calls)\\n \\n         # TODO we probably want to validate the HTTP code, media type, etc.\\n \\n         self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)\\n         self._responsef = self._responseexecutor.submit(self._handleresponse,\\n                                                         handler, resp)\\n \\n     def close(self):\\n         if self._closed:\\n             return\\n \\n         self.sendcommands()\\n \\n         self._closed = True\\n \\n         if not self._responsef:\\n             return\\n \\n         # TODO ^C here may not result in immediate program termination.\\n \\n         try:\\n             self._responsef.result()\\n         finally:\\n             self._responseexecutor.shutdown(wait=True)\\n             self._responsef = None\\n             self._responseexecutor = None\\n \\n             # If any of our futures are still in progress, mark them as\\n             # errored, otherwise a result() could wait indefinitely.\\n             for f in self._futures:\\n                 if not f.done():\\n                     f.set_exception(error.ResponseError(\\n                         _('unfulfilled command response')))\\n \\n             self._futures = None\\n \\n     def _handleresponse(self, handler, resp):\\n         # Called in a thread to read the response.\\n \\n         while handler.readframe(resp):\\n             pass\\n \\n # TODO implement interface for version 2 peers\\n @interfaceutil.implementer(repository.ipeerconnection,\\n                            repository.ipeercapabilities,\\n                            repository.ipeerrequests)\\n class httpv2peer(object):\\n     def __init__(self, ui, repourl, apipath, opener, requestbuilder,\\n                  apidescriptor):\\n         self.ui = ui\\n \\n         if repourl.endswith('\\/'):\\n             repourl = repourl[:-1]\\n \\n         self._url = repourl\\n         self._apipath = apipath\\n         self._apiurl = '%s\\/%s' % (repourl, apipath)\\n         self._opener = opener\\n         self._requestbuilder = requestbuilder\\n         self._descriptor = apidescriptor\\n \\n     # Start of ipeerconnection.\\n \\n     def url(self):\\n         return self._url\\n \\n     def local(self):\\n         return None\\n \\n     def peer(self):\\n         return self\\n \\n     def canpush(self):\\n         # TODO change once implemented.\\n         return False\\n \\n     def close(self):\\n         pass\\n \\n     # End of ipeerconnection.\\n \\n     # Start of ipeercapabilities.\\n \\n     def capable(self, name):\\n         # The capabilities used internally historically map to capabilities\\n         # advertised from the \\\"capabilities\\\" wire protocol command. However,\\n         # version 2 of that command works differently.\\n \\n         # Maps to commands that are available.\\n         if name in ('branchmap', 'getbundle', 'known', 'lookup', 'pushkey'):\\n             return True\\n \\n         # Other concepts.\\n-        if name in ('bundle2',):\\n+        # TODO remove exchangev2 once we have a command implemented.\\n+        if name in ('bundle2', 'exchangev2'):\\n             return True\\n \\n         # Alias command-* to presence of command of that name.\\n         if name.startswith('command-'):\\n             return name[len('command-'):] in self._descriptor['commands']\\n \\n         return False\\n \\n     def requirecap(self, name, purpose):\\n         if self.capable(name):\\n             return\\n \\n         raise error.CapabilityError(\\n             _('cannot %s; client or remote repository does not support the %r '\\n               'capability') % (purpose, name))\\n \\n     # End of ipeercapabilities.\\n \\n     def _call(self, name, **args):\\n         with self.commandexecutor() as e:\\n             return e.callcommand(name, args).result()\\n \\n     def commandexecutor(self):\\n         return httpv2executor(self.ui, self._opener, self._requestbuilder,\\n                               self._apiurl, self._descriptor)\\n \\n # Registry of API service names to metadata about peers that handle it.\\n #\\n # The following keys are meaningful:\\n #\\n # init\\n #    Callable receiving (ui, repourl, servicepath, opener, requestbuilder,\\n #                        apidescriptor) to create a peer.\\n #\\n # priority\\n #    Integer priority for the service. If we could choose from multiple\\n #    services, we choose the one with the highest priority.\\n API_PEERS = {\\n     wireprototypes.HTTP_WIREPROTO_V2: {\\n         'init': httpv2peer,\\n         'priority': 50,\\n     },\\n }\\n \\n def performhandshake(ui, url, opener, requestbuilder):\\n     # The handshake is a request to the capabilities command.\\n \\n     caps = None\\n     def capable(x):\\n         raise error.ProgrammingError('should not be called')\\n \\n     args = {}\\n \\n     # The client advertises support for newer protocols by adding an\\n     # X-HgUpgrade-* header with a list of supported APIs and an\\n     # X-HgProto-* header advertising which serializing formats it supports.\\n     # We only support the HTTP version 2 transport and CBOR responses for\\n     # now.\\n     advertisev2 = ui.configbool('experimental', 'httppeer.advertise-v2')\\n \\n     if advertisev2:\\n         args['headers'] = {\\n             r'X-HgProto-1': r'cbor',\\n         }\\n \\n         args['headers'].update(\\n             encodevalueinheaders(' '.join(sorted(API_PEERS)),\\n                                  'X-HgUpgrade',\\n                                  # We don't know the header limit this early.\\n                                  # So make it small.\\n                                  1024))\\n \\n     req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,\\n                                            capable, url, 'capabilities',\\n                                            args)\\n     resp = sendrequest(ui, opener, req)\\n \\n     # The server may redirect us to the repo root, stripping the\\n     # ?cmd=capabilities query string from the URL. The server would likely\\n     # return HTML in this case and ``parsev1commandresponse()`` would raise.\\n     # We catch this special case and re-issue the capabilities request against\\n     # the new URL.\\n     #\\n     # We should ideally not do this, as a redirect that drops the query\\n     # string from the URL is arguably a server bug. (Garbage in, garbage out).\\n     # However,  Mercurial clients for several years appeared to handle this\\n     # issue without behavior degradation. And according to issue 5860, it may\\n     # be a longstanding bug in some server implementations. So we allow a\\n     # redirect that drops the query string to \\\"just work.\\\"\\n     try:\\n         respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,\\n                                                    compressible=False,\\n                                                    allowcbor=advertisev2)\\n     except RedirectedRepoError as e:\\n         req, requrl, qs = makev1commandrequest(ui, requestbuilder, caps,\\n                                                capable, e.respurl,\\n                                                'capabilities', args)\\n         resp = sendrequest(ui, opener, req)\\n         respurl, ct, resp = parsev1commandresponse(ui, url, requrl, qs, resp,\\n                                                    compressible=False,\\n                                                    allowcbor=advertisev2)\\n \\n     try:\\n         rawdata = resp.read()\\n     finally:\\n         resp.close()\\n \\n     if not ct.startswith('application\\/mercurial-'):\\n         raise error.ProgrammingError('unexpected content-type: %s' % ct)\\n \\n     if advertisev2:\\n         if ct == 'application\\/mercurial-cbor':\\n             try:\\n                 info = cborutil.decodeall(rawdata)[0]\\n             except cborutil.CBORDecodeError:\\n                 raise error.Abort(_('error decoding CBOR from remote server'),\\n                                   hint=_('try again and consider contacting '\\n                                          'the server operator'))\\n \\n         # We got a legacy response. That's fine.\\n         elif ct in ('application\\/mercurial-0.1', 'application\\/mercurial-0.2'):\\n             info = {\\n                 'v1capabilities': set(rawdata.split())\\n             }\\n \\n         else:\\n             raise error.RepoError(\\n                 _('unexpected response type from server: %s') % ct)\\n     else:\\n         info = {\\n             'v1capabilities': set(rawdata.split())\\n         }\\n \\n     return respurl, info\\n \\n def makepeer(ui, path, opener=None, requestbuilder=urlreq.request):\\n     \\\"\\\"\\\"Construct an appropriate HTTP peer instance.\\n \\n     ``opener`` is an ``url.opener`` that should be used to establish\\n     connections, perform HTTP requests.\\n \\n     ``requestbuilder`` is the type used for constructing HTTP requests.\\n     It exists as an argument so extensions can override the default.\\n     \\\"\\\"\\\"\\n     u = util.url(path)\\n     if u.query or u.fragment:\\n         raise error.Abort(_('unsupported URL component: \\\"%s\\\"') %\\n                           (u.query or u.fragment))\\n \\n     # urllib cannot handle URLs with embedded user or passwd.\\n     url, authinfo = u.authinfo()\\n     ui.debug('using %s\\\\n' % url)\\n \\n     opener = opener or urlmod.opener(ui, authinfo)\\n \\n     respurl, info = performhandshake(ui, url, opener, requestbuilder)\\n \\n     # Given the intersection of APIs that both we and the server support,\\n     # sort by their advertised priority and pick the first one.\\n     #\\n     # TODO consider making this request-based and interface driven. For\\n     # example, the caller could say \\\"I want a peer that does X.\\\" It's quite\\n     # possible that not all peers would do that. Since we know the service\\n     # capabilities, we could filter out services not meeting the\\n     # requirements. Possibly by consulting the interfaces defined by the\\n     # peer type.\\n     apipeerchoices = set(info.get('apis', {}).keys()) & set(API_PEERS.keys())\\n \\n     preferredchoices = sorted(apipeerchoices,\\n                               key=lambda x: API_PEERS[x]['priority'],\\n                               reverse=True)\\n \\n     for service in preferredchoices:\\n         apipath = '%s\\/%s' % (info['apibase'].rstrip('\\/'), service)\\n \\n         return API_PEERS[service]['init'](ui, respurl, apipath, opener,\\n                                           requestbuilder,\\n                                           info['apis'][service])\\n \\n     # Failed to construct an API peer. Fall back to legacy.\\n     return httppeer(ui, path, respurl, opener, requestbuilder,\\n                     info['v1capabilities'])\\n \\n def instance(ui, path, create, intents=None, createopts=None):\\n     if create:\\n         raise error.Abort(_('cannot create new http repository'))\\n     try:\\n         if path.startswith('https:') and not urlmod.has_https:\\n             raise error.Abort(_('Python support for SSL and HTTPS '\\n                                 'is not installed'))\\n \\n         inst = makepeer(ui, path)\\n \\n         return inst\\n     except error.RepoError as httpexception:\\n         try:\\n             r = statichttprepo.instance(ui, \\\"static-\\\" + path, create)\\n             ui.note(_('(falling back to static-http)\\\\n'))\\n             return r\\n         except error.RepoError:\\n             raise httpexception # use the original http RepoError instead\\n\"}]},{\"id\":\"24368\",\"metadata\":{\"line:first\":1,\"copy:lines\":{\"4\":[\"mercurial\\/exchange.py\",4,\" \"],\"5\":[\"mercurial\\/exchange.py\",5,\" \"],\"6\":[\"mercurial\\/exchange.py\",6,\" \"],\"7\":[\"mercurial\\/exchange.py\",7,\" \"],\"8\":[\"mercurial\\/exchange.py\",8,\" \"],\"9\":[\"mercurial\\/exchange.py\",9,\" \"]}},\"oldPath\":null,\"currentPath\":\"mercurial\\/exchangev2.py\",\"awayPaths\":[],\"oldProperties\":[],\"newProperties\":{\"unix:filemode\":\"100644\"},\"type\":\"1\",\"fileType\":\"1\",\"commitHash\":null,\"addLines\":\"55\",\"delLines\":\"0\",\"hunks\":[{\"oldOffset\":\"0\",\"newOffset\":\"1\",\"oldLength\":\"0\",\"newLength\":\"55\",\"addLines\":null,\"delLines\":null,\"isMissingOldNewline\":null,\"isMissingNewNewline\":null,\"corpus\":\"+# exchangev2.py - repository exchange for wire protocol version 2\\n+#\\n+# Copyright 2018 Gregory Szorc \\u003cgregory.szorc@gmail.com\\u003e\\n+#\\n+# This software may be used and distributed according to the terms of the\\n+# GNU General Public License version 2 or any later version.\\n+\\n+from __future__ import absolute_import\\n+\\n+from .node import (\\n+    nullid,\\n+)\\n+from . import (\\n+    setdiscovery,\\n+)\\n+\\n+def pull(pullop):\\n+    \\\"\\\"\\\"Pull using wire protocol version 2.\\\"\\\"\\\"\\n+    repo = pullop.repo\\n+    remote = pullop.remote\\n+\\n+    # Figure out what needs to be fetched.\\n+    common, fetch, remoteheads = _pullchangesetdiscovery(\\n+        repo, remote, pullop.heads, abortwhenunrelated=pullop.force)\\n+\\n+def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True):\\n+    \\\"\\\"\\\"Determine which changesets need to be pulled.\\\"\\\"\\\"\\n+\\n+    if heads:\\n+        knownnode = repo.changelog.hasnode\\n+        if all(knownnode(head) for head in heads):\\n+            return heads, False, heads\\n+\\n+    # TODO wire protocol version 2 is capable of more efficient discovery\\n+    # than setdiscovery. Consider implementing something better.\\n+    common, fetch, remoteheads = setdiscovery.findcommonheads(\\n+        repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated)\\n+\\n+    common = set(common)\\n+    remoteheads = set(remoteheads)\\n+\\n+    # If a remote head is filtered locally, put it back in the common set.\\n+    # See the comment in exchange._pulldiscoverychangegroup() for more.\\n+\\n+    if fetch and remoteheads:\\n+        nodemap = repo.unfiltered().changelog.nodemap\\n+\\n+        common |= {head for head in remoteheads if head in nodemap}\\n+\\n+        if set(remoteheads).issubset(common):\\n+            fetch = []\\n+\\n+    common.discard(nullid)\\n+\\n+    return common, fetch, remoteheads\\n\"}]},{\"id\":\"24367\",\"metadata\":{\"line:first\":29,\"copy:lines\":{\"1514\":[\"\",1509,\"-\"],\"1515\":[\"\",1510,\"-\"],\"1516\":[\"\",1511,\"-\"],\"1517\":[\"\",1512,\"-\"],\"1518\":[\"\",1513,\"-\"],\"1519\":[\"\",1514,\"-\"],\"1520\":[\"\",1515,\"-\"],\"1521\":[\"\",1516,\"-\"],\"1522\":[\"\",1517,\"-\"],\"1523\":[\"\",1518,\"-\"],\"1524\":[\"\",1519,\"-\"],\"1525\":[\"\",1520,\" \"],\"1526\":[\"\",1521,\" \"],\"1527\":[\"\",1522,\" \"],\"1528\":[\"\",1523,\" \"],\"1529\":[\"\",1524,\" \"],\"1530\":[\"\",1525,\" \"],\"1531\":[\"\",1526,\" \"],\"1532\":[\"\",1527,\" \"],\"1533\":[\"\",1528,\" \"],\"1534\":[\"\",1529,\" \"],\"1535\":[\"\",1530,\" \"],\"1536\":[\"\",1531,\" \"],\"1537\":[\"\",1532,\" \"],\"1538\":[\"\",1533,\" \"],\"1539\":[\"\",1534,\" \"],\"1540\":[\"\",1535,\" \"],\"1541\":[\"\",1536,\" \"],\"1542\":[\"\",1537,\" \"],\"1543\":[\"\",1538,\" \"],\"1544\":[\"\",1539,\" \"],\"1545\":[\"\",1540,\" \"],\"1546\":[\"\",1541,\" \"],\"1547\":[\"\",1542,\" \"],\"1548\":[\"\",1543,\" \"],\"1549\":[\"\",1544,\" \"],\"1550\":[\"\",1545,\" \"],\"1551\":[\"\",1546,\" \"],\"1552\":[\"\",1547,\" \"],\"1553\":[\"\",1548,\" \"],\"1554\":[\"\",1549,\" \"],\"1555\":[\"\",1550,\" \"],\"1556\":[\"\",1551,\" \"],\"1557\":[\"\",1552,\" \"],\"1558\":[\"\",1553,\" \"],\"1559\":[\"\",1554,\" \"],\"1560\":[\"\",1555,\" \"],\"1561\":[\"\",1556,\" \"],\"1562\":[\"\",1557,\" \"],\"1563\":[\"\",1558,\" \"],\"1564\":[\"\",1559,\" \"],\"1565\":[\"\",1560,\" \"],\"1566\":[\"\",1561,\" \"],\"1567\":[\"\",1562,\" \"],\"1568\":[\"\",1563,\" \"],\"1569\":[\"\",1564,\" \"],\"1570\":[\"\",1565,\" \"],\"1571\":[\"\",1566,\" \"],\"1572\":[\"\",1567,\" \"],\"1573\":[\"\",1568,\" \"],\"1574\":[\"\",1569,\" \"],\"1575\":[\"\",1570,\" \"],\"1576\":[\"\",1571,\" \"],\"1577\":[\"\",1572,\" \"],\"1578\":[\"\",1573,\" \"],\"1579\":[\"\",1574,\" \"],\"1580\":[\"\",1575,\" \"],\"1581\":[\"\",1576,\" \"],\"1582\":[\"\",1577,\" \"],\"1583\":[\"\",1578,\" \"],\"1584\":[\"\",1579,\" \"],\"1585\":[\"\",1580,\" \"],\"1586\":[\"\",1581,\" \"],\"1587\":[\"\",1582,\" \"],\"1588\":[\"\",1583,\" \"],\"1589\":[\"\",1584,\" \"],\"1590\":[\"\",1585,\" \"],\"1591\":[\"\",1586,\" \"],\"1592\":[\"\",1587,\" \"],\"1593\":[\"\",1588,\" \"],\"1594\":[\"\",1589,\" \"],\"1595\":[\"\",1590,\" \"],\"1596\":[\"\",1591,\" \"],\"1597\":[\"\",1592,\" \"],\"1598\":[\"\",1593,\" \"],\"1599\":[\"\",1594,\" \"],\"1600\":[\"\",1595,\" \"],\"1601\":[\"\",1596,\" \"],\"1602\":[\"\",1597,\" \"],\"1603\":[\"\",1598,\" \"],\"1604\":[\"\",1599,\" \"],\"1605\":[\"\",1600,\" \"],\"1606\":[\"\",1601,\" \"],\"1607\":[\"\",1602,\" \"],\"1608\":[\"\",1603,\" \"],\"1609\":[\"\",1604,\" \"],\"1610\":[\"\",1605,\" \"],\"1611\":[\"\",1606,\" \"],\"1612\":[\"\",1607,\" \"],\"1613\":[\"\",1608,\" \"],\"1614\":[\"\",1609,\" \"],\"1615\":[\"\",1610,\" \"],\"1616\":[\"\",1611,\" \"],\"1617\":[\"\",1612,\" \"],\"1618\":[\"\",1613,\" \"],\"1619\":[\"\",1614,\" \"],\"1620\":[\"\",1615,\" \"],\"1621\":[\"\",1616,\" \"],\"1622\":[\"\",1617,\" \"],\"1623\":[\"\",1618,\" \"],\"1624\":[\"\",1619,\" \"],\"1625\":[\"\",1620,\" \"],\"1626\":[\"\",1621,\" \"],\"1627\":[\"\",1622,\" \"],\"1628\":[\"\",1623,\" \"],\"1629\":[\"\",1624,\" \"],\"1630\":[\"\",1625,\" \"],\"1631\":[\"\",1626,\" \"],\"1632\":[\"\",1627,\" \"],\"1633\":[\"\",1628,\" \"],\"1634\":[\"\",1629,\" \"],\"1635\":[\"\",1630,\" \"],\"1636\":[\"\",1631,\" \"],\"1637\":[\"\",1632,\" \"],\"1638\":[\"\",1633,\" \"],\"1639\":[\"\",1634,\" \"],\"1640\":[\"\",1635,\" \"],\"1641\":[\"\",1636,\" \"],\"1642\":[\"\",1637,\" \"],\"1643\":[\"\",1638,\" \"],\"1644\":[\"\",1639,\" \"],\"1645\":[\"\",1640,\" \"],\"1646\":[\"\",1641,\" \"],\"1647\":[\"\",1642,\" \"],\"1648\":[\"\",1643,\" \"],\"1649\":[\"\",1644,\" \"],\"1650\":[\"\",1645,\" \"],\"1651\":[\"\",1646,\" \"],\"1652\":[\"\",1647,\" \"],\"1653\":[\"\",1648,\" \"],\"1654\":[\"\",1649,\" \"],\"1655\":[\"\",1650,\" \"],\"1656\":[\"\",1651,\" \"],\"1657\":[\"\",1652,\" \"],\"1658\":[\"\",1653,\" \"],\"1659\":[\"\",1654,\" \"],\"1660\":[\"\",1655,\" \"],\"1661\":[\"\",1656,\" \"],\"1662\":[\"\",1657,\" \"],\"1663\":[\"\",1658,\" \"],\"1664\":[\"\",1659,\" \"],\"1665\":[\"\",1660,\" \"],\"1666\":[\"\",1661,\" \"],\"1667\":[\"\",1662,\" \"],\"1668\":[\"\",1663,\" \"],\"1669\":[\"\",1664,\" \"],\"1670\":[\"\",1665,\" \"],\"1671\":[\"\",1666,\" \"],\"1672\":[\"\",1667,\" \"],\"1673\":[\"\",1668,\" \"],\"1674\":[\"\",1669,\" \"],\"1675\":[\"\",1670,\" \"],\"1676\":[\"\",1671,\" \"],\"1677\":[\"\",1672,\" \"],\"1678\":[\"\",1673,\" \"],\"1679\":[\"\",1674,\" \"],\"1680\":[\"\",1675,\" \"],\"1681\":[\"\",1676,\" \"],\"1682\":[\"\",1677,\" \"],\"1683\":[\"\",1678,\" \"],\"1684\":[\"\",1679,\" \"],\"1685\":[\"\",1680,\" \"],\"1686\":[\"\",1681,\" \"],\"1687\":[\"\",1682,\" \"],\"1688\":[\"\",1683,\" \"],\"1689\":[\"\",1684,\" \"],\"1690\":[\"\",1685,\" \"],\"1691\":[\"\",1686,\" \"],\"1692\":[\"\",1687,\" \"],\"1693\":[\"\",1688,\" \"],\"1694\":[\"\",1689,\" \"],\"1695\":[\"\",1690,\" \"],\"1696\":[\"\",1691,\" \"],\"1697\":[\"\",1692,\" \"],\"1698\":[\"\",1693,\" \"],\"1699\":[\"\",1694,\" \"],\"1700\":[\"\",1695,\" \"],\"1701\":[\"\",1696,\" \"],\"1702\":[\"\",1697,\" \"],\"1703\":[\"\",1698,\" \"],\"1704\":[\"\",1699,\" \"],\"1705\":[\"\",1700,\" \"],\"1706\":[\"\",1701,\" \"],\"1707\":[\"\",1702,\" \"],\"1708\":[\"\",1703,\" \"],\"1709\":[\"\",1704,\" \"],\"1710\":[\"\",1705,\" \"],\"1711\":[\"\",1706,\" \"],\"1712\":[\"\",1707,\" \"],\"1713\":[\"\",1708,\" \"],\"1714\":[\"\",1709,\" \"],\"1715\":[\"\",1710,\" \"],\"1716\":[\"\",1711,\" \"],\"1717\":[\"\",1712,\" \"],\"1718\":[\"\",1713,\" \"],\"1719\":[\"\",1714,\" \"],\"1720\":[\"\",1715,\" \"],\"1721\":[\"\",1716,\" \"],\"1722\":[\"\",1717,\" \"],\"1723\":[\"\",1718,\" \"],\"1724\":[\"\",1719,\" \"],\"1725\":[\"\",1720,\" \"],\"1726\":[\"\",1721,\" \"],\"1727\":[\"\",1722,\" \"],\"1728\":[\"\",1723,\" \"],\"1729\":[\"\",1724,\" \"],\"1730\":[\"\",1725,\" \"],\"1731\":[\"\",1726,\" \"],\"1732\":[\"\",1727,\" \"],\"1733\":[\"\",1728,\" \"],\"1734\":[\"\",1729,\" \"],\"1735\":[\"\",1730,\" \"],\"1736\":[\"\",1731,\" \"],\"1737\":[\"\",1732,\" \"],\"1738\":[\"\",1733,\" \"],\"1739\":[\"\",1734,\" \"],\"1740\":[\"\",1735,\" \"],\"1741\":[\"\",1736,\" \"],\"1742\":[\"\",1737,\" \"],\"1743\":[\"\",1738,\" \"],\"1744\":[\"\",1739,\" \"],\"1745\":[\"\",1740,\" \"],\"1746\":[\"\",1741,\" \"],\"1747\":[\"\",1742,\" \"],\"1748\":[\"\",1743,\" \"],\"1749\":[\"\",1744,\" \"],\"1750\":[\"\",1745,\" \"],\"1751\":[\"\",1746,\" \"],\"1752\":[\"\",1747,\" \"],\"1753\":[\"\",1748,\" \"],\"1754\":[\"\",1749,\" \"],\"1755\":[\"\",1750,\" \"],\"1756\":[\"\",1751,\" \"],\"1757\":[\"\",1752,\" \"],\"1758\":[\"\",1753,\" \"],\"1759\":[\"\",1754,\" \"],\"1760\":[\"\",1755,\" \"],\"1761\":[\"\",1756,\" \"],\"1762\":[\"\",1757,\" \"],\"1763\":[\"\",1758,\" \"],\"1764\":[\"\",1759,\" \"],\"1765\":[\"\",1760,\" \"],\"1766\":[\"\",1761,\" \"],\"1767\":[\"\",1762,\" \"],\"1768\":[\"\",1763,\" \"],\"1769\":[\"\",1764,\" \"],\"1770\":[\"\",1765,\" \"],\"1771\":[\"\",1766,\" \"],\"1772\":[\"\",1767,\" \"],\"1773\":[\"\",1768,\" \"],\"1774\":[\"\",1769,\" \"],\"1775\":[\"\",1770,\" \"],\"1776\":[\"\",1771,\" \"],\"1777\":[\"\",1772,\" \"],\"1778\":[\"\",1773,\" \"],\"1779\":[\"\",1774,\" \"],\"1780\":[\"\",1775,\" \"],\"1781\":[\"\",1776,\" \"],\"1782\":[\"\",1777,\" \"],\"1783\":[\"\",1778,\" \"],\"1784\":[\"\",1779,\" \"],\"1785\":[\"\",1780,\" \"],\"1786\":[\"\",1781,\" \"],\"1787\":[\"\",1782,\" \"],\"1788\":[\"\",1783,\" \"],\"1789\":[\"\",1784,\" \"],\"1790\":[\"\",1785,\" \"],\"1791\":[\"\",1786,\" \"],\"1792\":[\"\",1787,\" \"],\"1793\":[\"\",1788,\" \"],\"1794\":[\"\",1789,\" \"],\"1795\":[\"\",1790,\" \"],\"1796\":[\"\",1791,\" \"],\"1797\":[\"\",1792,\" \"],\"1798\":[\"\",1793,\" \"],\"1799\":[\"\",1794,\" \"],\"1800\":[\"\",1795,\" \"],\"1801\":[\"\",1796,\" \"],\"1802\":[\"\",1797,\" \"],\"1803\":[\"\",1798,\" \"],\"1804\":[\"\",1799,\" \"],\"1805\":[\"\",1800,\" \"],\"1806\":[\"\",1801,\" \"],\"1807\":[\"\",1802,\" \"],\"1808\":[\"\",1803,\" \"],\"1809\":[\"\",1804,\" \"],\"1810\":[\"\",1805,\" \"],\"1811\":[\"\",1806,\" \"],\"1812\":[\"\",1807,\" \"],\"1813\":[\"\",1808,\" \"],\"1814\":[\"\",1809,\" \"],\"1815\":[\"\",1810,\" \"],\"1816\":[\"\",1811,\" \"],\"1817\":[\"\",1812,\" \"],\"1818\":[\"\",1813,\" \"],\"1819\":[\"\",1814,\" \"],\"1820\":[\"\",1815,\" \"],\"1821\":[\"\",1816,\" \"],\"1822\":[\"\",1817,\" \"],\"1823\":[\"\",1818,\" \"],\"1824\":[\"\",1819,\" \"],\"1825\":[\"\",1820,\" \"],\"1826\":[\"\",1821,\" \"],\"1827\":[\"\",1822,\" \"],\"1828\":[\"\",1823,\" \"],\"1829\":[\"\",1824,\" \"],\"1830\":[\"\",1825,\" \"],\"1831\":[\"\",1826,\" \"],\"1832\":[\"\",1827,\" \"],\"1833\":[\"\",1828,\" \"],\"1834\":[\"\",1829,\" \"],\"1835\":[\"\",1830,\" \"],\"1836\":[\"\",1831,\" \"],\"1837\":[\"\",1832,\" \"],\"1838\":[\"\",1833,\" \"],\"1839\":[\"\",1834,\" \"],\"1840\":[\"\",1835,\" \"],\"1841\":[\"\",1836,\" \"],\"1842\":[\"\",1837,\" \"],\"1843\":[\"\",1838,\" \"],\"1844\":[\"\",1839,\" \"],\"1845\":[\"\",1840,\" \"],\"1846\":[\"\",1841,\" \"],\"1847\":[\"\",1842,\" \"],\"1848\":[\"\",1843,\" \"],\"1849\":[\"\",1844,\" \"],\"1850\":[\"\",1845,\" \"],\"1851\":[\"\",1846,\" \"],\"1852\":[\"\",1847,\" \"],\"1853\":[\"\",1848,\" \"],\"1854\":[\"\",1849,\" \"],\"1855\":[\"\",1850,\" \"],\"1856\":[\"\",1851,\" \"],\"1857\":[\"\",1852,\" \"],\"1858\":[\"\",1853,\" \"],\"1859\":[\"\",1854,\" \"],\"1860\":[\"\",1855,\" \"],\"1861\":[\"\",1856,\" \"],\"1862\":[\"\",1857,\" \"],\"1863\":[\"\",1858,\" \"],\"1864\":[\"\",1859,\" \"],\"1865\":[\"\",1860,\" \"],\"1866\":[\"\",1861,\" \"],\"1867\":[\"\",1862,\" \"],\"1868\":[\"\",1863,\" \"],\"1869\":[\"\",1864,\" \"],\"1870\":[\"\",1865,\" \"],\"1871\":[\"\",1866,\" \"],\"1872\":[\"\",1867,\" \"],\"1873\":[\"\",1868,\" \"],\"1874\":[\"\",1869,\" \"],\"1875\":[\"\",1870,\" \"],\"1876\":[\"\",1871,\" \"],\"1877\":[\"\",1872,\" \"],\"1878\":[\"\",1873,\" \"],\"1879\":[\"\",1874,\" \"],\"1880\":[\"\",1875,\" \"],\"1881\":[\"\",1876,\" \"],\"1882\":[\"\",1877,\" \"],\"1883\":[\"\",1878,\" \"],\"1884\":[\"\",1879,\" \"],\"1885\":[\"\",1880,\" \"],\"1886\":[\"\",1881,\" \"],\"1887\":[\"\",1882,\" \"],\"1888\":[\"\",1883,\" \"],\"1889\":[\"\",1884,\" \"],\"1890\":[\"\",1885,\" \"],\"1891\":[\"\",1886,\" \"],\"1892\":[\"\",1887,\" \"],\"1893\":[\"\",1888,\" \"],\"1894\":[\"\",1889,\" \"],\"1895\":[\"\",1890,\" \"],\"1896\":[\"\",1891,\" \"],\"1897\":[\"\",1892,\" \"],\"1898\":[\"\",1893,\" \"],\"1899\":[\"\",1894,\" \"],\"1900\":[\"\",1895,\" \"],\"1901\":[\"\",1896,\" \"],\"1902\":[\"\",1897,\" \"],\"1903\":[\"\",1898,\" \"],\"1904\":[\"\",1899,\" \"],\"1905\":[\"\",1900,\" \"],\"1906\":[\"\",1901,\" \"],\"1907\":[\"\",1902,\" \"],\"1908\":[\"\",1903,\" \"],\"1909\":[\"\",1904,\" \"],\"1910\":[\"\",1905,\" \"],\"1911\":[\"\",1906,\" \"],\"1912\":[\"\",1907,\" \"],\"1913\":[\"\",1908,\" \"],\"1914\":[\"\",1909,\" \"],\"1915\":[\"\",1910,\" \"],\"1916\":[\"\",1911,\" \"],\"1917\":[\"\",1912,\" \"],\"1918\":[\"\",1913,\" \"],\"1919\":[\"\",1914,\" \"],\"1920\":[\"\",1915,\" \"],\"1921\":[\"\",1916,\" \"],\"1922\":[\"\",1917,\" \"],\"1923\":[\"\",1918,\" \"],\"1924\":[\"\",1919,\" \"],\"1925\":[\"\",1920,\" \"],\"1926\":[\"\",1921,\" \"],\"1927\":[\"\",1922,\" \"],\"1928\":[\"\",1923,\" \"],\"1929\":[\"\",1924,\" \"],\"1930\":[\"\",1925,\" \"],\"1931\":[\"\",1926,\" \"],\"1932\":[\"\",1927,\" \"],\"1933\":[\"\",1928,\" \"],\"1934\":[\"\",1929,\" \"],\"1935\":[\"\",1930,\" \"],\"1936\":[\"\",1931,\" \"],\"1937\":[\"\",1932,\" \"],\"1938\":[\"\",1933,\" \"],\"1939\":[\"\",1934,\" \"],\"1940\":[\"\",1935,\" \"],\"1941\":[\"\",1936,\" \"],\"1942\":[\"\",1937,\" \"],\"1943\":[\"\",1938,\" \"],\"1944\":[\"\",1939,\" \"],\"1945\":[\"\",1940,\" \"],\"1946\":[\"\",1941,\" \"],\"1947\":[\"\",1942,\" \"],\"1948\":[\"\",1943,\" \"],\"1949\":[\"\",1944,\" \"],\"1950\":[\"\",1945,\" \"],\"1951\":[\"\",1946,\" \"],\"1952\":[\"\",1947,\" \"],\"1953\":[\"\",1948,\" \"],\"1954\":[\"\",1949,\" \"],\"1955\":[\"\",1950,\" \"],\"1956\":[\"\",1951,\" \"],\"1957\":[\"\",1952,\" \"],\"1958\":[\"\",1953,\" \"],\"1959\":[\"\",1954,\" \"],\"1960\":[\"\",1955,\" \"],\"1961\":[\"\",1956,\" \"],\"1962\":[\"\",1957,\" \"],\"1963\":[\"\",1958,\" \"],\"1964\":[\"\",1959,\" \"],\"1965\":[\"\",1960,\" \"],\"1966\":[\"\",1961,\" \"],\"1967\":[\"\",1962,\" \"],\"1968\":[\"\",1963,\" \"],\"1969\":[\"\",1964,\" \"],\"1970\":[\"\",1965,\" \"],\"1971\":[\"\",1966,\" \"],\"1972\":[\"\",1967,\" \"],\"1973\":[\"\",1968,\" \"],\"1974\":[\"\",1969,\" \"],\"1975\":[\"\",1970,\" \"],\"1976\":[\"\",1971,\" \"],\"1977\":[\"\",1972,\" \"],\"1978\":[\"\",1973,\" \"],\"1979\":[\"\",1974,\" \"],\"1980\":[\"\",1975,\" \"],\"1981\":[\"\",1976,\" \"],\"1982\":[\"\",1977,\" \"],\"1983\":[\"\",1978,\" \"],\"1984\":[\"\",1979,\" \"],\"1985\":[\"\",1980,\" \"],\"1986\":[\"\",1981,\" \"],\"1987\":[\"\",1982,\" \"],\"1988\":[\"\",1983,\" \"],\"1989\":[\"\",1984,\" \"],\"1990\":[\"\",1985,\" \"],\"1991\":[\"\",1986,\" \"],\"1992\":[\"\",1987,\" \"],\"1993\":[\"\",1988,\" \"],\"1994\":[\"\",1989,\" \"],\"1995\":[\"\",1990,\" \"],\"1996\":[\"\",1991,\" \"],\"1997\":[\"\",1992,\" \"],\"1998\":[\"\",1993,\" \"],\"1999\":[\"\",1994,\" \"],\"2000\":[\"\",1995,\" \"],\"2001\":[\"\",1996,\" \"],\"2002\":[\"\",1997,\" \"],\"2003\":[\"\",1998,\" \"],\"2004\":[\"\",1999,\" \"],\"2005\":[\"\",2000,\" \"],\"2006\":[\"\",2001,\" \"],\"2007\":[\"\",2002,\" \"],\"2008\":[\"\",2003,\" \"],\"2009\":[\"\",2004,\" \"],\"2010\":[\"\",2005,\" \"],\"2011\":[\"\",2006,\" \"],\"2012\":[\"\",2007,\" \"],\"2013\":[\"\",2008,\" \"],\"2014\":[\"\",2009,\" \"],\"2015\":[\"\",2010,\" \"],\"2016\":[\"\",2011,\" \"],\"2017\":[\"\",2012,\" \"],\"2018\":[\"\",2013,\" \"],\"2019\":[\"\",2014,\" \"],\"2020\":[\"\",2015,\" \"],\"2021\":[\"\",2016,\" \"],\"2022\":[\"\",2017,\" \"],\"2023\":[\"\",2018,\" \"],\"2024\":[\"\",2019,\" \"],\"2025\":[\"\",2020,\" \"],\"2026\":[\"\",2021,\" \"],\"2027\":[\"\",2022,\" \"],\"2028\":[\"\",2023,\" \"],\"2029\":[\"\",2024,\" \"],\"2030\":[\"\",2025,\" \"],\"2031\":[\"\",2026,\" \"],\"2032\":[\"\",2027,\" \"],\"2033\":[\"\",2028,\" \"],\"2034\":[\"\",2029,\" \"],\"2035\":[\"\",2030,\" \"],\"2036\":[\"\",2031,\" \"],\"2037\":[\"\",2032,\" \"],\"2038\":[\"\",2033,\" \"],\"2039\":[\"\",2034,\" \"],\"2040\":[\"\",2035,\" \"],\"2041\":[\"\",2036,\" \"],\"2042\":[\"\",2037,\" \"],\"2043\":[\"\",2038,\" \"],\"2044\":[\"\",2039,\" \"],\"2045\":[\"\",2040,\" \"],\"2046\":[\"\",2041,\" \"],\"2047\":[\"\",2042,\" \"],\"2048\":[\"\",2043,\" \"],\"2049\":[\"\",2044,\" \"],\"2050\":[\"\",2045,\" \"],\"2051\":[\"\",2046,\" \"],\"2052\":[\"\",2047,\" \"],\"2053\":[\"\",2048,\" \"],\"2054\":[\"\",2049,\" \"],\"2055\":[\"\",2050,\" \"],\"2056\":[\"\",2051,\" \"],\"2057\":[\"\",2052,\" \"],\"2058\":[\"\",2053,\" \"],\"2059\":[\"\",2054,\" \"],\"2060\":[\"\",2055,\" \"],\"2061\":[\"\",2056,\" \"],\"2062\":[\"\",2057,\" \"],\"2063\":[\"\",2058,\" \"],\"2064\":[\"\",2059,\" \"],\"2065\":[\"\",2060,\" \"],\"2066\":[\"\",2061,\" \"],\"2067\":[\"\",2062,\" \"],\"2068\":[\"\",2063,\" \"],\"2069\":[\"\",2064,\" \"],\"2070\":[\"\",2065,\" \"],\"2071\":[\"\",2066,\" \"],\"2072\":[\"\",2067,\" \"],\"2073\":[\"\",2068,\" \"],\"2074\":[\"\",2069,\" \"],\"2075\":[\"\",2070,\" \"],\"2076\":[\"\",2071,\" \"],\"2077\":[\"\",2072,\" \"],\"2078\":[\"\",2073,\" \"],\"2079\":[\"\",2074,\" \"],\"2080\":[\"\",2075,\" \"],\"2081\":[\"\",2076,\" \"],\"2082\":[\"\",2077,\" \"],\"2083\":[\"\",2078,\" \"],\"2084\":[\"\",2079,\" \"],\"2085\":[\"\",2080,\" \"],\"2086\":[\"\",2081,\" \"],\"2087\":[\"\",2082,\" \"],\"2088\":[\"\",2083,\" \"],\"2089\":[\"\",2084,\" \"],\"2090\":[\"\",2085,\" \"],\"2091\":[\"\",2086,\" \"],\"2092\":[\"\",2087,\" \"],\"2093\":[\"\",2088,\" \"],\"2094\":[\"\",2089,\" \"],\"2095\":[\"\",2090,\" \"],\"2096\":[\"\",2091,\" \"],\"2097\":[\"\",2092,\" \"],\"2098\":[\"\",2093,\" \"],\"2099\":[\"\",2094,\" \"],\"2100\":[\"\",2095,\" \"],\"2101\":[\"\",2096,\" \"],\"2102\":[\"\",2097,\" \"],\"2103\":[\"\",2098,\" \"],\"2104\":[\"\",2099,\" \"],\"2105\":[\"\",2100,\" \"],\"2106\":[\"\",2101,\" \"],\"2107\":[\"\",2102,\" \"],\"2108\":[\"\",2103,\" \"],\"2109\":[\"\",2104,\" \"],\"2110\":[\"\",2105,\" \"],\"2111\":[\"\",2106,\" \"],\"2112\":[\"\",2107,\" \"],\"2113\":[\"\",2108,\" \"],\"2114\":[\"\",2109,\" \"],\"2115\":[\"\",2110,\" \"],\"2116\":[\"\",2111,\" \"],\"2117\":[\"\",2112,\" \"],\"2118\":[\"\",2113,\" \"],\"2119\":[\"\",2114,\" \"],\"2120\":[\"\",2115,\" \"],\"2121\":[\"\",2116,\" \"],\"2122\":[\"\",2117,\" \"],\"2123\":[\"\",2118,\" \"],\"2124\":[\"\",2119,\" \"],\"2125\":[\"\",2120,\" \"],\"2126\":[\"\",2121,\" \"],\"2127\":[\"\",2122,\" \"],\"2128\":[\"\",2123,\" \"],\"2129\":[\"\",2124,\" \"],\"2130\":[\"\",2125,\" \"],\"2131\":[\"\",2126,\" \"],\"2132\":[\"\",2127,\" \"],\"2133\":[\"\",2128,\" \"],\"2134\":[\"\",2129,\" \"],\"2135\":[\"\",2130,\" \"],\"2136\":[\"\",2131,\" \"],\"2137\":[\"\",2132,\" \"],\"2138\":[\"\",2133,\" \"],\"2139\":[\"\",2134,\" \"],\"2140\":[\"\",2135,\" \"],\"2141\":[\"\",2136,\" \"],\"2142\":[\"\",2137,\" \"],\"2143\":[\"\",2138,\" \"],\"2144\":[\"\",2139,\" \"],\"2145\":[\"\",2140,\" \"],\"2146\":[\"\",2141,\" \"],\"2147\":[\"\",2142,\" \"],\"2148\":[\"\",2143,\" \"],\"2149\":[\"\",2144,\" \"],\"2150\":[\"\",2145,\" \"],\"2151\":[\"\",2146,\" \"],\"2152\":[\"\",2147,\" \"],\"2153\":[\"\",2148,\" \"],\"2154\":[\"\",2149,\" \"],\"2155\":[\"\",2150,\" \"],\"2156\":[\"\",2151,\" \"],\"2157\":[\"\",2152,\" \"],\"2158\":[\"\",2153,\" \"],\"2159\":[\"\",2154,\" \"],\"2160\":[\"\",2155,\" \"],\"2161\":[\"\",2156,\" \"],\"2162\":[\"\",2157,\" \"],\"2163\":[\"\",2158,\" \"],\"2164\":[\"\",2159,\" \"],\"2165\":[\"\",2160,\" \"],\"2166\":[\"\",2161,\" \"],\"2167\":[\"\",2162,\" \"],\"2168\":[\"\",2163,\" \"],\"2169\":[\"\",2164,\" \"],\"2170\":[\"\",2165,\" \"],\"2171\":[\"\",2166,\" \"],\"2172\":[\"\",2167,\" \"],\"2173\":[\"\",2168,\" \"],\"2174\":[\"\",2169,\" \"],\"2175\":[\"\",2170,\" \"],\"2176\":[\"\",2171,\" \"],\"2177\":[\"\",2172,\" \"],\"2178\":[\"\",2173,\" \"],\"2179\":[\"\",2174,\" \"],\"2180\":[\"\",2175,\" \"],\"2181\":[\"\",2176,\" \"],\"2182\":[\"\",2177,\" \"],\"2183\":[\"\",2178,\" \"],\"2184\":[\"\",2179,\" \"],\"2185\":[\"\",2180,\" \"],\"2186\":[\"\",2181,\" \"],\"2187\":[\"\",2182,\" \"],\"2188\":[\"\",2183,\" \"],\"2189\":[\"\",2184,\" \"],\"2190\":[\"\",2185,\" \"],\"2191\":[\"\",2186,\" \"],\"2192\":[\"\",2187,\" \"],\"2193\":[\"\",2188,\" \"],\"2194\":[\"\",2189,\" \"],\"2195\":[\"\",2190,\" \"],\"2196\":[\"\",2191,\" \"],\"2197\":[\"\",2192,\" \"],\"2198\":[\"\",2193,\" \"],\"2199\":[\"\",2194,\" \"],\"2200\":[\"\",2195,\" \"],\"2201\":[\"\",2196,\" \"],\"2202\":[\"\",2197,\" \"],\"2203\":[\"\",2198,\" \"],\"2204\":[\"\",2199,\" \"],\"2205\":[\"\",2200,\" \"],\"2206\":[\"\",2201,\" \"],\"2207\":[\"\",2202,\" \"],\"2208\":[\"\",2203,\" \"],\"2209\":[\"\",2204,\" \"],\"2210\":[\"\",2205,\" \"],\"2211\":[\"\",2206,\" \"],\"2212\":[\"\",2207,\" \"],\"2213\":[\"\",2208,\" \"],\"2214\":[\"\",2209,\" \"],\"2215\":[\"\",2210,\" \"],\"2216\":[\"\",2211,\" \"],\"2217\":[\"\",2212,\" \"],\"2218\":[\"\",2213,\" \"],\"2219\":[\"\",2214,\" \"],\"2220\":[\"\",2215,\" \"],\"2221\":[\"\",2216,\" \"],\"2222\":[\"\",2217,\" \"],\"2223\":[\"\",2218,\" \"],\"2224\":[\"\",2219,\" \"],\"2225\":[\"\",2220,\" \"],\"2226\":[\"\",2221,\" \"],\"2227\":[\"\",2222,\" \"],\"2228\":[\"\",2223,\" \"],\"2229\":[\"\",2224,\" \"],\"2230\":[\"\",2225,\" \"],\"2231\":[\"\",2226,\" \"],\"2232\":[\"\",2227,\" \"],\"2233\":[\"\",2228,\" \"],\"2234\":[\"\",2229,\" \"],\"2235\":[\"\",2230,\" \"],\"2236\":[\"\",2231,\" \"],\"2237\":[\"\",2232,\" \"],\"2238\":[\"\",2233,\" \"],\"2239\":[\"\",2234,\" \"],\"2240\":[\"\",2235,\" \"],\"2241\":[\"\",2236,\" \"],\"2242\":[\"\",2237,\" \"],\"2243\":[\"\",2238,\" \"],\"2244\":[\"\",2239,\" \"],\"2245\":[\"\",2240,\" \"],\"2246\":[\"\",2241,\" \"],\"2247\":[\"\",2242,\" \"],\"2248\":[\"\",2243,\" \"],\"2249\":[\"\",2244,\" \"],\"2250\":[\"\",2245,\" \"],\"2251\":[\"\",2246,\" \"],\"2252\":[\"\",2247,\" \"],\"2253\":[\"\",2248,\" \"],\"2254\":[\"\",2249,\" \"],\"2255\":[\"\",2250,\" \"],\"2256\":[\"\",2251,\" \"],\"2257\":[\"\",2252,\" \"],\"2258\":[\"\",2253,\" \"],\"2259\":[\"\",2254,\" \"],\"2260\":[\"\",2255,\" \"],\"2261\":[\"\",2256,\" \"],\"2262\":[\"\",2257,\" \"],\"2263\":[\"\",2258,\" \"],\"2264\":[\"\",2259,\" \"],\"2265\":[\"\",2260,\" \"],\"2266\":[\"\",2261,\" \"],\"2267\":[\"\",2262,\" \"],\"2268\":[\"\",2263,\" \"],\"2269\":[\"\",2264,\" \"],\"2270\":[\"\",2265,\" \"],\"2271\":[\"\",2266,\" \"],\"2272\":[\"\",2267,\" \"],\"2273\":[\"\",2268,\" \"],\"2274\":[\"\",2269,\" \"],\"2275\":[\"\",2270,\" \"],\"2276\":[\"\",2271,\" \"],\"2277\":[\"\",2272,\" \"],\"2278\":[\"\",2273,\" \"],\"2279\":[\"\",2274,\" \"],\"2280\":[\"\",2275,\" \"],\"2281\":[\"\",2276,\" \"],\"2282\":[\"\",2277,\" \"],\"2283\":[\"\",2278,\" \"],\"2284\":[\"\",2279,\" \"],\"2285\":[\"\",2280,\" \"],\"2286\":[\"\",2281,\" \"],\"2287\":[\"\",2282,\" \"],\"2288\":[\"\",2283,\" \"],\"2289\":[\"\",2284,\" \"],\"2290\":[\"\",2285,\" \"],\"2291\":[\"\",2286,\" \"],\"2292\":[\"\",2287,\" \"],\"2293\":[\"\",2288,\" \"],\"2294\":[\"\",2289,\" \"],\"2295\":[\"\",2290,\" \"],\"2296\":[\"\",2291,\" \"],\"2297\":[\"\",2292,\" \"],\"2298\":[\"\",2293,\" \"],\"2299\":[\"\",2294,\" \"],\"2300\":[\"\",2295,\" \"],\"2301\":[\"\",2296,\" \"],\"2302\":[\"\",2297,\" \"],\"2303\":[\"\",2298,\" \"],\"2304\":[\"\",2299,\" \"],\"2305\":[\"\",2300,\" \"],\"2306\":[\"\",2301,\" \"],\"2307\":[\"\",2302,\" \"],\"2308\":[\"\",2303,\" \"],\"2309\":[\"\",2304,\" \"],\"2310\":[\"\",2305,\" \"],\"2311\":[\"\",2306,\" \"],\"2312\":[\"\",2307,\" \"],\"2313\":[\"\",2308,\" \"],\"2314\":[\"\",2309,\" \"],\"2315\":[\"\",2310,\" \"],\"2316\":[\"\",2311,\" \"],\"2317\":[\"\",2312,\" \"],\"2318\":[\"\",2313,\" \"],\"2319\":[\"\",2314,\" \"],\"2320\":[\"\",2315,\" \"],\"2321\":[\"\",2316,\" \"],\"2322\":[\"\",2317,\" \"],\"2323\":[\"\",2318,\" \"],\"2324\":[\"\",2319,\" \"],\"2325\":[\"\",2320,\" \"],\"2326\":[\"\",2321,\" \"],\"2327\":[\"\",2322,\" \"],\"2328\":[\"\",2323,\" \"],\"2329\":[\"\",2324,\" \"],\"2330\":[\"\",2325,\" \"],\"2331\":[\"\",2326,\" \"],\"2332\":[\"\",2327,\" \"],\"2333\":[\"\",2328,\" \"],\"2334\":[\"\",2329,\" \"],\"2335\":[\"\",2330,\" \"],\"2336\":[\"\",2331,\" \"],\"2337\":[\"\",2332,\" \"],\"2338\":[\"\",2333,\" \"],\"2339\":[\"\",2334,\" \"],\"2340\":[\"\",2335,\" \"],\"2341\":[\"\",2336,\" \"],\"2342\":[\"\",2337,\" \"],\"2343\":[\"\",2338,\" \"],\"2344\":[\"\",2339,\" \"],\"2345\":[\"\",2340,\" \"],\"2346\":[\"\",2341,\" \"],\"2347\":[\"\",2342,\" \"],\"2348\":[\"\",2343,\" \"],\"2349\":[\"\",2344,\" \"],\"2350\":[\"\",2345,\" \"],\"2351\":[\"\",2346,\" \"],\"2352\":[\"\",2347,\" \"],\"2353\":[\"\",2348,\" \"],\"2354\":[\"\",2349,\" \"],\"2355\":[\"\",2350,\" \"],\"2356\":[\"\",2351,\" \"],\"2357\":[\"\",2352,\" \"],\"2358\":[\"\",2353,\" \"],\"2359\":[\"\",2354,\" \"],\"2360\":[\"\",2355,\" \"],\"2361\":[\"\",2356,\" \"],\"2362\":[\"\",2357,\" \"],\"2363\":[\"\",2358,\" \"],\"2364\":[\"\",2359,\" \"],\"2365\":[\"\",2360,\" \"],\"2366\":[\"\",2361,\" \"],\"2367\":[\"\",2362,\" \"],\"2368\":[\"\",2363,\" \"],\"2369\":[\"\",2364,\" \"],\"2370\":[\"\",2365,\" \"],\"2371\":[\"\",2366,\" \"],\"2372\":[\"\",2367,\" \"],\"2373\":[\"\",2368,\" \"],\"2374\":[\"\",2369,\" \"],\"2375\":[\"\",2370,\" \"],\"2376\":[\"\",2371,\" \"],\"2377\":[\"\",2372,\" \"],\"2378\":[\"\",2373,\" \"],\"2379\":[\"\",2374,\" \"],\"2380\":[\"\",2375,\" \"],\"2381\":[\"\",2376,\" \"],\"2382\":[\"\",2377,\" \"],\"2383\":[\"\",2378,\" \"],\"2384\":[\"\",2379,\" \"],\"2385\":[\"\",2380,\" \"],\"2386\":[\"\",2381,\" \"],\"2387\":[\"\",2382,\" \"],\"2388\":[\"\",2383,\" \"],\"2389\":[\"\",2384,\" \"],\"2390\":[\"\",2385,\" \"],\"2391\":[\"\",2386,\" \"],\"2392\":[\"\",2387,\" \"],\"2393\":[\"\",2388,\" \"],\"2394\":[\"\",2389,\" \"],\"2395\":[\"\",2390,\" \"],\"2396\":[\"\",2391,\" \"],\"2397\":[\"\",2392,\" \"],\"2398\":[\"\",2393,\" \"],\"2399\":[\"\",2394,\" \"],\"2400\":[\"\",2395,\" \"],\"2401\":[\"\",2396,\" \"],\"2402\":[\"\",2397,\" \"],\"2403\":[\"\",2398,\" \"],\"2404\":[\"\",2399,\" \"],\"2405\":[\"\",2400,\" \"],\"2406\":[\"\",2401,\" \"],\"2407\":[\"\",2402,\" \"],\"2408\":[\"\",2403,\" \"],\"2409\":[\"\",2404,\" \"],\"2410\":[\"\",2405,\" \"],\"2411\":[\"\",2406,\" \"],\"2412\":[\"\",2407,\" \"],\"2413\":[\"\",2408,\" \"],\"2414\":[\"\",2409,\" \"],\"2415\":[\"\",2410,\" \"],\"2416\":[\"\",2411,\" \"],\"2417\":[\"\",2412,\" \"],\"2418\":[\"\",2413,\" \"],\"2419\":[\"\",2414,\" \"],\"2420\":[\"\",2415,\" \"],\"2421\":[\"\",2416,\" \"],\"2422\":[\"\",2417,\" \"],\"2423\":[\"\",2418,\" \"],\"2424\":[\"\",2419,\" \"],\"2425\":[\"\",2420,\" \"],\"2426\":[\"\",2421,\" \"],\"2427\":[\"\",2422,\" \"],\"2428\":[\"\",2423,\" \"],\"2429\":[\"\",2424,\" \"],\"2430\":[\"\",2425,\" \"],\"2431\":[\"\",2426,\" \"],\"2432\":[\"\",2427,\" \"],\"2433\":[\"\",2428,\" \"],\"2434\":[\"\",2429,\" \"],\"2435\":[\"\",2430,\" \"],\"2436\":[\"\",2431,\" \"],\"2437\":[\"\",2432,\" \"],\"2438\":[\"\",2433,\" \"],\"2439\":[\"\",2434,\" \"],\"2440\":[\"\",2435,\" \"],\"2441\":[\"\",2436,\" \"],\"2442\":[\"\",2437,\" \"],\"2443\":[\"\",2438,\" \"],\"2444\":[\"\",2439,\" \"],\"2445\":[\"\",2440,\" \"],\"2446\":[\"\",2441,\" \"],\"2447\":[\"\",2442,\" \"],\"2448\":[\"\",2443,\" \"],\"2449\":[\"\",2444,\" \"],\"2450\":[\"\",2445,\" \"],\"2451\":[\"\",2446,\" \"],\"2452\":[\"\",2447,\" \"],\"2453\":[\"\",2448,\" \"],\"2454\":[\"\",2449,\" \"],\"2455\":[\"\",2450,\" \"],\"2456\":[\"\",2451,\" \"],\"2457\":[\"\",2452,\" \"],\"2458\":[\"\",2453,\" \"],\"2459\":[\"\",2454,\" \"],\"2460\":[\"\",2455,\" \"],\"2461\":[\"\",2456,\" \"],\"2462\":[\"\",2457,\" \"],\"2463\":[\"\",2458,\" \"],\"2464\":[\"\",2459,\" \"],\"2465\":[\"\",2460,\" \"],\"2466\":[\"\",2461,\" \"],\"2467\":[\"\",2462,\" \"],\"2468\":[\"\",2463,\" \"],\"2469\":[\"\",2464,\" \"],\"2470\":[\"\",2465,\" \"],\"2471\":[\"\",2466,\" \"],\"2472\":[\"\",2467,\" \"],\"2473\":[\"\",2468,\" \"],\"2474\":[\"\",2469,\" \"],\"2475\":[\"\",2470,\" \"],\"2476\":[\"\",2471,\" \"],\"2477\":[\"\",2472,\" \"],\"2478\":[\"\",2473,\" \"],\"2479\":[\"\",2474,\" \"],\"2480\":[\"\",2475,\" \"],\"2481\":[\"\",2476,\" \"],\"2482\":[\"\",2477,\" \"],\"2483\":[\"\",2478,\" \"],\"2484\":[\"\",2479,\" \"],\"2485\":[\"\",2480,\" \"],\"2486\":[\"\",2481,\" \"],\"2487\":[\"\",2482,\" \"],\"2488\":[\"\",2483,\" \"],\"2489\":[\"\",2484,\" \"],\"2490\":[\"\",2485,\" \"],\"2491\":[\"\",2486,\" \"],\"2492\":[\"\",2487,\" \"],\"2493\":[\"\",2488,\" \"],\"2494\":[\"\",2489,\" \"],\"2495\":[\"\",2490,\" \"],\"2496\":[\"\",2491,\" \"],\"2497\":[\"\",2492,\" \"],\"2498\":[\"\",2493,\" \"],\"2499\":[\"\",2494,\" \"],\"2500\":[\"\",2495,\" \"],\"2501\":[\"\",2496,\" \"],\"2502\":[\"\",2497,\" \"],\"2503\":[\"\",2498,\" \"],\"2504\":[\"\",2499,\" \"],\"2505\":[\"\",2500,\" \"],\"2506\":[\"\",2501,\" \"],\"2507\":[\"\",2502,\" \"],\"2508\":[\"\",2503,\" \"],\"2509\":[\"\",2504,\" \"],\"2510\":[\"\",2505,\" \"],\"2511\":[\"\",2506,\" \"],\"2512\":[\"\",2507,\" \"],\"2513\":[\"\",2508,\" \"],\"2514\":[\"\",2509,\" \"],\"2515\":[\"\",2510,\" \"],\"2516\":[\"\",2511,\" \"],\"2517\":[\"\",2512,\" \"],\"2518\":[\"\",2513,\" \"],\"2519\":[\"\",2514,\" \"],\"2520\":[\"\",2515,\" \"],\"2521\":[\"\",2516,\" \"],\"2522\":[\"\",2517,\" \"],\"2523\":[\"\",2518,\" \"],\"2524\":[\"\",2519,\" \"],\"2525\":[\"\",2520,\" \"],\"2526\":[\"\",2521,\" \"],\"2527\":[\"\",2522,\" \"],\"2528\":[\"\",2523,\" \"],\"2529\":[\"\",2524,\" \"],\"2530\":[\"\",2525,\" \"],\"2531\":[\"\",2526,\" \"],\"2532\":[\"\",2527,\" \"],\"2533\":[\"\",2528,\" \"],\"2534\":[\"\",2529,\" \"],\"2535\":[\"\",2530,\" \"],\"2536\":[\"\",2531,\" \"],\"2537\":[\"\",2532,\" \"],\"2538\":[\"\",2533,\" \"],\"2539\":[\"\",2534,\" \"],\"2540\":[\"\",2535,\" \"],\"2541\":[\"\",2536,\" \"],\"2542\":[\"\",2537,\" \"],\"2543\":[\"\",2538,\" \"],\"2544\":[\"\",2539,\" \"],\"2545\":[\"\",2540,\" \"],\"2546\":[\"\",2541,\" \"],\"2547\":[\"\",2542,\" \"],\"2548\":[\"\",2543,\" \"],\"2549\":[\"\",2544,\" \"],\"2550\":[\"\",2545,\" \"],\"2551\":[\"\",2546,\" \"],\"2552\":[\"\",2547,\" \"],\"2553\":[\"\",2548,\" \"],\"2554\":[\"\",2549,\" \"],\"2555\":[\"\",2550,\" \"],\"2556\":[\"\",2551,\" \"],\"2557\":[\"\",2552,\" \"],\"2558\":[\"\",2553,\" \"],\"2559\":[\"\",2554,\" \"],\"2560\":[\"\",2555,\" \"],\"2561\":[\"\",2556,\" \"],\"2562\":[\"\",2557,\" \"],\"2563\":[\"\",2558,\" \"],\"2564\":[\"\",2559,\" \"],\"2565\":[\"\",2560,\" \"],\"2566\":[\"\",2561,\" \"],\"2567\":[\"\",2562,\" \"],\"2568\":[\"\",2563,\" \"],\"2569\":[\"\",2564,\" \"],\"2570\":[\"\",2565,\" \"],\"2571\":[\"\",2566,\" \"],\"2572\":[\"\",2567,\" \"],\"2573\":[\"\",2568,\" \"],\"2574\":[\"\",2569,\" \"],\"2575\":[\"\",2570,\" \"],\"2576\":[\"\",2571,\" \"],\"2577\":[\"\",2572,\" \"],\"2578\":[\"\",2573,\" \"],\"2579\":[\"\",2574,\" \"],\"2580\":[\"\",2575,\" \"],\"2581\":[\"\",2576,\" \"],\"2582\":[\"\",2577,\" \"],\"2583\":[\"\",2578,\" \"],\"2584\":[\"\",2579,\" \"],\"2585\":[\"\",2580,\" \"],\"2586\":[\"\",2581,\" \"],\"2587\":[\"\",2582,\" \"],\"2588\":[\"\",2583,\" \"],\"2589\":[\"\",2584,\" \"],\"2590\":[\"\",2585,\" \"],\"2591\":[\"\",2586,\" \"],\"2592\":[\"\",2587,\" \"],\"2593\":[\"\",2588,\" \"],\"2594\":[\"\",2589,\" \"],\"2595\":[\"\",2590,\" \"],\"2596\":[\"\",2591,\" \"],\"2597\":[\"\",2592,\" \"],\"2598\":[\"\",2593,\" \"],\"2599\":[\"\",2594,\" \"],\"2600\":[\"\",2595,\" \"],\"2601\":[\"\",2596,\" \"],\"2602\":[\"\",2597,\" \"],\"2603\":[\"\",2598,\" \"],\"2604\":[\"\",2599,\" \"],\"2605\":[\"\",2600,\" \"],\"2606\":[\"\",2601,\" \"],\"2607\":[\"\",2602,\" \"],\"2608\":[\"\",2603,\" \"],\"2609\":[\"\",2604,\" \"],\"2610\":[\"\",2605,\" \"],\"2611\":[\"\",2606,\" \"],\"2612\":[\"\",2607,\" \"],\"2613\":[\"\",2608,\" \"],\"2614\":[\"\",2609,\" \"],\"2615\":[\"\",2610,\" \"],\"2616\":[\"\",2611,\" \"],\"2617\":[\"\",2612,\" \"],\"2618\":[\"\",2613,\" \"],\"2619\":[\"\",2614,\" \"],\"2620\":[\"\",2615,\" \"],\"2621\":[\"\",2616,\" \"],\"2622\":[\"\",2617,\" \"],\"2623\":[\"\",2618,\" \"],\"2624\":[\"\",2619,\" \"],\"2625\":[\"\",2620,\" \"],\"2626\":[\"\",2621,\" \"],\"2627\":[\"\",2622,\" \"],\"2628\":[\"\",2623,\" \"],\"2629\":[\"\",2624,\" \"],\"2630\":[\"\",2625,\" \"],\"2631\":[\"\",2626,\" \"],\"2632\":[\"\",2627,\" \"],\"2633\":[\"\",2628,\" \"],\"2634\":[\"\",2629,\" \"],\"2635\":[\"\",2630,\" \"],\"2636\":[\"\",2631,\" \"],\"2637\":[\"\",2632,\" \"],\"2638\":[\"\",2633,\" \"],\"2639\":[\"\",2634,\" \"],\"2640\":[\"\",2635,\" \"],\"2641\":[\"\",2636,\" \"],\"2642\":[\"\",2637,\" \"],\"2643\":[\"\",2638,\" \"],\"2644\":[\"\",2639,\" \"],\"2645\":[\"\",2640,\" \"],\"2646\":[\"\",2641,\" \"],\"2647\":[\"\",2642,\" \"],\"2648\":[\"\",2643,\" \"],\"2649\":[\"\",2644,\" \"]}},\"oldPath\":\"mercurial\\/exchange.py\",\"currentPath\":\"mercurial\\/exchange.py\",\"awayPaths\":[],\"oldProperties\":[],\"newProperties\":[],\"type\":\"2\",\"fileType\":\"1\",\"commitHash\":null,\"addLines\":\"16\",\"delLines\":\"11\",\"hunks\":[{\"oldOffset\":\"1\",\"newOffset\":\"1\",\"oldLength\":\"2644\",\"newLength\":\"2649\",\"addLines\":null,\"delLines\":null,\"isMissingOldNewline\":null,\"isMissingNewNewline\":null,\"corpus\":\" # exchange.py - utility to exchange data between repos.\\n #\\n # Copyright 2005-2007 Matt Mackall \\u003cmpm@selenic.com\\u003e\\n #\\n # This software may be used and distributed according to the terms of the\\n # GNU General Public License version 2 or any later version.\\n \\n from __future__ import absolute_import\\n \\n import collections\\n import hashlib\\n \\n from .i18n import _\\n from .node import (\\n     bin,\\n     hex,\\n     nullid,\\n     nullrev,\\n )\\n from .thirdparty import (\\n     attr,\\n )\\n from . import (\\n     bookmarks as bookmod,\\n     bundle2,\\n     changegroup,\\n     discovery,\\n     error,\\n+    exchangev2,\\n     lock as lockmod,\\n     logexchange,\\n     narrowspec,\\n     obsolete,\\n     phases,\\n     pushkey,\\n     pycompat,\\n     repository,\\n     scmutil,\\n     sslutil,\\n     streamclone,\\n     url as urlmod,\\n     util,\\n )\\n from .utils import (\\n     stringutil,\\n )\\n \\n urlerr = util.urlerr\\n urlreq = util.urlreq\\n \\n _NARROWACL_SECTION = 'narrowhgacl'\\n \\n # Maps bundle version human names to changegroup versions.\\n _bundlespeccgversions = {'v1': '01',\\n                          'v2': '02',\\n                          'packed1': 's1',\\n                          'bundle2': '02', #legacy\\n                         }\\n \\n # Maps bundle version with content opts to choose which part to bundle\\n _bundlespeccontentopts = {\\n     'v1': {\\n         'changegroup': True,\\n         'cg.version': '01',\\n         'obsolescence': False,\\n         'phases': False,\\n         'tagsfnodescache': False,\\n         'revbranchcache': False\\n     },\\n     'v2': {\\n         'changegroup': True,\\n         'cg.version': '02',\\n         'obsolescence': False,\\n         'phases': False,\\n         'tagsfnodescache': True,\\n         'revbranchcache': True\\n     },\\n     'packed1' : {\\n         'cg.version': 's1'\\n     }\\n }\\n _bundlespeccontentopts['bundle2'] = _bundlespeccontentopts['v2']\\n \\n _bundlespecvariants = {\\\"streamv2\\\": {\\\"changegroup\\\": False, \\\"streamv2\\\": True,\\n                                     \\\"tagsfnodescache\\\": False,\\n                                     \\\"revbranchcache\\\": False}}\\n \\n # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.\\n _bundlespecv1compengines = {'gzip', 'bzip2', 'none'}\\n \\n @attr.s\\n class bundlespec(object):\\n     compression = attr.ib()\\n     wirecompression = attr.ib()\\n     version = attr.ib()\\n     wireversion = attr.ib()\\n     params = attr.ib()\\n     contentopts = attr.ib()\\n \\n def parsebundlespec(repo, spec, strict=True):\\n     \\\"\\\"\\\"Parse a bundle string specification into parts.\\n \\n     Bundle specifications denote a well-defined bundle\\/exchange format.\\n     The content of a given specification should not change over time in\\n     order to ensure that bundles produced by a newer version of Mercurial are\\n     readable from an older version.\\n \\n     The string currently has the form:\\n \\n        \\u003ccompression\\u003e-\\u003ctype\\u003e[;\\u003cparameter0\\u003e[;\\u003cparameter1\\u003e]]\\n \\n     Where \\u003ccompression\\u003e is one of the supported compression formats\\n     and \\u003ctype\\u003e is (currently) a version string. A \\\";\\\" can follow the type and\\n     all text afterwards is interpreted as URI encoded, \\\";\\\" delimited key=value\\n     pairs.\\n \\n     If ``strict`` is True (the default) \\u003ccompression\\u003e is required. Otherwise,\\n     it is optional.\\n \\n     Returns a bundlespec object of (compression, version, parameters).\\n     Compression will be ``None`` if not in strict mode and a compression isn't\\n     defined.\\n \\n     An ``InvalidBundleSpecification`` is raised when the specification is\\n     not syntactically well formed.\\n \\n     An ``UnsupportedBundleSpecification`` is raised when the compression or\\n     bundle type\\/version is not recognized.\\n \\n     Note: this function will likely eventually return a more complex data\\n     structure, including bundle2 part information.\\n     \\\"\\\"\\\"\\n     def parseparams(s):\\n         if ';' not in s:\\n             return s, {}\\n \\n         params = {}\\n         version, paramstr = s.split(';', 1)\\n \\n         for p in paramstr.split(';'):\\n             if '=' not in p:\\n                 raise error.InvalidBundleSpecification(\\n                     _('invalid bundle specification: '\\n                       'missing \\\"=\\\" in parameter: %s') % p)\\n \\n             key, value = p.split('=', 1)\\n             key = urlreq.unquote(key)\\n             value = urlreq.unquote(value)\\n             params[key] = value\\n \\n         return version, params\\n \\n \\n     if strict and '-' not in spec:\\n         raise error.InvalidBundleSpecification(\\n                 _('invalid bundle specification; '\\n                   'must be prefixed with compression: %s') % spec)\\n \\n     if '-' in spec:\\n         compression, version = spec.split('-', 1)\\n \\n         if compression not in util.compengines.supportedbundlenames:\\n             raise error.UnsupportedBundleSpecification(\\n                     _('%s compression is not supported') % compression)\\n \\n         version, params = parseparams(version)\\n \\n         if version not in _bundlespeccgversions:\\n             raise error.UnsupportedBundleSpecification(\\n                     _('%s is not a recognized bundle version') % version)\\n     else:\\n         # Value could be just the compression or just the version, in which\\n         # case some defaults are assumed (but only when not in strict mode).\\n         assert not strict\\n \\n         spec, params = parseparams(spec)\\n \\n         if spec in util.compengines.supportedbundlenames:\\n             compression = spec\\n             version = 'v1'\\n             # Generaldelta repos require v2.\\n             if 'generaldelta' in repo.requirements:\\n                 version = 'v2'\\n             # Modern compression engines require v2.\\n             if compression not in _bundlespecv1compengines:\\n                 version = 'v2'\\n         elif spec in _bundlespeccgversions:\\n             if spec == 'packed1':\\n                 compression = 'none'\\n             else:\\n                 compression = 'bzip2'\\n             version = spec\\n         else:\\n             raise error.UnsupportedBundleSpecification(\\n                     _('%s is not a recognized bundle specification') % spec)\\n \\n     # Bundle version 1 only supports a known set of compression engines.\\n     if version == 'v1' and compression not in _bundlespecv1compengines:\\n         raise error.UnsupportedBundleSpecification(\\n             _('compression engine %s is not supported on v1 bundles') %\\n             compression)\\n \\n     # The specification for packed1 can optionally declare the data formats\\n     # required to apply it. If we see this metadata, compare against what the\\n     # repo supports and error if the bundle isn't compatible.\\n     if version == 'packed1' and 'requirements' in params:\\n         requirements = set(params['requirements'].split(','))\\n         missingreqs = requirements - repo.supportedformats\\n         if missingreqs:\\n             raise error.UnsupportedBundleSpecification(\\n                     _('missing support for repository features: %s') %\\n                       ', '.join(sorted(missingreqs)))\\n \\n     # Compute contentopts based on the version\\n     contentopts = _bundlespeccontentopts.get(version, {}).copy()\\n \\n     # Process the variants\\n     if \\\"stream\\\" in params and params[\\\"stream\\\"] == \\\"v2\\\":\\n         variant = _bundlespecvariants[\\\"streamv2\\\"]\\n         contentopts.update(variant)\\n \\n     engine = util.compengines.forbundlename(compression)\\n     compression, wirecompression = engine.bundletype()\\n     wireversion = _bundlespeccgversions[version]\\n \\n     return bundlespec(compression, wirecompression, version, wireversion,\\n                       params, contentopts)\\n \\n def readbundle(ui, fh, fname, vfs=None):\\n     header = changegroup.readexactly(fh, 4)\\n \\n     alg = None\\n     if not fname:\\n         fname = \\\"stream\\\"\\n         if not header.startswith('HG') and header.startswith('\\\\0'):\\n             fh = changegroup.headerlessfixup(fh, header)\\n             header = \\\"HG10\\\"\\n             alg = 'UN'\\n     elif vfs:\\n         fname = vfs.join(fname)\\n \\n     magic, version = header[0:2], header[2:4]\\n \\n     if magic != 'HG':\\n         raise error.Abort(_('%s: not a Mercurial bundle') % fname)\\n     if version == '10':\\n         if alg is None:\\n             alg = changegroup.readexactly(fh, 2)\\n         return changegroup.cg1unpacker(fh, alg)\\n     elif version.startswith('2'):\\n         return bundle2.getunbundler(ui, fh, magicstring=magic + version)\\n     elif version == 'S1':\\n         return streamclone.streamcloneapplier(fh)\\n     else:\\n         raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))\\n \\n def getbundlespec(ui, fh):\\n     \\\"\\\"\\\"Infer the bundlespec from a bundle file handle.\\n \\n     The input file handle is seeked and the original seek position is not\\n     restored.\\n     \\\"\\\"\\\"\\n     def speccompression(alg):\\n         try:\\n             return util.compengines.forbundletype(alg).bundletype()[0]\\n         except KeyError:\\n             return None\\n \\n     b = readbundle(ui, fh, None)\\n     if isinstance(b, changegroup.cg1unpacker):\\n         alg = b._type\\n         if alg == '_truncatedBZ':\\n             alg = 'BZ'\\n         comp = speccompression(alg)\\n         if not comp:\\n             raise error.Abort(_('unknown compression algorithm: %s') % alg)\\n         return '%s-v1' % comp\\n     elif isinstance(b, bundle2.unbundle20):\\n         if 'Compression' in b.params:\\n             comp = speccompression(b.params['Compression'])\\n             if not comp:\\n                 raise error.Abort(_('unknown compression algorithm: %s') % comp)\\n         else:\\n             comp = 'none'\\n \\n         version = None\\n         for part in b.iterparts():\\n             if part.type == 'changegroup':\\n                 version = part.params['version']\\n                 if version in ('01', '02'):\\n                     version = 'v2'\\n                 else:\\n                     raise error.Abort(_('changegroup version %s does not have '\\n                                         'a known bundlespec') % version,\\n                                       hint=_('try upgrading your Mercurial '\\n                                               'client'))\\n             elif part.type == 'stream2' and version is None:\\n                 # A stream2 part requires to be part of a v2 bundle\\n                 version = \\\"v2\\\"\\n                 requirements = urlreq.unquote(part.params['requirements'])\\n                 splitted = requirements.split()\\n                 params = bundle2._formatrequirementsparams(splitted)\\n                 return 'none-v2;stream=v2;%s' % params\\n \\n         if not version:\\n             raise error.Abort(_('could not identify changegroup version in '\\n                                 'bundle'))\\n \\n         return '%s-%s' % (comp, version)\\n     elif isinstance(b, streamclone.streamcloneapplier):\\n         requirements = streamclone.readbundle1header(fh)[2]\\n         formatted = bundle2._formatrequirementsparams(requirements)\\n         return 'none-packed1;%s' % formatted\\n     else:\\n         raise error.Abort(_('unknown bundle type: %s') % b)\\n \\n def _computeoutgoing(repo, heads, common):\\n     \\\"\\\"\\\"Computes which revs are outgoing given a set of common\\n     and a set of heads.\\n \\n     This is a separate function so extensions can have access to\\n     the logic.\\n \\n     Returns a discovery.outgoing object.\\n     \\\"\\\"\\\"\\n     cl = repo.changelog\\n     if common:\\n         hasnode = cl.hasnode\\n         common = [n for n in common if hasnode(n)]\\n     else:\\n         common = [nullid]\\n     if not heads:\\n         heads = cl.heads()\\n     return discovery.outgoing(repo, common, heads)\\n \\n def _forcebundle1(op):\\n     \\\"\\\"\\\"return true if a pull\\/push must use bundle1\\n \\n     This function is used to allow testing of the older bundle version\\\"\\\"\\\"\\n     ui = op.repo.ui\\n     # The goal is this config is to allow developer to choose the bundle\\n     # version used during exchanged. This is especially handy during test.\\n     # Value is a list of bundle version to be picked from, highest version\\n     # should be used.\\n     #\\n     # developer config: devel.legacy.exchange\\n     exchange = ui.configlist('devel', 'legacy.exchange')\\n     forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange\\n     return forcebundle1 or not op.remote.capable('bundle2')\\n \\n class pushoperation(object):\\n     \\\"\\\"\\\"A object that represent a single push operation\\n \\n     Its purpose is to carry push related state and very common operations.\\n \\n     A new pushoperation should be created at the beginning of each push and\\n     discarded afterward.\\n     \\\"\\\"\\\"\\n \\n     def __init__(self, repo, remote, force=False, revs=None, newbranch=False,\\n                  bookmarks=(), pushvars=None):\\n         # repo we push from\\n         self.repo = repo\\n         self.ui = repo.ui\\n         # repo we push to\\n         self.remote = remote\\n         # force option provided\\n         self.force = force\\n         # revs to be pushed (None is \\\"all\\\")\\n         self.revs = revs\\n         # bookmark explicitly pushed\\n         self.bookmarks = bookmarks\\n         # allow push of new branch\\n         self.newbranch = newbranch\\n         # step already performed\\n         # (used to check what steps have been already performed through bundle2)\\n         self.stepsdone = set()\\n         # Integer version of the changegroup push result\\n         # - None means nothing to push\\n         # - 0 means HTTP error\\n         # - 1 means we pushed and remote head count is unchanged *or*\\n         #   we have outgoing changesets but refused to push\\n         # - other values as described by addchangegroup()\\n         self.cgresult = None\\n         # Boolean value for the bookmark push\\n         self.bkresult = None\\n         # discover.outgoing object (contains common and outgoing data)\\n         self.outgoing = None\\n         # all remote topological heads before the push\\n         self.remoteheads = None\\n         # Details of the remote branch pre and post push\\n         #\\n         # mapping: {'branch': ([remoteheads],\\n         #                      [newheads],\\n         #                      [unsyncedheads],\\n         #                      [discardedheads])}\\n         # - branch: the branch name\\n         # - remoteheads: the list of remote heads known locally\\n         #                None if the branch is new\\n         # - newheads: the new remote heads (known locally) with outgoing pushed\\n         # - unsyncedheads: the list of remote heads unknown locally.\\n         # - discardedheads: the list of remote heads made obsolete by the push\\n         self.pushbranchmap = None\\n         # testable as a boolean indicating if any nodes are missing locally.\\n         self.incoming = None\\n         # summary of the remote phase situation\\n         self.remotephases = None\\n         # phases changes that must be pushed along side the changesets\\n         self.outdatedphases = None\\n         # phases changes that must be pushed if changeset push fails\\n         self.fallbackoutdatedphases = None\\n         # outgoing obsmarkers\\n         self.outobsmarkers = set()\\n         # outgoing bookmarks\\n         self.outbookmarks = []\\n         # transaction manager\\n         self.trmanager = None\\n         # map { pushkey partid -\\u003e callback handling failure}\\n         # used to handle exception from mandatory pushkey part failure\\n         self.pkfailcb = {}\\n         # an iterable of pushvars or None\\n         self.pushvars = pushvars\\n \\n     @util.propertycache\\n     def futureheads(self):\\n         \\\"\\\"\\\"future remote heads if the changeset push succeeds\\\"\\\"\\\"\\n         return self.outgoing.missingheads\\n \\n     @util.propertycache\\n     def fallbackheads(self):\\n         \\\"\\\"\\\"future remote heads if the changeset push fails\\\"\\\"\\\"\\n         if self.revs is None:\\n             # not target to push, all common are relevant\\n             return self.outgoing.commonheads\\n         unfi = self.repo.unfiltered()\\n         # I want cheads = heads(::missingheads and ::commonheads)\\n         # (missingheads is revs with secret changeset filtered out)\\n         #\\n         # This can be expressed as:\\n         #     cheads = ( (missingheads and ::commonheads)\\n         #              + (commonheads and ::missingheads))\\\"\\n         #              )\\n         #\\n         # while trying to push we already computed the following:\\n         #     common = (::commonheads)\\n         #     missing = ((commonheads::missingheads) - commonheads)\\n         #\\n         # We can pick:\\n         # * missingheads part of common (::commonheads)\\n         common = self.outgoing.common\\n         nm = self.repo.changelog.nodemap\\n         cheads = [node for node in self.revs if nm[node] in common]\\n         # and\\n         # * commonheads parents on missing\\n         revset = unfi.set('%ln and parents(roots(%ln))',\\n                          self.outgoing.commonheads,\\n                          self.outgoing.missing)\\n         cheads.extend(c.node() for c in revset)\\n         return cheads\\n \\n     @property\\n     def commonheads(self):\\n         \\\"\\\"\\\"set of all common heads after changeset bundle push\\\"\\\"\\\"\\n         if self.cgresult:\\n             return self.futureheads\\n         else:\\n             return self.fallbackheads\\n \\n # mapping of message used when pushing bookmark\\n bookmsgmap = {'update': (_(\\\"updating bookmark %s\\\\n\\\"),\\n                          _('updating bookmark %s failed!\\\\n')),\\n               'export': (_(\\\"exporting bookmark %s\\\\n\\\"),\\n                          _('exporting bookmark %s failed!\\\\n')),\\n               'delete': (_(\\\"deleting remote bookmark %s\\\\n\\\"),\\n                          _('deleting remote bookmark %s failed!\\\\n')),\\n               }\\n \\n \\n def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),\\n          opargs=None):\\n     '''Push outgoing changesets (limited by revs) from a local\\n     repository to remote. Return an integer:\\n       - None means nothing to push\\n       - 0 means HTTP error\\n       - 1 means we pushed and remote head count is unchanged *or*\\n         we have outgoing changesets but refused to push\\n       - other values as described by addchangegroup()\\n     '''\\n     if opargs is None:\\n         opargs = {}\\n     pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,\\n                            **pycompat.strkwargs(opargs))\\n     if pushop.remote.local():\\n         missing = (set(pushop.repo.requirements)\\n                    - pushop.remote.local().supported)\\n         if missing:\\n             msg = _(\\\"required features are not\\\"\\n                     \\\" supported in the destination:\\\"\\n                     \\\" %s\\\") % (', '.join(sorted(missing)))\\n             raise error.Abort(msg)\\n \\n     if not pushop.remote.canpush():\\n         raise error.Abort(_(\\\"destination does not support push\\\"))\\n \\n     if not pushop.remote.capable('unbundle'):\\n         raise error.Abort(_('cannot push: destination does not support the '\\n                             'unbundle wire protocol command'))\\n \\n     # get lock as we might write phase data\\n     wlock = lock = None\\n     try:\\n         # bundle2 push may receive a reply bundle touching bookmarks or other\\n         # things requiring the wlock. Take it now to ensure proper ordering.\\n         maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')\\n         if (not _forcebundle1(pushop)) and maypushback:\\n             wlock = pushop.repo.wlock()\\n         lock = pushop.repo.lock()\\n         pushop.trmanager = transactionmanager(pushop.repo,\\n                                               'push-response',\\n                                               pushop.remote.url())\\n     except error.LockUnavailable as err:\\n         # source repo cannot be locked.\\n         # We do not abort the push, but just disable the local phase\\n         # synchronisation.\\n         msg = 'cannot lock source repository: %s\\\\n' % err\\n         pushop.ui.debug(msg)\\n \\n     with wlock or util.nullcontextmanager(), \\\\\\n             lock or util.nullcontextmanager(), \\\\\\n             pushop.trmanager or util.nullcontextmanager():\\n         pushop.repo.checkpush(pushop)\\n         _pushdiscovery(pushop)\\n         if not _forcebundle1(pushop):\\n             _pushbundle2(pushop)\\n         _pushchangeset(pushop)\\n         _pushsyncphase(pushop)\\n         _pushobsolete(pushop)\\n         _pushbookmark(pushop)\\n \\n     if repo.ui.configbool('experimental', 'remotenames'):\\n         logexchange.pullremotenames(repo, remote)\\n \\n     return pushop\\n \\n # list of steps to perform discovery before push\\n pushdiscoveryorder = []\\n \\n # Mapping between step name and function\\n #\\n # This exists to help extensions wrap steps if necessary\\n pushdiscoverymapping = {}\\n \\n def pushdiscovery(stepname):\\n     \\\"\\\"\\\"decorator for function performing discovery before push\\n \\n     The function is added to the step -\\u003e function mapping and appended to the\\n     list of steps.  Beware that decorated function will be added in order (this\\n     may matter).\\n \\n     You can only use this decorator for a new step, if you want to wrap a step\\n     from an extension, change the pushdiscovery dictionary directly.\\\"\\\"\\\"\\n     def dec(func):\\n         assert stepname not in pushdiscoverymapping\\n         pushdiscoverymapping[stepname] = func\\n         pushdiscoveryorder.append(stepname)\\n         return func\\n     return dec\\n \\n def _pushdiscovery(pushop):\\n     \\\"\\\"\\\"Run all discovery steps\\\"\\\"\\\"\\n     for stepname in pushdiscoveryorder:\\n         step = pushdiscoverymapping[stepname]\\n         step(pushop)\\n \\n @pushdiscovery('changeset')\\n def _pushdiscoverychangeset(pushop):\\n     \\\"\\\"\\\"discover the changeset that need to be pushed\\\"\\\"\\\"\\n     fci = discovery.findcommonincoming\\n     if pushop.revs:\\n         commoninc = fci(pushop.repo, pushop.remote, force=pushop.force,\\n                         ancestorsof=pushop.revs)\\n     else:\\n         commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)\\n     common, inc, remoteheads = commoninc\\n     fco = discovery.findcommonoutgoing\\n     outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,\\n                    commoninc=commoninc, force=pushop.force)\\n     pushop.outgoing = outgoing\\n     pushop.remoteheads = remoteheads\\n     pushop.incoming = inc\\n \\n @pushdiscovery('phase')\\n def _pushdiscoveryphase(pushop):\\n     \\\"\\\"\\\"discover the phase that needs to be pushed\\n \\n     (computed for both success and failure case for changesets push)\\\"\\\"\\\"\\n     outgoing = pushop.outgoing\\n     unfi = pushop.repo.unfiltered()\\n     remotephases = listkeys(pushop.remote, 'phases')\\n \\n     if (pushop.ui.configbool('ui', '_usedassubrepo')\\n         and remotephases    # server supports phases\\n         and not pushop.outgoing.missing # no changesets to be pushed\\n         and remotephases.get('publishing', False)):\\n         # When:\\n         # - this is a subrepo push\\n         # - and remote support phase\\n         # - and no changeset are to be pushed\\n         # - and remote is publishing\\n         # We may be in issue 3781 case!\\n         # We drop the possible phase synchronisation done by\\n         # courtesy to publish changesets possibly locally draft\\n         # on the remote.\\n         pushop.outdatedphases = []\\n         pushop.fallbackoutdatedphases = []\\n         return\\n \\n     pushop.remotephases = phases.remotephasessummary(pushop.repo,\\n                                                      pushop.fallbackheads,\\n                                                      remotephases)\\n     droots = pushop.remotephases.draftroots\\n \\n     extracond = ''\\n     if not pushop.remotephases.publishing:\\n         extracond = ' and public()'\\n     revset = 'heads((%%ln::%%ln) %s)' % extracond\\n     # Get the list of all revs draft on remote by public here.\\n     # XXX Beware that revset break if droots is not strictly\\n     # XXX root we may want to ensure it is but it is costly\\n     fallback = list(unfi.set(revset, droots, pushop.fallbackheads))\\n     if not outgoing.missing:\\n         future = fallback\\n     else:\\n         # adds changeset we are going to push as draft\\n         #\\n         # should not be necessary for publishing server, but because of an\\n         # issue fixed in xxxxx we have to do it anyway.\\n         fdroots = list(unfi.set('roots(%ln  + %ln::)',\\n                        outgoing.missing, droots))\\n         fdroots = [f.node() for f in fdroots]\\n         future = list(unfi.set(revset, fdroots, pushop.futureheads))\\n     pushop.outdatedphases = future\\n     pushop.fallbackoutdatedphases = fallback\\n \\n @pushdiscovery('obsmarker')\\n def _pushdiscoveryobsmarkers(pushop):\\n     if not obsolete.isenabled(pushop.repo, obsolete.exchangeopt):\\n         return\\n \\n     if not pushop.repo.obsstore:\\n         return\\n \\n     if 'obsolete' not in listkeys(pushop.remote, 'namespaces'):\\n         return\\n \\n     repo = pushop.repo\\n     # very naive computation, that can be quite expensive on big repo.\\n     # However: evolution is currently slow on them anyway.\\n     nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))\\n     pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)\\n \\n @pushdiscovery('bookmarks')\\n def _pushdiscoverybookmarks(pushop):\\n     ui = pushop.ui\\n     repo = pushop.repo.unfiltered()\\n     remote = pushop.remote\\n     ui.debug(\\\"checking for updated bookmarks\\\\n\\\")\\n     ancestors = ()\\n     if pushop.revs:\\n         revnums = pycompat.maplist(repo.changelog.rev, pushop.revs)\\n         ancestors = repo.changelog.ancestors(revnums, inclusive=True)\\n \\n     remotebookmark = listkeys(remote, 'bookmarks')\\n \\n     explicit = set([repo._bookmarks.expandname(bookmark)\\n                     for bookmark in pushop.bookmarks])\\n \\n     remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)\\n     comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)\\n \\n     def safehex(x):\\n         if x is None:\\n             return x\\n         return hex(x)\\n \\n     def hexifycompbookmarks(bookmarks):\\n         return [(b, safehex(scid), safehex(dcid))\\n                 for (b, scid, dcid) in bookmarks]\\n \\n     comp = [hexifycompbookmarks(marks) for marks in comp]\\n     return _processcompared(pushop, ancestors, explicit, remotebookmark, comp)\\n \\n def _processcompared(pushop, pushed, explicit, remotebms, comp):\\n     \\\"\\\"\\\"take decision on bookmark to pull from the remote bookmark\\n \\n     Exist to help extensions who want to alter this behavior.\\n     \\\"\\\"\\\"\\n     addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp\\n \\n     repo = pushop.repo\\n \\n     for b, scid, dcid in advsrc:\\n         if b in explicit:\\n             explicit.remove(b)\\n         if not pushed or repo[scid].rev() in pushed:\\n             pushop.outbookmarks.append((b, dcid, scid))\\n     # search added bookmark\\n     for b, scid, dcid in addsrc:\\n         if b in explicit:\\n             explicit.remove(b)\\n             pushop.outbookmarks.append((b, '', scid))\\n     # search for overwritten bookmark\\n     for b, scid, dcid in list(advdst) + list(diverge) + list(differ):\\n         if b in explicit:\\n             explicit.remove(b)\\n             pushop.outbookmarks.append((b, dcid, scid))\\n     # search for bookmark to delete\\n     for b, scid, dcid in adddst:\\n         if b in explicit:\\n             explicit.remove(b)\\n             # treat as \\\"deleted locally\\\"\\n             pushop.outbookmarks.append((b, dcid, ''))\\n     # identical bookmarks shouldn't get reported\\n     for b, scid, dcid in same:\\n         if b in explicit:\\n             explicit.remove(b)\\n \\n     if explicit:\\n         explicit = sorted(explicit)\\n         # we should probably list all of them\\n         pushop.ui.warn(_('bookmark %s does not exist on the local '\\n                          'or remote repository!\\\\n') % explicit[0])\\n         pushop.bkresult = 2\\n \\n     pushop.outbookmarks.sort()\\n \\n def _pushcheckoutgoing(pushop):\\n     outgoing = pushop.outgoing\\n     unfi = pushop.repo.unfiltered()\\n     if not outgoing.missing:\\n         # nothing to push\\n         scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)\\n         return False\\n     # something to push\\n     if not pushop.force:\\n         # if repo.obsstore == False --\\u003e no obsolete\\n         # then, save the iteration\\n         if unfi.obsstore:\\n             # this message are here for 80 char limit reason\\n             mso = _(\\\"push includes obsolete changeset: %s!\\\")\\n             mspd = _(\\\"push includes phase-divergent changeset: %s!\\\")\\n             mscd = _(\\\"push includes content-divergent changeset: %s!\\\")\\n             mst = {\\\"orphan\\\": _(\\\"push includes orphan changeset: %s!\\\"),\\n                    \\\"phase-divergent\\\": mspd,\\n                    \\\"content-divergent\\\": mscd}\\n             # If we are to push if there is at least one\\n             # obsolete or unstable changeset in missing, at\\n             # least one of the missinghead will be obsolete or\\n             # unstable. So checking heads only is ok\\n             for node in outgoing.missingheads:\\n                 ctx = unfi[node]\\n                 if ctx.obsolete():\\n                     raise error.Abort(mso % ctx)\\n                 elif ctx.isunstable():\\n                     # TODO print more than one instability in the abort\\n                     # message\\n                     raise error.Abort(mst[ctx.instabilities()[0]] % ctx)\\n \\n         discovery.checkheads(pushop)\\n     return True\\n \\n # List of names of steps to perform for an outgoing bundle2, order matters.\\n b2partsgenorder = []\\n \\n # Mapping between step name and function\\n #\\n # This exists to help extensions wrap steps if necessary\\n b2partsgenmapping = {}\\n \\n def b2partsgenerator(stepname, idx=None):\\n     \\\"\\\"\\\"decorator for function generating bundle2 part\\n \\n     The function is added to the step -\\u003e function mapping and appended to the\\n     list of steps.  Beware that decorated functions will be added in order\\n     (this may matter).\\n \\n     You can only use this decorator for new steps, if you want to wrap a step\\n     from an extension, attack the b2partsgenmapping dictionary directly.\\\"\\\"\\\"\\n     def dec(func):\\n         assert stepname not in b2partsgenmapping\\n         b2partsgenmapping[stepname] = func\\n         if idx is None:\\n             b2partsgenorder.append(stepname)\\n         else:\\n             b2partsgenorder.insert(idx, stepname)\\n         return func\\n     return dec\\n \\n def _pushb2ctxcheckheads(pushop, bundler):\\n     \\\"\\\"\\\"Generate race condition checking parts\\n \\n     Exists as an independent function to aid extensions\\n     \\\"\\\"\\\"\\n     # * 'force' do not check for push race,\\n     # * if we don't push anything, there are nothing to check.\\n     if not pushop.force and pushop.outgoing.missingheads:\\n         allowunrelated = 'related' in bundler.capabilities.get('checkheads', ())\\n         emptyremote = pushop.pushbranchmap is None\\n         if not allowunrelated or emptyremote:\\n             bundler.newpart('check:heads', data=iter(pushop.remoteheads))\\n         else:\\n             affected = set()\\n             for branch, heads in pushop.pushbranchmap.iteritems():\\n                 remoteheads, newheads, unsyncedheads, discardedheads = heads\\n                 if remoteheads is not None:\\n                     remote = set(remoteheads)\\n                     affected |= set(discardedheads) & remote\\n                     affected |= remote - set(newheads)\\n             if affected:\\n                 data = iter(sorted(affected))\\n                 bundler.newpart('check:updated-heads', data=data)\\n \\n def _pushing(pushop):\\n     \\\"\\\"\\\"return True if we are pushing anything\\\"\\\"\\\"\\n     return bool(pushop.outgoing.missing\\n                 or pushop.outdatedphases\\n                 or pushop.outobsmarkers\\n                 or pushop.outbookmarks)\\n \\n @b2partsgenerator('check-bookmarks')\\n def _pushb2checkbookmarks(pushop, bundler):\\n     \\\"\\\"\\\"insert bookmark move checking\\\"\\\"\\\"\\n     if not _pushing(pushop) or pushop.force:\\n         return\\n     b2caps = bundle2.bundle2caps(pushop.remote)\\n     hasbookmarkcheck = 'bookmarks' in b2caps\\n     if not (pushop.outbookmarks and hasbookmarkcheck):\\n         return\\n     data = []\\n     for book, old, new in pushop.outbookmarks:\\n         old = bin(old)\\n         data.append((book, old))\\n     checkdata = bookmod.binaryencode(data)\\n     bundler.newpart('check:bookmarks', data=checkdata)\\n \\n @b2partsgenerator('check-phases')\\n def _pushb2checkphases(pushop, bundler):\\n     \\\"\\\"\\\"insert phase move checking\\\"\\\"\\\"\\n     if not _pushing(pushop) or pushop.force:\\n         return\\n     b2caps = bundle2.bundle2caps(pushop.remote)\\n     hasphaseheads = 'heads' in b2caps.get('phases', ())\\n     if pushop.remotephases is not None and hasphaseheads:\\n         # check that the remote phase has not changed\\n         checks = [[] for p in phases.allphases]\\n         checks[phases.public].extend(pushop.remotephases.publicheads)\\n         checks[phases.draft].extend(pushop.remotephases.draftroots)\\n         if any(checks):\\n             for nodes in checks:\\n                 nodes.sort()\\n             checkdata = phases.binaryencode(checks)\\n             bundler.newpart('check:phases', data=checkdata)\\n \\n @b2partsgenerator('changeset')\\n def _pushb2ctx(pushop, bundler):\\n     \\\"\\\"\\\"handle changegroup push through bundle2\\n \\n     addchangegroup result is stored in the ``pushop.cgresult`` attribute.\\n     \\\"\\\"\\\"\\n     if 'changesets' in pushop.stepsdone:\\n         return\\n     pushop.stepsdone.add('changesets')\\n     # Send known heads to the server for race detection.\\n     if not _pushcheckoutgoing(pushop):\\n         return\\n     pushop.repo.prepushoutgoinghooks(pushop)\\n \\n     _pushb2ctxcheckheads(pushop, bundler)\\n \\n     b2caps = bundle2.bundle2caps(pushop.remote)\\n     version = '01'\\n     cgversions = b2caps.get('changegroup')\\n     if cgversions:  # 3.1 and 3.2 ship with an empty value\\n         cgversions = [v for v in cgversions\\n                       if v in changegroup.supportedoutgoingversions(\\n                           pushop.repo)]\\n         if not cgversions:\\n             raise ValueError(_('no common changegroup version'))\\n         version = max(cgversions)\\n     cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,\\n                                       'push')\\n     cgpart = bundler.newpart('changegroup', data=cgstream)\\n     if cgversions:\\n         cgpart.addparam('version', version)\\n     if 'treemanifest' in pushop.repo.requirements:\\n         cgpart.addparam('treemanifest', '1')\\n     def handlereply(op):\\n         \\\"\\\"\\\"extract addchangegroup returns from server reply\\\"\\\"\\\"\\n         cgreplies = op.records.getreplies(cgpart.id)\\n         assert len(cgreplies['changegroup']) == 1\\n         pushop.cgresult = cgreplies['changegroup'][0]['return']\\n     return handlereply\\n \\n @b2partsgenerator('phase')\\n def _pushb2phases(pushop, bundler):\\n     \\\"\\\"\\\"handle phase push through bundle2\\\"\\\"\\\"\\n     if 'phases' in pushop.stepsdone:\\n         return\\n     b2caps = bundle2.bundle2caps(pushop.remote)\\n     ui = pushop.repo.ui\\n \\n     legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')\\n     haspushkey = 'pushkey' in b2caps\\n     hasphaseheads = 'heads' in b2caps.get('phases', ())\\n \\n     if hasphaseheads and not legacyphase:\\n         return _pushb2phaseheads(pushop, bundler)\\n     elif haspushkey:\\n         return _pushb2phasespushkey(pushop, bundler)\\n \\n def _pushb2phaseheads(pushop, bundler):\\n     \\\"\\\"\\\"push phase information through a bundle2 - binary part\\\"\\\"\\\"\\n     pushop.stepsdone.add('phases')\\n     if pushop.outdatedphases:\\n         updates = [[] for p in phases.allphases]\\n         updates[0].extend(h.node() for h in pushop.outdatedphases)\\n         phasedata = phases.binaryencode(updates)\\n         bundler.newpart('phase-heads', data=phasedata)\\n \\n def _pushb2phasespushkey(pushop, bundler):\\n     \\\"\\\"\\\"push phase information through a bundle2 - pushkey part\\\"\\\"\\\"\\n     pushop.stepsdone.add('phases')\\n     part2node = []\\n \\n     def handlefailure(pushop, exc):\\n         targetid = int(exc.partid)\\n         for partid, node in part2node:\\n             if partid == targetid:\\n                 raise error.Abort(_('updating %s to public failed') % node)\\n \\n     enc = pushkey.encode\\n     for newremotehead in pushop.outdatedphases:\\n         part = bundler.newpart('pushkey')\\n         part.addparam('namespace', enc('phases'))\\n         part.addparam('key', enc(newremotehead.hex()))\\n         part.addparam('old', enc('%d' % phases.draft))\\n         part.addparam('new', enc('%d' % phases.public))\\n         part2node.append((part.id, newremotehead))\\n         pushop.pkfailcb[part.id] = handlefailure\\n \\n     def handlereply(op):\\n         for partid, node in part2node:\\n             partrep = op.records.getreplies(partid)\\n             results = partrep['pushkey']\\n             assert len(results) \\u003c= 1\\n             msg = None\\n             if not results:\\n                 msg = _('server ignored update of %s to public!\\\\n') % node\\n             elif not int(results[0]['return']):\\n                 msg = _('updating %s to public failed!\\\\n') % node\\n             if msg is not None:\\n                 pushop.ui.warn(msg)\\n     return handlereply\\n \\n @b2partsgenerator('obsmarkers')\\n def _pushb2obsmarkers(pushop, bundler):\\n     if 'obsmarkers' in pushop.stepsdone:\\n         return\\n     remoteversions = bundle2.obsmarkersversion(bundler.capabilities)\\n     if obsolete.commonversion(remoteversions) is None:\\n         return\\n     pushop.stepsdone.add('obsmarkers')\\n     if pushop.outobsmarkers:\\n         markers = sorted(pushop.outobsmarkers)\\n         bundle2.buildobsmarkerspart(bundler, markers)\\n \\n @b2partsgenerator('bookmarks')\\n def _pushb2bookmarks(pushop, bundler):\\n     \\\"\\\"\\\"handle bookmark push through bundle2\\\"\\\"\\\"\\n     if 'bookmarks' in pushop.stepsdone:\\n         return\\n     b2caps = bundle2.bundle2caps(pushop.remote)\\n \\n     legacy = pushop.repo.ui.configlist('devel', 'legacy.exchange')\\n     legacybooks = 'bookmarks' in legacy\\n \\n     if not legacybooks and 'bookmarks' in b2caps:\\n         return _pushb2bookmarkspart(pushop, bundler)\\n     elif 'pushkey' in b2caps:\\n         return _pushb2bookmarkspushkey(pushop, bundler)\\n \\n def _bmaction(old, new):\\n     \\\"\\\"\\\"small utility for bookmark pushing\\\"\\\"\\\"\\n     if not old:\\n         return 'export'\\n     elif not new:\\n         return 'delete'\\n     return 'update'\\n \\n def _pushb2bookmarkspart(pushop, bundler):\\n     pushop.stepsdone.add('bookmarks')\\n     if not pushop.outbookmarks:\\n         return\\n \\n     allactions = []\\n     data = []\\n     for book, old, new in pushop.outbookmarks:\\n         new = bin(new)\\n         data.append((book, new))\\n         allactions.append((book, _bmaction(old, new)))\\n     checkdata = bookmod.binaryencode(data)\\n     bundler.newpart('bookmarks', data=checkdata)\\n \\n     def handlereply(op):\\n         ui = pushop.ui\\n         # if success\\n         for book, action in allactions:\\n             ui.status(bookmsgmap[action][0] % book)\\n \\n     return handlereply\\n \\n def _pushb2bookmarkspushkey(pushop, bundler):\\n     pushop.stepsdone.add('bookmarks')\\n     part2book = []\\n     enc = pushkey.encode\\n \\n     def handlefailure(pushop, exc):\\n         targetid = int(exc.partid)\\n         for partid, book, action in part2book:\\n             if partid == targetid:\\n                 raise error.Abort(bookmsgmap[action][1].rstrip() % book)\\n         # we should not be called for part we did not generated\\n         assert False\\n \\n     for book, old, new in pushop.outbookmarks:\\n         part = bundler.newpart('pushkey')\\n         part.addparam('namespace', enc('bookmarks'))\\n         part.addparam('key', enc(book))\\n         part.addparam('old', enc(old))\\n         part.addparam('new', enc(new))\\n         action = 'update'\\n         if not old:\\n             action = 'export'\\n         elif not new:\\n             action = 'delete'\\n         part2book.append((part.id, book, action))\\n         pushop.pkfailcb[part.id] = handlefailure\\n \\n     def handlereply(op):\\n         ui = pushop.ui\\n         for partid, book, action in part2book:\\n             partrep = op.records.getreplies(partid)\\n             results = partrep['pushkey']\\n             assert len(results) \\u003c= 1\\n             if not results:\\n                 pushop.ui.warn(_('server ignored bookmark %s update\\\\n') % book)\\n             else:\\n                 ret = int(results[0]['return'])\\n                 if ret:\\n                     ui.status(bookmsgmap[action][0] % book)\\n                 else:\\n                     ui.warn(bookmsgmap[action][1] % book)\\n                     if pushop.bkresult is not None:\\n                         pushop.bkresult = 1\\n     return handlereply\\n \\n @b2partsgenerator('pushvars', idx=0)\\n def _getbundlesendvars(pushop, bundler):\\n     '''send shellvars via bundle2'''\\n     pushvars = pushop.pushvars\\n     if pushvars:\\n         shellvars = {}\\n         for raw in pushvars:\\n             if '=' not in raw:\\n                 msg = (\\\"unable to parse variable '%s', should follow \\\"\\n                         \\\"'KEY=VALUE' or 'KEY=' format\\\")\\n                 raise error.Abort(msg % raw)\\n             k, v = raw.split('=', 1)\\n             shellvars[k] = v\\n \\n         part = bundler.newpart('pushvars')\\n \\n         for key, value in shellvars.iteritems():\\n             part.addparam(key, value, mandatory=False)\\n \\n def _pushbundle2(pushop):\\n     \\\"\\\"\\\"push data to the remote using bundle2\\n \\n     The only currently supported type of data is changegroup but this will\\n     evolve in the future.\\\"\\\"\\\"\\n     bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))\\n     pushback = (pushop.trmanager\\n                 and pushop.ui.configbool('experimental', 'bundle2.pushback'))\\n \\n     # create reply capability\\n     capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,\\n                                                       allowpushback=pushback,\\n                                                       role='client'))\\n     bundler.newpart('replycaps', data=capsblob)\\n     replyhandlers = []\\n     for partgenname in b2partsgenorder:\\n         partgen = b2partsgenmapping[partgenname]\\n         ret = partgen(pushop, bundler)\\n         if callable(ret):\\n             replyhandlers.append(ret)\\n     # do not push if nothing to push\\n     if bundler.nbparts \\u003c= 1:\\n         return\\n     stream = util.chunkbuffer(bundler.getchunks())\\n     try:\\n         try:\\n             with pushop.remote.commandexecutor() as e:\\n                 reply = e.callcommand('unbundle', {\\n                     'bundle': stream,\\n                     'heads': ['force'],\\n                     'url': pushop.remote.url(),\\n                 }).result()\\n         except error.BundleValueError as exc:\\n             raise error.Abort(_('missing support for %s') % exc)\\n         try:\\n             trgetter = None\\n             if pushback:\\n                 trgetter = pushop.trmanager.transaction\\n             op = bundle2.processbundle(pushop.repo, reply, trgetter)\\n         except error.BundleValueError as exc:\\n             raise error.Abort(_('missing support for %s') % exc)\\n         except bundle2.AbortFromPart as exc:\\n             pushop.ui.status(_('remote: %s\\\\n') % exc)\\n             if exc.hint is not None:\\n                 pushop.ui.status(_('remote: %s\\\\n') % ('(%s)' % exc.hint))\\n             raise error.Abort(_('push failed on remote'))\\n     except error.PushkeyFailed as exc:\\n         partid = int(exc.partid)\\n         if partid not in pushop.pkfailcb:\\n             raise\\n         pushop.pkfailcb[partid](pushop, exc)\\n     for rephand in replyhandlers:\\n         rephand(op)\\n \\n def _pushchangeset(pushop):\\n     \\\"\\\"\\\"Make the actual push of changeset bundle to remote repo\\\"\\\"\\\"\\n     if 'changesets' in pushop.stepsdone:\\n         return\\n     pushop.stepsdone.add('changesets')\\n     if not _pushcheckoutgoing(pushop):\\n         return\\n \\n     # Should have verified this in push().\\n     assert pushop.remote.capable('unbundle')\\n \\n     pushop.repo.prepushoutgoinghooks(pushop)\\n     outgoing = pushop.outgoing\\n     # TODO: get bundlecaps from remote\\n     bundlecaps = None\\n     # create a changegroup from local\\n     if pushop.revs is None and not (outgoing.excluded\\n                             or pushop.repo.changelog.filteredrevs):\\n         # push everything,\\n         # use the fast path, no race possible on push\\n         cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',\\n                 fastpath=True, bundlecaps=bundlecaps)\\n     else:\\n         cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',\\n                                         'push', bundlecaps=bundlecaps)\\n \\n     # apply changegroup to remote\\n     # local repo finds heads on server, finds out what\\n     # revs it must push. once revs transferred, if server\\n     # finds it has different heads (someone else won\\n     # commit\\/push race), server aborts.\\n     if pushop.force:\\n         remoteheads = ['force']\\n     else:\\n         remoteheads = pushop.remoteheads\\n     # ssh: return remote's addchangegroup()\\n     # http: return remote's addchangegroup() or 0 for error\\n     pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,\\n                                         pushop.repo.url())\\n \\n def _pushsyncphase(pushop):\\n     \\\"\\\"\\\"synchronise phase information locally and remotely\\\"\\\"\\\"\\n     cheads = pushop.commonheads\\n     # even when we don't push, exchanging phase data is useful\\n     remotephases = listkeys(pushop.remote, 'phases')\\n     if (pushop.ui.configbool('ui', '_usedassubrepo')\\n         and remotephases    # server supports phases\\n         and pushop.cgresult is None # nothing was pushed\\n         and remotephases.get('publishing', False)):\\n         # When:\\n         # - this is a subrepo push\\n         # - and remote support phase\\n         # - and no changeset was pushed\\n         # - and remote is publishing\\n         # We may be in issue 3871 case!\\n         # We drop the possible phase synchronisation done by\\n         # courtesy to publish changesets possibly locally draft\\n         # on the remote.\\n         remotephases = {'publishing': 'True'}\\n     if not remotephases: # old server or public only reply from non-publishing\\n         _localphasemove(pushop, cheads)\\n         # don't push any phase data as there is nothing to push\\n     else:\\n         ana = phases.analyzeremotephases(pushop.repo, cheads,\\n                                          remotephases)\\n         pheads, droots = ana\\n         ### Apply remote phase on local\\n         if remotephases.get('publishing', False):\\n             _localphasemove(pushop, cheads)\\n         else: # publish = False\\n             _localphasemove(pushop, pheads)\\n             _localphasemove(pushop, cheads, phases.draft)\\n         ### Apply local phase on remote\\n \\n         if pushop.cgresult:\\n             if 'phases' in pushop.stepsdone:\\n                 # phases already pushed though bundle2\\n                 return\\n             outdated = pushop.outdatedphases\\n         else:\\n             outdated = pushop.fallbackoutdatedphases\\n \\n         pushop.stepsdone.add('phases')\\n \\n         # filter heads already turned public by the push\\n         outdated = [c for c in outdated if c.node() not in pheads]\\n         # fallback to independent pushkey command\\n         for newremotehead in outdated:\\n             with pushop.remote.commandexecutor() as e:\\n                 r = e.callcommand('pushkey', {\\n                     'namespace': 'phases',\\n                     'key': newremotehead.hex(),\\n                     'old': '%d' % phases.draft,\\n                     'new': '%d' % phases.public\\n                 }).result()\\n \\n             if not r:\\n                 pushop.ui.warn(_('updating %s to public failed!\\\\n')\\n                                % newremotehead)\\n \\n def _localphasemove(pushop, nodes, phase=phases.public):\\n     \\\"\\\"\\\"move \\u003cnodes\\u003e to \\u003cphase\\u003e in the local source repo\\\"\\\"\\\"\\n     if pushop.trmanager:\\n         phases.advanceboundary(pushop.repo,\\n                                pushop.trmanager.transaction(),\\n                                phase,\\n                                nodes)\\n     else:\\n         # repo is not locked, do not change any phases!\\n         # Informs the user that phases should have been moved when\\n         # applicable.\\n         actualmoves = [n for n in nodes if phase \\u003c pushop.repo[n].phase()]\\n         phasestr = phases.phasenames[phase]\\n         if actualmoves:\\n             pushop.ui.status(_('cannot lock source repo, skipping '\\n                                'local %s phase update\\\\n') % phasestr)\\n \\n def _pushobsolete(pushop):\\n     \\\"\\\"\\\"utility function to push obsolete markers to a remote\\\"\\\"\\\"\\n     if 'obsmarkers' in pushop.stepsdone:\\n         return\\n     repo = pushop.repo\\n     remote = pushop.remote\\n     pushop.stepsdone.add('obsmarkers')\\n     if pushop.outobsmarkers:\\n         pushop.ui.debug('try to push obsolete markers to remote\\\\n')\\n         rslts = []\\n         remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))\\n         for key in sorted(remotedata, reverse=True):\\n             # reverse sort to ensure we end with dump0\\n             data = remotedata[key]\\n             rslts.append(remote.pushkey('obsolete', key, '', data))\\n         if [r for r in rslts if not r]:\\n             msg = _('failed to push some obsolete markers!\\\\n')\\n             repo.ui.warn(msg)\\n \\n def _pushbookmark(pushop):\\n     \\\"\\\"\\\"Update bookmark position on remote\\\"\\\"\\\"\\n     if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:\\n         return\\n     pushop.stepsdone.add('bookmarks')\\n     ui = pushop.ui\\n     remote = pushop.remote\\n \\n     for b, old, new in pushop.outbookmarks:\\n         action = 'update'\\n         if not old:\\n             action = 'export'\\n         elif not new:\\n             action = 'delete'\\n \\n         with remote.commandexecutor() as e:\\n             r = e.callcommand('pushkey', {\\n                 'namespace': 'bookmarks',\\n                 'key': b,\\n                 'old': old,\\n                 'new': new,\\n             }).result()\\n \\n         if r:\\n             ui.status(bookmsgmap[action][0] % b)\\n         else:\\n             ui.warn(bookmsgmap[action][1] % b)\\n             # discovery can have set the value form invalid entry\\n             if pushop.bkresult is not None:\\n                 pushop.bkresult = 1\\n \\n class pulloperation(object):\\n     \\\"\\\"\\\"A object that represent a single pull operation\\n \\n     It purpose is to carry pull related state and very common operation.\\n \\n     A new should be created at the beginning of each pull and discarded\\n     afterward.\\n     \\\"\\\"\\\"\\n \\n     def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),\\n                  remotebookmarks=None, streamclonerequested=None,\\n                  includepats=None, excludepats=None):\\n         # repo we pull into\\n         self.repo = repo\\n         # repo we pull from\\n         self.remote = remote\\n         # revision we try to pull (None is \\\"all\\\")\\n         self.heads = heads\\n         # bookmark pulled explicitly\\n         self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)\\n                                   for bookmark in bookmarks]\\n         # do we force pull?\\n         self.force = force\\n         # whether a streaming clone was requested\\n         self.streamclonerequested = streamclonerequested\\n         # transaction manager\\n         self.trmanager = None\\n         # set of common changeset between local and remote before pull\\n         self.common = None\\n         # set of pulled head\\n         self.rheads = None\\n         # list of missing changeset to fetch remotely\\n         self.fetch = None\\n         # remote bookmarks data\\n         self.remotebookmarks = remotebookmarks\\n         # result of changegroup pulling (used as return code by pull)\\n         self.cgresult = None\\n         # list of step already done\\n         self.stepsdone = set()\\n         # Whether we attempted a clone from pre-generated bundles.\\n         self.clonebundleattempted = False\\n         # Set of file patterns to include.\\n         self.includepats = includepats\\n         # Set of file patterns to exclude.\\n         self.excludepats = excludepats\\n \\n     @util.propertycache\\n     def pulledsubset(self):\\n         \\\"\\\"\\\"heads of the set of changeset target by the pull\\\"\\\"\\\"\\n         # compute target subset\\n         if self.heads is None:\\n             # We pulled every thing possible\\n             # sync on everything common\\n             c = set(self.common)\\n             ret = list(self.common)\\n             for n in self.rheads:\\n                 if n not in c:\\n                     ret.append(n)\\n             return ret\\n         else:\\n             # We pulled a specific subset\\n             # sync on this subset\\n             return self.heads\\n \\n     @util.propertycache\\n     def canusebundle2(self):\\n         return not _forcebundle1(self)\\n \\n     @util.propertycache\\n     def remotebundle2caps(self):\\n         return bundle2.bundle2caps(self.remote)\\n \\n     def gettransaction(self):\\n         # deprecated; talk to trmanager directly\\n         return self.trmanager.transaction()\\n \\n class transactionmanager(util.transactional):\\n     \\\"\\\"\\\"An object to manage the life cycle of a transaction\\n \\n     It creates the transaction on demand and calls the appropriate hooks when\\n     closing the transaction.\\\"\\\"\\\"\\n     def __init__(self, repo, source, url):\\n         self.repo = repo\\n         self.source = source\\n         self.url = url\\n         self._tr = None\\n \\n     def transaction(self):\\n         \\\"\\\"\\\"Return an open transaction object, constructing if necessary\\\"\\\"\\\"\\n         if not self._tr:\\n             trname = '%s\\\\n%s' % (self.source, util.hidepassword(self.url))\\n             self._tr = self.repo.transaction(trname)\\n             self._tr.hookargs['source'] = self.source\\n             self._tr.hookargs['url'] = self.url\\n         return self._tr\\n \\n     def close(self):\\n         \\\"\\\"\\\"close transaction if created\\\"\\\"\\\"\\n         if self._tr is not None:\\n             self._tr.close()\\n \\n     def release(self):\\n         \\\"\\\"\\\"release transaction if created\\\"\\\"\\\"\\n         if self._tr is not None:\\n             self._tr.release()\\n \\n def listkeys(remote, namespace):\\n     with remote.commandexecutor() as e:\\n         return e.callcommand('listkeys', {'namespace': namespace}).result()\\n \\n def _fullpullbundle2(repo, pullop):\\n     # The server may send a partial reply, i.e. when inlining\\n     # pre-computed bundles. In that case, update the common\\n     # set based on the results and pull another bundle.\\n     #\\n     # There are two indicators that the process is finished:\\n     # - no changeset has been added, or\\n     # - all remote heads are known locally.\\n     # The head check must use the unfiltered view as obsoletion\\n     # markers can hide heads.\\n     unfi = repo.unfiltered()\\n     unficl = unfi.changelog\\n     def headsofdiff(h1, h2):\\n         \\\"\\\"\\\"Returns heads(h1 % h2)\\\"\\\"\\\"\\n         res = unfi.set('heads(%ln %% %ln)', h1, h2)\\n         return set(ctx.node() for ctx in res)\\n     def headsofunion(h1, h2):\\n         \\\"\\\"\\\"Returns heads((h1 + h2) - null)\\\"\\\"\\\"\\n         res = unfi.set('heads((%ln + %ln - null))', h1, h2)\\n         return set(ctx.node() for ctx in res)\\n     while True:\\n         old_heads = unficl.heads()\\n         clstart = len(unficl)\\n         _pullbundle2(pullop)\\n         if repository.NARROW_REQUIREMENT in repo.requirements:\\n             # XXX narrow clones filter the heads on the server side during\\n             # XXX getbundle and result in partial replies as well.\\n             # XXX Disable pull bundles in this case as band aid to avoid\\n             # XXX extra round trips.\\n             break\\n         if clstart == len(unficl):\\n             break\\n         if all(unficl.hasnode(n) for n in pullop.rheads):\\n             break\\n         new_heads = headsofdiff(unficl.heads(), old_heads)\\n         pullop.common = headsofunion(new_heads, pullop.common)\\n         pullop.rheads = set(pullop.rheads) - pullop.common\\n \\n def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,\\n          streamclonerequested=None, includepats=None, excludepats=None):\\n     \\\"\\\"\\\"Fetch repository data from a remote.\\n \\n     This is the main function used to retrieve data from a remote repository.\\n \\n     ``repo`` is the local repository to clone into.\\n     ``remote`` is a peer instance.\\n     ``heads`` is an iterable of revisions we want to pull. ``None`` (the\\n     default) means to pull everything from the remote.\\n     ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By\\n     default, all remote bookmarks are pulled.\\n     ``opargs`` are additional keyword arguments to pass to ``pulloperation``\\n     initialization.\\n     ``streamclonerequested`` is a boolean indicating whether a \\\"streaming\\n     clone\\\" is requested. A \\\"streaming clone\\\" is essentially a raw file copy\\n     of revlogs from the server. This only works when the local repository is\\n     empty. The default value of ``None`` means to respect the server\\n     configuration for preferring stream clones.\\n     ``includepats`` and ``excludepats`` define explicit file patterns to\\n     include and exclude in storage, respectively. If not defined, narrow\\n     patterns from the repo instance are used, if available.\\n \\n     Returns the ``pulloperation`` created for this pull.\\n     \\\"\\\"\\\"\\n     if opargs is None:\\n         opargs = {}\\n \\n     # We allow the narrow patterns to be passed in explicitly to provide more\\n     # flexibility for API consumers.\\n     if includepats or excludepats:\\n         includepats = includepats or set()\\n         excludepats = excludepats or set()\\n     else:\\n         includepats, excludepats = repo.narrowpats\\n \\n     narrowspec.validatepatterns(includepats)\\n     narrowspec.validatepatterns(excludepats)\\n \\n     pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,\\n                            streamclonerequested=streamclonerequested,\\n                            includepats=includepats, excludepats=excludepats,\\n                            **pycompat.strkwargs(opargs))\\n \\n     peerlocal = pullop.remote.local()\\n     if peerlocal:\\n         missing = set(peerlocal.requirements) - pullop.repo.supported\\n         if missing:\\n             msg = _(\\\"required features are not\\\"\\n                     \\\" supported in the destination:\\\"\\n                     \\\" %s\\\") % (', '.join(sorted(missing)))\\n             raise error.Abort(msg)\\n \\n     pullop.trmanager = transactionmanager(repo, 'pull', remote.url())\\n     with repo.wlock(), repo.lock(), pullop.trmanager:\\n-        # This should ideally be in _pullbundle2(). However, it needs to run\\n-        # before discovery to avoid extra work.\\n-        _maybeapplyclonebundle(pullop)\\n-        streamclone.maybeperformlegacystreamclone(pullop)\\n-        _pulldiscovery(pullop)\\n-        if pullop.canusebundle2:\\n-            _fullpullbundle2(repo, pullop)\\n-        _pullchangeset(pullop)\\n-        _pullphase(pullop)\\n-        _pullbookmarks(pullop)\\n-        _pullobsolete(pullop)\\n+        # Use the modern wire protocol, if available.\\n+        if remote.capable('exchangev2'):\\n+            exchangev2.pull(pullop)\\n+        else:\\n+            # This should ideally be in _pullbundle2(). However, it needs to run\\n+            # before discovery to avoid extra work.\\n+            _maybeapplyclonebundle(pullop)\\n+            streamclone.maybeperformlegacystreamclone(pullop)\\n+            _pulldiscovery(pullop)\\n+            if pullop.canusebundle2:\\n+                _fullpullbundle2(repo, pullop)\\n+            _pullchangeset(pullop)\\n+            _pullphase(pullop)\\n+            _pullbookmarks(pullop)\\n+            _pullobsolete(pullop)\\n \\n     # storing remotenames\\n     if repo.ui.configbool('experimental', 'remotenames'):\\n         logexchange.pullremotenames(repo, remote)\\n \\n     return pullop\\n \\n # list of steps to perform discovery before pull\\n pulldiscoveryorder = []\\n \\n # Mapping between step name and function\\n #\\n # This exists to help extensions wrap steps if necessary\\n pulldiscoverymapping = {}\\n \\n def pulldiscovery(stepname):\\n     \\\"\\\"\\\"decorator for function performing discovery before pull\\n \\n     The function is added to the step -\\u003e function mapping and appended to the\\n     list of steps.  Beware that decorated function will be added in order (this\\n     may matter).\\n \\n     You can only use this decorator for a new step, if you want to wrap a step\\n     from an extension, change the pulldiscovery dictionary directly.\\\"\\\"\\\"\\n     def dec(func):\\n         assert stepname not in pulldiscoverymapping\\n         pulldiscoverymapping[stepname] = func\\n         pulldiscoveryorder.append(stepname)\\n         return func\\n     return dec\\n \\n def _pulldiscovery(pullop):\\n     \\\"\\\"\\\"Run all discovery steps\\\"\\\"\\\"\\n     for stepname in pulldiscoveryorder:\\n         step = pulldiscoverymapping[stepname]\\n         step(pullop)\\n \\n @pulldiscovery('b1:bookmarks')\\n def _pullbookmarkbundle1(pullop):\\n     \\\"\\\"\\\"fetch bookmark data in bundle1 case\\n \\n     If not using bundle2, we have to fetch bookmarks before changeset\\n     discovery to reduce the chance and impact of race conditions.\\\"\\\"\\\"\\n     if pullop.remotebookmarks is not None:\\n         return\\n     if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:\\n         # all known bundle2 servers now support listkeys, but lets be nice with\\n         # new implementation.\\n         return\\n     books = listkeys(pullop.remote, 'bookmarks')\\n     pullop.remotebookmarks = bookmod.unhexlifybookmarks(books)\\n \\n \\n @pulldiscovery('changegroup')\\n def _pulldiscoverychangegroup(pullop):\\n     \\\"\\\"\\\"discovery phase for the pull\\n \\n     Current handle changeset discovery only, will change handle all discovery\\n     at some point.\\\"\\\"\\\"\\n     tmp = discovery.findcommonincoming(pullop.repo,\\n                                        pullop.remote,\\n                                        heads=pullop.heads,\\n                                        force=pullop.force)\\n     common, fetch, rheads = tmp\\n     nm = pullop.repo.unfiltered().changelog.nodemap\\n     if fetch and rheads:\\n         # If a remote heads is filtered locally, put in back in common.\\n         #\\n         # This is a hackish solution to catch most of \\\"common but locally\\n         # hidden situation\\\".  We do not performs discovery on unfiltered\\n         # repository because it end up doing a pathological amount of round\\n         # trip for w huge amount of changeset we do not care about.\\n         #\\n         # If a set of such \\\"common but filtered\\\" changeset exist on the server\\n         # but are not including a remote heads, we'll not be able to detect it,\\n         scommon = set(common)\\n         for n in rheads:\\n             if n in nm:\\n                 if n not in scommon:\\n                     common.append(n)\\n         if set(rheads).issubset(set(common)):\\n             fetch = []\\n     pullop.common = common\\n     pullop.fetch = fetch\\n     pullop.rheads = rheads\\n \\n def _pullbundle2(pullop):\\n     \\\"\\\"\\\"pull data using bundle2\\n \\n     For now, the only supported data are changegroup.\\\"\\\"\\\"\\n     kwargs = {'bundlecaps': caps20to10(pullop.repo, role='client')}\\n \\n     # make ui easier to access\\n     ui = pullop.repo.ui\\n \\n     # At the moment we don't do stream clones over bundle2. If that is\\n     # implemented then here's where the check for that will go.\\n     streaming = streamclone.canperformstreamclone(pullop, bundle2=True)[0]\\n \\n     # declare pull perimeters\\n     kwargs['common'] = pullop.common\\n     kwargs['heads'] = pullop.heads or pullop.rheads\\n \\n     if streaming:\\n         kwargs['cg'] = False\\n         kwargs['stream'] = True\\n         pullop.stepsdone.add('changegroup')\\n         pullop.stepsdone.add('phases')\\n \\n     else:\\n         # pulling changegroup\\n         pullop.stepsdone.add('changegroup')\\n \\n         kwargs['cg'] = pullop.fetch\\n \\n         legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')\\n         hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())\\n         if (not legacyphase and hasbinaryphase):\\n             kwargs['phases'] = True\\n             pullop.stepsdone.add('phases')\\n \\n         if 'listkeys' in pullop.remotebundle2caps:\\n             if 'phases' not in pullop.stepsdone:\\n                 kwargs['listkeys'] = ['phases']\\n \\n     bookmarksrequested = False\\n     legacybookmark = 'bookmarks' in ui.configlist('devel', 'legacy.exchange')\\n     hasbinarybook = 'bookmarks' in pullop.remotebundle2caps\\n \\n     if pullop.remotebookmarks is not None:\\n         pullop.stepsdone.add('request-bookmarks')\\n \\n     if ('request-bookmarks' not in pullop.stepsdone\\n         and pullop.remotebookmarks is None\\n         and not legacybookmark and hasbinarybook):\\n         kwargs['bookmarks'] = True\\n         bookmarksrequested = True\\n \\n     if 'listkeys' in pullop.remotebundle2caps:\\n         if 'request-bookmarks' not in pullop.stepsdone:\\n             # make sure to always includes bookmark data when migrating\\n             # `hg incoming --bundle` to using this function.\\n             pullop.stepsdone.add('request-bookmarks')\\n             kwargs.setdefault('listkeys', []).append('bookmarks')\\n \\n     # If this is a full pull \\/ clone and the server supports the clone bundles\\n     # feature, tell the server whether we attempted a clone bundle. The\\n     # presence of this flag indicates the client supports clone bundles. This\\n     # will enable the server to treat clients that support clone bundles\\n     # differently from those that don't.\\n     if (pullop.remote.capable('clonebundles')\\n         and pullop.heads is None and list(pullop.common) == [nullid]):\\n         kwargs['cbattempted'] = pullop.clonebundleattempted\\n \\n     if streaming:\\n         pullop.repo.ui.status(_('streaming all changes\\\\n'))\\n     elif not pullop.fetch:\\n         pullop.repo.ui.status(_(\\\"no changes found\\\\n\\\"))\\n         pullop.cgresult = 0\\n     else:\\n         if pullop.heads is None and list(pullop.common) == [nullid]:\\n             pullop.repo.ui.status(_(\\\"requesting all changes\\\\n\\\"))\\n     if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):\\n         remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)\\n         if obsolete.commonversion(remoteversions) is not None:\\n             kwargs['obsmarkers'] = True\\n             pullop.stepsdone.add('obsmarkers')\\n     _pullbundle2extraprepare(pullop, kwargs)\\n \\n     with pullop.remote.commandexecutor() as e:\\n         args = dict(kwargs)\\n         args['source'] = 'pull'\\n         bundle = e.callcommand('getbundle', args).result()\\n \\n         try:\\n             op = bundle2.bundleoperation(pullop.repo, pullop.gettransaction,\\n                                          source='pull')\\n             op.modes['bookmarks'] = 'records'\\n             bundle2.processbundle(pullop.repo, bundle, op=op)\\n         except bundle2.AbortFromPart as exc:\\n             pullop.repo.ui.status(_('remote: abort: %s\\\\n') % exc)\\n             raise error.Abort(_('pull failed on remote'), hint=exc.hint)\\n         except error.BundleValueError as exc:\\n             raise error.Abort(_('missing support for %s') % exc)\\n \\n     if pullop.fetch:\\n         pullop.cgresult = bundle2.combinechangegroupresults(op)\\n \\n     # processing phases change\\n     for namespace, value in op.records['listkeys']:\\n         if namespace == 'phases':\\n             _pullapplyphases(pullop, value)\\n \\n     # processing bookmark update\\n     if bookmarksrequested:\\n         books = {}\\n         for record in op.records['bookmarks']:\\n             books[record['bookmark']] = record[\\\"node\\\"]\\n         pullop.remotebookmarks = books\\n     else:\\n         for namespace, value in op.records['listkeys']:\\n             if namespace == 'bookmarks':\\n                 pullop.remotebookmarks = bookmod.unhexlifybookmarks(value)\\n \\n     # bookmark data were either already there or pulled in the bundle\\n     if pullop.remotebookmarks is not None:\\n         _pullbookmarks(pullop)\\n \\n def _pullbundle2extraprepare(pullop, kwargs):\\n     \\\"\\\"\\\"hook function so that extensions can extend the getbundle call\\\"\\\"\\\"\\n \\n def _pullchangeset(pullop):\\n     \\\"\\\"\\\"pull changeset from unbundle into the local repo\\\"\\\"\\\"\\n     # We delay the open of the transaction as late as possible so we\\n     # don't open transaction for nothing or you break future useful\\n     # rollback call\\n     if 'changegroup' in pullop.stepsdone:\\n         return\\n     pullop.stepsdone.add('changegroup')\\n     if not pullop.fetch:\\n         pullop.repo.ui.status(_(\\\"no changes found\\\\n\\\"))\\n         pullop.cgresult = 0\\n         return\\n     tr = pullop.gettransaction()\\n     if pullop.heads is None and list(pullop.common) == [nullid]:\\n         pullop.repo.ui.status(_(\\\"requesting all changes\\\\n\\\"))\\n     elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):\\n         # issue1320, avoid a race if remote changed after discovery\\n         pullop.heads = pullop.rheads\\n \\n     if pullop.remote.capable('getbundle'):\\n         # TODO: get bundlecaps from remote\\n         cg = pullop.remote.getbundle('pull', common=pullop.common,\\n                                      heads=pullop.heads or pullop.rheads)\\n     elif pullop.heads is None:\\n         with pullop.remote.commandexecutor() as e:\\n             cg = e.callcommand('changegroup', {\\n                 'nodes': pullop.fetch,\\n                 'source': 'pull',\\n             }).result()\\n \\n     elif not pullop.remote.capable('changegroupsubset'):\\n         raise error.Abort(_(\\\"partial pull cannot be done because \\\"\\n                            \\\"other repository doesn't support \\\"\\n                            \\\"changegroupsubset.\\\"))\\n     else:\\n         with pullop.remote.commandexecutor() as e:\\n             cg = e.callcommand('changegroupsubset', {\\n                 'bases': pullop.fetch,\\n                 'heads': pullop.heads,\\n                 'source': 'pull',\\n             }).result()\\n \\n     bundleop = bundle2.applybundle(pullop.repo, cg, tr, 'pull',\\n                                    pullop.remote.url())\\n     pullop.cgresult = bundle2.combinechangegroupresults(bundleop)\\n \\n def _pullphase(pullop):\\n     # Get remote phases data from remote\\n     if 'phases' in pullop.stepsdone:\\n         return\\n     remotephases = listkeys(pullop.remote, 'phases')\\n     _pullapplyphases(pullop, remotephases)\\n \\n def _pullapplyphases(pullop, remotephases):\\n     \\\"\\\"\\\"apply phase movement from observed remote state\\\"\\\"\\\"\\n     if 'phases' in pullop.stepsdone:\\n         return\\n     pullop.stepsdone.add('phases')\\n     publishing = bool(remotephases.get('publishing', False))\\n     if remotephases and not publishing:\\n         # remote is new and non-publishing\\n         pheads, _dr = phases.analyzeremotephases(pullop.repo,\\n                                                  pullop.pulledsubset,\\n                                                  remotephases)\\n         dheads = pullop.pulledsubset\\n     else:\\n         # Remote is old or publishing all common changesets\\n         # should be seen as public\\n         pheads = pullop.pulledsubset\\n         dheads = []\\n     unfi = pullop.repo.unfiltered()\\n     phase = unfi._phasecache.phase\\n     rev = unfi.changelog.nodemap.get\\n     public = phases.public\\n     draft = phases.draft\\n \\n     # exclude changesets already public locally and update the others\\n     pheads = [pn for pn in pheads if phase(unfi, rev(pn)) \\u003e public]\\n     if pheads:\\n         tr = pullop.gettransaction()\\n         phases.advanceboundary(pullop.repo, tr, public, pheads)\\n \\n     # exclude changesets already draft locally and update the others\\n     dheads = [pn for pn in dheads if phase(unfi, rev(pn)) \\u003e draft]\\n     if dheads:\\n         tr = pullop.gettransaction()\\n         phases.advanceboundary(pullop.repo, tr, draft, dheads)\\n \\n def _pullbookmarks(pullop):\\n     \\\"\\\"\\\"process the remote bookmark information to update the local one\\\"\\\"\\\"\\n     if 'bookmarks' in pullop.stepsdone:\\n         return\\n     pullop.stepsdone.add('bookmarks')\\n     repo = pullop.repo\\n     remotebookmarks = pullop.remotebookmarks\\n     bookmod.updatefromremote(repo.ui, repo, remotebookmarks,\\n                              pullop.remote.url(),\\n                              pullop.gettransaction,\\n                              explicit=pullop.explicitbookmarks)\\n \\n def _pullobsolete(pullop):\\n     \\\"\\\"\\\"utility function to pull obsolete markers from a remote\\n \\n     The `gettransaction` is function that return the pull transaction, creating\\n     one if necessary. We return the transaction to inform the calling code that\\n     a new transaction have been created (when applicable).\\n \\n     Exists mostly to allow overriding for experimentation purpose\\\"\\\"\\\"\\n     if 'obsmarkers' in pullop.stepsdone:\\n         return\\n     pullop.stepsdone.add('obsmarkers')\\n     tr = None\\n     if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):\\n         pullop.repo.ui.debug('fetching remote obsolete markers\\\\n')\\n         remoteobs = listkeys(pullop.remote, 'obsolete')\\n         if 'dump0' in remoteobs:\\n             tr = pullop.gettransaction()\\n             markers = []\\n             for key in sorted(remoteobs, reverse=True):\\n                 if key.startswith('dump'):\\n                     data = util.b85decode(remoteobs[key])\\n                     version, newmarks = obsolete._readmarkers(data)\\n                     markers += newmarks\\n             if markers:\\n                 pullop.repo.obsstore.add(tr, markers)\\n             pullop.repo.invalidatevolatilesets()\\n     return tr\\n \\n def applynarrowacl(repo, kwargs):\\n     \\\"\\\"\\\"Apply narrow fetch access control.\\n \\n     This massages the named arguments for getbundle wire protocol commands\\n     so requested data is filtered through access control rules.\\n     \\\"\\\"\\\"\\n     ui = repo.ui\\n     # TODO this assumes existence of HTTP and is a layering violation.\\n     username = ui.shortuser(ui.environ.get('REMOTE_USER') or ui.username())\\n     user_includes = ui.configlist(\\n         _NARROWACL_SECTION, username + '.includes',\\n         ui.configlist(_NARROWACL_SECTION, 'default.includes'))\\n     user_excludes = ui.configlist(\\n         _NARROWACL_SECTION, username + '.excludes',\\n         ui.configlist(_NARROWACL_SECTION, 'default.excludes'))\\n     if not user_includes:\\n         raise error.Abort(_(\\\"{} configuration for user {} is empty\\\")\\n                           .format(_NARROWACL_SECTION, username))\\n \\n     user_includes = [\\n         'path:.' if p == '*' else 'path:' + p for p in user_includes]\\n     user_excludes = [\\n         'path:.' if p == '*' else 'path:' + p for p in user_excludes]\\n \\n     req_includes = set(kwargs.get(r'includepats', []))\\n     req_excludes = set(kwargs.get(r'excludepats', []))\\n \\n     req_includes, req_excludes, invalid_includes = narrowspec.restrictpatterns(\\n         req_includes, req_excludes, user_includes, user_excludes)\\n \\n     if invalid_includes:\\n         raise error.Abort(\\n             _(\\\"The following includes are not accessible for {}: {}\\\")\\n             .format(username, invalid_includes))\\n \\n     new_args = {}\\n     new_args.update(kwargs)\\n     new_args[r'narrow'] = True\\n     new_args[r'includepats'] = req_includes\\n     if req_excludes:\\n         new_args[r'excludepats'] = req_excludes\\n \\n     return new_args\\n \\n def _computeellipsis(repo, common, heads, known, match, depth=None):\\n     \\\"\\\"\\\"Compute the shape of a narrowed DAG.\\n \\n     Args:\\n       repo: The repository we're transferring.\\n       common: The roots of the DAG range we're transferring.\\n               May be just [nullid], which means all ancestors of heads.\\n       heads: The heads of the DAG range we're transferring.\\n       match: The narrowmatcher that allows us to identify relevant changes.\\n       depth: If not None, only consider nodes to be full nodes if they are at\\n              most depth changesets away from one of heads.\\n \\n     Returns:\\n       A tuple of (visitnodes, relevant_nodes, ellipsisroots) where:\\n \\n         visitnodes: The list of nodes (either full or ellipsis) which\\n                     need to be sent to the client.\\n         relevant_nodes: The set of changelog nodes which change a file inside\\n                  the narrowspec. The client needs these as non-ellipsis nodes.\\n         ellipsisroots: A dict of {rev: parents} that is used in\\n                        narrowchangegroup to produce ellipsis nodes with the\\n                        correct parents.\\n     \\\"\\\"\\\"\\n     cl = repo.changelog\\n     mfl = repo.manifestlog\\n \\n     clrev = cl.rev\\n \\n     commonrevs = {clrev(n) for n in common} | {nullrev}\\n     headsrevs = {clrev(n) for n in heads}\\n \\n     if depth:\\n         revdepth = {h: 0 for h in headsrevs}\\n \\n     ellipsisheads = collections.defaultdict(set)\\n     ellipsisroots = collections.defaultdict(set)\\n \\n     def addroot(head, curchange):\\n         \\\"\\\"\\\"Add a root to an ellipsis head, splitting heads with 3 roots.\\\"\\\"\\\"\\n         ellipsisroots[head].add(curchange)\\n         # Recursively split ellipsis heads with 3 roots by finding the\\n         # roots' youngest common descendant which is an elided merge commit.\\n         # That descendant takes 2 of the 3 roots as its own, and becomes a\\n         # root of the head.\\n         while len(ellipsisroots[head]) \\u003e 2:\\n             child, roots = splithead(head)\\n             splitroots(head, child, roots)\\n             head = child  # Recurse in case we just added a 3rd root\\n \\n     def splitroots(head, child, roots):\\n         ellipsisroots[head].difference_update(roots)\\n         ellipsisroots[head].add(child)\\n         ellipsisroots[child].update(roots)\\n         ellipsisroots[child].discard(child)\\n \\n     def splithead(head):\\n         r1, r2, r3 = sorted(ellipsisroots[head])\\n         for nr1, nr2 in ((r2, r3), (r1, r3), (r1, r2)):\\n             mid = repo.revs('sort(merge() & %d::%d & %d::%d, -rev)',\\n                             nr1, head, nr2, head)\\n             for j in mid:\\n                 if j == nr2:\\n                     return nr2, (nr1, nr2)\\n                 if j not in ellipsisroots or len(ellipsisroots[j]) \\u003c 2:\\n                     return j, (nr1, nr2)\\n         raise error.Abort(_('Failed to split up ellipsis node! head: %d, '\\n                             'roots: %d %d %d') % (head, r1, r2, r3))\\n \\n     missing = list(cl.findmissingrevs(common=commonrevs, heads=headsrevs))\\n     visit = reversed(missing)\\n     relevant_nodes = set()\\n     visitnodes = [cl.node(m) for m in missing]\\n     required = set(headsrevs) | known\\n     for rev in visit:\\n         clrev = cl.changelogrevision(rev)\\n         ps = [prev for prev in cl.parentrevs(rev) if prev != nullrev]\\n         if depth is not None:\\n             curdepth = revdepth[rev]\\n             for p in ps:\\n                 revdepth[p] = min(curdepth + 1, revdepth.get(p, depth + 1))\\n         needed = False\\n         shallow_enough = depth is None or revdepth[rev] \\u003c= depth\\n         if shallow_enough:\\n             curmf = mfl[clrev.manifest].read()\\n             if ps:\\n                 # We choose to not trust the changed files list in\\n                 # changesets because it's not always correct. TODO: could\\n                 # we trust it for the non-merge case?\\n                 p1mf = mfl[cl.changelogrevision(ps[0]).manifest].read()\\n                 needed = bool(curmf.diff(p1mf, match))\\n                 if not needed and len(ps) \\u003e 1:\\n                     # For merge changes, the list of changed files is not\\n                     # helpful, since we need to emit the merge if a file\\n                     # in the narrow spec has changed on either side of the\\n                     # merge. As a result, we do a manifest diff to check.\\n                     p2mf = mfl[cl.changelogrevision(ps[1]).manifest].read()\\n                     needed = bool(curmf.diff(p2mf, match))\\n             else:\\n                 # For a root node, we need to include the node if any\\n                 # files in the node match the narrowspec.\\n                 needed = any(curmf.walk(match))\\n \\n         if needed:\\n             for head in ellipsisheads[rev]:\\n                 addroot(head, rev)\\n             for p in ps:\\n                 required.add(p)\\n             relevant_nodes.add(cl.node(rev))\\n         else:\\n             if not ps:\\n                 ps = [nullrev]\\n             if rev in required:\\n                 for head in ellipsisheads[rev]:\\n                     addroot(head, rev)\\n                 for p in ps:\\n                     ellipsisheads[p].add(rev)\\n             else:\\n                 for p in ps:\\n                     ellipsisheads[p] |= ellipsisheads[rev]\\n \\n     # add common changesets as roots of their reachable ellipsis heads\\n     for c in commonrevs:\\n         for head in ellipsisheads[c]:\\n             addroot(head, c)\\n     return visitnodes, relevant_nodes, ellipsisroots\\n \\n def caps20to10(repo, role):\\n     \\\"\\\"\\\"return a set with appropriate options to use bundle20 during getbundle\\\"\\\"\\\"\\n     caps = {'HG20'}\\n     capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo, role=role))\\n     caps.add('bundle2=' + urlreq.quote(capsblob))\\n     return caps\\n \\n # List of names of steps to perform for a bundle2 for getbundle, order matters.\\n getbundle2partsorder = []\\n \\n # Mapping between step name and function\\n #\\n # This exists to help extensions wrap steps if necessary\\n getbundle2partsmapping = {}\\n \\n def getbundle2partsgenerator(stepname, idx=None):\\n     \\\"\\\"\\\"decorator for function generating bundle2 part for getbundle\\n \\n     The function is added to the step -\\u003e function mapping and appended to the\\n     list of steps.  Beware that decorated functions will be added in order\\n     (this may matter).\\n \\n     You can only use this decorator for new steps, if you want to wrap a step\\n     from an extension, attack the getbundle2partsmapping dictionary directly.\\\"\\\"\\\"\\n     def dec(func):\\n         assert stepname not in getbundle2partsmapping\\n         getbundle2partsmapping[stepname] = func\\n         if idx is None:\\n             getbundle2partsorder.append(stepname)\\n         else:\\n             getbundle2partsorder.insert(idx, stepname)\\n         return func\\n     return dec\\n \\n def bundle2requested(bundlecaps):\\n     if bundlecaps is not None:\\n         return any(cap.startswith('HG2') for cap in bundlecaps)\\n     return False\\n \\n def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,\\n                     **kwargs):\\n     \\\"\\\"\\\"Return chunks constituting a bundle's raw data.\\n \\n     Could be a bundle HG10 or a bundle HG20 depending on bundlecaps\\n     passed.\\n \\n     Returns a 2-tuple of a dict with metadata about the generated bundle\\n     and an iterator over raw chunks (of varying sizes).\\n     \\\"\\\"\\\"\\n     kwargs = pycompat.byteskwargs(kwargs)\\n     info = {}\\n     usebundle2 = bundle2requested(bundlecaps)\\n     # bundle10 case\\n     if not usebundle2:\\n         if bundlecaps and not kwargs.get('cg', True):\\n             raise ValueError(_('request for bundle10 must include changegroup'))\\n \\n         if kwargs:\\n             raise ValueError(_('unsupported getbundle arguments: %s')\\n                              % ', '.join(sorted(kwargs.keys())))\\n         outgoing = _computeoutgoing(repo, heads, common)\\n         info['bundleversion'] = 1\\n         return info, changegroup.makestream(repo, outgoing, '01', source,\\n                                             bundlecaps=bundlecaps)\\n \\n     # bundle20 case\\n     info['bundleversion'] = 2\\n     b2caps = {}\\n     for bcaps in bundlecaps:\\n         if bcaps.startswith('bundle2='):\\n             blob = urlreq.unquote(bcaps[len('bundle2='):])\\n             b2caps.update(bundle2.decodecaps(blob))\\n     bundler = bundle2.bundle20(repo.ui, b2caps)\\n \\n     kwargs['heads'] = heads\\n     kwargs['common'] = common\\n \\n     for name in getbundle2partsorder:\\n         func = getbundle2partsmapping[name]\\n         func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,\\n              **pycompat.strkwargs(kwargs))\\n \\n     info['prefercompressed'] = bundler.prefercompressed\\n \\n     return info, bundler.getchunks()\\n \\n @getbundle2partsgenerator('stream2')\\n def _getbundlestream2(bundler, repo, *args, **kwargs):\\n     return bundle2.addpartbundlestream2(bundler, repo, **kwargs)\\n \\n @getbundle2partsgenerator('changegroup')\\n def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,\\n                               b2caps=None, heads=None, common=None, **kwargs):\\n     \\\"\\\"\\\"add a changegroup part to the requested bundle\\\"\\\"\\\"\\n     if not kwargs.get(r'cg', True):\\n         return\\n \\n     version = '01'\\n     cgversions = b2caps.get('changegroup')\\n     if cgversions:  # 3.1 and 3.2 ship with an empty value\\n         cgversions = [v for v in cgversions\\n                       if v in changegroup.supportedoutgoingversions(repo)]\\n         if not cgversions:\\n             raise ValueError(_('no common changegroup version'))\\n         version = max(cgversions)\\n \\n     outgoing = _computeoutgoing(repo, heads, common)\\n     if not outgoing.missing:\\n         return\\n \\n     if kwargs.get(r'narrow', False):\\n         include = sorted(filter(bool, kwargs.get(r'includepats', [])))\\n         exclude = sorted(filter(bool, kwargs.get(r'excludepats', [])))\\n         filematcher = narrowspec.match(repo.root, include=include,\\n                                        exclude=exclude)\\n     else:\\n         filematcher = None\\n \\n     cgstream = changegroup.makestream(repo, outgoing, version, source,\\n                                       bundlecaps=bundlecaps,\\n                                       filematcher=filematcher)\\n \\n     part = bundler.newpart('changegroup', data=cgstream)\\n     if cgversions:\\n         part.addparam('version', version)\\n \\n     part.addparam('nbchanges', '%d' % len(outgoing.missing),\\n                   mandatory=False)\\n \\n     if 'treemanifest' in repo.requirements:\\n         part.addparam('treemanifest', '1')\\n \\n     if kwargs.get(r'narrow', False) and (include or exclude):\\n         narrowspecpart = bundler.newpart('narrow:spec')\\n         if include:\\n             narrowspecpart.addparam(\\n                 'include', '\\\\n'.join(include), mandatory=True)\\n         if exclude:\\n             narrowspecpart.addparam(\\n                 'exclude', '\\\\n'.join(exclude), mandatory=True)\\n \\n @getbundle2partsgenerator('bookmarks')\\n def _getbundlebookmarkpart(bundler, repo, source, bundlecaps=None,\\n                               b2caps=None, **kwargs):\\n     \\\"\\\"\\\"add a bookmark part to the requested bundle\\\"\\\"\\\"\\n     if not kwargs.get(r'bookmarks', False):\\n         return\\n     if 'bookmarks' not in b2caps:\\n         raise ValueError(_('no common bookmarks exchange method'))\\n     books  = bookmod.listbinbookmarks(repo)\\n     data = bookmod.binaryencode(books)\\n     if data:\\n         bundler.newpart('bookmarks', data=data)\\n \\n @getbundle2partsgenerator('listkeys')\\n def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,\\n                             b2caps=None, **kwargs):\\n     \\\"\\\"\\\"add parts containing listkeys namespaces to the requested bundle\\\"\\\"\\\"\\n     listkeys = kwargs.get(r'listkeys', ())\\n     for namespace in listkeys:\\n         part = bundler.newpart('listkeys')\\n         part.addparam('namespace', namespace)\\n         keys = repo.listkeys(namespace).items()\\n         part.data = pushkey.encodekeys(keys)\\n \\n @getbundle2partsgenerator('obsmarkers')\\n def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,\\n                             b2caps=None, heads=None, **kwargs):\\n     \\\"\\\"\\\"add an obsolescence markers part to the requested bundle\\\"\\\"\\\"\\n     if kwargs.get(r'obsmarkers', False):\\n         if heads is None:\\n             heads = repo.heads()\\n         subset = [c.node() for c in repo.set('::%ln', heads)]\\n         markers = repo.obsstore.relevantmarkers(subset)\\n         markers = sorted(markers)\\n         bundle2.buildobsmarkerspart(bundler, markers)\\n \\n @getbundle2partsgenerator('phases')\\n def _getbundlephasespart(bundler, repo, source, bundlecaps=None,\\n                             b2caps=None, heads=None, **kwargs):\\n     \\\"\\\"\\\"add phase heads part to the requested bundle\\\"\\\"\\\"\\n     if kwargs.get(r'phases', False):\\n         if not 'heads' in b2caps.get('phases'):\\n             raise ValueError(_('no common phases exchange method'))\\n         if heads is None:\\n             heads = repo.heads()\\n \\n         headsbyphase = collections.defaultdict(set)\\n         if repo.publishing():\\n             headsbyphase[phases.public] = heads\\n         else:\\n             # find the appropriate heads to move\\n \\n             phase = repo._phasecache.phase\\n             node = repo.changelog.node\\n             rev = repo.changelog.rev\\n             for h in heads:\\n                 headsbyphase[phase(repo, rev(h))].add(h)\\n             seenphases = list(headsbyphase.keys())\\n \\n             # We do not handle anything but public and draft phase for now)\\n             if seenphases:\\n                 assert max(seenphases) \\u003c= phases.draft\\n \\n             # if client is pulling non-public changesets, we need to find\\n             # intermediate public heads.\\n             draftheads = headsbyphase.get(phases.draft, set())\\n             if draftheads:\\n                 publicheads = headsbyphase.get(phases.public, set())\\n \\n                 revset = 'heads(only(%ln, %ln) and public())'\\n                 extraheads = repo.revs(revset, draftheads, publicheads)\\n                 for r in extraheads:\\n                     headsbyphase[phases.public].add(node(r))\\n \\n         # transform data in a format used by the encoding function\\n         phasemapping = []\\n         for phase in phases.allphases:\\n             phasemapping.append(sorted(headsbyphase[phase]))\\n \\n         # generate the actual part\\n         phasedata = phases.binaryencode(phasemapping)\\n         bundler.newpart('phase-heads', data=phasedata)\\n \\n @getbundle2partsgenerator('hgtagsfnodes')\\n def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,\\n                          b2caps=None, heads=None, common=None,\\n                          **kwargs):\\n     \\\"\\\"\\\"Transfer the .hgtags filenodes mapping.\\n \\n     Only values for heads in this bundle will be transferred.\\n \\n     The part data consists of pairs of 20 byte changeset node and .hgtags\\n     filenodes raw values.\\n     \\\"\\\"\\\"\\n     # Don't send unless:\\n     # - changeset are being exchanged,\\n     # - the client supports it.\\n     if not (kwargs.get(r'cg', True) and 'hgtagsfnodes' in b2caps):\\n         return\\n \\n     outgoing = _computeoutgoing(repo, heads, common)\\n     bundle2.addparttagsfnodescache(repo, bundler, outgoing)\\n \\n @getbundle2partsgenerator('cache:rev-branch-cache')\\n def _getbundlerevbranchcache(bundler, repo, source, bundlecaps=None,\\n                              b2caps=None, heads=None, common=None,\\n                              **kwargs):\\n     \\\"\\\"\\\"Transfer the rev-branch-cache mapping\\n \\n     The payload is a series of data related to each branch\\n \\n     1) branch name length\\n     2) number of open heads\\n     3) number of closed heads\\n     4) open heads nodes\\n     5) closed heads nodes\\n     \\\"\\\"\\\"\\n     # Don't send unless:\\n     # - changeset are being exchanged,\\n     # - the client supports it.\\n     # - narrow bundle isn't in play (not currently compatible).\\n     if (not kwargs.get(r'cg', True)\\n         or 'rev-branch-cache' not in b2caps\\n         or kwargs.get(r'narrow', False)\\n         or repo.ui.has_section(_NARROWACL_SECTION)):\\n         return\\n \\n     outgoing = _computeoutgoing(repo, heads, common)\\n     bundle2.addpartrevbranchcache(repo, bundler, outgoing)\\n \\n def check_heads(repo, their_heads, context):\\n     \\\"\\\"\\\"check if the heads of a repo have been modified\\n \\n     Used by peer for unbundling.\\n     \\\"\\\"\\\"\\n     heads = repo.heads()\\n     heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()\\n     if not (their_heads == ['force'] or their_heads == heads or\\n             their_heads == ['hashed', heads_hash]):\\n         # someone else committed\\/pushed\\/unbundled while we\\n         # were transferring data\\n         raise error.PushRaced('repository changed while %s - '\\n                               'please try again' % context)\\n \\n def unbundle(repo, cg, heads, source, url):\\n     \\\"\\\"\\\"Apply a bundle to a repo.\\n \\n     this function makes sure the repo is locked during the application and have\\n     mechanism to check that no push race occurred between the creation of the\\n     bundle and its application.\\n \\n     If the push was raced as PushRaced exception is raised.\\\"\\\"\\\"\\n     r = 0\\n     # need a transaction when processing a bundle2 stream\\n     # [wlock, lock, tr] - needs to be an array so nested functions can modify it\\n     lockandtr = [None, None, None]\\n     recordout = None\\n     # quick fix for output mismatch with bundle2 in 3.4\\n     captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')\\n     if url.startswith('remote:http:') or url.startswith('remote:https:'):\\n         captureoutput = True\\n     try:\\n         # note: outside bundle1, 'heads' is expected to be empty and this\\n         # 'check_heads' call wil be a no-op\\n         check_heads(repo, heads, 'uploading changes')\\n         # push can proceed\\n         if not isinstance(cg, bundle2.unbundle20):\\n             # legacy case: bundle1 (changegroup 01)\\n             txnname = \\\"\\\\n\\\".join([source, util.hidepassword(url)])\\n             with repo.lock(), repo.transaction(txnname) as tr:\\n                 op = bundle2.applybundle(repo, cg, tr, source, url)\\n                 r = bundle2.combinechangegroupresults(op)\\n         else:\\n             r = None\\n             try:\\n                 def gettransaction():\\n                     if not lockandtr[2]:\\n                         lockandtr[0] = repo.wlock()\\n                         lockandtr[1] = repo.lock()\\n                         lockandtr[2] = repo.transaction(source)\\n                         lockandtr[2].hookargs['source'] = source\\n                         lockandtr[2].hookargs['url'] = url\\n                         lockandtr[2].hookargs['bundle2'] = '1'\\n                     return lockandtr[2]\\n \\n                 # Do greedy locking by default until we're satisfied with lazy\\n                 # locking.\\n                 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):\\n                     gettransaction()\\n \\n                 op = bundle2.bundleoperation(repo, gettransaction,\\n                                              captureoutput=captureoutput,\\n                                              source='push')\\n                 try:\\n                     op = bundle2.processbundle(repo, cg, op=op)\\n                 finally:\\n                     r = op.reply\\n                     if captureoutput and r is not None:\\n                         repo.ui.pushbuffer(error=True, subproc=True)\\n                         def recordout(output):\\n                             r.newpart('output', data=output, mandatory=False)\\n                 if lockandtr[2] is not None:\\n                     lockandtr[2].close()\\n             except BaseException as exc:\\n                 exc.duringunbundle2 = True\\n                 if captureoutput and r is not None:\\n                     parts = exc._bundle2salvagedoutput = r.salvageoutput()\\n                     def recordout(output):\\n                         part = bundle2.bundlepart('output', data=output,\\n                                                   mandatory=False)\\n                         parts.append(part)\\n                 raise\\n     finally:\\n         lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])\\n         if recordout is not None:\\n             recordout(repo.ui.popbuffer())\\n     return r\\n \\n def _maybeapplyclonebundle(pullop):\\n     \\\"\\\"\\\"Apply a clone bundle from a remote, if possible.\\\"\\\"\\\"\\n \\n     repo = pullop.repo\\n     remote = pullop.remote\\n \\n     if not repo.ui.configbool('ui', 'clonebundles'):\\n         return\\n \\n     # Only run if local repo is empty.\\n     if len(repo):\\n         return\\n \\n     if pullop.heads:\\n         return\\n \\n     if not remote.capable('clonebundles'):\\n         return\\n \\n     with remote.commandexecutor() as e:\\n         res = e.callcommand('clonebundles', {}).result()\\n \\n     # If we call the wire protocol command, that's good enough to record the\\n     # attempt.\\n     pullop.clonebundleattempted = True\\n \\n     entries = parseclonebundlesmanifest(repo, res)\\n     if not entries:\\n         repo.ui.note(_('no clone bundles available on remote; '\\n                        'falling back to regular clone\\\\n'))\\n         return\\n \\n     entries = filterclonebundleentries(\\n         repo, entries, streamclonerequested=pullop.streamclonerequested)\\n \\n     if not entries:\\n         # There is a thundering herd concern here. However, if a server\\n         # operator doesn't advertise bundles appropriate for its clients,\\n         # they deserve what's coming. Furthermore, from a client's\\n         # perspective, no automatic fallback would mean not being able to\\n         # clone!\\n         repo.ui.warn(_('no compatible clone bundles available on server; '\\n                        'falling back to regular clone\\\\n'))\\n         repo.ui.warn(_('(you may want to report this to the server '\\n                        'operator)\\\\n'))\\n         return\\n \\n     entries = sortclonebundleentries(repo.ui, entries)\\n \\n     url = entries[0]['URL']\\n     repo.ui.status(_('applying clone bundle from %s\\\\n') % url)\\n     if trypullbundlefromurl(repo.ui, repo, url):\\n         repo.ui.status(_('finished applying clone bundle\\\\n'))\\n     # Bundle failed.\\n     #\\n     # We abort by default to avoid the thundering herd of\\n     # clients flooding a server that was expecting expensive\\n     # clone load to be offloaded.\\n     elif repo.ui.configbool('ui', 'clonebundlefallback'):\\n         repo.ui.warn(_('falling back to normal clone\\\\n'))\\n     else:\\n         raise error.Abort(_('error applying bundle'),\\n                           hint=_('if this error persists, consider contacting '\\n                                  'the server operator or disable clone '\\n                                  'bundles via '\\n                                  '\\\"--config ui.clonebundles=false\\\"'))\\n \\n def parseclonebundlesmanifest(repo, s):\\n     \\\"\\\"\\\"Parses the raw text of a clone bundles manifest.\\n \\n     Returns a list of dicts. The dicts have a ``URL`` key corresponding\\n     to the URL and other keys are the attributes for the entry.\\n     \\\"\\\"\\\"\\n     m = []\\n     for line in s.splitlines():\\n         fields = line.split()\\n         if not fields:\\n             continue\\n         attrs = {'URL': fields[0]}\\n         for rawattr in fields[1:]:\\n             key, value = rawattr.split('=', 1)\\n             key = urlreq.unquote(key)\\n             value = urlreq.unquote(value)\\n             attrs[key] = value\\n \\n             # Parse BUNDLESPEC into components. This makes client-side\\n             # preferences easier to specify since you can prefer a single\\n             # component of the BUNDLESPEC.\\n             if key == 'BUNDLESPEC':\\n                 try:\\n                     bundlespec = parsebundlespec(repo, value)\\n                     attrs['COMPRESSION'] = bundlespec.compression\\n                     attrs['VERSION'] = bundlespec.version\\n                 except error.InvalidBundleSpecification:\\n                     pass\\n                 except error.UnsupportedBundleSpecification:\\n                     pass\\n \\n         m.append(attrs)\\n \\n     return m\\n \\n def isstreamclonespec(bundlespec):\\n     # Stream clone v1\\n     if (bundlespec.wirecompression == 'UN' and bundlespec.wireversion == 's1'):\\n         return True\\n \\n     # Stream clone v2\\n     if (bundlespec.wirecompression == 'UN' and \\\\\\n         bundlespec.wireversion == '02' and \\\\\\n         bundlespec.contentopts.get('streamv2')):\\n         return True\\n \\n     return False\\n \\n def filterclonebundleentries(repo, entries, streamclonerequested=False):\\n     \\\"\\\"\\\"Remove incompatible clone bundle manifest entries.\\n \\n     Accepts a list of entries parsed with ``parseclonebundlesmanifest``\\n     and returns a new list consisting of only the entries that this client\\n     should be able to apply.\\n \\n     There is no guarantee we'll be able to apply all returned entries because\\n     the metadata we use to filter on may be missing or wrong.\\n     \\\"\\\"\\\"\\n     newentries = []\\n     for entry in entries:\\n         spec = entry.get('BUNDLESPEC')\\n         if spec:\\n             try:\\n                 bundlespec = parsebundlespec(repo, spec, strict=True)\\n \\n                 # If a stream clone was requested, filter out non-streamclone\\n                 # entries.\\n                 if streamclonerequested and not isstreamclonespec(bundlespec):\\n                     repo.ui.debug('filtering %s because not a stream clone\\\\n' %\\n                                   entry['URL'])\\n                     continue\\n \\n             except error.InvalidBundleSpecification as e:\\n                 repo.ui.debug(stringutil.forcebytestr(e) + '\\\\n')\\n                 continue\\n             except error.UnsupportedBundleSpecification as e:\\n                 repo.ui.debug('filtering %s because unsupported bundle '\\n                               'spec: %s\\\\n' % (\\n                                   entry['URL'], stringutil.forcebytestr(e)))\\n                 continue\\n         # If we don't have a spec and requested a stream clone, we don't know\\n         # what the entry is so don't attempt to apply it.\\n         elif streamclonerequested:\\n             repo.ui.debug('filtering %s because cannot determine if a stream '\\n                           'clone bundle\\\\n' % entry['URL'])\\n             continue\\n \\n         if 'REQUIRESNI' in entry and not sslutil.hassni:\\n             repo.ui.debug('filtering %s because SNI not supported\\\\n' %\\n                           entry['URL'])\\n             continue\\n \\n         newentries.append(entry)\\n \\n     return newentries\\n \\n class clonebundleentry(object):\\n     \\\"\\\"\\\"Represents an item in a clone bundles manifest.\\n \\n     This rich class is needed to support sorting since sorted() in Python 3\\n     doesn't support ``cmp`` and our comparison is complex enough that ``key=``\\n     won't work.\\n     \\\"\\\"\\\"\\n \\n     def __init__(self, value, prefers):\\n         self.value = value\\n         self.prefers = prefers\\n \\n     def _cmp(self, other):\\n         for prefkey, prefvalue in self.prefers:\\n             avalue = self.value.get(prefkey)\\n             bvalue = other.value.get(prefkey)\\n \\n             # Special case for b missing attribute and a matches exactly.\\n             if avalue is not None and bvalue is None and avalue == prefvalue:\\n                 return -1\\n \\n             # Special case for a missing attribute and b matches exactly.\\n             if bvalue is not None and avalue is None and bvalue == prefvalue:\\n                 return 1\\n \\n             # We can't compare unless attribute present on both.\\n             if avalue is None or bvalue is None:\\n                 continue\\n \\n             # Same values should fall back to next attribute.\\n             if avalue == bvalue:\\n                 continue\\n \\n             # Exact matches come first.\\n             if avalue == prefvalue:\\n                 return -1\\n             if bvalue == prefvalue:\\n                 return 1\\n \\n             # Fall back to next attribute.\\n             continue\\n \\n         # If we got here we couldn't sort by attributes and prefers. Fall\\n         # back to index order.\\n         return 0\\n \\n     def __lt__(self, other):\\n         return self._cmp(other) \\u003c 0\\n \\n     def __gt__(self, other):\\n         return self._cmp(other) \\u003e 0\\n \\n     def __eq__(self, other):\\n         return self._cmp(other) == 0\\n \\n     def __le__(self, other):\\n         return self._cmp(other) \\u003c= 0\\n \\n     def __ge__(self, other):\\n         return self._cmp(other) \\u003e= 0\\n \\n     def __ne__(self, other):\\n         return self._cmp(other) != 0\\n \\n def sortclonebundleentries(ui, entries):\\n     prefers = ui.configlist('ui', 'clonebundleprefers')\\n     if not prefers:\\n         return list(entries)\\n \\n     prefers = [p.split('=', 1) for p in prefers]\\n \\n     items = sorted(clonebundleentry(v, prefers) for v in entries)\\n     return [i.value for i in items]\\n \\n def trypullbundlefromurl(ui, repo, url):\\n     \\\"\\\"\\\"Attempt to apply a bundle from a URL.\\\"\\\"\\\"\\n     with repo.lock(), repo.transaction('bundleurl') as tr:\\n         try:\\n             fh = urlmod.open(ui, url)\\n             cg = readbundle(ui, fh, 'stream')\\n \\n             if isinstance(cg, streamclone.streamcloneapplier):\\n                 cg.apply(repo)\\n             else:\\n                 bundle2.applybundle(repo, cg, tr, 'clonebundles', url)\\n             return True\\n         except urlerr.httperror as e:\\n             ui.warn(_('HTTP error fetching bundle: %s\\\\n') %\\n                     stringutil.forcebytestr(e))\\n         except urlerr.urlerror as e:\\n             ui.warn(_('error fetching bundle: %s\\\\n') %\\n                     stringutil.forcebytestr(e.reason))\\n \\n         return False\\n\"}]}],\"properties\":[]}},\"error_code\":null,\"error_info\":null}"
+                }
+            }, 
+            "request": {
+                "headers": {
+                    "content-length": [
+                        "59"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+861-aa7e312375cf)"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ]
+                }, 
+                "uri": "https://phab.mercurial-scm.org//api/differential.querydiffs", 
+                "method": "POST", 
+                "body": "ids%5B0%5D=11058&api.token=cli-hahayouwish"
+            }
+        }, 
+        {
+            "response": {
+                "headers": {
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fsh6hsdu5dzfurm5gsiy2cmi6kqw33cqikoawcqz2; expires=Thu, 14-Sep-2023 04:15:58 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:15:58 GMT"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ]
+                }, 
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "body": {
+                    "string": "{\"result\":\"diff --git a\\/tests\\/wireprotohelpers.sh b\\/tests\\/wireprotohelpers.sh\\n--- a\\/tests\\/wireprotohelpers.sh\\n+++ b\\/tests\\/wireprotohelpers.sh\\n@@ -56,3 +56,10 @@\\n web.api.http-v2 = true\\n EOF\\n }\\n+\\n+enablehttpv2client() {\\n+  cat \\u003e\\u003e $HGRCPATH \\u003c\\u003c EOF\\n+[experimental]\\n+httppeer.advertise-v2 = true\\n+EOF\\n+}\\ndiff --git a\\/tests\\/test-wireproto-exchangev2.t b\\/tests\\/test-wireproto-exchangev2.t\\nnew file mode 100644\\n--- \\/dev\\/null\\n+++ b\\/tests\\/test-wireproto-exchangev2.t\\n@@ -0,0 +1,53 @@\\n+Tests for wire protocol version 2 exchange.\\n+Tests in this file should be folded into existing tests once protocol\\n+v2 has enough features that it can be enabled via #testcase in existing\\n+tests.\\n+\\n+  $ . $TESTDIR\\/wireprotohelpers.sh\\n+  $ enablehttpv2client\\n+\\n+  $ hg init server-simple\\n+  $ enablehttpv2 server-simple\\n+  $ cd server-simple\\n+  $ cat \\u003e\\u003e .hg\\/hgrc \\u003c\\u003c EOF\\n+  \\u003e [phases]\\n+  \\u003e publish = false\\n+  \\u003e EOF\\n+  $ echo a0 \\u003e a\\n+  $ echo b0 \\u003e b\\n+  $ hg -q commit -A -m 'commit 0'\\n+\\n+  $ echo a1 \\u003e a\\n+  $ hg commit -m 'commit 1'\\n+  $ hg phase --public -r .\\n+  $ echo a2 \\u003e a\\n+  $ hg commit -m 'commit 2'\\n+\\n+  $ hg -q up -r 0\\n+  $ echo b1 \\u003e b\\n+  $ hg -q commit -m 'head 2 commit 1'\\n+  $ echo b2 \\u003e b\\n+  $ hg -q commit -m 'head 2 commit 2'\\n+\\n+  $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log\\n+  $ cat hg.pid \\u003e $DAEMON_PIDS\\n+\\n+  $ cd ..\\n+\\n+Test basic clone\\n+\\n+  $ hg --debug clone -U http:\\/\\/localhost:$HGPORT client-simple\\n+  using http:\\/\\/localhost:$HGPORT\\/\\n+  sending capabilities command\\n+  query 1; heads\\n+  sending 2 commands\\n+  sending command heads: {}\\n+  sending command known: {\\n+    'nodes': []\\n+  }\\n+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)\\n+  received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)\\n+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)\\n+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)\\n+  received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)\\n+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)\\ndiff --git a\\/mercurial\\/httppeer.py b\\/mercurial\\/httppeer.py\\n--- a\\/mercurial\\/httppeer.py\\n+++ b\\/mercurial\\/httppeer.py\\n@@ -802,7 +802,8 @@\\n             return True\\n \\n         # Other concepts.\\n-        if name in ('bundle2',):\\n+        # TODO remove exchangev2 once we have a command implemented.\\n+        if name in ('bundle2', 'exchangev2'):\\n             return True\\n \\n         # Alias command-* to presence of command of that name.\\ndiff --git a\\/mercurial\\/exchangev2.py b\\/mercurial\\/exchangev2.py\\nnew file mode 100644\\n--- \\/dev\\/null\\n+++ b\\/mercurial\\/exchangev2.py\\n@@ -0,0 +1,55 @@\\n+# exchangev2.py - repository exchange for wire protocol version 2\\n+#\\n+# Copyright 2018 Gregory Szorc \\u003cgregory.szorc@gmail.com\\u003e\\n+#\\n+# This software may be used and distributed according to the terms of the\\n+# GNU General Public License version 2 or any later version.\\n+\\n+from __future__ import absolute_import\\n+\\n+from .node import (\\n+    nullid,\\n+)\\n+from . import (\\n+    setdiscovery,\\n+)\\n+\\n+def pull(pullop):\\n+    \\\"\\\"\\\"Pull using wire protocol version 2.\\\"\\\"\\\"\\n+    repo = pullop.repo\\n+    remote = pullop.remote\\n+\\n+    # Figure out what needs to be fetched.\\n+    common, fetch, remoteheads = _pullchangesetdiscovery(\\n+        repo, remote, pullop.heads, abortwhenunrelated=pullop.force)\\n+\\n+def _pullchangesetdiscovery(repo, remote, heads, abortwhenunrelated=True):\\n+    \\\"\\\"\\\"Determine which changesets need to be pulled.\\\"\\\"\\\"\\n+\\n+    if heads:\\n+        knownnode = repo.changelog.hasnode\\n+        if all(knownnode(head) for head in heads):\\n+            return heads, False, heads\\n+\\n+    # TODO wire protocol version 2 is capable of more efficient discovery\\n+    # than setdiscovery. Consider implementing something better.\\n+    common, fetch, remoteheads = setdiscovery.findcommonheads(\\n+        repo.ui, repo, remote, abortwhenunrelated=abortwhenunrelated)\\n+\\n+    common = set(common)\\n+    remoteheads = set(remoteheads)\\n+\\n+    # If a remote head is filtered locally, put it back in the common set.\\n+    # See the comment in exchange._pulldiscoverychangegroup() for more.\\n+\\n+    if fetch and remoteheads:\\n+        nodemap = repo.unfiltered().changelog.nodemap\\n+\\n+        common |= {head for head in remoteheads if head in nodemap}\\n+\\n+        if set(remoteheads).issubset(common):\\n+            fetch = []\\n+\\n+    common.discard(nullid)\\n+\\n+    return common, fetch, remoteheads\\ndiff --git a\\/mercurial\\/exchange.py b\\/mercurial\\/exchange.py\\n--- a\\/mercurial\\/exchange.py\\n+++ b\\/mercurial\\/exchange.py\\n@@ -26,6 +26,7 @@\\n     changegroup,\\n     discovery,\\n     error,\\n+    exchangev2,\\n     lock as lockmod,\\n     logexchange,\\n     narrowspec,\\n@@ -1506,17 +1507,21 @@\\n \\n     pullop.trmanager = transactionmanager(repo, 'pull', remote.url())\\n     with repo.wlock(), repo.lock(), pullop.trmanager:\\n-        # This should ideally be in _pullbundle2(). However, it needs to run\\n-        # before discovery to avoid extra work.\\n-        _maybeapplyclonebundle(pullop)\\n-        streamclone.maybeperformlegacystreamclone(pullop)\\n-        _pulldiscovery(pullop)\\n-        if pullop.canusebundle2:\\n-            _fullpullbundle2(repo, pullop)\\n-        _pullchangeset(pullop)\\n-        _pullphase(pullop)\\n-        _pullbookmarks(pullop)\\n-        _pullobsolete(pullop)\\n+        # Use the modern wire protocol, if available.\\n+        if remote.capable('exchangev2'):\\n+            exchangev2.pull(pullop)\\n+        else:\\n+            # This should ideally be in _pullbundle2(). However, it needs to run\\n+            # before discovery to avoid extra work.\\n+            _maybeapplyclonebundle(pullop)\\n+            streamclone.maybeperformlegacystreamclone(pullop)\\n+            _pulldiscovery(pullop)\\n+            if pullop.canusebundle2:\\n+                _fullpullbundle2(repo, pullop)\\n+            _pullchangeset(pullop)\\n+            _pullphase(pullop)\\n+            _pullbookmarks(pullop)\\n+            _pullobsolete(pullop)\\n \\n     # storing remotenames\\n     if repo.ui.configbool('experimental', 'remotenames'):\\n\\n\",\"error_code\":null,\"error_info\":null}"
+                }
+            }, 
+            "request": {
+                "headers": {
+                    "content-length": [
+                        "55"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+861-aa7e312375cf)"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ]
+                }, 
+                "uri": "https://phab.mercurial-scm.org//api/differential.getrawdiff", 
+                "method": "POST", 
+                "body": "diffID=11058&api.token=cli-hahayouwish"
+            }
+        }
+    ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/phabricator/phabread-conduit-error.json	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,70 @@
+{
+    "interactions": [
+        {
+            "response": {
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":\"ERR-INVALID-AUTH\",\"error_info\":\"API token \\\"cli-notavalidtoken\\\" has the wrong length. API tokens should be 32 characters long.\"}"
+                }, 
+                "headers": {
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F6jvmizfvgaa6bkls264secsim5nlgid4vj55jpe6; expires=Thu, 14-Sep-2023 04:38:21 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:38:21 GMT"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ]
+                }, 
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }
+            }, 
+            "request": {
+                "body": "api.token=cli-notavalidtoken&ids%5B0%5D=4480", 
+                "headers": {
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "content-length": [
+                        "44"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+861-aa7e312375cf)"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ]
+                }, 
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.query"
+            }
+        }
+    ], 
+    "version": 1
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/phabricator/phabsend-create-alpha.json	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,590 @@
+{
+    "version": 1, 
+    "interactions": [
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"data\":[{\"id\":2,\"type\":\"REPO\",\"phid\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"fields\":{\"name\":\"Mercurial\",\"vcs\":\"hg\",\"callsign\":\"HG\",\"shortName\":null,\"status\":\"active\",\"isImporting\":false,\"spacePHID\":null,\"dateCreated\":1498761653,\"dateModified\":1500403184,\"policy\":{\"view\":\"public\",\"edit\":\"admin\",\"diffusion.push\":\"users\"}},\"attachments\":{}}],\"maps\":{},\"query\":{\"queryKey\":null},\"cursor\":{\"limit\":100,\"after\":null,\"before\":null,\"order\":null}},\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F4wycgjx3wajuukr7ggfpqedpe7czucr7mvmaems3; expires=Thu, 14-Sep-2023 04:47:40 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:40 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/diffusion.repository.search", 
+                "body": "constraints%5Bcallsigns%5D%5B0%5D=HG&api.token=cli-hahayouwish", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "79"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }, 
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"id\":11072,\"phid\":\"PHID-DIFF-xm6cw76uivc6g56xiuv2\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/11072\\/\"},\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fll65pt562b6d7ifhjva4jwqqzxh2oopj4tuc6lfa; expires=Thu, 14-Sep-2023 04:47:40 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:40 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.createrawdiff", 
+                "body": "repositoryPHID=PHID-REPO-bvunnehri4u2isyr7bc3&diff=diff+--git+a%2Falpha+b%2Falpha%0Anew+file+mode+100644%0A---+%2Fdev%2Fnull%0A%2B%2B%2B+b%2Falpha%0A%40%40+-0%2C0+%2B1%2C1+%40%40%0A%2Balpha%0A&api.token=cli-hahayouwish", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "235"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }, 
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F5ivszbehkvbetlnks7omsqmbsu7r5by3p3yqw3ep; expires=Thu, 14-Sep-2023 04:47:41 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:41 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "body": "data=%7B%22date%22%3A+%220+0%22%2C+%22node%22%3A+%225206a4fa1e6cd7dbc027640267c109e05a9d2341%22%2C+%22user%22%3A+%22test%22%2C+%22parent%22%3A+%220000000000000000000000000000000000000000%22%7D&name=hg%3Ameta&diff_id=11072&api.token=cli-hahayouwish", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "264"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }, 
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fxvwxxrmwpjntx6dlohrstyox7yjssdbzufiwygcg; expires=Thu, 14-Sep-2023 04:47:41 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:41 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "body": "data=%7B%225206a4fa1e6cd7dbc027640267c109e05a9d2341%22%3A+%7B%22time%22%3A+0.0%2C+%22author%22%3A+%22test%22%2C+%22authorEmail%22%3A+%22test%22%7D%7D&name=local%3Acommits&diff_id=11072&api.token=cli-hahayouwish", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "227"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }, 
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"create alpha for phabricator test\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"}},\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fy3s5iysh6h2javfdo2u7myspyjypv4mvojegqr6j; expires=Thu, 14-Sep-2023 04:47:42 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:42 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage", 
+                "body": "corpus=create+alpha+for+phabricator+test&api.token=cli-hahayouwish", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "83"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }, 
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"object\":{\"id\":4596,\"phid\":\"PHID-DREV-bntcdwe74cw3vwkzt6nq\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-mnqxquobbhdgttd\"},{\"phid\":\"PHID-XACT-DREV-nd34pqrjamxbhop\"},{\"phid\":\"PHID-XACT-DREV-4ka4rghn6b7xooc\"},{\"phid\":\"PHID-XACT-DREV-mfuvfyiijdqwpyg\"},{\"phid\":\"PHID-XACT-DREV-ckar54h6yenx24s\"}]},\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Foe7kd7hhldo25tzbegntkyfxm6wnztgdfmsfubo2; expires=Thu, 14-Sep-2023 04:47:42 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:42 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit", 
+                "body": "transactions%5B0%5D%5Bvalue%5D=PHID-DIFF-xm6cw76uivc6g56xiuv2&transactions%5B0%5D%5Btype%5D=update&transactions%5B1%5D%5Bvalue%5D=create+alpha+for+phabricator+test&transactions%5B1%5D%5Btype%5D=title&api.token=cli-hahayouwish", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "242"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }, 
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":[{\"id\":\"4596\",\"phid\":\"PHID-DREV-bntcdwe74cw3vwkzt6nq\",\"title\":\"create alpha for phabricator test\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D4596\",\"dateCreated\":\"1536986862\",\"dateModified\":\"1536986862\",\"authorPHID\":\"PHID-USER-cgcdlc6c3gpxapbmkwa2\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":[],\"branch\":null,\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"1\",\"activeDiffPHID\":\"PHID-DIFF-xm6cw76uivc6g56xiuv2\",\"diffs\":[\"11072\"],\"commits\":[],\"reviewers\":{\"PHID-PROJ-3dvcxzznrjru2xmmses3\":\"PHID-PROJ-3dvcxzznrjru2xmmses3\"},\"ccs\":[\"PHID-USER-q42dn7cc3donqriafhjx\"],\"hashes\":[],\"auxiliary\":{\"phabricator:projects\":[],\"phabricator:depends-on\":[]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":null}],\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F5d2bgafhoqhg5thqxeu6y4fngq7lqezf5h6eo5pd; expires=Thu, 14-Sep-2023 04:47:43 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:43 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.query", 
+                "body": "api.token=cli-hahayouwish&ids%5B0%5D=4596", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "58"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }, 
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F2cewrqifmvko6evm2sy2nvksvcvhk6hpsj36lcv2; expires=Thu, 14-Sep-2023 04:47:43 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:43 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "body": "data=%7B%22date%22%3A+%220+0%22%2C+%22node%22%3A+%22d8f232f7d799e1064d3da179df41a2b5d04334e9%22%2C+%22user%22%3A+%22test%22%2C+%22parent%22%3A+%220000000000000000000000000000000000000000%22%7D&name=hg%3Ameta&diff_id=11072&api.token=cli-hahayouwish", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "264"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }, 
+        {
+            "response": {
+                "status": {
+                    "message": "OK", 
+                    "code": 200
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }, 
+                "headers": {
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fped6v7jlldydnkfolkdmecyyjrkciqhkr7opvbt2; expires=Thu, 14-Sep-2023 04:47:44 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:47:44 GMT"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ]
+                }
+            }, 
+            "request": {
+                "method": "POST", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "body": "data=%7B%22d8f232f7d799e1064d3da179df41a2b5d04334e9%22%3A+%7B%22time%22%3A+0.0%2C+%22author%22%3A+%22test%22%2C+%22authorEmail%22%3A+%22test%22%7D%7D&name=local%3Acommits&diff_id=11072&api.token=cli-hahayouwish", 
+                "headers": {
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-length": [
+                        "227"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+866-5f07496726a1+20180915)"
+                    ]
+                }
+            }
+        }
+    ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/phabricator/phabsend-update-alpha-create-beta.json	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,915 @@
+{
+    "version": 1, 
+    "interactions": [
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&revisionIDs%5B0%5D=4596", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.querydiffs", 
+                "headers": {
+                    "content-length": [
+                        "66"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F5bjqjyefdbiq65cc3qepzxq7ncczgfqo2xxsybaf; expires=Thu, 14-Sep-2023 04:53:46 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:46 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"11073\":{\"id\":\"11073\",\"revisionID\":\"4596\",\"dateCreated\":\"1536986866\",\"dateModified\":\"1536986868\",\"sourceControlBaseRevision\":null,\"sourceControlPath\":null,\"sourceControlSystem\":null,\"branch\":null,\"bookmark\":null,\"creationMethod\":\"web\",\"description\":null,\"unitStatus\":\"4\",\"lintStatus\":\"4\",\"changes\":[{\"id\":\"24417\",\"metadata\":{\"line:first\":1},\"oldPath\":null,\"currentPath\":\"alpha\",\"awayPaths\":[],\"oldProperties\":[],\"newProperties\":{\"unix:filemode\":\"100644\"},\"type\":\"1\",\"fileType\":\"1\",\"commitHash\":null,\"addLines\":\"2\",\"delLines\":\"0\",\"hunks\":[{\"oldOffset\":\"0\",\"newOffset\":\"1\",\"oldLength\":\"0\",\"newLength\":\"2\",\"addLines\":null,\"delLines\":null,\"isMissingOldNewline\":null,\"isMissingNewNewline\":null,\"corpus\":\"+alpha\\n+more\\n\"}]}],\"properties\":{\"hg:meta\":{\"parent\":\"0000000000000000000000000000000000000000\",\"node\":\"f70265671c65ab4b5416e611a6bd61887c013122\",\"user\":\"test\",\"date\":\"0 0\"},\"local:commits\":{\"f70265671c65ab4b5416e611a6bd61887c013122\":{\"time\":0,\"authorEmail\":\"test\",\"author\":\"test\"}}},\"authorName\":\"test\",\"authorEmail\":\"test\"},\"11072\":{\"id\":\"11072\",\"revisionID\":\"4596\",\"dateCreated\":\"1536986860\",\"dateModified\":\"1536986862\",\"sourceControlBaseRevision\":null,\"sourceControlPath\":null,\"sourceControlSystem\":null,\"branch\":null,\"bookmark\":null,\"creationMethod\":\"web\",\"description\":null,\"unitStatus\":\"4\",\"lintStatus\":\"4\",\"changes\":[{\"id\":\"24416\",\"metadata\":{\"line:first\":1},\"oldPath\":null,\"currentPath\":\"alpha\",\"awayPaths\":[],\"oldProperties\":[],\"newProperties\":{\"unix:filemode\":\"100644\"},\"type\":\"1\",\"fileType\":\"1\",\"commitHash\":null,\"addLines\":\"1\",\"delLines\":\"0\",\"hunks\":[{\"oldOffset\":\"0\",\"newOffset\":\"1\",\"oldLength\":\"0\",\"newLength\":\"1\",\"addLines\":null,\"delLines\":null,\"isMissingOldNewline\":null,\"isMissingNewNewline\":null,\"corpus\":\"+alpha\\n\"}]}],\"properties\":{\"hg:meta\":{\"date\":\"0 0\",\"node\":\"d8f232f7d799e1064d3da179df41a2b5d04334e9\",\"user\":\"test\",\"parent\":\"0000000000000000000000000000000000000000\"},\"local:commits\":{\"d8f232f7d799e1064d3da179df41a2b5d04334e9\":{\"time\":0,\"author\":\"test\",\"authorEmail\":\"test\"}}},\"authorName\":\"test\",\"authorEmail\":\"test\"}},\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "diff_id=11073&api.token=cli-hahayouwish&data=%7B%22parent%22%3A+%220000000000000000000000000000000000000000%22%2C+%22node%22%3A+%22f70265671c65ab4b5416e611a6bd61887c013122%22%2C+%22user%22%3A+%22test%22%2C+%22date%22%3A+%220+0%22%7D&name=hg%3Ameta", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "headers": {
+                    "content-length": [
+                        "264"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Ff6o4ingm2wmr3ma4aht2kytfrrxvrkitj6ipkf5k; expires=Thu, 14-Sep-2023 04:53:46 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:46 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "diff_id=11073&api.token=cli-hahayouwish&data=%7B%22f70265671c65ab4b5416e611a6bd61887c013122%22%3A+%7B%22time%22%3A+0.0%2C+%22authorEmail%22%3A+%22test%22%2C+%22author%22%3A+%22test%22%7D%7D&name=local%3Acommits", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "headers": {
+                    "content-length": [
+                        "227"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F4fitvy4kno46zkca6hq7npvuxvnh4dxlbvscmodb; expires=Thu, 14-Sep-2023 04:53:47 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:47 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&corpus=create+alpha+for+phabricator+test%0A%0ADifferential+Revision%3A+https%3A%2F%2Fphab.mercurial-scm.org%2FD4596", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage", 
+                "headers": {
+                    "content-length": [
+                        "158"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F7u2j7nsrtq2dtxqws7pnsnjyaufsamwj44e45euz; expires=Thu, 14-Sep-2023 04:53:47 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:47 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"create alpha for phabricator test\",\"revisionID\":4596},\"revisionIDFieldInfo\":{\"value\":4596,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"}},\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&objectIdentifier=4596&transactions%5B0%5D%5Btype%5D=title&transactions%5B0%5D%5Bvalue%5D=create+alpha+for+phabricator+test", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit", 
+                "headers": {
+                    "content-length": [
+                        "165"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F7ubtculubfazivfxjxbmnyt3wzjcgdxnfdn57t42; expires=Thu, 14-Sep-2023 04:53:48 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:47 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"object\":{\"id\":\"4596\",\"phid\":\"PHID-DREV-bntcdwe74cw3vwkzt6nq\"},\"transactions\":[]},\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&constraints%5Bcallsigns%5D%5B0%5D=HG", 
+                "uri": "https://phab.mercurial-scm.org//api/diffusion.repository.search", 
+                "headers": {
+                    "content-length": [
+                        "79"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fdpvy3rwephm5krs7posuadvjmkh7o7wbytgdhisv; expires=Thu, 14-Sep-2023 04:53:48 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:48 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"data\":[{\"id\":2,\"type\":\"REPO\",\"phid\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"fields\":{\"name\":\"Mercurial\",\"vcs\":\"hg\",\"callsign\":\"HG\",\"shortName\":null,\"status\":\"active\",\"isImporting\":false,\"spacePHID\":null,\"dateCreated\":1498761653,\"dateModified\":1500403184,\"policy\":{\"view\":\"public\",\"edit\":\"admin\",\"diffusion.push\":\"users\"}},\"attachments\":{}}],\"maps\":{},\"query\":{\"queryKey\":null},\"cursor\":{\"limit\":100,\"after\":null,\"before\":null,\"order\":null}},\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&diff=diff+--git+a%2Fbeta+b%2Fbeta%0Anew+file+mode+100644%0A---+%2Fdev%2Fnull%0A%2B%2B%2B+b%2Fbeta%0A%40%40+-0%2C0+%2B1%2C1+%40%40%0A%2Bbeta%0A&repositoryPHID=PHID-REPO-bvunnehri4u2isyr7bc3", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.createrawdiff", 
+                "headers": {
+                    "content-length": [
+                        "231"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fafqgsnm7vbqi3vyfg5c7xgxyiv7fgi77vauw6wnv; expires=Thu, 14-Sep-2023 04:53:49 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:49 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"id\":11074,\"phid\":\"PHID-DIFF-sitmath22fwgsfsbdmne\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/differential\\/diff\\/11074\\/\"},\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "diff_id=11074&api.token=cli-hahayouwish&data=%7B%22parent%22%3A+%22f70265671c65ab4b5416e611a6bd61887c013122%22%2C+%22node%22%3A+%221a5640df7bbfc26fc4f6ef38e4d1581d5b2a3122%22%2C+%22user%22%3A+%22test%22%2C+%22date%22%3A+%220+0%22%7D&name=hg%3Ameta", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "headers": {
+                    "content-length": [
+                        "264"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Frvpld6nyjmtrq3qynmldbquhgwbrhcdhythbot6r; expires=Thu, 14-Sep-2023 04:53:49 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:49 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "diff_id=11074&api.token=cli-hahayouwish&data=%7B%221a5640df7bbfc26fc4f6ef38e4d1581d5b2a3122%22%3A+%7B%22time%22%3A+0.0%2C+%22authorEmail%22%3A+%22test%22%2C+%22author%22%3A+%22test%22%7D%7D&name=local%3Acommits", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "headers": {
+                    "content-length": [
+                        "227"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Flpkv333zitgztqx2clpg2uibjy633myliembguf2; expires=Thu, 14-Sep-2023 04:53:50 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:49 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&corpus=create+beta+for+phabricator+test", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.parsecommitmessage", 
+                "headers": {
+                    "content-length": [
+                        "82"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fav6ovbqxoy3dijysouoabcz7jqescejugeedwspi; expires=Thu, 14-Sep-2023 04:53:50 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:50 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"errors\":[],\"fields\":{\"title\":\"create beta for phabricator test\"},\"revisionIDFieldInfo\":{\"value\":null,\"validDomain\":\"https:\\/\\/phab.mercurial-scm.org\"}},\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&transactions%5B0%5D%5Btype%5D=update&transactions%5B0%5D%5Bvalue%5D=PHID-DIFF-sitmath22fwgsfsbdmne&transactions%5B1%5D%5Btype%5D=summary&transactions%5B1%5D%5Bvalue%5D=Depends+on+D4596&transactions%5B2%5D%5Btype%5D=summary&transactions%5B2%5D%5Bvalue%5D=+&transactions%5B3%5D%5Btype%5D=title&transactions%5B3%5D%5Bvalue%5D=create+beta+for+phabricator+test", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.revision.edit", 
+                "headers": {
+                    "content-length": [
+                        "398"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fywrdtdafcn5p267qiqfgfh7h4buaqxmnrgan6fh2; expires=Thu, 14-Sep-2023 04:53:50 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:50 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":{\"object\":{\"id\":4597,\"phid\":\"PHID-DREV-as7flhipq636gqvnyrsf\"},\"transactions\":[{\"phid\":\"PHID-XACT-DREV-bwzosyyqmzlhe6g\"},{\"phid\":\"PHID-XACT-DREV-ina5ktuwp6eiwv6\"},{\"phid\":\"PHID-XACT-DREV-22bjztn3szeyicy\"},{\"phid\":\"PHID-XACT-DREV-kcv6zk2yboepbmo\"},{\"phid\":\"PHID-XACT-DREV-mnbp6f6sq54hzs2\"},{\"phid\":\"PHID-XACT-DREV-qlakltzsdzclpha\"},{\"phid\":\"PHID-XACT-DREV-a5347cobhvqnc22\"},{\"phid\":\"PHID-XACT-DREV-sciqq5cqfuqfh67\"}]},\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "api.token=cli-hahayouwish&ids%5B0%5D=4596&ids%5B1%5D=4597", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.query", 
+                "headers": {
+                    "content-length": [
+                        "74"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2F2iio6iugurtd7ml2tnwfwv24hkrfhs62yshvmouv; expires=Thu, 14-Sep-2023 04:53:51 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:51 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":[{\"id\":\"4597\",\"phid\":\"PHID-DREV-as7flhipq636gqvnyrsf\",\"title\":\"create beta for phabricator test\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D4597\",\"dateCreated\":\"1536987231\",\"dateModified\":\"1536987231\",\"authorPHID\":\"PHID-USER-cgcdlc6c3gpxapbmkwa2\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":[],\"branch\":null,\"summary\":\" \",\"testPlan\":\"\",\"lineCount\":\"1\",\"activeDiffPHID\":\"PHID-DIFF-sitmath22fwgsfsbdmne\",\"diffs\":[\"11074\"],\"commits\":[],\"reviewers\":{\"PHID-PROJ-3dvcxzznrjru2xmmses3\":\"PHID-PROJ-3dvcxzznrjru2xmmses3\"},\"ccs\":[\"PHID-USER-q42dn7cc3donqriafhjx\"],\"hashes\":[],\"auxiliary\":{\"phabricator:projects\":[],\"phabricator:depends-on\":[\"PHID-DREV-bntcdwe74cw3vwkzt6nq\"]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":null},{\"id\":\"4596\",\"phid\":\"PHID-DREV-bntcdwe74cw3vwkzt6nq\",\"title\":\"create alpha for phabricator test\",\"uri\":\"https:\\/\\/phab.mercurial-scm.org\\/D4596\",\"dateCreated\":\"1536986862\",\"dateModified\":\"1536987231\",\"authorPHID\":\"PHID-USER-cgcdlc6c3gpxapbmkwa2\",\"status\":\"0\",\"statusName\":\"Needs Review\",\"properties\":[],\"branch\":null,\"summary\":\"\",\"testPlan\":\"\",\"lineCount\":\"2\",\"activeDiffPHID\":\"PHID-DIFF-vwre7kpjdq52wbt56ftl\",\"diffs\":[\"11073\",\"11072\"],\"commits\":[],\"reviewers\":{\"PHID-PROJ-3dvcxzznrjru2xmmses3\":\"PHID-PROJ-3dvcxzznrjru2xmmses3\"},\"ccs\":[\"PHID-USER-q42dn7cc3donqriafhjx\"],\"hashes\":[],\"auxiliary\":{\"phabricator:projects\":[],\"phabricator:depends-on\":[]},\"repositoryPHID\":\"PHID-REPO-bvunnehri4u2isyr7bc3\",\"sourcePath\":null}],\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "diff_id=11074&api.token=cli-hahayouwish&data=%7B%22parent%22%3A+%22f70265671c65ab4b5416e611a6bd61887c013122%22%2C+%22node%22%3A+%22c2b605ada280b38c38031b5d31622869c72b0d8d%22%2C+%22user%22%3A+%22test%22%2C+%22date%22%3A+%220+0%22%7D&name=hg%3Ameta", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "headers": {
+                    "content-length": [
+                        "264"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fvwsd2gtkeg64gticvthsxnpufne42t4eqityra25; expires=Thu, 14-Sep-2023 04:53:52 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:52 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }, 
+        {
+            "request": {
+                "body": "diff_id=11074&api.token=cli-hahayouwish&data=%7B%22c2b605ada280b38c38031b5d31622869c72b0d8d%22%3A+%7B%22time%22%3A+0.0%2C+%22authorEmail%22%3A+%22test%22%2C+%22author%22%3A+%22test%22%7D%7D&name=local%3Acommits", 
+                "uri": "https://phab.mercurial-scm.org//api/differential.setdiffproperty", 
+                "headers": {
+                    "content-length": [
+                        "227"
+                    ], 
+                    "host": [
+                        "phab.mercurial-scm.org"
+                    ], 
+                    "content-type": [
+                        "application/x-www-form-urlencoded"
+                    ], 
+                    "accept": [
+                        "application/mercurial-0.1"
+                    ], 
+                    "user-agent": [
+                        "mercurial/proto-1.0 (Mercurial 4.7.1+867-34bcd3af7109+20180915)"
+                    ]
+                }, 
+                "method": "POST"
+            }, 
+            "response": {
+                "status": {
+                    "code": 200, 
+                    "message": "OK"
+                }, 
+                "headers": {
+                    "server": [
+                        "Apache/2.4.10 (Debian)"
+                    ], 
+                    "strict-transport-security": [
+                        "max-age=0; includeSubdomains; preload"
+                    ], 
+                    "x-frame-options": [
+                        "Deny"
+                    ], 
+                    "x-content-type-options": [
+                        "nosniff"
+                    ], 
+                    "expires": [
+                        "Sat, 01 Jan 2000 00:00:00 GMT"
+                    ], 
+                    "set-cookie": [
+                        "phsid=A%2Fflxjbmx24qcq7qhggolo6b7iue7utwp7kyoazduk; expires=Thu, 14-Sep-2023 04:53:52 GMT; Max-Age=157680000; path=/; domain=phab.mercurial-scm.org; secure; httponly"
+                    ], 
+                    "x-xss-protection": [
+                        "1; mode=block"
+                    ], 
+                    "content-type": [
+                        "application/json"
+                    ], 
+                    "cache-control": [
+                        "no-store"
+                    ], 
+                    "date": [
+                        "Sat, 15 Sep 2018 04:53:52 GMT"
+                    ]
+                }, 
+                "body": {
+                    "string": "{\"result\":null,\"error_code\":null,\"error_info\":null}"
+                }
+            }
+        }
+    ]
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/printrevset.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,41 @@
+from __future__ import absolute_import
+from mercurial import (
+  cmdutil,
+  commands,
+  extensions,
+  logcmdutil,
+  revsetlang,
+  smartset,
+)
+
+from mercurial.utils import (
+  stringutil,
+)
+
+def logrevset(repo, pats, opts):
+    revs = logcmdutil._initialrevs(repo, opts)
+    if not revs:
+        return None
+    match, pats, slowpath = logcmdutil._makematcher(repo, revs, pats, opts)
+    return logcmdutil._makerevset(repo, match, pats, slowpath, opts)
+
+def uisetup(ui):
+    def printrevset(orig, repo, pats, opts):
+        revs, filematcher = orig(repo, pats, opts)
+        if opts.get(b'print_revset'):
+            expr = logrevset(repo, pats, opts)
+            if expr:
+                tree = revsetlang.parse(expr)
+                tree = revsetlang.analyze(tree)
+            else:
+                tree = []
+            ui = repo.ui
+            ui.write(b'%s\n' % stringutil.pprint(opts.get(b'rev', [])))
+            ui.write(revsetlang.prettyformat(tree) + b'\n')
+            ui.write(stringutil.prettyrepr(revs) + b'\n')
+            revs = smartset.baseset()  # display no revisions
+        return revs, filematcher
+    extensions.wrapfunction(logcmdutil, 'getrevs', printrevset)
+    aliases, entry = cmdutil.findcmd(b'log', commands.table)
+    entry[1].append((b'', b'print-revset', False,
+                     b'print generated revset and exit (DEPRECATED)'))
--- a/tests/run-tests.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/run-tests.py	Wed Sep 26 20:33:09 2018 +0900
@@ -64,6 +64,7 @@
 import threading
 import time
 import unittest
+import uuid
 import xml.dom.minidom as minidom
 
 try:
@@ -85,8 +86,6 @@
     except NameError:
         pass
 
-origenviron = os.environ.copy()
-osenvironb = getattr(os, 'environb', os.environ)
 processlock = threading.Lock()
 
 pygmentspresent = False
@@ -140,6 +139,8 @@
     runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
     runnerlexer = TestRunnerLexer()
 
+origenviron = os.environ.copy()
+
 if sys.version_info > (3, 5, 0):
     PYTHON3 = True
     xrange = range # we use xrange in one place, and we'd rather not use range
@@ -153,6 +154,40 @@
             return p
         return p.decode('utf-8')
 
+    osenvironb = getattr(os, 'environb', None)
+    if osenvironb is None:
+        # Windows lacks os.environb, for instance.  A proxy over the real thing
+        # instead of a copy allows the environment to be updated via bytes on
+        # all platforms.
+        class environbytes(object):
+            def __init__(self, strenv):
+                self.__len__ = strenv.__len__
+                self.clear = strenv.clear
+                self._strenv = strenv
+            def __getitem__(self, k):
+                v = self._strenv.__getitem__(_strpath(k))
+                return _bytespath(v)
+            def __setitem__(self, k, v):
+                self._strenv.__setitem__(_strpath(k), _strpath(v))
+            def __delitem__(self, k):
+                self._strenv.__delitem__(_strpath(k))
+            def __contains__(self, k):
+                return self._strenv.__contains__(_strpath(k))
+            def __iter__(self):
+                return iter([_bytespath(k) for k in iter(self._strenv)])
+            def get(self, k, default=None):
+                v = self._strenv.get(_strpath(k), _strpath(default))
+                return _bytespath(v)
+            def pop(self, k, default=None):
+                v = self._strenv.pop(_strpath(k), _strpath(default))
+                return _bytespath(v)
+
+        osenvironb = environbytes(os.environ)
+
+    getcwdb = getattr(os, 'getcwdb')
+    if not getcwdb or os.name == 'nt':
+        getcwdb = lambda: _bytespath(os.getcwd())
+
 elif sys.version_info >= (3, 0, 0):
     print('%s is only supported on Python 3.5+ and 2.7, not %s' %
           (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3])))
@@ -168,6 +203,8 @@
         return p
 
     _strpath = _bytespath
+    osenvironb = os.environ
+    getcwdb = os.getcwd
 
 # For Windows support
 wifexited = getattr(os, "WIFEXITED", lambda x: False)
@@ -220,7 +257,8 @@
 closefds = os.name == 'posix'
 def Popen4(cmd, wd, timeout, env=None):
     processlock.acquire()
-    p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
+    p = subprocess.Popen(_strpath(cmd), shell=True, bufsize=-1,
+                         cwd=_strpath(wd), env=env,
                          close_fds=closefds,
                          stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                          stderr=subprocess.STDOUT)
@@ -285,12 +323,12 @@
 
     If path does not exist, return an empty set.
     """
-    cases = set()
+    cases = []
     try:
         with open(path, 'rb') as f:
             for l in f:
                 if l.startswith(b'#testcases '):
-                    cases.update(l[11:].split())
+                    cases.append(sorted(l[11:].split()))
     except IOError as ex:
         if ex.errno != errno.ENOENT:
             raise
@@ -394,11 +432,6 @@
         metavar="HG",
         help="test using specified hg script rather than a "
              "temporary installation")
-    # This option should be deleted once test-check-py3-compat.t and other
-    # Python 3 tests run with Python 3.
-    hgconf.add_argument("--with-python3", metavar="PYTHON3",
-        help="Python 3 interpreter (if running under Python 2)"
-             " (TEMPORARY)")
 
     reporting = parser.add_argument_group('Results Reporting')
     reporting.add_argument("-C", "--annotate", action="store_true",
@@ -532,27 +565,6 @@
         if PYTHON3:
             parser.error(
                 '--py3k-warnings can only be used on Python 2.7')
-    if options.with_python3:
-        if PYTHON3:
-            parser.error('--with-python3 cannot be used when executing with '
-                         'Python 3')
-
-        options.with_python3 = canonpath(options.with_python3)
-        # Verify Python3 executable is acceptable.
-        proc = subprocess.Popen([options.with_python3, b'--version'],
-                                stdout=subprocess.PIPE,
-                                stderr=subprocess.STDOUT)
-        out, _err = proc.communicate()
-        ret = proc.wait()
-        if ret != 0:
-            parser.error('could not determine version of python 3')
-        if not out.startswith('Python '):
-            parser.error('unexpected output from python3 --version: %s' %
-                         out)
-        vers = version.LooseVersion(out[len('Python '):])
-        if vers < version.LooseVersion('3.5.0'):
-            parser.error('--with-python3 version must be 3.5.0 or greater; '
-                         'got %s' % out)
 
     if options.blacklist:
         options.blacklist = parselistfiles(options.blacklist, 'blacklist')
@@ -1005,7 +1017,7 @@
             return (
                 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
                     c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
-                    for c in p))
+                    for c in [p[i:i + 1] for i in range(len(p))]))
             )
         else:
             return re.escape(p)
@@ -1021,7 +1033,7 @@
         environment."""
         # Put the restoreenv script inside self._threadtmp
         scriptpath = os.path.join(self._threadtmp, b'restoreenv.sh')
-        testenv['HGTEST_RESTOREENV'] = scriptpath
+        testenv['HGTEST_RESTOREENV'] = _strpath(scriptpath)
 
         # Only restore environment variable names that the shell allows
         # us to export.
@@ -1053,22 +1065,26 @@
         env = os.environ.copy()
         env['PYTHONUSERBASE'] = sysconfig.get_config_var('userbase') or ''
         env['HGEMITWARNINGS'] = '1'
-        env['TESTTMP'] = self._testtmp
+        env['TESTTMP'] = _strpath(self._testtmp)
         env['TESTNAME'] = self.name
-        env['HOME'] = self._testtmp
+        env['HOME'] = _strpath(self._testtmp)
         # This number should match portneeded in _getport
         for port in xrange(3):
             # This list should be parallel to _portmap in _getreplacements
             defineport(port)
-        env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
-        env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
+        env["HGRCPATH"] = _strpath(os.path.join(self._threadtmp, b'.hgrc'))
+        env["DAEMON_PIDS"] = _strpath(os.path.join(self._threadtmp,
+                                                   b'daemon.pids'))
         env["HGEDITOR"] = ('"' + sys.executable + '"'
                            + ' -c "import sys; sys.exit(0)"')
         env["HGMERGE"] = "internal:merge"
         env["HGUSER"]   = "test"
         env["HGENCODING"] = "ascii"
         env["HGENCODINGMODE"] = "strict"
+        env["HGHOSTNAME"] = "test-hostname"
         env['HGIPV6'] = str(int(self._useipv6))
+        if 'HGCATAPULTSERVERPIPE' not in env:
+            env['HGCATAPULTSERVERPIPE'] = os.devnull
 
         extraextensions = []
         for opt in self._extraconfigopts:
@@ -1083,7 +1099,7 @@
 
         # LOCALIP could be ::1 or 127.0.0.1. Useful for tests that require raw
         # IP addresses.
-        env['LOCALIP'] = self._localip()
+        env['LOCALIP'] = _strpath(self._localip())
 
         # Reset some environment variables to well-known values so that
         # the tests produce repeatable output.
@@ -1150,7 +1166,8 @@
         Return a tuple (exitcode, output). output is None in debug mode.
         """
         if self._debug:
-            proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
+            proc = subprocess.Popen(_strpath(cmd), shell=True,
+                                    cwd=_strpath(self._testtmp),
                                     env=env)
             ret = proc.wait()
             return (ret, None)
@@ -1164,7 +1181,7 @@
             killdaemons(env['DAEMON_PIDS'])
             return ret
 
-        output = ''
+        output = b''
         proc.tochild.close()
 
         try:
@@ -1188,7 +1205,7 @@
             output = re.sub(s, r, output)
 
         if normalizenewlines:
-            output = output.replace('\r\n', '\n')
+            output = output.replace(b'\r\n', b'\n')
 
         return ret, output.splitlines(True)
 
@@ -1201,7 +1218,7 @@
 
     def _run(self, env):
         py3kswitch = self._py3kwarnings and b' -3' or b''
-        cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
+        cmd = b'"%s"%s "%s"' % (PYTHON, py3kswitch, self.path)
         vlog("# Running", cmd)
         normalizenewlines = os.name == 'nt'
         result = self._runcommand(cmd, env,
@@ -1242,14 +1259,15 @@
 
     def __init__(self, path, *args, **kwds):
         # accept an extra "case" parameter
-        case = kwds.pop('case', None)
+        case = kwds.pop('case', [])
         self._case = case
-        self._allcases = parsettestcases(path)
+        self._allcases = {x for y in parsettestcases(path) for x in y}
         super(TTest, self).__init__(path, *args, **kwds)
         if case:
-            self.name = '%s#%s' % (self.name, _strpath(case))
-            self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
-            self._tmpname += b'-%s' % case
+            casepath = b'#'.join(case)
+            self.name = '%s#%s' % (self.name, _strpath(casepath))
+            self.errpath = b'%s#%s.err' % (self.errpath[:-4], casepath)
+            self._tmpname += b'-%s' % casepath
         self._have = {}
 
     @property
@@ -1323,10 +1341,10 @@
         reqs = []
         for arg in args:
             if arg.startswith(b'no-') and arg[3:] in self._allcases:
-                if arg[3:] == self._case:
+                if arg[3:] in self._case:
                     return False
             elif arg in self._allcases:
-                if arg != self._case:
+                if arg not in self._case:
                     return False
             else:
                 reqs.append(arg)
@@ -1342,6 +1360,24 @@
                 script.append(b'%s %d 0\n' % (salt, line))
             else:
                 script.append(b'echo %s %d $?\n' % (salt, line))
+        active = []
+        session = str(uuid.uuid4())
+        if PYTHON3:
+            session = session.encode('ascii')
+        def toggletrace(cmd):
+            if isinstance(cmd, str):
+                quoted = shellquote(cmd.strip())
+            else:
+                quoted = shellquote(cmd.strip().decode('utf8')).encode('utf8')
+            quoted = quoted.replace(b'\\', b'\\\\')
+            if active:
+                script.append(
+                    b'echo END %s %s >> "$HGCATAPULTSERVERPIPE"\n' % (
+                        session, active[0]))
+                script.append(
+                    b'echo START %s %s >> "$HGCATAPULTSERVERPIPE"\n' % (
+                        session, quoted))
+                active[0:] = [quoted]
 
         script = []
 
@@ -1369,11 +1405,36 @@
             script.append(b'alias hg="%s"\n' % self._hgcommand)
         if os.getenv('MSYSTEM'):
             script.append(b'alias pwd="pwd -W"\n')
+
+        hgcatapult = os.getenv('HGCATAPULTSERVERPIPE')
+        if hgcatapult and hgcatapult != os.devnull:
+            # Kludge: use a while loop to keep the pipe from getting
+            # closed by our echo commands. The still-running file gets
+            # reaped at the end of the script, which causes the while
+            # loop to exit and closes the pipe. Sigh.
+            script.append(
+                b'rtendtracing() {\n'
+                b'  echo END %(session)s %(name)s >> $HGCATAPULTSERVERPIPE\n'
+                b'  rm -f "$TESTTMP/.still-running"\n'
+                b'}\n'
+                b'trap "rtendtracing" 0\n'
+                b'touch "$TESTTMP/.still-running"\n'
+                b'while [ -f "$TESTTMP/.still-running" ]; do sleep 1; done '
+                b'> $HGCATAPULTSERVERPIPE &\n'
+                b'HGCATAPULTSESSION=%(session)s ; export HGCATAPULTSESSION\n'
+                b'echo START %(session)s %(name)s >> $HGCATAPULTSERVERPIPE\n'
+                % {
+                    'name': self.name,
+                    'session': session,
+                }
+            )
+
         if self._case:
+            casestr = b'#'.join(self._case)
             if isinstance(self._case, str):
-                quoted = shellquote(self._case)
+                quoted = shellquote(casestr)
             else:
-                quoted = shellquote(self._case.decode('utf8')).encode('utf8')
+                quoted = shellquote(casestr.decode('utf8')).encode('utf8')
             script.append(b'TESTCASE=%s\n' % quoted)
             script.append(b'export TESTCASE\n')
 
@@ -1419,7 +1480,7 @@
                     # We've just entered a Python block. Add the header.
                     inpython = True
                     addsalt(prepos, False) # Make sure we report the exit code.
-                    script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
+                    script.append(b'"%s" -m heredoctest <<EOF\n' % PYTHON)
                 addsalt(n, True)
                 script.append(l[2:])
             elif l.startswith(b'  ... '): # python inlines
@@ -1433,10 +1494,12 @@
                 prepos = pos
                 pos = n
                 addsalt(n, False)
-                cmd = l[4:].split()
+                rawcmd = l[4:]
+                cmd = rawcmd.split()
+                toggletrace(rawcmd)
                 if len(cmd) == 2 and cmd[0] == b'cd':
                     l = b'  $ cd %s || exit 1\n' % cmd[1]
-                script.append(l[4:])
+                script.append(rawcmd)
             elif l.startswith(b'  > '): # continuations
                 after.setdefault(prepos, []).append(l)
                 script.append(l[4:])
@@ -1455,7 +1518,6 @@
         if skipping is not None:
             after.setdefault(pos, []).append('  !!! missing #endif\n')
         addsalt(n + 1, False)
-
         return salt, script, after, expected
 
     def _processoutput(self, exitcode, output, salt, after, expected):
@@ -1785,10 +1847,8 @@
                 pass
             elif self._options.view:
                 v = self._options.view
-                if PYTHON3:
-                    v = _bytespath(v)
-                os.system(b"%s %s %s" %
-                          (v, test.refpath, test.errpath))
+                os.system(r"%s %s %s" %
+                          (v, _strpath(test.refpath), _strpath(test.errpath)))
             else:
                 servefail, lines = getdiff(expected, got,
                                            test.refpath, test.errpath)
@@ -2464,8 +2524,7 @@
             os.umask(oldmask)
 
     def _run(self, testdescs):
-        self._testdir = osenvironb[b'TESTDIR'] = getattr(
-            os, 'getcwdb', os.getcwd)()
+        self._testdir = osenvironb[b'TESTDIR'] = getcwdb()
         # assume all tests in same folder for now
         if testdescs:
             pathname = os.path.dirname(testdescs[0]['path'])
@@ -2562,9 +2621,6 @@
         osenvironb[b"BINDIR"] = self._bindir
         osenvironb[b"PYTHON"] = PYTHON
 
-        if self.options.with_python3:
-            osenvironb[b'PYTHON3'] = self.options.with_python3
-
         fileb = _bytespath(__file__)
         runtestdir = os.path.abspath(os.path.dirname(fileb))
         osenvironb[b'RUNTESTDIR'] = runtestdir
@@ -2666,31 +2722,42 @@
                 expanded_args.append(arg)
         args = expanded_args
 
-        testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.]+))')
+        testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.#]+))')
         tests = []
         for t in args:
-            case = None
+            case = []
 
             if not (os.path.basename(t).startswith(b'test-')
                     and (t.endswith(b'.py') or t.endswith(b'.t'))):
 
                 m = testcasepattern.match(t)
                 if m is not None:
-                    t, _, case = m.groups()
+                    t, _, casestr = m.groups()
+                    if casestr:
+                        case = casestr.split(b'#')
                 else:
                     continue
 
             if t.endswith(b'.t'):
                 # .t file may contain multiple test cases
-                cases = sorted(parsettestcases(t))
-                if cases:
-                    if case is not None and case in cases:
-                        tests += [{'path': t, 'case': case}]
-                    elif case is not None and case not in cases:
+                casedimensions = parsettestcases(t)
+                if casedimensions:
+                    cases = []
+                    def addcases(case, casedimensions):
+                        if not casedimensions:
+                            cases.append(case)
+                        else:
+                            for c in casedimensions[0]:
+                                addcases(case + [c], casedimensions[1:])
+                    addcases([], casedimensions)
+                    if case and case in cases:
+                        cases = [case]
+                    elif case:
                         # Ignore invalid cases
-                        pass
+                        cases = []
                     else:
-                        tests += [{'path': t, 'case': c} for c in sorted(cases)]
+                        pass
+                    tests += [{'path': t, 'case': c} for c in sorted(cases)]
                 else:
                     tests.append({'path': t})
             else:
@@ -2701,7 +2768,7 @@
         def _reloadtest(test, i):
             # convert a test back to its description dict
             desc = {'path': test.path}
-            case = getattr(test, '_case', None)
+            case = getattr(test, '_case', [])
             if case:
                 desc['case'] = case
             return self._gettest(desc, i)
@@ -2713,7 +2780,8 @@
                     desc = testdescs[0]
                     # desc['path'] is a relative path
                     if 'case' in desc:
-                        errpath = b'%s.%s.err' % (desc['path'], desc['case'])
+                        casestr = b'#'.join(desc['case'])
+                        errpath = b'%s#%s.err' % (desc['path'], casestr)
                     else:
                         errpath = b'%s.err' % desc['path']
                     errpath = os.path.join(self._outputdir, errpath)
@@ -2847,7 +2915,10 @@
         """Configure the environment to use the appropriate Python in tests."""
         # Tests must use the same interpreter as us or bad things will happen.
         pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
-        if getattr(os, 'symlink', None):
+
+        # os.symlink() is a thing with py3 on Windows, but it requires
+        # Administrator rights.
+        if getattr(os, 'symlink', None) and os.name != 'nt':
             vlog("# Making python executable in test path a symlink to '%s'" %
                  sys.executable)
             mypython = os.path.join(self._tmpbindir, pyexename)
@@ -2932,7 +3003,7 @@
         makedirs(self._bindir)
 
         vlog("# Running", cmd)
-        if os.system(cmd) == 0:
+        if os.system(_strpath(cmd)) == 0:
             if not self.options.verbose:
                 try:
                     os.remove(installerrs)
@@ -3086,8 +3157,8 @@
     def _checktools(self):
         """Ensure tools required to run tests are present."""
         for p in self.REQUIREDTOOLS:
-            if os.name == 'nt' and not p.endswith('.exe'):
-                p += '.exe'
+            if os.name == 'nt' and not p.endswith(b'.exe'):
+                p += b'.exe'
             found = self._findprogram(p)
             if found:
                 vlog("# Found prerequisite", p, "at", found)
--- a/tests/simplestorerepo.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/simplestorerepo.py	Wed Sep 26 20:33:09 2018 +0900
@@ -22,6 +22,7 @@
     nullrev,
 )
 from mercurial.thirdparty import (
+    attr,
     cbor,
 )
 from mercurial import (
@@ -60,6 +61,22 @@
     if not isinstance(rev, int):
         raise ValueError('expected int')
 
+class simplestoreerror(error.StorageError):
+    pass
+
+@interfaceutil.implementer(repository.irevisiondelta)
+@attr.s(slots=True, frozen=True)
+class simplestorerevisiondelta(object):
+    node = attr.ib()
+    p1node = attr.ib()
+    p2node = attr.ib()
+    basenode = attr.ib()
+    linknode = attr.ib()
+    flags = attr.ib()
+    baserevisionsize = attr.ib()
+    revision = attr.ib()
+    delta = attr.ib()
+
 @interfaceutil.implementer(repository.ifilestorage)
 class filestorage(object):
     """Implements storage for a tracked path.
@@ -91,7 +108,6 @@
 
         # This is used by changegroup code :/
         self._generaldelta = True
-        self.storedeltachains = False
 
         self.version = 1
 
@@ -228,7 +244,7 @@
         p1node = self.parents(self.node(rev))[0]
         return self.rev(p1node)
 
-    def candelta(self, baserev, rev):
+    def _candelta(self, baserev, rev):
         validaterev(baserev)
         validaterev(rev)
 
@@ -248,8 +264,8 @@
             return text, True
 
         if flags & ~revlog.REVIDX_KNOWN_FLAGS:
-            raise error.RevlogError(_("incompatible revision flag '%#x'") %
-                                    (flags & ~revlog.REVIDX_KNOWN_FLAGS))
+            raise simplestoreerror(_("incompatible revision flag '%#x'") %
+                                   (flags & ~revlog.REVIDX_KNOWN_FLAGS))
 
         validatehash = True
         # Depending on the operation (read or write), the order might be
@@ -266,7 +282,7 @@
 
                 if flag not in revlog._flagprocessors:
                     message = _("missing processor for flag '%#x'") % (flag)
-                    raise revlog.RevlogError(message)
+                    raise simplestoreerror(message)
 
                 processor = revlog._flagprocessors[flag]
                 if processor is not None:
@@ -286,7 +302,7 @@
         if p1 is None and p2 is None:
             p1, p2 = self.parents(node)
         if node != revlog.hash(text, p1, p2):
-            raise error.RevlogError(_("integrity check failed on %s") %
+            raise simplestoreerror(_("integrity check failed on %s") %
                 self._path)
 
     def revision(self, node, raw=False):
@@ -500,17 +516,53 @@
         return mdiff.textdiff(self.revision(node1, raw=True),
                               self.revision(node2, raw=True))
 
-    def headrevs(self):
-        # Assume all revisions are heads by default.
-        revishead = {rev: True for rev in self._indexbyrev}
+    def emitrevisiondeltas(self, requests):
+        for request in requests:
+            node = request.node
+            rev = self.rev(node)
+
+            if request.basenode == nullid:
+                baserev = nullrev
+            elif request.basenode is not None:
+                baserev = self.rev(request.basenode)
+            else:
+                # This is a test extension and we can do simple things
+                # for choosing a delta parent.
+                baserev = self.deltaparent(rev)
+
+                if baserev != nullrev and not self._candelta(baserev, rev):
+                    baserev = nullrev
+
+            revision = None
+            delta = None
+            baserevisionsize = None
 
-        for rev, entry in self._indexbyrev.items():
-            # Unset head flag for all seen parents.
-            revishead[self.rev(entry[b'p1'])] = False
-            revishead[self.rev(entry[b'p2'])] = False
+            if self.iscensored(baserev) or self.iscensored(rev):
+                try:
+                    revision = self.revision(node, raw=True)
+                except error.CensoredNodeError as e:
+                    revision = e.tombstone
+
+                if baserev != nullrev:
+                    baserevisionsize = self.rawsize(baserev)
 
-        return [rev for rev, ishead in sorted(revishead.items())
-                if ishead]
+            elif baserev == nullrev:
+                revision = self.revision(node, raw=True)
+            else:
+                delta = self.revdiff(baserev, rev)
+
+            extraflags = revlog.REVIDX_ELLIPSIS if request.ellipsis else 0
+
+            yield simplestorerevisiondelta(
+                node=node,
+                p1node=request.p1node,
+                p2node=request.p2node,
+                linknode=request.linknode,
+                basenode=self.node(baserev),
+                flags=self.flags(rev) | extraflags,
+                baserevisionsize=baserevisionsize,
+                revision=revision,
+                delta=delta)
 
     def heads(self, start=None, stop=None):
         # This is copied from revlog.py.
@@ -564,8 +616,8 @@
 
         heads = {}
         futurelargelinkrevs = set()
-        for head in self.headrevs():
-            headlinkrev = self.linkrev(head)
+        for head in self.heads():
+            headlinkrev = self.linkrev(self.rev(head))
             heads[head] = headlinkrev
             if headlinkrev >= minlink:
                 futurelargelinkrevs.add(headlinkrev)
@@ -651,9 +703,9 @@
 def featuresetup(ui, supported):
     supported.add(REQUIREMENT)
 
-def newreporequirements(orig, repo):
+def newreporequirements(orig, ui):
     """Modifies default requirements for new repos to use the simple store."""
-    requirements = orig(repo)
+    requirements = orig(ui)
 
     # These requirements are only used to affect creation of the store
     # object. We have our own store. So we can remove them.
--- a/tests/svn-safe-append.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/svn-safe-append.py	Wed Sep 26 20:33:09 2018 +0900
@@ -9,14 +9,18 @@
 import stat
 import sys
 
-text = sys.argv[1]
-fname = sys.argv[2]
+if sys.version_info[0] >= 3:
+    text = os.fsencode(sys.argv[1])
+    fname = os.fsencode(sys.argv[2])
+else:
+    text = sys.argv[1]
+    fname = sys.argv[2]
 
 f = open(fname, "ab")
 try:
     before = os.fstat(f.fileno())[stat.ST_MTIME]
     f.write(text)
-    f.write("\n")
+    f.write(b"\n")
 finally:
     f.close()
 inc = 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-absorb-edit-lines.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,61 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > absorb=
+  > EOF
+
+  $ hg init repo1
+  $ cd repo1
+
+Make some commits:
+
+  $ for i in 1 2 3; do
+  >   echo $i >> a
+  >   hg commit -A a -m "commit $i" -q
+  > done
+
+absorb --edit-lines will run the editor if filename is provided:
+
+  $ hg absorb --edit-lines
+  nothing applied
+  [1]
+  $ HGEDITOR=cat hg absorb --edit-lines a
+  HG: editing a
+  HG: "y" means the line to the right exists in the changeset to the top
+  HG:
+  HG: /---- 4ec16f85269a commit 1
+  HG: |/--- 5c5f95224a50 commit 2
+  HG: ||/-- 43f0a75bede7 commit 3
+  HG: |||
+      yyy : 1
+       yy : 2
+        y : 3
+  nothing applied
+  [1]
+
+Edit the file using --edit-lines:
+
+  $ cat > editortext << EOF
+  >       y : a
+  >      yy :  b
+  >      y  : c
+  >     yy  : d  
+  >     y y : e
+  >     y   : f
+  >     yyy : g
+  > EOF
+  $ HGEDITOR='cat editortext >' hg absorb -q --edit-lines a
+  $ hg cat -r 0 a
+  d  
+  e
+  f
+  g
+  $ hg cat -r 1 a
+   b
+  c
+  d  
+  g
+  $ hg cat -r 2 a
+  a
+   b
+  e
+  g
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-absorb-filefixupstate.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,207 @@
+from __future__ import absolute_import, print_function
+
+import itertools
+from mercurial import pycompat
+from hgext import absorb
+
+class simplefctx(object):
+    def __init__(self, content):
+        self.content = content
+
+    def data(self):
+        return self.content
+
+def insertreturns(x):
+    # insert "\n"s after each single char
+    if isinstance(x, bytes):
+        return b''.join(ch + b'\n' for ch in pycompat.bytestr(x))
+    else:
+        return pycompat.maplist(insertreturns, x)
+
+def removereturns(x):
+    # the revert of "insertreturns"
+    if isinstance(x, bytes):
+        return x.replace(b'\n', b'')
+    else:
+        return pycompat.maplist(removereturns, x)
+
+def assertlistequal(lhs, rhs, decorator=lambda x: x):
+    if lhs != rhs:
+        raise RuntimeError('mismatch:\n actual:   %r\n expected: %r'
+                           % tuple(map(decorator, [lhs, rhs])))
+
+def testfilefixup(oldcontents, workingcopy, expectedcontents, fixups=None):
+    """([str], str, [str], [(rev, a1, a2, b1, b2)]?) -> None
+
+    workingcopy is a string, of which every character denotes a single line.
+
+    oldcontents, expectedcontents are lists of strings, every character of
+    every string denots a single line.
+
+    if fixups is not None, it's the expected fixups list and will be checked.
+    """
+    expectedcontents = insertreturns(expectedcontents)
+    oldcontents = insertreturns(oldcontents)
+    workingcopy = insertreturns(workingcopy)
+    state = absorb.filefixupstate(pycompat.maplist(simplefctx, oldcontents))
+    state.diffwith(simplefctx(workingcopy))
+    if fixups is not None:
+        assertlistequal(state.fixups, fixups)
+    state.apply()
+    assertlistequal(state.finalcontents, expectedcontents, removereturns)
+
+def buildcontents(linesrevs):
+    # linesrevs: [(linecontent : str, revs : [int])]
+    revs = set(itertools.chain(*[revs for line, revs in linesrevs]))
+    return [b''] + [
+        b''.join([l for l, rs in linesrevs if r in rs])
+        for r in sorted(revs)
+    ]
+
+# input case 0: one single commit
+case0 = [b'', b'11']
+
+# replace a single chunk
+testfilefixup(case0, b'', [b'', b''])
+testfilefixup(case0, b'2', [b'', b'2'])
+testfilefixup(case0, b'22', [b'', b'22'])
+testfilefixup(case0, b'222', [b'', b'222'])
+
+# input case 1: 3 lines, each commit adds one line
+case1 = buildcontents([
+    (b'1', [1, 2, 3]),
+    (b'2', [   2, 3]),
+    (b'3', [      3]),
+])
+
+# 1:1 line mapping
+testfilefixup(case1, b'123', case1)
+testfilefixup(case1, b'12c', [b'', b'1', b'12', b'12c'])
+testfilefixup(case1, b'1b3', [b'', b'1', b'1b', b'1b3'])
+testfilefixup(case1, b'1bc', [b'', b'1', b'1b', b'1bc'])
+testfilefixup(case1, b'a23', [b'', b'a', b'a2', b'a23'])
+testfilefixup(case1, b'a2c', [b'', b'a', b'a2', b'a2c'])
+testfilefixup(case1, b'ab3', [b'', b'a', b'ab', b'ab3'])
+testfilefixup(case1, b'abc', [b'', b'a', b'ab', b'abc'])
+
+# non 1:1 edits
+testfilefixup(case1, b'abcd', case1)
+testfilefixup(case1, b'ab', case1)
+
+# deletion
+testfilefixup(case1, b'',   [b'', b'', b'', b''])
+testfilefixup(case1, b'1',  [b'', b'1', b'1', b'1'])
+testfilefixup(case1, b'2',  [b'', b'', b'2', b'2'])
+testfilefixup(case1, b'3',  [b'', b'', b'', b'3'])
+testfilefixup(case1, b'13', [b'', b'1', b'1', b'13'])
+
+# replaces
+testfilefixup(case1, b'1bb3', [b'', b'1', b'1bb', b'1bb3'])
+
+# (confusing) replaces
+testfilefixup(case1, b'1bbb', case1)
+testfilefixup(case1, b'bbbb', case1)
+testfilefixup(case1, b'bbb3', case1)
+testfilefixup(case1, b'1b', case1)
+testfilefixup(case1, b'bb', case1)
+testfilefixup(case1, b'b3', case1)
+
+# insertions at the beginning and the end
+testfilefixup(case1, b'123c', [b'', b'1', b'12', b'123c'])
+testfilefixup(case1, b'a123', [b'', b'a1', b'a12', b'a123'])
+
+# (confusing) insertions
+testfilefixup(case1, b'1a23', case1)
+testfilefixup(case1, b'12b3', case1)
+
+# input case 2: delete in the middle
+case2 = buildcontents([
+    (b'11', [1, 2]),
+    (b'22', [1   ]),
+    (b'33', [1, 2]),
+])
+
+# deletion (optimize code should make it 2 chunks)
+testfilefixup(case2, b'', [b'', b'22', b''],
+              fixups=[(4, 0, 2, 0, 0), (4, 2, 4, 0, 0)])
+
+# 1:1 line mapping
+testfilefixup(case2, b'aaaa', [b'', b'aa22aa', b'aaaa'])
+
+# non 1:1 edits
+# note: unlike case0, the chunk is not "continuous" and no edit allowed
+testfilefixup(case2, b'aaa', case2)
+
+# input case 3: rev 3 reverts rev 2
+case3 = buildcontents([
+    (b'1', [1, 2, 3]),
+    (b'2', [   2   ]),
+    (b'3', [1, 2, 3]),
+])
+
+# 1:1 line mapping
+testfilefixup(case3, b'13', case3)
+testfilefixup(case3, b'1b', [b'', b'1b', b'12b', b'1b'])
+testfilefixup(case3, b'a3', [b'', b'a3', b'a23', b'a3'])
+testfilefixup(case3, b'ab', [b'', b'ab', b'a2b', b'ab'])
+
+# non 1:1 edits
+testfilefixup(case3, b'a', case3)
+testfilefixup(case3, b'abc', case3)
+
+# deletion
+testfilefixup(case3, b'', [b'', b'', b'2', b''])
+
+# insertion
+testfilefixup(case3, b'a13c', [b'', b'a13c', b'a123c', b'a13c'])
+
+# input case 4: a slightly complex case
+case4 = buildcontents([
+    (b'1', [1, 2, 3]),
+    (b'2', [   2, 3]),
+    (b'3', [1, 2,  ]),
+    (b'4', [1,    3]),
+    (b'5', [      3]),
+    (b'6', [   2, 3]),
+    (b'7', [   2   ]),
+    (b'8', [   2, 3]),
+    (b'9', [      3]),
+])
+
+testfilefixup(case4, b'1245689', case4)
+testfilefixup(case4, b'1a2456bbb', case4)
+testfilefixup(case4, b'1abc5689', case4)
+testfilefixup(case4, b'1ab5689', [b'', b'134', b'1a3678', b'1ab5689'])
+testfilefixup(case4, b'aa2bcd8ee', [b'', b'aa34', b'aa23d78', b'aa2bcd8ee'])
+testfilefixup(case4, b'aa2bcdd8ee',[b'', b'aa34', b'aa23678', b'aa24568ee'])
+testfilefixup(case4, b'aaaaaa', case4)
+testfilefixup(case4, b'aa258b', [b'', b'aa34', b'aa2378', b'aa258b'])
+testfilefixup(case4, b'25bb', [b'', b'34', b'23678', b'25689'])
+testfilefixup(case4, b'27', [b'', b'34', b'23678', b'245689'])
+testfilefixup(case4, b'28', [b'', b'34', b'2378', b'28'])
+testfilefixup(case4, b'', [b'', b'34', b'37', b''])
+
+# input case 5: replace a small chunk which is near a deleted line
+case5 = buildcontents([
+    (b'12', [1, 2]),
+    (b'3',  [1]),
+    (b'4',  [1, 2]),
+])
+
+testfilefixup(case5, b'1cd4', [b'', b'1cd34', b'1cd4'])
+
+# input case 6: base "changeset" is immutable
+case6 = [b'1357', b'0125678']
+
+testfilefixup(case6, b'0125678', case6)
+testfilefixup(case6, b'0a25678', case6)
+testfilefixup(case6, b'0a256b8', case6)
+testfilefixup(case6, b'abcdefg', [b'1357', b'a1c5e7g'])
+testfilefixup(case6, b'abcdef', case6)
+testfilefixup(case6, b'', [b'1357', b'157'])
+testfilefixup(case6, b'0123456789', [b'1357', b'0123456789'])
+
+# input case 7: change an empty file
+case7 = [b'']
+
+testfilefixup(case7, b'1', case7)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-absorb-phase.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,30 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > absorb=
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+  $ hg init
+  $ hg debugdrawdag <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > A
+  > EOS
+
+  $ hg phase -r A --public -q
+  $ hg phase -r C --secret --force -q
+
+  $ hg update C -q
+  $ printf B1 > B
+
+  $ hg absorb -q
+
+  $ hg log -G -T '{desc} {phase}'
+  @  C secret
+  |
+  o  B draft
+  |
+  o  A public
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-absorb-rename.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,359 @@
+  $ cat >> $HGRCPATH << EOF
+  > [diff]
+  > git=1
+  > [extensions]
+  > absorb=
+  > EOF
+
+  $ sedi() { # workaround check-code
+  > pattern="$1"
+  > shift
+  > for i in "$@"; do
+  >     sed "$pattern" "$i" > "$i".tmp
+  >     mv "$i".tmp "$i"
+  > done
+  > }
+
+rename a to b, then b to a
+
+  $ hg init repo1
+  $ cd repo1
+
+  $ echo 1 > a
+  $ hg ci -A a -m 1
+  $ hg mv a b
+  $ echo 2 >> b
+  $ hg ci -m 2
+  $ hg mv b a
+  $ echo 3 >> a
+  $ hg ci -m 3
+
+  $ hg annotate -ncf a
+  0 eff892de26ec a: 1
+  1 bf56e1f4f857 b: 2
+  2 0b888b00216c a: 3
+
+  $ sedi 's/$/a/' a
+  $ hg absorb -pq
+  showing changes for a
+          @@ -0,3 +0,3 @@
+  eff892d -1
+  bf56e1f -2
+  0b888b0 -3
+  eff892d +1a
+  bf56e1f +2a
+  0b888b0 +3a
+
+  $ hg status
+
+  $ hg annotate -ncf a
+  0 5d1c5620e6f2 a: 1a
+  1 9a14ffe67ae9 b: 2a
+  2 9191d121a268 a: 3a
+
+when the first changeset is public
+
+  $ hg phase --public -r 0
+
+  $ sedi 's/a/A/' a
+
+  $ hg absorb -pq
+  showing changes for a
+          @@ -0,3 +0,3 @@
+          -1a
+  9a14ffe -2a
+  9191d12 -3a
+          +1A
+  9a14ffe +2A
+  9191d12 +3A
+
+  $ hg diff
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,3 +1,3 @@
+  -1a
+  +1A
+   2A
+   3A
+
+copy a to b
+
+  $ cd ..
+  $ hg init repo2
+  $ cd repo2
+
+  $ echo 1 > a
+  $ hg ci -A a -m 1
+  $ hg cp a b
+  $ echo 2 >> b
+  $ hg ci -m 2
+
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  1:17b72129ab68 2
+  0:eff892de26ec 1
+
+  $ sedi 's/$/a/' a
+  $ sedi 's/$/b/' b
+
+  $ hg absorb -pq
+  showing changes for a
+          @@ -0,1 +0,1 @@
+  eff892d -1
+  eff892d +1a
+  showing changes for b
+          @@ -0,2 +0,2 @@
+          -1
+  17b7212 -2
+          +1b
+  17b7212 +2b
+
+  $ hg diff
+  diff --git a/b b/b
+  --- a/b
+  +++ b/b
+  @@ -1,2 +1,2 @@
+  -1
+  +1b
+   2b
+
+copy b to a
+
+  $ cd ..
+  $ hg init repo3
+  $ cd repo3
+
+  $ echo 1 > b
+  $ hg ci -A b -m 1
+  $ hg cp b a
+  $ echo 2 >> a
+  $ hg ci -m 2
+
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  1:e62c256d8b24 2
+  0:55105f940d5c 1
+
+  $ sedi 's/$/a/' a
+  $ sedi 's/$/a/' b
+
+  $ hg absorb -pq
+  showing changes for a
+          @@ -0,2 +0,2 @@
+          -1
+  e62c256 -2
+          +1a
+  e62c256 +2a
+  showing changes for b
+          @@ -0,1 +0,1 @@
+  55105f9 -1
+  55105f9 +1a
+
+  $ hg diff
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,2 +1,2 @@
+  -1
+  +1a
+   2a
+
+"move" b to both a and c, follow a - sorted alphabetically
+
+  $ cd ..
+  $ hg init repo4
+  $ cd repo4
+
+  $ echo 1 > b
+  $ hg ci -A b -m 1
+  $ hg cp b a
+  $ hg cp b c
+  $ hg rm b
+  $ echo 2 >> a
+  $ echo 3 >> c
+  $ hg commit -m cp
+
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  1:366daad8e679 cp
+  0:55105f940d5c 1
+
+  $ sedi 's/$/a/' a
+  $ sedi 's/$/c/' c
+
+  $ hg absorb -pq
+  showing changes for a
+          @@ -0,2 +0,2 @@
+  55105f9 -1
+  366daad -2
+  55105f9 +1a
+  366daad +2a
+  showing changes for c
+          @@ -0,2 +0,2 @@
+          -1
+  366daad -3
+          +1c
+  366daad +3c
+
+  $ hg log -G -p -T '{rev}:{node|short} {desc}\n'
+  @  1:70606019f91b cp
+  |  diff --git a/b b/a
+  |  rename from b
+  |  rename to a
+  |  --- a/b
+  |  +++ b/a
+  |  @@ -1,1 +1,2 @@
+  |   1a
+  |  +2a
+  |  diff --git a/b b/c
+  |  copy from b
+  |  copy to c
+  |  --- a/b
+  |  +++ b/c
+  |  @@ -1,1 +1,2 @@
+  |  -1a
+  |  +1
+  |  +3c
+  |
+  o  0:bfb67c3539c1 1
+     diff --git a/b b/b
+     new file mode 100644
+     --- /dev/null
+     +++ b/b
+     @@ -0,0 +1,1 @@
+     +1a
+  
+run absorb again would apply the change to c
+
+  $ hg absorb -pq
+  showing changes for c
+          @@ -0,1 +0,1 @@
+  7060601 -1
+  7060601 +1c
+
+  $ hg log -G -p -T '{rev}:{node|short} {desc}\n'
+  @  1:8bd536cce368 cp
+  |  diff --git a/b b/a
+  |  rename from b
+  |  rename to a
+  |  --- a/b
+  |  +++ b/a
+  |  @@ -1,1 +1,2 @@
+  |   1a
+  |  +2a
+  |  diff --git a/b b/c
+  |  copy from b
+  |  copy to c
+  |  --- a/b
+  |  +++ b/c
+  |  @@ -1,1 +1,2 @@
+  |  -1a
+  |  +1c
+  |  +3c
+  |
+  o  0:bfb67c3539c1 1
+     diff --git a/b b/b
+     new file mode 100644
+     --- /dev/null
+     +++ b/b
+     @@ -0,0 +1,1 @@
+     +1a
+  
+"move" b to a, c and d, follow d if a gets renamed to e, and c is deleted
+
+  $ cd ..
+  $ hg init repo5
+  $ cd repo5
+
+  $ echo 1 > b
+  $ hg ci -A b -m 1
+  $ hg cp b a
+  $ hg cp b c
+  $ hg cp b d
+  $ hg rm b
+  $ echo 2 >> a
+  $ echo 3 >> c
+  $ echo 4 >> d
+  $ hg commit -m cp
+  $ hg mv a e
+  $ hg rm c
+  $ hg commit -m mv
+
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  2:49911557c471 mv
+  1:7bc3d43ede83 cp
+  0:55105f940d5c 1
+
+  $ sedi 's/$/e/' e
+  $ sedi 's/$/d/' d
+
+  $ hg absorb -pq
+  showing changes for d
+          @@ -0,2 +0,2 @@
+  55105f9 -1
+  7bc3d43 -4
+  55105f9 +1d
+  7bc3d43 +4d
+  showing changes for e
+          @@ -0,2 +0,2 @@
+          -1
+  7bc3d43 -2
+          +1e
+  7bc3d43 +2e
+
+  $ hg diff
+  diff --git a/e b/e
+  --- a/e
+  +++ b/e
+  @@ -1,2 +1,2 @@
+  -1
+  +1e
+   2e
+
+  $ hg log -G -p -T '{rev}:{node|short} {desc}\n'
+  @  2:34be9b0c786e mv
+  |  diff --git a/c b/c
+  |  deleted file mode 100644
+  |  --- a/c
+  |  +++ /dev/null
+  |  @@ -1,2 +0,0 @@
+  |  -1
+  |  -3
+  |  diff --git a/a b/e
+  |  rename from a
+  |  rename to e
+  |
+  o  1:13e56db5948d cp
+  |  diff --git a/b b/a
+  |  rename from b
+  |  rename to a
+  |  --- a/b
+  |  +++ b/a
+  |  @@ -1,1 +1,2 @@
+  |  -1d
+  |  +1
+  |  +2e
+  |  diff --git a/b b/c
+  |  copy from b
+  |  copy to c
+  |  --- a/b
+  |  +++ b/c
+  |  @@ -1,1 +1,2 @@
+  |  -1d
+  |  +1
+  |  +3
+  |  diff --git a/b b/d
+  |  copy from b
+  |  copy to d
+  |  --- a/b
+  |  +++ b/d
+  |  @@ -1,1 +1,2 @@
+  |   1d
+  |  +4d
+  |
+  o  0:0037613a5dc6 1
+     diff --git a/b b/b
+     new file mode 100644
+     --- /dev/null
+     +++ b/b
+     @@ -0,0 +1,1 @@
+     +1d
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-absorb-strip.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,45 @@
+Do not strip innocent children. See https://bitbucket.org/facebook/hg-experimental/issues/6/hg-absorb-merges-diverged-commits
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > absorb=
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+  $ hg init
+  $ hg debugdrawdag << EOF
+  > E
+  > |
+  > D F
+  > |/
+  > C
+  > |
+  > B
+  > |
+  > A
+  > EOF
+
+  $ hg up E -q
+  $ echo 1 >> B
+  $ echo 2 >> D
+  $ hg absorb
+  saved backup bundle to * (glob)
+  2 of 2 chunk(s) applied
+
+  $ hg log -G -T '{desc}'
+  @  E
+  |
+  o  D
+  |
+  o  C
+  |
+  o  B
+  |
+  | o  F
+  | |
+  | o  C
+  | |
+  | o  B
+  |/
+  o  A
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-absorb.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,451 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > absorb=
+  > EOF
+
+  $ sedi() { # workaround check-code
+  > pattern="$1"
+  > shift
+  > for i in "$@"; do
+  >     sed "$pattern" "$i" > "$i".tmp
+  >     mv "$i".tmp "$i"
+  > done
+  > }
+
+  $ hg init repo1
+  $ cd repo1
+
+Do not crash with empty repo:
+
+  $ hg absorb
+  abort: no mutable changeset to change
+  [255]
+
+Make some commits:
+
+  $ for i in 1 2 3 4 5; do
+  >   echo $i >> a
+  >   hg commit -A a -m "commit $i" -q
+  > done
+
+  $ hg annotate a
+  0: 1
+  1: 2
+  2: 3
+  3: 4
+  4: 5
+
+Change a few lines:
+
+  $ cat > a <<EOF
+  > 1a
+  > 2b
+  > 3
+  > 4d
+  > 5e
+  > EOF
+
+Preview absorb changes:
+
+  $ hg absorb --print-changes --dry-run
+  showing changes for a
+          @@ -0,2 +0,2 @@
+  4ec16f8 -1
+  5c5f952 -2
+  4ec16f8 +1a
+  5c5f952 +2b
+          @@ -3,2 +3,2 @@
+  ad8b8b7 -4
+  4f55fa6 -5
+  ad8b8b7 +4d
+  4f55fa6 +5e
+
+Run absorb:
+
+  $ hg absorb
+  saved backup bundle to * (glob)
+  2 of 2 chunk(s) applied
+  $ hg annotate a
+  0: 1a
+  1: 2b
+  2: 3
+  3: 4d
+  4: 5e
+
+Delete a few lines and related commits will be removed if they will be empty:
+
+  $ cat > a <<EOF
+  > 2b
+  > 4d
+  > EOF
+  $ hg absorb
+  saved backup bundle to * (glob)
+  3 of 3 chunk(s) applied
+  $ hg annotate a
+  1: 2b
+  2: 4d
+  $ hg log -T '{rev} {desc}\n' -Gp
+  @  2 commit 4
+  |  diff -r 1cae118c7ed8 -r 58a62bade1c6 a
+  |  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -1,1 +1,2 @@
+  |   2b
+  |  +4d
+  |
+  o  1 commit 2
+  |  diff -r 84add69aeac0 -r 1cae118c7ed8 a
+  |  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  |  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  |  @@ -0,0 +1,1 @@
+  |  +2b
+  |
+  o  0 commit 1
+  
+
+Non 1:1 map changes will be ignored:
+
+  $ echo 1 > a
+  $ hg absorb
+  nothing applied
+  [1]
+
+Insertaions:
+
+  $ cat > a << EOF
+  > insert before 2b
+  > 2b
+  > 4d
+  > insert aftert 4d
+  > EOF
+  $ hg absorb -q
+  $ hg status
+  $ hg annotate a
+  1: insert before 2b
+  1: 2b
+  2: 4d
+  2: insert aftert 4d
+
+Bookmarks are moved:
+
+  $ hg bookmark -r 1 b1
+  $ hg bookmark -r 2 b2
+  $ hg bookmark ba
+  $ hg bookmarks
+     b1                        1:b35060a57a50
+     b2                        2:946e4bc87915
+   * ba                        2:946e4bc87915
+  $ sedi 's/insert/INSERT/' a
+  $ hg absorb -q
+  $ hg status
+  $ hg bookmarks
+     b1                        1:a4183e9b3d31
+     b2                        2:c9b20c925790
+   * ba                        2:c9b20c925790
+
+Non-mofified files are ignored:
+
+  $ touch b
+  $ hg commit -A b -m b
+  $ touch c
+  $ hg add c
+  $ hg rm b
+  $ hg absorb
+  nothing applied
+  [1]
+  $ sedi 's/INSERT/Insert/' a
+  $ hg absorb
+  saved backup bundle to * (glob)
+  2 of 2 chunk(s) applied
+  $ hg status
+  A c
+  R b
+
+Public commits will not be changed:
+
+  $ hg phase -p 1
+  $ sedi 's/Insert/insert/' a
+  $ hg absorb -pn
+  showing changes for a
+          @@ -0,1 +0,1 @@
+          -Insert before 2b
+          +insert before 2b
+          @@ -3,1 +3,1 @@
+  85b4e0e -Insert aftert 4d
+  85b4e0e +insert aftert 4d
+  $ hg absorb
+  saved backup bundle to * (glob)
+  1 of 2 chunk(s) applied
+  $ hg diff -U 0
+  diff -r 1c8eadede62a a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	* (glob)
+  @@ -1,1 +1,1 @@
+  -Insert before 2b
+  +insert before 2b
+  $ hg annotate a
+  1: Insert before 2b
+  1: 2b
+  2: 4d
+  2: insert aftert 4d
+
+Make working copy clean:
+
+  $ hg revert -q -C a b
+  $ hg forget c
+  $ rm c
+  $ hg status
+
+Merge commit will not be changed:
+
+  $ echo 1 > m1
+  $ hg commit -A m1 -m m1
+  $ hg bookmark -q -i m1
+  $ hg update -q '.^'
+  $ echo 2 > m2
+  $ hg commit -q -A m2 -m m2
+  $ hg merge -q m1
+  $ hg commit -m merge
+  $ hg bookmark -d m1
+  $ hg log -G -T '{rev} {desc} {phase}\n'
+  @    6 merge draft
+  |\
+  | o  5 m2 draft
+  | |
+  o |  4 m1 draft
+  |/
+  o  3 b draft
+  |
+  o  2 commit 4 draft
+  |
+  o  1 commit 2 public
+  |
+  o  0 commit 1 public
+  
+  $ echo 2 >> m1
+  $ echo 2 >> m2
+  $ hg absorb
+  abort: no mutable changeset to change
+  [255]
+  $ hg revert -q -C m1 m2
+
+Use a new repo:
+
+  $ cd ..
+  $ hg init repo2
+  $ cd repo2
+
+Make some commits to multiple files:
+
+  $ for f in a b; do
+  >   for i in 1 2; do
+  >     echo $f line $i >> $f
+  >     hg commit -A $f -m "commit $f $i" -q
+  >   done
+  > done
+
+Use pattern to select files to be fixed up:
+
+  $ sedi 's/line/Line/' a b
+  $ hg status
+  M a
+  M b
+  $ hg absorb a
+  saved backup bundle to * (glob)
+  1 of 1 chunk(s) applied
+  $ hg status
+  M b
+  $ hg absorb --exclude b
+  nothing applied
+  [1]
+  $ hg absorb b
+  saved backup bundle to * (glob)
+  1 of 1 chunk(s) applied
+  $ hg status
+  $ cat a b
+  a Line 1
+  a Line 2
+  b Line 1
+  b Line 2
+
+Test config option absorb.max-stack-size:
+
+  $ sedi 's/Line/line/' a b
+  $ hg log -T '{rev}:{node} {desc}\n'
+  3:712d16a8f445834e36145408eabc1d29df05ec09 commit b 2
+  2:74cfa6294160149d60adbf7582b99ce37a4597ec commit b 1
+  1:28f10dcf96158f84985358a2e5d5b3505ca69c22 commit a 2
+  0:f9a81da8dc53380ed91902e5b82c1b36255a4bd0 commit a 1
+  $ hg --config absorb.max-stack-size=1 absorb -pn
+  absorb: only the recent 1 changesets will be analysed
+  showing changes for a
+          @@ -0,2 +0,2 @@
+          -a Line 1
+          -a Line 2
+          +a line 1
+          +a line 2
+  showing changes for b
+          @@ -0,2 +0,2 @@
+          -b Line 1
+  712d16a -b Line 2
+          +b line 1
+  712d16a +b line 2
+
+Test obsolete markers creation:
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers
+  > [absorb]
+  > add-noise=1
+  > EOF
+
+  $ hg --config absorb.max-stack-size=3 absorb
+  absorb: only the recent 3 changesets will be analysed
+  2 of 2 chunk(s) applied
+  $ hg log -T '{rev}:{node|short} {desc} {get(extras, "absorb_source")}\n'
+  6:3dfde4199b46 commit b 2 712d16a8f445834e36145408eabc1d29df05ec09
+  5:99cfab7da5ff commit b 1 74cfa6294160149d60adbf7582b99ce37a4597ec
+  4:fec2b3bd9e08 commit a 2 28f10dcf96158f84985358a2e5d5b3505ca69c22
+  0:f9a81da8dc53 commit a 1 
+  $ hg absorb
+  1 of 1 chunk(s) applied
+  $ hg log -T '{rev}:{node|short} {desc} {get(extras, "absorb_source")}\n'
+  10:e1c8c1e030a4 commit b 2 3dfde4199b4610ea6e3c6fa9f5bdad8939d69524
+  9:816c30955758 commit b 1 99cfab7da5ffdaf3b9fc6643b14333e194d87f46
+  8:5867d584106b commit a 2 fec2b3bd9e0834b7cb6a564348a0058171aed811
+  7:8c76602baf10 commit a 1 f9a81da8dc53380ed91902e5b82c1b36255a4bd0
+
+Executable files:
+
+  $ cat >> $HGRCPATH << EOF
+  > [diff]
+  > git=True
+  > EOF
+  $ cd ..
+  $ hg init repo3
+  $ cd repo3
+
+#if execbit
+  $ echo > foo.py
+  $ chmod +x foo.py
+  $ hg add foo.py
+  $ hg commit -mfoo
+#else
+  $ hg import -q --bypass - <<EOF
+  > # HG changeset patch
+  > foo
+  > 
+  > diff --git a/foo.py b/foo.py
+  > new file mode 100755
+  > --- /dev/null
+  > +++ b/foo.py
+  > @@ -0,0 +1,1 @@
+  > +
+  > EOF
+  $ hg up -q
+#endif
+
+  $ echo bla > foo.py
+  $ hg absorb --dry-run --print-changes
+  showing changes for foo.py
+          @@ -0,1 +0,1 @@
+  99b4ae7 -
+  99b4ae7 +bla
+  $ hg absorb
+  1 of 1 chunk(s) applied
+  $ hg diff -c .
+  diff --git a/foo.py b/foo.py
+  new file mode 100755
+  --- /dev/null
+  +++ b/foo.py
+  @@ -0,0 +1,1 @@
+  +bla
+  $ hg diff
+
+Remove lines may delete changesets:
+
+  $ cd ..
+  $ hg init repo4
+  $ cd repo4
+  $ cat > a <<EOF
+  > 1
+  > 2
+  > EOF
+  $ hg commit -m a12 -A a
+  $ cat > b <<EOF
+  > 1
+  > 2
+  > EOF
+  $ hg commit -m b12 -A b
+  $ echo 3 >> b
+  $ hg commit -m b3
+  $ echo 4 >> b
+  $ hg commit -m b4
+  $ echo 1 > b
+  $ echo 3 >> a
+  $ hg absorb -pn
+  showing changes for a
+          @@ -2,0 +2,1 @@
+  bfafb49 +3
+  showing changes for b
+          @@ -1,3 +1,0 @@
+  1154859 -2
+  30970db -3
+  a393a58 -4
+  $ hg absorb -v | grep became
+  bfafb49242db: 1 file(s) changed, became 1a2de97fc652
+  115485984805: 2 file(s) changed, became 0c930dfab74c
+  30970dbf7b40: became empty and was dropped
+  a393a58b9a85: became empty and was dropped
+  $ hg log -T '{rev} {desc}\n' -Gp
+  @  5 b12
+  |  diff --git a/b b/b
+  |  new file mode 100644
+  |  --- /dev/null
+  |  +++ b/b
+  |  @@ -0,0 +1,1 @@
+  |  +1
+  |
+  o  4 a12
+     diff --git a/a b/a
+     new file mode 100644
+     --- /dev/null
+     +++ b/a
+     @@ -0,0 +1,3 @@
+     +1
+     +2
+     +3
+  
+
+Use revert to make the current change and its parent disappear.
+This should move us to the non-obsolete ancestor.
+
+  $ cd ..
+  $ hg init repo5
+  $ cd repo5
+  $ cat > a <<EOF
+  > 1
+  > 2
+  > EOF
+  $ hg commit -m a12 -A a
+  $ hg id
+  bfafb49242db tip
+  $ echo 3 >> a
+  $ hg commit -m a123 a
+  $ echo 4 >> a
+  $ hg commit -m a1234 a
+  $ hg id
+  82dbe7fd19f0 tip
+  $ hg revert -r 0 a
+  $ hg absorb -pn
+  showing changes for a
+          @@ -2,2 +2,0 @@
+  f1c23dd -3
+  82dbe7f -4
+  $ hg absorb --verbose
+  f1c23dd5d08d: became empty and was dropped
+  82dbe7fd19f0: became empty and was dropped
+  a: 1 of 1 chunk(s) applied
+  $ hg id
+  bfafb49242db tip
--- a/tests/test-add.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-add.t	Wed Sep 26 20:33:09 2018 +0900
@@ -12,6 +12,9 @@
   $ hg forget a
   $ hg add
   adding a
+  $ hg forget a
+  $ hg add --color debug
+  [addremove.added ui.status|adding a]
   $ hg st
   A a
   $ mkdir dir
--- a/tests/test-addremove-similar.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-addremove-similar.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1,7 +1,7 @@
   $ hg init rep; cd rep
 
   $ touch empty-file
-  $ $PYTHON -c 'for x in range(10000): print(x)' > large-file
+  $ "$PYTHON" -c 'for x in range(10000): print(x)' > large-file
 
   $ hg addremove
   adding empty-file
@@ -10,7 +10,7 @@
   $ hg commit -m A
 
   $ rm large-file empty-file
-  $ $PYTHON -c 'for x in range(10,10000): print(x)' > another-file
+  $ "$PYTHON" -c 'for x in range(10,10000): print(x)' > another-file
 
   $ hg addremove -s50
   adding another-file
@@ -34,8 +34,8 @@
 
   $ hg init rep2; cd rep2
 
-  $ $PYTHON -c 'for x in range(10000): print(x)' > large-file
-  $ $PYTHON -c 'for x in range(50): print(x)' > tiny-file
+  $ "$PYTHON" -c 'for x in range(10000): print(x)' > large-file
+  $ "$PYTHON" -c 'for x in range(50): print(x)' > tiny-file
 
   $ hg addremove
   adding large-file
@@ -43,7 +43,7 @@
 
   $ hg commit -m A
 
-  $ $PYTHON -c 'for x in range(70): print(x)' > small-file
+  $ "$PYTHON" -c 'for x in range(70): print(x)' > small-file
   $ rm tiny-file
   $ rm large-file
 
@@ -57,7 +57,7 @@
 
 should be sorted by path for stable result
 
-  $ for i in `$PYTHON $TESTDIR/seq.py 0 9`; do
+  $ for i in `"$PYTHON" $TESTDIR/seq.py 0 9`; do
   >     cp small-file $i
   > done
   $ rm small-file
@@ -88,7 +88,7 @@
 pick one from many identical files
 
   $ cp 0 a
-  $ rm `$PYTHON $TESTDIR/seq.py 0 9`
+  $ rm `"$PYTHON" $TESTDIR/seq.py 0 9`
   $ hg addremove
   removing 0
   removing 1
@@ -107,11 +107,11 @@
 pick one from many similar files
 
   $ cp 0 a
-  $ for i in `$PYTHON $TESTDIR/seq.py 0 9`; do
+  $ for i in `"$PYTHON" $TESTDIR/seq.py 0 9`; do
   >     echo $i >> $i
   > done
   $ hg commit -m 'make them slightly different'
-  $ rm `$PYTHON $TESTDIR/seq.py 0 9`
+  $ rm `"$PYTHON" $TESTDIR/seq.py 0 9`
   $ hg addremove -s50
   removing 0
   removing 1
--- a/tests/test-addremove.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-addremove.t	Wed Sep 26 20:33:09 2018 +0900
@@ -69,6 +69,12 @@
   removing c
   adding d
   recording removal of a as rename to b (100% similar)
+  $ hg addremove -ns 50 --color debug
+  [addremove.removed ui.status|removing a]
+  [addremove.added ui.status|adding b]
+  [addremove.removed ui.status|removing c]
+  [addremove.added ui.status|adding d]
+  [ ui.status|recording removal of a as rename to b (100% similar)]
   $ hg addremove -s 50
   removing a
   adding b
--- a/tests/test-alias.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-alias.t	Wed Sep 26 20:33:09 2018 +0900
@@ -651,81 +651,15 @@
 
   $ hg --invalid root
   hg: option --invalid not recognized
-  Mercurial Distributed SCM
-  
-  basic commands:
-  
-   add           add the specified files on the next commit
-   annotate      show changeset information by line for each file
-   clone         make a copy of an existing repository
-   commit        commit the specified files or all outstanding changes
-   diff          diff repository (or selected files)
-   export        dump the header and diffs for one or more changesets
-   forget        forget the specified files on the next commit
-   init          create a new repository in the given directory
-   log           show revision history of entire repository or files
-   merge         merge another revision into working directory
-   pull          pull changes from the specified source
-   push          push changes to the specified destination
-   remove        remove the specified files on the next commit
-   serve         start stand-alone webserver
-   status        show changed files in the working directory
-   summary       summarize working directory state
-   update        update working directory (or switch revisions)
-  
-  (use 'hg help' for the full list of commands or 'hg -v' for details)
+  (use 'hg help -v' for a list of global options)
   [255]
   $ hg --invalid mylog
   hg: option --invalid not recognized
-  Mercurial Distributed SCM
-  
-  basic commands:
-  
-   add           add the specified files on the next commit
-   annotate      show changeset information by line for each file
-   clone         make a copy of an existing repository
-   commit        commit the specified files or all outstanding changes
-   diff          diff repository (or selected files)
-   export        dump the header and diffs for one or more changesets
-   forget        forget the specified files on the next commit
-   init          create a new repository in the given directory
-   log           show revision history of entire repository or files
-   merge         merge another revision into working directory
-   pull          pull changes from the specified source
-   push          push changes to the specified destination
-   remove        remove the specified files on the next commit
-   serve         start stand-alone webserver
-   status        show changed files in the working directory
-   summary       summarize working directory state
-   update        update working directory (or switch revisions)
-  
-  (use 'hg help' for the full list of commands or 'hg -v' for details)
+  (use 'hg help -v' for a list of global options)
   [255]
   $ hg --invalid blank
   hg: option --invalid not recognized
-  Mercurial Distributed SCM
-  
-  basic commands:
-  
-   add           add the specified files on the next commit
-   annotate      show changeset information by line for each file
-   clone         make a copy of an existing repository
-   commit        commit the specified files or all outstanding changes
-   diff          diff repository (or selected files)
-   export        dump the header and diffs for one or more changesets
-   forget        forget the specified files on the next commit
-   init          create a new repository in the given directory
-   log           show revision history of entire repository or files
-   merge         merge another revision into working directory
-   pull          pull changes from the specified source
-   push          push changes to the specified destination
-   remove        remove the specified files on the next commit
-   serve         start stand-alone webserver
-   status        show changed files in the working directory
-   summary       summarize working directory state
-   update        update working directory (or switch revisions)
-  
-  (use 'hg help' for the full list of commands or 'hg -v' for details)
+  (use 'hg help -v' for a list of global options)
   [255]
 
 environment variable changes in alias commands
--- a/tests/test-amend.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-amend.t	Wed Sep 26 20:33:09 2018 +0900
@@ -250,15 +250,15 @@
   $ hg init $TESTTMP/wcstates
   $ cd $TESTTMP/wcstates
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 1
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 1
   $ hg addremove -q --similarity 0
   $ hg commit -m0
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 2
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 2
   $ hg addremove -q --similarity 0
   $ hg commit -m1
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 wc
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 wc
   $ hg addremove -q --similarity 0
   $ hg forget *_*_*-untracked
   $ rm *_*_missing-*
@@ -331,3 +331,37 @@
   ? missing_content2_content2-untracked
   ? missing_content2_content3-untracked
   ? missing_missing_content3-untracked
+
+==========================================
+Test history-editing-backup config option|
+==========================================
+  $ hg init $TESTTMP/repo4
+  $ cd $TESTTMP/repo4
+  $ echo a>a
+  $ hg ci -Aqma
+  $ echo oops>b
+  $ hg ci -Aqm "b"
+  $ echo partiallyfixed > b
+
+#if obsstore-off
+  $ hg amend
+  saved backup bundle to $TESTTMP/repo4/.hg/strip-backup/95e899acf2ce-f11cb050-amend.hg
+When history-editing-backup config option is set:
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > history-editing-backup = False
+  > EOF
+  $ echo fixed > b
+  $ hg amend
+
+#else
+  $ hg amend
+When history-editing-backup config option is set:
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > history-editing-backup = False
+  > EOF
+  $ echo fixed > b
+  $ hg amend
+
+#endif
--- a/tests/test-ancestor.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-ancestor.py	Wed Sep 26 20:33:09 2018 +0900
@@ -178,9 +178,9 @@
 # |
 # o  0
 
-graph = {0: [-1], 1: [0], 2: [1], 3: [1], 4: [2], 5: [4], 6: [4],
-         7: [4], 8: [-1], 9: [6, 7], 10: [5], 11: [3, 7], 12: [9],
-         13: [8]}
+graph = {0: [-1, -1], 1: [0, -1], 2: [1, -1], 3: [1, -1], 4: [2, -1],
+         5: [4, -1], 6: [4, -1], 7: [4, -1], 8: [-1, -1], 9: [6, 7],
+         10: [5, -1], 11: [3, 7], 12: [9, -1], 13: [8, -1]}
 
 def genlazyancestors(revs, stoprev=0, inclusive=False):
     print(("%% lazy ancestor set for %s, stoprev = %s, inclusive = %s" %
@@ -215,6 +215,15 @@
     s = genlazyancestors([11, 13], stoprev=6, inclusive=True)
     printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
 
+    # Test with stoprev >= min(initrevs)
+    s = genlazyancestors([11, 13], stoprev=11, inclusive=True)
+    printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
+    s = genlazyancestors([11, 13], stoprev=12, inclusive=True)
+    printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
+
+    # Contiguous chains: 5->4, 2->1 (where 1 is in seen set), 1->0
+    s = genlazyancestors([10, 1], inclusive=True)
+    printlazyancestors(s, [2, 10, 4, 5, -1, 0, 1])
 
 # The C gca algorithm requires a real repo. These are textual descriptions of
 # DAGs that have been known to be problematic, and, optionally, known pairs
--- a/tests/test-ancestor.py.out	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-ancestor.py.out	Wed Sep 26 20:33:09 2018 +0900
@@ -3,16 +3,25 @@
 iteration:  []
 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = False
 membership: [7, 8, 3, 4, 1, 0]
-iteration:  [3, 7, 8, 1, 4, 0, 2]
+iteration:  [8, 7, 4, 3, 2, 1, 0]
 % lazy ancestor set for [1, 3], stoprev = 0, inclusive = False
 membership: [1, 0]
-iteration:  [0, 1]
+iteration:  [1, 0]
 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = True
 membership: [11, 13, 7, 8, 3, 4, 1, 0]
-iteration:  [11, 13, 3, 7, 8, 1, 4, 0, 2]
+iteration:  [13, 11, 8, 7, 4, 3, 2, 1, 0]
 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = False
 membership: [7, 8]
-iteration:  [7, 8]
+iteration:  [8, 7]
 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = True
 membership: [11, 13, 7, 8]
-iteration:  [11, 13, 7, 8]
+iteration:  [13, 11, 8, 7]
+% lazy ancestor set for [11, 13], stoprev = 11, inclusive = True
+membership: [11, 13]
+iteration:  [13, 11]
+% lazy ancestor set for [11, 13], stoprev = 12, inclusive = True
+membership: [13]
+iteration:  [13]
+% lazy ancestor set for [10, 1], stoprev = 0, inclusive = True
+membership: [2, 10, 4, 5, 0, 1]
+iteration:  [10, 5, 4, 2, 1, 0]
--- a/tests/test-annotate.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-annotate.t	Wed Sep 26 20:33:09 2018 +0900
@@ -56,7 +56,6 @@
   $ hg annotate -Tjson a
   [
    {
-    "abspath": "a",
     "lines": [{"line": "a\n", "rev": 0}],
     "path": "a"
    }
@@ -65,8 +64,7 @@
   $ hg annotate -Tjson -cdfnul a
   [
    {
-    "abspath": "a",
-    "lines": [{"date": [1.0, 0], "file": "a", "line": "a\n", "line_number": 1, "node": "8435f90966e442695d2ded29fdade2bac5ad8065", "rev": 0, "user": "nobody"}],
+    "lines": [{"date": [1.0, 0], "line": "a\n", "line_number": 1, "node": "8435f90966e442695d2ded29fdade2bac5ad8065", "path": "a", "rev": 0, "user": "nobody"}],
     "path": "a"
    }
   ]
@@ -127,12 +125,10 @@
   $ hg annotate -Tjson a b
   [
    {
-    "abspath": "a",
     "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}],
     "path": "a"
    },
    {
-    "abspath": "b",
     "lines": [{"line": "a\n", "rev": 0}, {"line": "a\n", "rev": 1}, {"line": "a\n", "rev": 1}, {"line": "b4\n", "rev": 3}, {"line": "b5\n", "rev": 3}, {"line": "b6\n", "rev": 3}],
     "path": "b"
    }
@@ -140,7 +136,7 @@
 
 annotate multiple files (template)
 
-  $ hg annotate -T'== {abspath} ==\n{lines % "{rev}: {line}"}' a b
+  $ hg annotate -T'== {path} ==\n{lines % "{rev}: {line}"}' a b
   == a ==
   0: a
   1: a
@@ -568,8 +564,7 @@
   $ hg annotate -ncr "wdir()" -Tjson foo
   [
    {
-    "abspath": "foo",
-    "lines": [{"line": "foo\n", "node": "472b18db256d1e8282064eab4bfdaf48cbfe83cd", "rev": 11}, {"line": "foofoo\n", "node": null, "rev": null}],
+    "lines": [{"line": "foo\n", "node": "472b18db256d1e8282064eab4bfdaf48cbfe83cd", "rev": 11}, {"line": "foofoo\n", "node": "ffffffffffffffffffffffffffffffffffffffff", "rev": 2147483647}],
     "path": "foo"
    }
   ]
@@ -870,11 +865,9 @@
   $ hg annotate -Tjson binary empty
   [
    {
-    "abspath": "binary",
     "path": "binary"
    },
    {
-    "abspath": "empty",
     "lines": [],
     "path": "empty"
    }
@@ -957,13 +950,13 @@
   ...     f.write(b'0a\r0b\r\n1c\r1d\r\n0e\n1f\n0g') and None
   $ hg ci -m1
 
-  $ hg annotate -r0 a | $PYTHON "$TESTTMP/substcr.py"
+  $ hg annotate -r0 a | "$PYTHON" "$TESTTMP/substcr.py"
   0: 0a[CR]0b[CR]
   0: 0c[CR]0d[CR]
   0: 0e
   0: 0f
   0: 0g
-  $ hg annotate -r1 a | $PYTHON "$TESTTMP/substcr.py"
+  $ hg annotate -r1 a | "$PYTHON" "$TESTTMP/substcr.py"
   0: 0a[CR]0b[CR]
   1: 1c[CR]1d[CR]
   0: 0e
--- a/tests/test-archive.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-archive.t	Wed Sep 26 20:33:09 2018 +0900
@@ -341,7 +341,7 @@
   > except util.urlerr.httperror as e:
   >     sys.stderr.write(str(e) + '\n')
   > EOF
-  $ $PYTHON getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
+  $ "$PYTHON" getarchive.py "$TIP" gz | gunzip | tar tf - 2>/dev/null
   test-archive-1701ef1f1510/.hg_archival.txt
   test-archive-1701ef1f1510/.hgsub
   test-archive-1701ef1f1510/.hgsubstate
@@ -349,7 +349,7 @@
   test-archive-1701ef1f1510/baz/bletch
   test-archive-1701ef1f1510/foo
   test-archive-1701ef1f1510/subrepo/sub
-  $ $PYTHON getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
+  $ "$PYTHON" getarchive.py "$TIP" bz2 | bunzip2 | tar tf - 2>/dev/null
   test-archive-1701ef1f1510/.hg_archival.txt
   test-archive-1701ef1f1510/.hgsub
   test-archive-1701ef1f1510/.hgsubstate
@@ -357,7 +357,7 @@
   test-archive-1701ef1f1510/baz/bletch
   test-archive-1701ef1f1510/foo
   test-archive-1701ef1f1510/subrepo/sub
-  $ $PYTHON getarchive.py "$TIP" zip > archive.zip
+  $ "$PYTHON" getarchive.py "$TIP" zip > archive.zip
   $ unzip -t archive.zip
   Archive:  archive.zip
       testing: test-archive-1701ef1f1510/.hg_archival.txt*OK (glob)
@@ -371,19 +371,19 @@
 
 test that we can download single directories and files
 
-  $ $PYTHON getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
+  $ "$PYTHON" getarchive.py "$TIP" gz baz | gunzip | tar tf - 2>/dev/null
   test-archive-1701ef1f1510/baz/bletch
-  $ $PYTHON getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
+  $ "$PYTHON" getarchive.py "$TIP" gz foo | gunzip | tar tf - 2>/dev/null
   test-archive-1701ef1f1510/foo
 
 test that we detect file patterns that match no files
 
-  $ $PYTHON getarchive.py "$TIP" gz foobar
+  $ "$PYTHON" getarchive.py "$TIP" gz foobar
   HTTP Error 404: file(s) not found: foobar
 
 test that we reject unsafe patterns
 
-  $ $PYTHON getarchive.py "$TIP" gz relre:baz
+  $ "$PYTHON" getarchive.py "$TIP" gz relre:baz
   HTTP Error 404: file(s) not found: relre:baz
 
   $ killdaemons.py
@@ -464,7 +464,7 @@
   $ sleep 1
   $ hg archive -t tgz tip.tar.gz
   $ mv tip.tar.gz tip2.tar.gz
-  $ $PYTHON md5comp.py tip1.tar.gz tip2.tar.gz
+  $ "$PYTHON" md5comp.py tip1.tar.gz tip2.tar.gz
   True
 
   $ hg archive -t zip -p /illegal test.zip
@@ -598,12 +598,12 @@
 
   $ hg -R repo archive --prefix tar-extracted archive.tar
   $ (TZ=UTC-3; export TZ; tar xf archive.tar)
-  $ $PYTHON show_mtime.py tar-extracted/a
+  $ "$PYTHON" show_mtime.py tar-extracted/a
   456789012
 
   $ hg -R repo archive --prefix zip-extracted archive.zip
   $ (TZ=UTC-3; export TZ; unzip -q archive.zip)
-  $ $PYTHON show_mtime.py zip-extracted/a
+  $ "$PYTHON" show_mtime.py zip-extracted/a
   456789012
 
   $ cd ..
--- a/tests/test-audit-path.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-audit-path.t	Wed Sep 26 20:33:09 2018 +0900
@@ -82,7 +82,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 6 files (+4 heads)
-  new changesets b7da9bf6b037:fc1393d727bc
+  new changesets b7da9bf6b037:fc1393d727bc (5 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 attack .hg/test
--- a/tests/test-backout.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-backout.t	Wed Sep 26 20:33:09 2018 +0900
@@ -144,17 +144,17 @@
   $ touch -t 200001010000 c
   $ hg status -A
   C c
-  $ hg debugstate --nodates
+  $ hg debugstate --no-dates
   n 644         12 set                 c
   $ hg backout -d '6 0' -m 'to be rollback-ed soon' -r .
+  removing c
   adding b
-  removing c
   changeset 6:4bfec048029d backs out changeset 5:fac0b729a654
   $ hg rollback -q
   $ hg status -A
   A b
   R c
-  $ hg debugstate --nodates
+  $ hg debugstate --no-dates
   a   0         -1 unset               b
   r   0          0 set                 c
 
--- a/tests/test-bad-extension.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bad-extension.t	Wed Sep 26 20:33:09 2018 +0900
@@ -72,23 +72,60 @@
   $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
   BADEXTS: badext badext2
 
+#if no-extraextensions
 show traceback for ImportError of hgext.name if devel.debug.extensions is set
 
   $ (hg help help --traceback --debug --config devel.debug.extensions=yes 2>&1) \
   > | grep -v '^ ' \
   > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import'
+  debug.extensions: loading extensions
+  debug.extensions: - processing 5 entries
+  debug.extensions:   - loading extension: 'gpg'
+  debug.extensions:   > 'gpg' extension loaded in * (glob)
+  debug.extensions:     - validating extension tables: 'gpg'
+  debug.extensions:     - invoking registered callbacks: 'gpg'
+  debug.extensions:     > callbacks completed in * (glob)
+  debug.extensions:   - loading extension: 'badext'
   *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
   Traceback (most recent call last):
   Exception: bit bucket overflow
-  could not import hgext.badext2 (No module named *badext2): trying hgext3rd.badext2 (glob)
+  debug.extensions:   - loading extension: 'baddocext'
+  debug.extensions:   > 'baddocext' extension loaded in * (glob)
+  debug.extensions:     - validating extension tables: 'baddocext'
+  debug.extensions:     - invoking registered callbacks: 'baddocext'
+  debug.extensions:     > callbacks completed in * (glob)
+  debug.extensions:   - loading extension: 'badext2'
+  debug.extensions:     - could not import hgext.badext2 (No module named badext2): trying hgext3rd.badext2
   Traceback (most recent call last):
   ImportError: No module named *badext2 (glob)
-  could not import hgext3rd.badext2 (No module named *badext2): trying badext2 (glob)
+  debug.extensions:     - could not import hgext3rd.badext2 (No module named badext2): trying badext2
   Traceback (most recent call last):
   ImportError: No module named *badext2 (glob)
   *** failed to import extension badext2: No module named badext2
   Traceback (most recent call last):
   ImportError: No module named badext2
+  debug.extensions: > loaded 2 extensions, total time * (glob)
+  debug.extensions: - loading configtable attributes
+  debug.extensions: - executing uisetup hooks
+  debug.extensions:   - running uisetup for 'gpg'
+  debug.extensions:   > uisetup for 'gpg' took * (glob)
+  debug.extensions:   - running uisetup for 'baddocext'
+  debug.extensions:   > uisetup for 'baddocext' took * (glob)
+  debug.extensions: > all uisetup took * (glob)
+  debug.extensions: - executing extsetup hooks
+  debug.extensions:   - running extsetup for 'gpg'
+  debug.extensions:   > extsetup for 'gpg' took * (glob)
+  debug.extensions:   - running extsetup for 'baddocext'
+  debug.extensions:   > extsetup for 'baddocext' took * (glob)
+  debug.extensions: > all extsetup took * (glob)
+  debug.extensions: - executing remaining aftercallbacks
+  debug.extensions: > remaining aftercallbacks completed in * (glob)
+  debug.extensions: - loading extension registration objects
+  debug.extensions: > extension registration object loading took * (glob)
+  debug.extensions: > extension baddocext take a total of * to load (glob)
+  debug.extensions: > extension gpg take a total of * to load (glob)
+  debug.extensions: extension loading complete
+#endif
 
 confirm that there's no crash when an extension's documentation is bad
 
--- a/tests/test-bad-pull.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bad-pull.t	Wed Sep 26 20:33:09 2018 +0900
@@ -7,7 +7,7 @@
   $ test -d copy
   [1]
 
-  $ $PYTHON "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
+  $ "$PYTHON" "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
   $ cat dumb.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/foo copy2
   abort: HTTP Error 404: * (glob)
--- a/tests/test-basic.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-basic.t	Wed Sep 26 20:33:09 2018 +0900
@@ -68,7 +68,7 @@
   > EOF
   $ hg up null
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ $PYTHON ./update_to_rev0.py
+  $ "$PYTHON" ./update_to_rev0.py
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg identify -n
   0
@@ -89,7 +89,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
 Repository root:
 
--- a/tests/test-bdiff.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bdiff.py	Wed Sep 26 20:33:09 2018 +0900
@@ -29,25 +29,25 @@
 
     def test_bdiff_basic(self):
         cases = [
-            ("a\nc\n\n\n\n", "a\nb\n\n\n"),
-            ("a\nb\nc\n", "a\nc\n"),
-            ("", ""),
-            ("a\nb\nc", "a\nb\nc"),
-            ("a\nb\nc\nd\n", "a\nd\n"),
-            ("a\nb\nc\nd\n", "a\nc\ne\n"),
-            ("a\nb\nc\n", "a\nc\n"),
-            ("a\n", "c\na\nb\n"),
-            ("a\n", ""),
-            ("a\n", "b\nc\n"),
-            ("a\n", "c\na\n"),
-            ("", "adjfkjdjksdhfksj"),
-            ("", "ab"),
-            ("", "abc"),
-            ("a", "a"),
-            ("ab", "ab"),
-            ("abc", "abc"),
-            ("a\n", "a\n"),
-            ("a\nb", "a\nb"),
+            (b"a\nc\n\n\n\n", b"a\nb\n\n\n"),
+            (b"a\nb\nc\n", b"a\nc\n"),
+            (b"", b""),
+            (b"a\nb\nc", b"a\nb\nc"),
+            (b"a\nb\nc\nd\n", b"a\nd\n"),
+            (b"a\nb\nc\nd\n", b"a\nc\ne\n"),
+            (b"a\nb\nc\n", b"a\nc\n"),
+            (b"a\n", b"c\na\nb\n"),
+            (b"a\n", b""),
+            (b"a\n", b"b\nc\n"),
+            (b"a\n", b"c\na\n"),
+            (b"", b"adjfkjdjksdhfksj"),
+            (b"", b"ab"),
+            (b"", b"abc"),
+            (b"a", b"a"),
+            (b"ab", b"ab"),
+            (b"abc", b"abc"),
+            (b"a\n", b"a\n"),
+            (b"a\nb", b"a\nb"),
         ]
         for a, b in cases:
             self.assert_bdiff(a, b)
@@ -71,42 +71,44 @@
 
     def test_issue1295(self):
         cases = [
-            ("x\n\nx\n\nx\n\nx\n\nz\n", "x\n\nx\n\ny\n\nx\n\nx\n\nz\n",
-             ['x\n\nx\n\n', diffreplace(6, 6, '', 'y\n\n'), 'x\n\nx\n\nz\n']),
-            ("x\n\nx\n\nx\n\nx\n\nz\n", "x\n\nx\n\ny\n\nx\n\ny\n\nx\n\nz\n",
-             ['x\n\nx\n\n',
-              diffreplace(6, 6, '', 'y\n\n'),
-              'x\n\n',
-              diffreplace(9, 9, '', 'y\n\n'),
-              'x\n\nz\n']),
+            (b"x\n\nx\n\nx\n\nx\n\nz\n", b"x\n\nx\n\ny\n\nx\n\nx\n\nz\n",
+             [b'x\n\nx\n\n',
+              diffreplace(6, 6, b'', b'y\n\n'),
+              b'x\n\nx\n\nz\n']),
+            (b"x\n\nx\n\nx\n\nx\n\nz\n", b"x\n\nx\n\ny\n\nx\n\ny\n\nx\n\nz\n",
+             [b'x\n\nx\n\n',
+              diffreplace(6, 6, b'', b'y\n\n'),
+              b'x\n\n',
+              diffreplace(9, 9, b'', b'y\n\n'),
+              b'x\n\nz\n']),
         ]
         for old, new, want in cases:
             self.assertEqual(self.showdiff(old, new), want)
 
     def test_issue1295_varies_on_pure(self):
             # we should pick up abbbc. rather than bc.de as the longest match
-        got = self.showdiff("a\nb\nb\nb\nc\n.\nd\ne\n.\nf\n",
-                            "a\nb\nb\na\nb\nb\nb\nc\n.\nb\nc\n.\nd\ne\nf\n")
-        want_c = ['a\nb\nb\n',
-                  diffreplace(6, 6, '', 'a\nb\nb\nb\nc\n.\n'),
-                  'b\nc\n.\nd\ne\n',
-                  diffreplace(16, 18, '.\n', ''),
-                  'f\n']
-        want_pure = [diffreplace(0, 0, '', 'a\nb\nb\n'),
-                     'a\nb\nb\nb\nc\n.\n',
-                     diffreplace(12, 12, '', 'b\nc\n.\n'),
-                     'd\ne\n',
-                     diffreplace(16, 18, '.\n', ''), 'f\n']
-        self.assert_(got in (want_c, want_pure),
-                     'got: %r, wanted either %r or %r' % (
-                         got, want_c, want_pure))
+        got = self.showdiff(b"a\nb\nb\nb\nc\n.\nd\ne\n.\nf\n",
+                            b"a\nb\nb\na\nb\nb\nb\nc\n.\nb\nc\n.\nd\ne\nf\n")
+        want_c = [b'a\nb\nb\n',
+                  diffreplace(6, 6, b'', b'a\nb\nb\nb\nc\n.\n'),
+                  b'b\nc\n.\nd\ne\n',
+                  diffreplace(16, 18, b'.\n', b''),
+                  b'f\n']
+        want_pure = [diffreplace(0, 0, b'', b'a\nb\nb\n'),
+                     b'a\nb\nb\nb\nc\n.\n',
+                     diffreplace(12, 12, b'', b'b\nc\n.\n'),
+                     b'd\ne\n',
+                     diffreplace(16, 18, b'.\n', b''), b'f\n']
+        self.assertTrue(got in (want_c, want_pure),
+                        'got: %r, wanted either %r or %r' % (
+                            got, want_c, want_pure))
 
     def test_fixws(self):
         cases = [
-            (" \ta\r b\t\n", "ab\n", 1),
-            (" \ta\r b\t\n", " a b\n", 0),
-            ("", "", 1),
-            ("", "", 0),
+            (b" \ta\r b\t\n", b"ab\n", 1),
+            (b" \ta\r b\t\n", b" a b\n", 0),
+            (b"", b"", 1),
+            (b"", b"", 0),
         ]
         for a, b, allws in cases:
             c = mdiff.fixws(a, allws)
@@ -115,34 +117,34 @@
 
     def test_nice_diff_for_trivial_change(self):
         self.assertEqual(self.showdiff(
-            ''.join('<%s\n-\n' % i for i in range(5)),
-            ''.join('>%s\n-\n' % i for i in range(5))),
-                         [diffreplace(0, 3, '<0\n', '>0\n'),
-                          '-\n',
-                          diffreplace(5, 8, '<1\n', '>1\n'),
-                          '-\n',
-                          diffreplace(10, 13, '<2\n', '>2\n'),
-                          '-\n',
-                          diffreplace(15, 18, '<3\n', '>3\n'),
-                          '-\n',
-                          diffreplace(20, 23, '<4\n', '>4\n'),
-                          '-\n'])
+            b''.join(b'<%d\n-\n' % i for i in range(5)),
+            b''.join(b'>%d\n-\n' % i for i in range(5))),
+                         [diffreplace(0, 3, b'<0\n', b'>0\n'),
+                          b'-\n',
+                          diffreplace(5, 8, b'<1\n', b'>1\n'),
+                          b'-\n',
+                          diffreplace(10, 13, b'<2\n', b'>2\n'),
+                          b'-\n',
+                          diffreplace(15, 18, b'<3\n', b'>3\n'),
+                          b'-\n',
+                          diffreplace(20, 23, b'<4\n', b'>4\n'),
+                          b'-\n'])
 
     def test_prefer_appending(self):
         # 1 line to 3 lines
-        self.assertEqual(self.showdiff('a\n', 'a\n' * 3),
-                         ['a\n', diffreplace(2, 2, '', 'a\na\n')])
+        self.assertEqual(self.showdiff(b'a\n', b'a\n' * 3),
+                         [b'a\n', diffreplace(2, 2, b'', b'a\na\n')])
         # 1 line to 5 lines
-        self.assertEqual(self.showdiff('a\n', 'a\n' * 5),
-                         ['a\n', diffreplace(2, 2, '', 'a\na\na\na\n')])
+        self.assertEqual(self.showdiff(b'a\n', b'a\n' * 5),
+                         [b'a\n', diffreplace(2, 2, b'', b'a\na\na\na\n')])
 
     def test_prefer_removing_trailing(self):
         # 3 lines to 1 line
-        self.assertEqual(self.showdiff('a\n' * 3, 'a\n'),
-                         ['a\n', diffreplace(2, 6, 'a\na\n', '')])
+        self.assertEqual(self.showdiff(b'a\n' * 3, b'a\n'),
+                         [b'a\n', diffreplace(2, 6, b'a\na\n', b'')])
         # 5 lines to 1 line
-        self.assertEqual(self.showdiff('a\n' * 5, 'a\n'),
-                         ['a\n', diffreplace(2, 10, 'a\na\na\na\n', '')])
+        self.assertEqual(self.showdiff(b'a\n' * 5, b'a\n'),
+                         [b'a\n', diffreplace(2, 10, b'a\na\na\na\n', b'')])
 
 if __name__ == '__main__':
     import silenttestrunner
--- a/tests/test-blackbox.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-blackbox.t	Wed Sep 26 20:33:09 2018 +0900
@@ -233,7 +233,7 @@
   $ sed -e 's/\(.*test1.*\)/#\1/; s#\(.*commit2.*\)#os.rmdir(".hg/blackbox.log")\
   > os.rename(".hg/blackbox.log-", ".hg/blackbox.log")\
   > \1#' $TESTDIR/test-dispatch.py > ../test-dispatch.py
-  $ $PYTHON $TESTDIR/blackbox-readonly-dispatch.py
+  $ "$PYTHON" $TESTDIR/blackbox-readonly-dispatch.py
   running: --debug add foo
   warning: cannot write to blackbox.log: Is a directory (no-windows !)
   warning: cannot write to blackbox.log: $TESTTMP/blackboxtest3/.hg/blackbox.log: Access is denied (windows !)
@@ -343,7 +343,7 @@
   > noop=$TESTTMP/noop.py
   > EOF
 
-  $ $PYTHON -c 'print("a" * 400)' > .hg/blackbox.log
+  $ "$PYTHON" -c 'print("a" * 400)' > .hg/blackbox.log
   $ chg noop
   $ chg noop
   $ chg noop
@@ -362,7 +362,7 @@
   >     print('%s: %s %d' % (p, desc, limit))
   > EOF
 
-  $ $PYTHON showsize.py .hg/blackbox*
+  $ "$PYTHON" showsize.py .hg/blackbox*
   .hg/blackbox.log: < 500
   .hg/blackbox.log.1: >= 500
   .hg/blackbox.log.2: >= 500
--- a/tests/test-bookmarks-current.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bookmarks-current.t	Wed Sep 26 20:33:09 2018 +0900
@@ -222,3 +222,27 @@
      Z                         0:719295282060
   $ hg parents -q
   4:8fa964221e8e
+
+Checks command to retrieve active bookmark
+------------------------------------------
+
+display how "{activebookmark}" template is unsuitable for the task
+
+  $ hg book -T '- {activebookmark}\n'
+  - 
+  - Y
+  - 
+
+  $ hg book -r . W
+  $ hg book -T '- {activebookmark}\n'
+  - Y
+  - 
+  - Y
+  - 
+
+  $ hg bookmarks -ql .
+  Y
+  $ hg bookmarks --inactive
+  $ hg bookmarks -ql .
+  abort: no active bookmark!
+  [255]
--- a/tests/test-bookmarks-pushpull.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bookmarks-pushpull.t	Wed Sep 26 20:33:09 2018 +0900
@@ -55,7 +55,7 @@
   adding remote bookmark X
   updating bookmark Y
   adding remote bookmark Z
-  new changesets 4e3505fd9583
+  new changesets 4e3505fd9583 (1 drafts)
   test-hook-bookmark: X:   -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
   test-hook-bookmark: Y:  0000000000000000000000000000000000000000 -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
   test-hook-bookmark: Z:   -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
@@ -345,7 +345,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark V)
   $ hg push -B . ../a
-  abort: no active bookmark
+  abort: no active bookmark!
   [255]
   $ hg update -r V
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -418,7 +418,7 @@
   divergent bookmark @ stored as @foo
   divergent bookmark X stored as X@foo
   updating bookmark Z
-  new changesets 0d2164f0ce0d
+  new changesets 0d2164f0ce0d (1 drafts)
   test-hook-bookmark: @foo:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
   test-hook-bookmark: X@foo:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
   test-hook-bookmark: Z:  4e3505fd95835d721066b76e75dbb8cc554d7f77 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
@@ -435,7 +435,7 @@
 
 (test that too many divergence of bookmark)
 
-  $ $PYTHON $TESTDIR/seq.py 1 100 | while read i; do hg bookmarks -r 000000000000 "X@${i}"; done
+  $ "$PYTHON" $TESTDIR/seq.py 1 100 | while read i; do hg bookmarks -r 000000000000 "X@${i}"; done
   $ hg pull ../a
   pulling from ../a
   searching for changes
@@ -463,7 +463,7 @@
      @1                        2:0d2164f0ce0d
      @foo                      2:0d2164f0ce0d
 
-  $ $PYTHON $TESTDIR/seq.py 1 100 | while read i; do hg bookmarks -d "X@${i}"; done
+  $ "$PYTHON" $TESTDIR/seq.py 1 100 | while read i; do hg bookmarks -d "X@${i}"; done
   $ hg bookmarks -d "@1"
 
   $ hg push -f ../a
@@ -582,7 +582,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   updating bookmark Y
-  new changesets b0a5eff05604
+  new changesets b0a5eff05604 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg book
    * @                         1:0d2164f0ce0d
@@ -632,7 +632,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   updating bookmark Y
-  new changesets 35d1ef0a8d1b
+  new changesets 35d1ef0a8d1b (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg book
      @                         1:0d2164f0ce0d
@@ -796,7 +796,7 @@
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
   2 new obsolescence markers
-  new changesets 4e3505fd9583:c922c0139ca0
+  new changesets 4e3505fd9583:c922c0139ca0 (5 drafts)
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks bookmarks
@@ -933,7 +933,7 @@
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
   2 new obsolescence markers
-  new changesets 4e3505fd9583:c922c0139ca0
+  new changesets 4e3505fd9583:c922c0139ca0 (5 drafts)
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd addmarks
@@ -1082,7 +1082,7 @@
   > ssh=ssh://user@dummy/issue4455-dest
   > http=http://localhost:$HGPORT/
   > [ui]
-  > ssh=$PYTHON "$TESTDIR/dummyssh"
+  > ssh="$PYTHON" "$TESTDIR/dummyssh"
   > EOF
   $ cat >> ../issue4455-dest/.hg/hgrc << EOF
   > [hooks]
@@ -1225,7 +1225,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 79513d0d7716
+  new changesets 79513d0d7716 (1 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-bookmarks.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bookmarks.t	Wed Sep 26 20:33:09 2018 +0900
@@ -68,6 +68,25 @@
      X                         0:f7b1eb17ad24
    * X2                        0:f7b1eb17ad24
      Y                         -1:000000000000
+  $ hg bookmarks -l
+     X                         0:f7b1eb17ad24
+   * X2                        0:f7b1eb17ad24
+     Y                         -1:000000000000
+  $ hg bookmarks -l X Y
+     X                         0:f7b1eb17ad24
+     Y                         -1:000000000000
+  $ hg bookmarks -l .
+   * X2                        0:f7b1eb17ad24
+  $ hg bookmarks -l X A Y
+  abort: bookmark 'A' does not exist
+  [255]
+  $ hg bookmarks -l -r0
+  abort: --rev is incompatible with --list
+  [255]
+  $ hg bookmarks -l --inactive
+  abort: --inactive is incompatible with --list
+  [255]
+
   $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
   0 X
   0 X2
@@ -151,6 +170,31 @@
   summary:     0
   
 
+"." is expanded to the active bookmark:
+
+  $ hg log -r 'bookmark(.)'
+  changeset:   1:925d80f479bb
+  bookmark:    X2
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1
+  
+
+but "literal:." is not since "." seems not a literal bookmark:
+
+  $ hg log -r 'bookmark("literal:.")'
+  abort: bookmark '.' does not exist!
+  [255]
+
+"." should fail if there's no active bookmark:
+
+  $ hg bookmark --inactive
+  $ hg log -r 'bookmark(.)'
+  abort: no active bookmark!
+  [255]
+  $ hg log -r 'present(bookmark(.))'
+
   $ hg log -r 'bookmark(unknown)'
   abort: bookmark 'unknown' does not exist!
   [255]
@@ -166,6 +210,12 @@
   $ hg help revsets | grep 'bookmark('
       "bookmark([name])"
 
+reactivate "X2"
+
+  $ hg update X2
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark X2)
+
 bookmarks X and X2 moved to rev 1, Y at rev -1
 
   $ hg bookmarks
@@ -229,7 +279,7 @@
   $ hg book rename-me
   $ hg book -i rename-me
   $ hg book -m . renamed
-  abort: no active bookmark
+  abort: no active bookmark!
   [255]
   $ hg up -q Y
   $ hg book -d rename-me
@@ -249,7 +299,7 @@
   $ hg book delete-me
   $ hg book -i delete-me
   $ hg book -d .
-  abort: no active bookmark
+  abort: no active bookmark!
   [255]
   $ hg up -q Y
   $ hg book -d delete-me
@@ -296,6 +346,12 @@
   abort: bookmark 'A' does not exist
   [255]
 
+delete with --inactive
+
+  $ hg bookmark -d --inactive Y
+  abort: --inactive is incompatible with --delete
+  [255]
+
 bookmark name with spaces should be stripped
 
   $ hg bookmark ' x  y '
@@ -663,7 +719,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
-  new changesets 125c9a1d6df6:9ba5f110a0b3
+  new changesets 125c9a1d6df6:9ba5f110a0b3 (2 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 update to active bookmark if it's not the parent
--- a/tests/test-branches.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-branches.t	Wed Sep 26 20:33:09 2018 +0900
@@ -91,7 +91,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files
-  new changesets f0e4c7f04036:33c2ceb9310b
+  new changesets f0e4c7f04036:33c2ceb9310b (3 drafts)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg update '"colon:test"'
--- a/tests/test-bundle-r.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bundle-r.t	Wed Sep 26 20:33:09 2018 +0900
@@ -5,7 +5,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets bfaf4b5cbf01:916f1afdef90
+  new changesets bfaf4b5cbf01:916f1afdef90 (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -27,13 +27,13 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets bfaf4b5cbf01
+  new changesets bfaf4b5cbf01 (1 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   0:bfaf4b5cbf01
   searching for changes
   2 changesets found
@@ -41,13 +41,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
-  new changesets bfaf4b5cbf01:21f32785131f
+  new changesets bfaf4b5cbf01:21f32785131f (2 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   1:21f32785131f
   searching for changes
   3 changesets found
@@ -55,13 +55,13 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
-  new changesets bfaf4b5cbf01:4ce51a113780
+  new changesets bfaf4b5cbf01:4ce51a113780 (3 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   2:4ce51a113780
   searching for changes
   4 changesets found
@@ -69,13 +69,13 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
-  new changesets bfaf4b5cbf01:93ee6ab32777
+  new changesets bfaf4b5cbf01:93ee6ab32777 (4 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 4 changesets, 4 total revisions
+  checked 4 changesets with 4 changes to 1 files
   3:93ee6ab32777
   searching for changes
   2 changesets found
@@ -83,13 +83,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
-  new changesets bfaf4b5cbf01:c70afb1ee985
+  new changesets bfaf4b5cbf01:c70afb1ee985 (2 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   1:c70afb1ee985
   searching for changes
   3 changesets found
@@ -97,13 +97,13 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
-  new changesets bfaf4b5cbf01:f03ae5a9b979
+  new changesets bfaf4b5cbf01:f03ae5a9b979 (3 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   2:f03ae5a9b979
   searching for changes
   4 changesets found
@@ -111,13 +111,13 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
-  new changesets bfaf4b5cbf01:095cb14b1b4d
+  new changesets bfaf4b5cbf01:095cb14b1b4d (4 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 4 changesets, 5 total revisions
+  checked 4 changesets with 5 changes to 2 files
   3:095cb14b1b4d
   searching for changes
   5 changesets found
@@ -125,13 +125,13 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
-  new changesets bfaf4b5cbf01:faa2e4234c7a
+  new changesets bfaf4b5cbf01:faa2e4234c7a (5 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 5 changesets, 6 total revisions
+  checked 5 changesets with 6 changes to 3 files
   4:faa2e4234c7a
   searching for changes
   5 changesets found
@@ -139,13 +139,13 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
-  new changesets bfaf4b5cbf01:916f1afdef90
+  new changesets bfaf4b5cbf01:916f1afdef90 (5 drafts)
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
   4:916f1afdef90
   $ cd test-8
   $ hg pull ../test-7
@@ -163,7 +163,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
   $ hg rollback
   repository tip rolled back to revision 4 (undo pull)
   $ cd ..
@@ -235,7 +235,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 4 changes to 4 files (+1 heads)
-  new changesets 93ee6ab32777:916f1afdef90
+  new changesets 93ee6ab32777:916f1afdef90 (6 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 revision 8
@@ -247,7 +247,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
   $ hg rollback
   repository tip rolled back to revision 2 (undo unbundle)
 
@@ -260,7 +260,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  new changesets 93ee6ab32777:916f1afdef90
+  new changesets 93ee6ab32777:916f1afdef90 (2 drafts)
   (run 'hg update' to get a working copy)
 
 revision 4
@@ -272,7 +272,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
   $ hg rollback
   repository tip rolled back to revision 2 (undo unbundle)
   $ hg unbundle ../test-bundle-branch2.hg
@@ -280,7 +280,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files (+1 heads)
-  new changesets c70afb1ee985:faa2e4234c7a
+  new changesets c70afb1ee985:faa2e4234c7a (4 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 revision 6
@@ -292,7 +292,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 7 changesets, 6 total revisions
+  checked 7 changesets with 6 changes to 3 files
   $ hg rollback
   repository tip rolled back to revision 2 (undo unbundle)
   $ hg unbundle ../test-bundle-cset-7.hg
@@ -300,7 +300,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  new changesets 93ee6ab32777:916f1afdef90
+  new changesets 93ee6ab32777:916f1afdef90 (2 drafts)
   (run 'hg update' to get a working copy)
 
 revision 4
@@ -312,7 +312,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
 
   $ cd ../test
   $ hg merge 7
@@ -334,7 +334,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 4 changes to 4 files
-  new changesets 93ee6ab32777:03fc0b0e347c
+  new changesets 93ee6ab32777:03fc0b0e347c (7 drafts)
   (run 'hg update' to get a working copy)
 
 revision 9
@@ -346,6 +346,6 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 10 changesets, 7 total revisions
+  checked 10 changesets with 7 changes to 4 files
 
   $ cd ..
--- a/tests/test-bundle-type.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bundle-type.t	Wed Sep 26 20:33:09 2018 +0900
@@ -18,7 +18,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets c35a0f9217e6
+  new changesets c35a0f9217e6 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-bundle.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bundle.t	Wed Sep 26 20:33:09 2018 +0900
@@ -33,7 +33,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
   $ cd ..
   $ hg init empty
 
@@ -75,7 +75,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  0 files, 0 changesets, 0 total revisions
+  checked 0 changesets with 0 changes to 0 files
 
 #if repobundlerepo
 
@@ -100,7 +100,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets f9ee2f85a263:aa35859c02ea
+  new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Rollback empty
@@ -117,7 +117,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets f9ee2f85a263:aa35859c02ea
+  new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Pull full.hg into test (using -R)
@@ -148,7 +148,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets f9ee2f85a263:aa35859c02ea
+  new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Log -R full.hg in fresh empty
@@ -231,7 +231,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets f9ee2f85a263:aa35859c02ea
+  new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle*../full.hg (glob)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -255,7 +255,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets f9ee2f85a263:aa35859c02ea
+  new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle:empty+full.hg
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -556,7 +556,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets f9ee2f85a263:aa35859c02ea
+  new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R full-clone heads
@@ -596,12 +596,12 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets f9ee2f85a263
+  new changesets f9ee2f85a263 (1 drafts)
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 34c2bf6b0626
+  new changesets 34c2bf6b0626 (1 drafts)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 View full contents of the bundle
@@ -729,7 +729,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
 
 #if repobundlerepo
 diff against bundle
@@ -796,13 +796,13 @@
   057f4db07f61970e1c11e83be79e9d08adc4dc31
   bundle2-output-bundle: "HG20", (1 params) 2 parts total
   bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
-  bundling: 1/2 changesets (50.00%)
-  bundling: 2/2 changesets (100.00%)
-  bundling: 1/2 manifests (50.00%)
-  bundling: 2/2 manifests (100.00%)
-  bundling: b 1/3 files (33.33%)
-  bundling: b1 2/3 files (66.67%)
-  bundling: x 3/3 files (100.00%)
+  changesets: 1/2 chunks (50.00%)
+  changesets: 2/2 chunks (100.00%)
+  manifests: 1/2 chunks (50.00%)
+  manifests: 2/2 chunks (100.00%)
+  files: b 1/3 files (33.33%)
+  files: b1 2/3 files (66.67%)
+  files: x 3/3 files (100.00%)
   bundle2-output-part: "cache:rev-branch-cache" (advisory) streamed payload
 
 #if repobundlerepo
@@ -815,7 +815,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 3 changesets, 5 total revisions
+  checked 3 changesets with 5 changes to 4 files
 #endif
 
 == Test bundling no commits
--- a/tests/test-bundle2-exchange.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bundle2-exchange.t	Wed Sep 26 20:33:09 2018 +0900
@@ -60,7 +60,7 @@
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
   pre-close-tip:02de42196ebe draft 
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   postclose-tip:02de42196ebe draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:$ID$ HG_TXNNAME=unbundle
   bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
@@ -95,7 +95,7 @@
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
   pre-close-tip:9520eea781bc draft 
-  new changesets cd010b8cd998:9520eea781bc
+  new changesets cd010b8cd998:9520eea781bc (1 drafts)
   postclose-tip:9520eea781bc draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=9520eea781bcca16c1e15acc0ba14335a0e8e5ba HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -124,7 +124,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   pre-close-tip:24b6387c8c8c draft 
-  new changesets 24b6387c8c8c
+  new changesets 24b6387c8c8c (1 drafts)
   postclose-tip:24b6387c8c8c draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_NODE_LAST=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -300,7 +300,7 @@
   1 new obsolescence markers
   updating bookmark book_02de
   pre-close-tip:02de42196ebe draft book_02de
-  new changesets 02de42196ebe
+  new changesets 02de42196ebe (1 drafts)
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   ssh://user@dummy/main HG_URL=ssh://user@dummy/main
@@ -326,7 +326,7 @@
   1 new obsolescence markers
   updating bookmark book_42cc
   pre-close-tip:42ccdea3bb16 draft book_42cc
-  new changesets 42ccdea3bb16
+  new changesets 42ccdea3bb16 (1 drafts)
   postclose-tip:42ccdea3bb16 draft book_42cc
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_NODE_LAST=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
--- a/tests/test-bundle2-format.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bundle2-format.t	Wed Sep 26 20:33:09 2018 +0900
@@ -232,7 +232,7 @@
   > [experimental]
   > evolution.createmarkers=True
   > [ui]
-  > ssh=$PYTHON "$TESTDIR/dummyssh"
+  > ssh="$PYTHON" "$TESTDIR/dummyssh"
   > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
   > [web]
   > push_ssl = false
@@ -835,7 +835,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg log -G
@@ -873,17 +873,17 @@
   bundle2-output-part: "changegroup" (advisory) streamed payload
   bundle2-output: part 0: "changegroup"
   bundle2-output: header chunk size: 18
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: D 1/3 files (33.33%)
-  bundling: E 2/3 files (66.67%)
-  bundling: H 3/3 files (100.00%)
+  changesets: 1/4 chunks (25.00%)
+  changesets: 2/4 chunks (50.00%)
+  changesets: 3/4 chunks (75.00%)
+  changesets: 4/4 chunks (100.00%)
+  manifests: 1/4 chunks (25.00%)
+  manifests: 2/4 chunks (50.00%)
+  manifests: 3/4 chunks (75.00%)
+  manifests: 4/4 chunks (100.00%)
+  files: D 1/3 files (33.33%)
+  files: E 2/3 files (66.67%)
+  files: H 3/3 files (100.00%)
   bundle2-output: payload chunk size: 1555
   bundle2-output: closing payload chunk
   bundle2-output: end of bundle
--- a/tests/test-bundle2-pushback.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bundle2-pushback.t	Wed Sep 26 20:33:09 2018 +0900
@@ -36,7 +36,7 @@
 
   $ cat >> $HGRCPATH <<EOF
   > [ui]
-  > ssh = $PYTHON "$TESTDIR/dummyssh"
+  > ssh = "$PYTHON" "$TESTDIR/dummyssh"
   > username = nobody <no.reply@example.com>
   > 
   > [alias]
--- a/tests/test-bundle2-remote-changegroup.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-bundle2-remote-changegroup.t	Wed Sep 26 20:33:09 2018 +0900
@@ -90,12 +90,12 @@
 
 Start a simple HTTP server to serve bundles
 
-  $ $PYTHON "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
+  $ "$PYTHON" "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
   $ cat dumb.pid >> $DAEMON_PIDS
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > ssh=$PYTHON "$TESTDIR/dummyssh"
+  > ssh="$PYTHON" "$TESTDIR/dummyssh"
   > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
   > EOF
 
@@ -106,7 +106,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R repo log -G
--- a/tests/test-casefolding.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-casefolding.t	Wed Sep 26 20:33:09 2018 +0900
@@ -52,7 +52,8 @@
   $ hg ci -Am addb D/b
   $ hg mv D/b d/b
   D/b: not overwriting - file already committed
-  (hg rename --force to replace the file by recording a rename)
+  ('hg rename --force' to replace the file by recording a rename)
+  [1]
   $ hg mv D/b d/c
   $ hg st
   A D/c
--- a/tests/test-cat.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-cat.t	Wed Sep 26 20:33:09 2018 +0900
@@ -65,7 +65,7 @@
 
 Test template output
 
-  $ hg --cwd tmp cat ../b ../c -T '== {path} ({abspath}) r{rev} ==\n{data}'
+  $ hg --cwd tmp cat ../b ../c -T '== {path|relpath} ({path}) r{rev} ==\n{data}'
   == ../b (b) r2 ==
   1
   == ../c (c) r2 ==
@@ -74,12 +74,10 @@
   $ hg cat b c -Tjson --output -
   [
    {
-    "abspath": "b",
     "data": "1\n",
     "path": "b"
    },
    {
-    "abspath": "c",
     "data": "3\n",
     "path": "c"
    }
@@ -89,7 +87,6 @@
   $ cat tmp/b.json
   [
    {
-    "abspath": "b",
     "data": "1\n",
     "path": "b"
    }
@@ -97,7 +94,6 @@
   $ cat tmp/c.json
   [
    {
-    "abspath": "c",
     "data": "3\n",
     "path": "c"
    }
--- a/tests/test-cbor.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-cbor.py	Wed Sep 26 20:33:09 2018 +0900
@@ -1,6 +1,5 @@
 from __future__ import absolute_import
 
-import io
 import unittest
 
 from mercurial.thirdparty import (
@@ -10,10 +9,17 @@
     cborutil,
 )
 
+class TestCase(unittest.TestCase):
+    if not getattr(unittest.TestCase, 'assertRaisesRegex', False):
+        # Python 3.7 deprecates the regex*p* version, but 2.7 lacks
+        # the regex version.
+        assertRaisesRegex = (# camelcase-required
+            unittest.TestCase.assertRaisesRegexp)
+
 def loadit(it):
     return cbor.loads(b''.join(it))
 
-class BytestringTests(unittest.TestCase):
+class BytestringTests(TestCase):
     def testsimple(self):
         self.assertEqual(
             list(cborutil.streamencode(b'foobar')),
@@ -23,11 +29,20 @@
             loadit(cborutil.streamencode(b'foobar')),
             b'foobar')
 
+        self.assertEqual(cborutil.decodeall(b'\x46foobar'),
+                         [b'foobar'])
+
+        self.assertEqual(cborutil.decodeall(b'\x46foobar\x45fizbi'),
+                         [b'foobar', b'fizbi'])
+
     def testlong(self):
         source = b'x' * 1048576
 
         self.assertEqual(loadit(cborutil.streamencode(source)), source)
 
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
     def testfromiter(self):
         # This is the example from RFC 7049 Section 2.2.2.
         source = [b'\xaa\xbb\xcc\xdd', b'\xee\xff\x99']
@@ -47,6 +62,25 @@
             loadit(cborutil.streamencodebytestringfromiter(source)),
             b''.join(source))
 
+        self.assertEqual(cborutil.decodeall(b'\x5f\x44\xaa\xbb\xcc\xdd'
+                                            b'\x43\xee\xff\x99\xff'),
+                         [b'\xaa\xbb\xcc\xdd', b'\xee\xff\x99', b''])
+
+        for i, chunk in enumerate(
+            cborutil.decodeall(b'\x5f\x44\xaa\xbb\xcc\xdd'
+                               b'\x43\xee\xff\x99\xff')):
+            self.assertIsInstance(chunk, cborutil.bytestringchunk)
+
+            if i == 0:
+                self.assertTrue(chunk.isfirst)
+            else:
+                self.assertFalse(chunk.isfirst)
+
+            if i == 2:
+                self.assertTrue(chunk.islast)
+            else:
+                self.assertFalse(chunk.islast)
+
     def testfromiterlarge(self):
         source = [b'a' * 16, b'b' * 128, b'c' * 1024, b'd' * 1048576]
 
@@ -71,52 +105,417 @@
             source, chunksize=42))
         self.assertEqual(cbor.loads(dest), source)
 
-    def testreadtoiter(self):
-        source = io.BytesIO(b'\x5f\x44\xaa\xbb\xcc\xdd\x43\xee\xff\x99\xff')
+        self.assertEqual(b''.join(cborutil.decodeall(dest)), source)
+
+        for chunk in cborutil.decodeall(dest):
+            self.assertIsInstance(chunk, cborutil.bytestringchunk)
+            self.assertIn(len(chunk), (0, 8, 42))
+
+        encoded = b'\x5f\xff'
+        b = cborutil.decodeall(encoded)
+        self.assertEqual(b, [b''])
+        self.assertTrue(b[0].isfirst)
+        self.assertTrue(b[0].islast)
+
+    def testdecodevariouslengths(self):
+        for i in (0, 1, 22, 23, 24, 25, 254, 255, 256, 65534, 65535, 65536):
+            source = b'x' * i
+            encoded = b''.join(cborutil.streamencode(source))
+
+            if len(source) < 24:
+                hlen = 1
+            elif len(source) < 256:
+                hlen = 2
+            elif len(source) < 65536:
+                hlen = 3
+            elif len(source) < 1048576:
+                hlen = 5
+
+            self.assertEqual(cborutil.decodeitem(encoded),
+                             (True, source, hlen + len(source),
+                              cborutil.SPECIAL_NONE))
+
+    def testpartialdecode(self):
+        encoded = b''.join(cborutil.streamencode(b'foobar'))
+
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -6, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -5, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -4, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (False, None, -3, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:6]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:7]),
+                         (True, b'foobar', 7, cborutil.SPECIAL_NONE))
+
+    def testpartialdecodevariouslengths(self):
+        lens = [
+            2,
+            3,
+            10,
+            23,
+            24,
+            25,
+            31,
+            100,
+            254,
+            255,
+            256,
+            257,
+            16384,
+            65534,
+            65535,
+            65536,
+            65537,
+            131071,
+            131072,
+            131073,
+            1048575,
+            1048576,
+            1048577,
+        ]
+
+        for size in lens:
+            if size < 24:
+                hlen = 1
+            elif size < 2**8:
+                hlen = 2
+            elif size < 2**16:
+                hlen = 3
+            elif size < 2**32:
+                hlen = 5
+            else:
+                assert False
+
+            source = b'x' * size
+            encoded = b''.join(cborutil.streamencode(source))
+
+            res = cborutil.decodeitem(encoded[0:1])
+
+            if hlen > 1:
+                self.assertEqual(res, (False, None, -(hlen - 1),
+                                       cborutil.SPECIAL_NONE))
+            else:
+                self.assertEqual(res, (False, None, -(size + hlen - 1),
+                                       cborutil.SPECIAL_NONE))
+
+            # Decoding partial header reports remaining header size.
+            for i in range(hlen - 1):
+                self.assertEqual(cborutil.decodeitem(encoded[0:i + 1]),
+                                 (False, None, -(hlen - i - 1),
+                                  cborutil.SPECIAL_NONE))
+
+            # Decoding complete header reports item size.
+            self.assertEqual(cborutil.decodeitem(encoded[0:hlen]),
+                             (False, None, -size, cborutil.SPECIAL_NONE))
 
-        it = cborutil.readindefinitebytestringtoiter(source)
-        self.assertEqual(next(it), b'\xaa\xbb\xcc\xdd')
-        self.assertEqual(next(it), b'\xee\xff\x99')
+            # Decoding single byte after header reports item size - 1
+            self.assertEqual(cborutil.decodeitem(encoded[0:hlen + 1]),
+                             (False, None, -(size - 1), cborutil.SPECIAL_NONE))
+
+            # Decoding all but the last byte reports -1 needed.
+            self.assertEqual(cborutil.decodeitem(encoded[0:hlen + size - 1]),
+                             (False, None, -1, cborutil.SPECIAL_NONE))
+
+            # Decoding last byte retrieves value.
+            self.assertEqual(cborutil.decodeitem(encoded[0:hlen + size]),
+                             (True, source, hlen + size, cborutil.SPECIAL_NONE))
+
+    def testindefinitepartialdecode(self):
+        encoded = b''.join(cborutil.streamencodebytestringfromiter(
+            [b'foobar', b'biz']))
+
+        # First item should be begin of bytestring special.
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (True, None, 1,
+                          cborutil.SPECIAL_START_INDEFINITE_BYTESTRING))
+
+        # Second item should be the first chunk. But only available when
+        # we give it 7 bytes (1 byte header + 6 byte chunk).
+        self.assertEqual(cborutil.decodeitem(encoded[1:2]),
+                         (False, None, -6, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[1:3]),
+                         (False, None, -5, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[1:4]),
+                         (False, None, -4, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[1:5]),
+                         (False, None, -3, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[1:6]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[1:7]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+
+        self.assertEqual(cborutil.decodeitem(encoded[1:8]),
+                         (True, b'foobar', 7, cborutil.SPECIAL_NONE))
+
+        # Third item should be second chunk. But only available when
+        # we give it 4 bytes (1 byte header + 3 byte chunk).
+        self.assertEqual(cborutil.decodeitem(encoded[8:9]),
+                         (False, None, -3, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[8:10]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[8:11]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+
+        self.assertEqual(cborutil.decodeitem(encoded[8:12]),
+                         (True, b'biz', 4, cborutil.SPECIAL_NONE))
+
+        # Fourth item should be end of indefinite stream marker.
+        self.assertEqual(cborutil.decodeitem(encoded[12:13]),
+                         (True, None, 1, cborutil.SPECIAL_INDEFINITE_BREAK))
+
+        # Now test the behavior when going through the decoder.
 
-        with self.assertRaises(StopIteration):
-            next(it)
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:1]),
+                         (False, 1, 0))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:2]),
+                         (False, 1, 6))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:3]),
+                         (False, 1, 5))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:4]),
+                         (False, 1, 4))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:5]),
+                         (False, 1, 3))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:6]),
+                         (False, 1, 2))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:7]),
+                         (False, 1, 1))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:8]),
+                         (True, 8, 0))
+
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:9]),
+                         (True, 8, 3))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:10]),
+                         (True, 8, 2))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:11]),
+                         (True, 8, 1))
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:12]),
+                         (True, 12, 0))
+
+        self.assertEqual(cborutil.sansiodecoder().decode(encoded[0:13]),
+                         (True, 13, 0))
 
-class IntTests(unittest.TestCase):
+        decoder = cborutil.sansiodecoder()
+        decoder.decode(encoded[0:8])
+        values = decoder.getavailable()
+        self.assertEqual(values, [b'foobar'])
+        self.assertTrue(values[0].isfirst)
+        self.assertFalse(values[0].islast)
+
+        self.assertEqual(decoder.decode(encoded[8:12]),
+                         (True, 4, 0))
+        values = decoder.getavailable()
+        self.assertEqual(values, [b'biz'])
+        self.assertFalse(values[0].isfirst)
+        self.assertFalse(values[0].islast)
+
+        self.assertEqual(decoder.decode(encoded[12:]),
+                         (True, 1, 0))
+        values = decoder.getavailable()
+        self.assertEqual(values, [b''])
+        self.assertFalse(values[0].isfirst)
+        self.assertTrue(values[0].islast)
+
+class StringTests(TestCase):
+    def testdecodeforbidden(self):
+        encoded = b'\x63foo'
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'string major type not supported'):
+            cborutil.decodeall(encoded)
+
+class IntTests(TestCase):
     def testsmall(self):
         self.assertEqual(list(cborutil.streamencode(0)), [b'\x00'])
+        self.assertEqual(cborutil.decodeall(b'\x00'), [0])
+
         self.assertEqual(list(cborutil.streamencode(1)), [b'\x01'])
+        self.assertEqual(cborutil.decodeall(b'\x01'), [1])
+
         self.assertEqual(list(cborutil.streamencode(2)), [b'\x02'])
+        self.assertEqual(cborutil.decodeall(b'\x02'), [2])
+
         self.assertEqual(list(cborutil.streamencode(3)), [b'\x03'])
+        self.assertEqual(cborutil.decodeall(b'\x03'), [3])
+
         self.assertEqual(list(cborutil.streamencode(4)), [b'\x04'])
+        self.assertEqual(cborutil.decodeall(b'\x04'), [4])
+
+        # Multiple value decode works.
+        self.assertEqual(cborutil.decodeall(b'\x00\x01\x02\x03\x04'),
+                         [0, 1, 2, 3, 4])
 
     def testnegativesmall(self):
         self.assertEqual(list(cborutil.streamencode(-1)), [b'\x20'])
+        self.assertEqual(cborutil.decodeall(b'\x20'), [-1])
+
         self.assertEqual(list(cborutil.streamencode(-2)), [b'\x21'])
+        self.assertEqual(cborutil.decodeall(b'\x21'), [-2])
+
         self.assertEqual(list(cborutil.streamencode(-3)), [b'\x22'])
+        self.assertEqual(cborutil.decodeall(b'\x22'), [-3])
+
         self.assertEqual(list(cborutil.streamencode(-4)), [b'\x23'])
+        self.assertEqual(cborutil.decodeall(b'\x23'), [-4])
+
         self.assertEqual(list(cborutil.streamencode(-5)), [b'\x24'])
+        self.assertEqual(cborutil.decodeall(b'\x24'), [-5])
+
+        # Multiple value decode works.
+        self.assertEqual(cborutil.decodeall(b'\x20\x21\x22\x23\x24'),
+                         [-1, -2, -3, -4, -5])
 
     def testrange(self):
         for i in range(-70000, 70000, 10):
-            self.assertEqual(
-                b''.join(cborutil.streamencode(i)),
-                cbor.dumps(i))
+            encoded = b''.join(cborutil.streamencode(i))
+
+            self.assertEqual(encoded, cbor.dumps(i))
+            self.assertEqual(cborutil.decodeall(encoded), [i])
+
+    def testdecodepartialubyte(self):
+        encoded = b''.join(cborutil.streamencode(250))
+
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (True, 250, 2, cborutil.SPECIAL_NONE))
+
+    def testdecodepartialbyte(self):
+        encoded = b''.join(cborutil.streamencode(-42))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (True, -42, 2, cborutil.SPECIAL_NONE))
+
+    def testdecodepartialushort(self):
+        encoded = b''.join(cborutil.streamencode(2**15))
+
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (True, 2**15, 3, cborutil.SPECIAL_NONE))
+
+    def testdecodepartialshort(self):
+        encoded = b''.join(cborutil.streamencode(-1024))
+
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (True, -1024, 3, cborutil.SPECIAL_NONE))
+
+    def testdecodepartialulong(self):
+        encoded = b''.join(cborutil.streamencode(2**28))
+
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -4, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -3, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (True, 2**28, 5, cborutil.SPECIAL_NONE))
+
+    def testdecodepartiallong(self):
+        encoded = b''.join(cborutil.streamencode(-1048580))
 
-class ArrayTests(unittest.TestCase):
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -4, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -3, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (True, -1048580, 5, cborutil.SPECIAL_NONE))
+
+    def testdecodepartialulonglong(self):
+        encoded = b''.join(cborutil.streamencode(2**32))
+
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -8, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -7, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -6, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (False, None, -5, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (False, None, -4, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:6]),
+                         (False, None, -3, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:7]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:8]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:9]),
+                         (True, 2**32, 9, cborutil.SPECIAL_NONE))
+
+        with self.assertRaisesRegex(
+            cborutil.CBORDecodeError, 'input data not fully consumed'):
+            cborutil.decodeall(encoded[0:1])
+
+        with self.assertRaisesRegex(
+            cborutil.CBORDecodeError, 'input data not fully consumed'):
+            cborutil.decodeall(encoded[0:2])
+
+    def testdecodepartiallonglong(self):
+        encoded = b''.join(cborutil.streamencode(-7000000000))
+
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -8, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -7, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -6, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (False, None, -5, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (False, None, -4, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:6]),
+                         (False, None, -3, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:7]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:8]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:9]),
+                         (True, -7000000000, 9, cborutil.SPECIAL_NONE))
+
+class ArrayTests(TestCase):
     def testempty(self):
         self.assertEqual(list(cborutil.streamencode([])), [b'\x80'])
         self.assertEqual(loadit(cborutil.streamencode([])), [])
 
+        self.assertEqual(cborutil.decodeall(b'\x80'), [[]])
+
     def testbasic(self):
         source = [b'foo', b'bar', 1, -10]
 
-        self.assertEqual(list(cborutil.streamencode(source)), [
-            b'\x84', b'\x43', b'foo', b'\x43', b'bar', b'\x01', b'\x29'])
+        chunks = [
+            b'\x84', b'\x43', b'foo', b'\x43', b'bar', b'\x01', b'\x29']
+
+        self.assertEqual(list(cborutil.streamencode(source)), chunks)
+
+        self.assertEqual(cborutil.decodeall(b''.join(chunks)), [source])
 
     def testemptyfromiter(self):
         self.assertEqual(b''.join(cborutil.streamencodearrayfromiter([])),
                          b'\x9f\xff')
 
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'indefinite length uint not allowed'):
+            cborutil.decodeall(b'\x9f\xff')
+
     def testfromiter1(self):
         source = [b'foo']
 
@@ -129,26 +528,193 @@
         dest = b''.join(cborutil.streamencodearrayfromiter(source))
         self.assertEqual(cbor.loads(dest), source)
 
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'indefinite length uint not allowed'):
+            cborutil.decodeall(dest)
+
     def testtuple(self):
         source = (b'foo', None, 42)
+        encoded = b''.join(cborutil.streamencode(source))
 
-        self.assertEqual(cbor.loads(b''.join(cborutil.streamencode(source))),
-                         list(source))
+        self.assertEqual(cbor.loads(encoded), list(source))
+
+        self.assertEqual(cborutil.decodeall(encoded), [list(source)])
+
+    def testpartialdecode(self):
+        source = list(range(4))
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (True, 4, 1, cborutil.SPECIAL_START_ARRAY))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (True, 4, 1, cborutil.SPECIAL_START_ARRAY))
+
+        source = list(range(23))
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (True, 23, 1, cborutil.SPECIAL_START_ARRAY))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (True, 23, 1, cborutil.SPECIAL_START_ARRAY))
+
+        source = list(range(24))
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (True, 24, 2, cborutil.SPECIAL_START_ARRAY))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (True, 24, 2, cborutil.SPECIAL_START_ARRAY))
 
-class SetTests(unittest.TestCase):
+        source = list(range(256))
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (True, 256, 3, cborutil.SPECIAL_START_ARRAY))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (True, 256, 3, cborutil.SPECIAL_START_ARRAY))
+
+    def testnested(self):
+        source = [[], [], [[], [], []]]
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
+        source = [True, None, [True, 0, 2], [None], [], [[[]], -87]]
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
+        # A set within an array.
+        source = [None, {b'foo', b'bar', None, False}, set()]
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
+        # A map within an array.
+        source = [None, {}, {b'foo': b'bar', True: False}, [{}]]
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
+    def testindefinitebytestringvalues(self):
+        # Single value array whose value is an empty indefinite bytestring.
+        encoded = b'\x81\x5f\x40\xff'
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'indefinite length bytestrings not '
+                                    'allowed as array values'):
+            cborutil.decodeall(encoded)
+
+class SetTests(TestCase):
     def testempty(self):
         self.assertEqual(list(cborutil.streamencode(set())), [
             b'\xd9\x01\x02',
             b'\x80',
         ])
 
+        self.assertEqual(cborutil.decodeall(b'\xd9\x01\x02\x80'), [set()])
+
     def testset(self):
         source = {b'foo', None, 42}
+        encoded = b''.join(cborutil.streamencode(source))
 
-        self.assertEqual(cbor.loads(b''.join(cborutil.streamencode(source))),
-                         source)
+        self.assertEqual(cbor.loads(encoded), source)
+
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
+    def testinvalidtag(self):
+        # Must use array to encode sets.
+        encoded = b'\xd9\x01\x02\xa0'
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'expected array after finite set '
+                                    'semantic tag'):
+            cborutil.decodeall(encoded)
+
+    def testpartialdecode(self):
+        # Semantic tag item will be 3 bytes. Set header will be variable
+        # depending on length.
+        encoded = b''.join(cborutil.streamencode({i for i in range(23)}))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (True, 23, 4, cborutil.SPECIAL_START_SET))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (True, 23, 4, cborutil.SPECIAL_START_SET))
+
+        encoded = b''.join(cborutil.streamencode({i for i in range(24)}))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (True, 24, 5, cborutil.SPECIAL_START_SET))
+        self.assertEqual(cborutil.decodeitem(encoded[0:6]),
+                         (True, 24, 5, cborutil.SPECIAL_START_SET))
 
-class BoolTests(unittest.TestCase):
+        encoded = b''.join(cborutil.streamencode({i for i in range(256)}))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:6]),
+                         (True, 256, 6, cborutil.SPECIAL_START_SET))
+
+    def testinvalidvalue(self):
+        encoded = b''.join([
+            b'\xd9\x01\x02', # semantic tag
+            b'\x81', # array of size 1
+            b'\x5f\x43foo\xff', # indefinite length bytestring "foo"
+        ])
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'indefinite length bytestrings not '
+                                    'allowed as set values'):
+            cborutil.decodeall(encoded)
+
+        encoded = b''.join([
+            b'\xd9\x01\x02',
+            b'\x81',
+            b'\x80', # empty array
+        ])
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'collections not allowed as set values'):
+            cborutil.decodeall(encoded)
+
+        encoded = b''.join([
+            b'\xd9\x01\x02',
+            b'\x81',
+            b'\xa0', # empty map
+        ])
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'collections not allowed as set values'):
+            cborutil.decodeall(encoded)
+
+        encoded = b''.join([
+            b'\xd9\x01\x02',
+            b'\x81',
+            b'\xd9\x01\x02\x81\x01', # set with integer 1
+        ])
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'collections not allowed as set values'):
+            cborutil.decodeall(encoded)
+
+class BoolTests(TestCase):
     def testbasic(self):
         self.assertEqual(list(cborutil.streamencode(True)),  [b'\xf5'])
         self.assertEqual(list(cborutil.streamencode(False)), [b'\xf4'])
@@ -156,23 +722,38 @@
         self.assertIs(loadit(cborutil.streamencode(True)), True)
         self.assertIs(loadit(cborutil.streamencode(False)), False)
 
-class NoneTests(unittest.TestCase):
+        self.assertEqual(cborutil.decodeall(b'\xf4'), [False])
+        self.assertEqual(cborutil.decodeall(b'\xf5'), [True])
+
+        self.assertEqual(cborutil.decodeall(b'\xf4\xf5\xf5\xf4'),
+                         [False, True, True, False])
+
+class NoneTests(TestCase):
     def testbasic(self):
         self.assertEqual(list(cborutil.streamencode(None)), [b'\xf6'])
 
         self.assertIs(loadit(cborutil.streamencode(None)), None)
 
-class MapTests(unittest.TestCase):
+        self.assertEqual(cborutil.decodeall(b'\xf6'), [None])
+        self.assertEqual(cborutil.decodeall(b'\xf6\xf6'), [None, None])
+
+class MapTests(TestCase):
     def testempty(self):
         self.assertEqual(list(cborutil.streamencode({})), [b'\xa0'])
         self.assertEqual(loadit(cborutil.streamencode({})), {})
 
+        self.assertEqual(cborutil.decodeall(b'\xa0'), [{}])
+
     def testemptyindefinite(self):
         self.assertEqual(list(cborutil.streamencodemapfromiter([])), [
             b'\xbf', b'\xff'])
 
         self.assertEqual(loadit(cborutil.streamencodemapfromiter([])), {})
 
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'indefinite length uint not allowed'):
+            cborutil.decodeall(b'\xbf\xff')
+
     def testone(self):
         source = {b'foo': b'bar'}
         self.assertEqual(list(cborutil.streamencode(source)), [
@@ -180,6 +761,8 @@
 
         self.assertEqual(loadit(cborutil.streamencode(source)), source)
 
+        self.assertEqual(cborutil.decodeall(b'\xa1\x43foo\x43bar'), [source])
+
     def testmultiple(self):
         source = {
             b'foo': b'bar',
@@ -192,6 +775,9 @@
             loadit(cborutil.streamencodemapfromiter(source.items())),
             source)
 
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
     def testcomplex(self):
         source = {
             b'key': 1,
@@ -205,6 +791,194 @@
             loadit(cborutil.streamencodemapfromiter(source.items())),
             source)
 
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
+    def testnested(self):
+        source = {b'key1': None, b'key2': {b'sub1': b'sub2'}, b'sub2': {}}
+        encoded = b''.join(cborutil.streamencode(source))
+
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
+        source = {
+            b'key1': [],
+            b'key2': [None, False],
+            b'key3': {b'foo', b'bar'},
+            b'key4': {},
+        }
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeall(encoded), [source])
+
+    def testillegalkey(self):
+        encoded = b''.join([
+            # map header + len 1
+            b'\xa1',
+            # indefinite length bytestring "foo" in key position
+            b'\x5f\x03foo\xff'
+        ])
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'indefinite length bytestrings not '
+                                    'allowed as map keys'):
+            cborutil.decodeall(encoded)
+
+        encoded = b''.join([
+            b'\xa1',
+            b'\x80', # empty array
+            b'\x43foo',
+        ])
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'collections not supported as map keys'):
+            cborutil.decodeall(encoded)
+
+    def testillegalvalue(self):
+        encoded = b''.join([
+            b'\xa1', # map headers
+            b'\x43foo', # key
+            b'\x5f\x03bar\xff', # indefinite length value
+        ])
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'indefinite length bytestrings not '
+                                    'allowed as map values'):
+            cborutil.decodeall(encoded)
+
+    def testpartialdecode(self):
+        source = {b'key1': b'value1'}
+        encoded = b''.join(cborutil.streamencode(source))
+
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (True, 1, 1, cborutil.SPECIAL_START_MAP))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (True, 1, 1, cborutil.SPECIAL_START_MAP))
+
+        source = {b'key%d' % i: None for i in range(23)}
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (True, 23, 1, cborutil.SPECIAL_START_MAP))
+
+        source = {b'key%d' % i: None for i in range(24)}
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (True, 24, 2, cborutil.SPECIAL_START_MAP))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (True, 24, 2, cborutil.SPECIAL_START_MAP))
+
+        source = {b'key%d' % i: None for i in range(256)}
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (True, 256, 3, cborutil.SPECIAL_START_MAP))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (True, 256, 3, cborutil.SPECIAL_START_MAP))
+
+        source = {b'key%d' % i: None for i in range(65536)}
+        encoded = b''.join(cborutil.streamencode(source))
+        self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                         (False, None, -4, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                         (False, None, -3, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:3]),
+                         (False, None, -2, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:4]),
+                         (False, None, -1, cborutil.SPECIAL_NONE))
+        self.assertEqual(cborutil.decodeitem(encoded[0:5]),
+                         (True, 65536, 5, cborutil.SPECIAL_START_MAP))
+        self.assertEqual(cborutil.decodeitem(encoded[0:6]),
+                         (True, 65536, 5, cborutil.SPECIAL_START_MAP))
+
+class SemanticTagTests(TestCase):
+    def testdecodeforbidden(self):
+        for i in range(500):
+            if i == cborutil.SEMANTIC_TAG_FINITE_SET:
+                continue
+
+            tag = cborutil.encodelength(cborutil.MAJOR_TYPE_SEMANTIC,
+                                        i)
+
+            encoded = tag + cborutil.encodelength(cborutil.MAJOR_TYPE_UINT, 42)
+
+            # Partial decode is incomplete.
+            if i < 24:
+                pass
+            elif i < 256:
+                self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                                 (False, None, -1, cborutil.SPECIAL_NONE))
+            elif i < 65536:
+                self.assertEqual(cborutil.decodeitem(encoded[0:1]),
+                                 (False, None, -2, cborutil.SPECIAL_NONE))
+                self.assertEqual(cborutil.decodeitem(encoded[0:2]),
+                                 (False, None, -1, cborutil.SPECIAL_NONE))
+
+            with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                        'semantic tag \d+ not allowed'):
+                cborutil.decodeitem(encoded)
+
+class SpecialTypesTests(TestCase):
+    def testforbiddentypes(self):
+        for i in range(256):
+            if i == cborutil.SUBTYPE_FALSE:
+                continue
+            elif i == cborutil.SUBTYPE_TRUE:
+                continue
+            elif i == cborutil.SUBTYPE_NULL:
+                continue
+
+            encoded = cborutil.encodelength(cborutil.MAJOR_TYPE_SPECIAL, i)
+
+            with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                        'special type \d+ not allowed'):
+                cborutil.decodeitem(encoded)
+
+class SansIODecoderTests(TestCase):
+    def testemptyinput(self):
+        decoder = cborutil.sansiodecoder()
+        self.assertEqual(decoder.decode(b''), (False, 0, 0))
+
+class BufferingDecoderTests(TestCase):
+    def testsimple(self):
+        source = [
+            b'foobar',
+            b'x' * 128,
+            {b'foo': b'bar'},
+            True,
+            False,
+            None,
+            [None for i in range(128)],
+        ]
+
+        encoded = b''.join(cborutil.streamencode(source))
+
+        for step in range(1, 32):
+            decoder = cborutil.bufferingdecoder()
+            start = 0
+
+            while start < len(encoded):
+                decoder.decode(encoded[start:start + step])
+                start += step
+
+            self.assertEqual(decoder.getavailable(), [source])
+
+class DecodeallTests(TestCase):
+    def testemptyinput(self):
+        self.assertEqual(cborutil.decodeall(b''), [])
+
+    def testpartialinput(self):
+        encoded = b''.join([
+            b'\x82', # array of 2 elements
+            b'\x01', # integer 1
+        ])
+
+        with self.assertRaisesRegex(cborutil.CBORDecodeError,
+                                    'input data not complete'):
+            cborutil.decodeall(encoded)
+
 if __name__ == '__main__':
     import silenttestrunner
     silenttestrunner.main(__name__)
--- a/tests/test-censor.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-censor.t	Wed Sep 26 20:33:09 2018 +0900
@@ -180,7 +180,7 @@
   checking files
    target@1: censored file data
    target@2: censored file data
-  2 files, 5 changesets, 7 total revisions
+  checked 5 changesets with 7 changes to 2 files
   2 integrity errors encountered!
   (first damaged changeset appears to be 1)
   [1]
@@ -215,7 +215,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 7 total revisions
+  checked 5 changesets with 7 changes to 2 files
 
 May update to revision with censored data with explicit config
 
@@ -306,7 +306,7 @@
 
 Can censor after revlog has expanded to no longer permit inline storage
 
-  $ for x in `$PYTHON $TESTDIR/seq.py 0 50000`
+  $ for x in `"$PYTHON" $TESTDIR/seq.py 0 50000`
   > do
   >   echo "Password: hunter$x" >> target
   > done
@@ -341,7 +341,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 12 changesets, 13 total revisions
+  checked 12 changesets with 13 changes to 2 files
 
 Repo cloned before tainted content introduced can pull censored nodes
 
@@ -353,7 +353,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 1 changesets, 2 total revisions
+  checked 1 changesets with 2 changes to 2 files
   $ hg pull -r $H1 -r $H2
   pulling from $TESTTMP/r
   searching for changes
@@ -380,7 +380,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 12 changesets, 13 total revisions
+  checked 12 changesets with 13 changes to 2 files
 
 Censored nodes can be pushed if they censor previously unexchanged nodes
 
@@ -426,7 +426,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
-  new changesets 075be80ac777:dcbaf17bf3a1
+  new changesets 075be80ac777:dcbaf17bf3a1 (2 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg cat -r $REV target
   $ hg cat -r $CLEANREV target
@@ -440,7 +440,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 14 changesets, 15 total revisions
+  checked 14 changesets with 15 changes to 2 files
 
 Censored nodes can be imported on top of censored nodes, consecutively
 
@@ -461,7 +461,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 2 files (+1 heads)
-  new changesets efbe78065929:683e4645fded
+  new changesets efbe78065929:683e4645fded (6 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg update $H2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -472,7 +472,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 14 changesets, 15 total revisions
+  checked 14 changesets with 15 changes to 2 files
   $ cd ../r
 
 Can import bundle where first revision of a file is censored
@@ -487,6 +487,6 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
-  new changesets e97f55b2665a
+  new changesets e97f55b2665a (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg cat -r 0 target
--- a/tests/test-check-code.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-check-code.t	Wed Sep 26 20:33:09 2018 +0900
@@ -22,7 +22,7 @@
   >>> commands = []
   >>> with open('mercurial/debugcommands.py', 'rb') as fh:
   ...     for line in fh:
-  ...         m = re.match("^@command\('([a-z]+)", line)
+  ...         m = re.match(b"^@command\('([a-z]+)", line)
   ...         if m:
   ...             commands.append(m.group(1))
   >>> scommands = list(sorted(commands))
--- a/tests/test-check-config.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-check-config.t	Wed Sep 26 20:33:09 2018 +0900
@@ -30,7 +30,7 @@
 
   $ cd "$TESTDIR"/..
 
-  $ $PYTHON contrib/check-config.py < $TESTTMP/files
+  $ "$PYTHON" contrib/check-config.py < $TESTTMP/files
   foo = ui.configint('ui', 'intdefault', default=42)
   conflict on ui.intdefault: ('int', '42') != ('int', '1')
   at $TESTTMP/testfile.py:12:
@@ -44,4 +44,4 @@
 New errors are not allowed. Warnings are strongly discouraged.
 
   $ testrepohg files "set:(**.py or **.txt) - tests/**" | sed 's|\\|/|g' |
-  >   $PYTHON contrib/check-config.py
+  >   "$PYTHON" contrib/check-config.py
--- a/tests/test-check-help.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-check-help.t	Wed Sep 26 20:33:09 2018 +0900
@@ -25,5 +25,5 @@
 
   $ testrepohg files 'glob:{hgdemandimport,hgext,mercurial}/**/*.py' \
   > | sed 's|\\|/|g' \
-  > | xargs $PYTHON "$TESTTMP/scanhelptopics.py" \
-  > | xargs -n1 hg help > /dev/null
+  > | xargs "$PYTHON" "$TESTTMP/scanhelptopics.py" \
+  > | xargs -n1 hg help --config extensions.phabricator= > /dev/null
--- a/tests/test-check-interfaces.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-check-interfaces.py	Wed Sep 26 20:33:09 2018 +0900
@@ -21,6 +21,7 @@
     verify as ziverify,
 )
 from mercurial import (
+    changegroup,
     bundlerepo,
     filelog,
     httppeer,
@@ -28,6 +29,7 @@
     manifest,
     pycompat,
     repository,
+    revlog,
     sshpeer,
     statichttprepo,
     ui as uimod,
@@ -140,9 +142,11 @@
     ziverify.verifyClass(repository.ipeerbase, unionrepo.unionpeer)
     checkzobject(unionrepo.unionpeer(dummyrepo()))
 
-    ziverify.verifyClass(repository.completelocalrepository,
+    ziverify.verifyClass(repository.ilocalrepositorymain,
                          localrepo.localrepository)
-    repo = localrepo.localrepository(ui, rootdir)
+    ziverify.verifyClass(repository.ilocalrepositoryfilestorage,
+                         localrepo.revlogfilestorage)
+    repo = localrepo.makelocalrepository(ui, rootdir)
     checkzobject(repo)
 
     ziverify.verifyClass(wireprototypes.baseprotocolhandler,
@@ -175,13 +179,14 @@
     ziverify.verifyClass(repository.imanifestrevisionwritable,
                          manifest.memtreemanifestctx)
     ziverify.verifyClass(repository.imanifestlog, manifest.manifestlog)
+    ziverify.verifyClass(repository.imanifeststorage, manifest.manifestrevlog)
 
     vfs = vfsmod.vfs(b'.')
     fl = filelog.filelog(vfs, b'dummy.i')
     checkzobject(fl, allowextra=True)
 
     # Conforms to imanifestlog.
-    ml = manifest.manifestlog(vfs, repo)
+    ml = manifest.manifestlog(vfs, repo, manifest.manifestrevlog(repo.svfs))
     checkzobject(ml)
     checkzobject(repo.manifestlog)
 
@@ -196,4 +201,32 @@
     # Conforms to imanifestdict.
     checkzobject(mctx.read())
 
+    mrl = manifest.manifestrevlog(vfs)
+    checkzobject(mrl)
+
+    ziverify.verifyClass(repository.irevisiondelta,
+                         revlog.revlogrevisiondelta)
+    ziverify.verifyClass(repository.irevisiondeltarequest,
+                         changegroup.revisiondeltarequest)
+
+    rd = revlog.revlogrevisiondelta(
+        node=b'',
+        p1node=b'',
+        p2node=b'',
+        basenode=b'',
+        linknode=b'',
+        flags=b'',
+        baserevisionsize=None,
+        revision=b'',
+        delta=None)
+    checkzobject(rd)
+
+    rdr = changegroup.revisiondeltarequest(
+        node=b'',
+        linknode=b'',
+        p1node=b'',
+        p2node=b'',
+        basenode=b'')
+    checkzobject(rdr)
+
 main()
--- a/tests/test-check-module-imports.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-check-module-imports.t	Wed Sep 26 20:33:09 2018 +0900
@@ -43,4 +43,4 @@
   > -X tests/test-imports-checker.t \
   > -X tests/test-lock.py \
   > -X tests/test-verify-repo-operations.py \
-  > | sed 's-\\-/-g' | $PYTHON "$import_checker" -
+  > | sed 's-\\-/-g' | "$PYTHON" "$import_checker" -
--- a/tests/test-check-py3-compat.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-check-py3-compat.t	Wed Sep 26 20:33:09 2018 +0900
@@ -3,10 +3,11 @@
   $ . "$TESTDIR/helpers-testrepo.sh"
   $ cd "$TESTDIR"/..
 
+#if no-py3k
   $ testrepohg files 'set:(**.py)' \
   > -X hgdemandimport/demandimportpy2.py \
   > -X mercurial/thirdparty/cbor \
-  > | sed 's|\\|/|g' | xargs $PYTHON contrib/check-py3-compat.py
+  > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py
   contrib/python-zstandard/setup.py not using absolute_import
   contrib/python-zstandard/setup_zstd.py not using absolute_import
   contrib/python-zstandard/tests/common.py not using absolute_import
@@ -21,27 +22,27 @@
   contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
   contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
   setup.py not using absolute_import
+#endif
 
-#if py3exe
+#if py3k
   $ testrepohg files 'set:(**.py) - grep(pygments)' \
   > -X hgdemandimport/demandimportpy2.py \
   > -X hgext/fsmonitor/pywatchman \
-  > | sed 's|\\|/|g' | xargs $PYTHON3 contrib/check-py3-compat.py \
+  > -X mercurial/cffi \
+  > -X mercurial/thirdparty \
+  > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py \
   > | sed 's/[0-9][0-9]*)$/*)/'
-  hgext/convert/transport.py: error importing: <*Error> No module named 'svn.client' (error at transport.py:*) (glob)
-  mercurial/cffi/bdiff.py: error importing: <ImportError> cannot import name '_bdiff' (error at bdiff.py:*)
-  mercurial/cffi/bdiffbuild.py: error importing: <ImportError> No module named 'cffi' (error at bdiffbuild.py:*)
-  mercurial/cffi/mpatch.py: error importing: <ImportError> cannot import name '_mpatch' (error at mpatch.py:*)
-  mercurial/cffi/mpatchbuild.py: error importing: <ImportError> No module named 'cffi' (error at mpatchbuild.py:*)
-  mercurial/cffi/osutilbuild.py: error importing: <ImportError> No module named 'cffi' (error at osutilbuild.py:*)
-  mercurial/scmwindows.py: error importing: <*Error> No module named 'msvcrt' (error at win32.py:*) (glob)
-  mercurial/win32.py: error importing: <*Error> No module named 'msvcrt' (error at win32.py:*) (glob)
-  mercurial/windows.py: error importing: <*Error> No module named 'msvcrt' (error at windows.py:*) (glob)
-
+  hgext/convert/transport.py: error importing: <*Error> No module named 'svn.client' (error at transport.py:*) (glob) (?)
+  hgext/infinitepush/sqlindexapi.py: error importing: <*Error> No module named 'mysql' (error at sqlindexapi.py:*) (glob) (?)
+  mercurial/scmwindows.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
+  mercurial/win32.py: error importing: <ValueError> _type_ 'v' not supported (error at win32.py:*) (no-windows !)
+  mercurial/windows.py: error importing: <ModuleNotFoundError> No module named 'msvcrt' (error at windows.py:*) (no-windows !)
+  mercurial/posix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at posix.py:*) (windows !)
+  mercurial/scmposix.py: error importing: <ModuleNotFoundError> No module named 'fcntl' (error at scmposix.py:*) (windows !)
 #endif
 
-#if py3exe py3pygments
+#if py3k pygments
   $ testrepohg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
-  > | xargs $PYTHON3 contrib/check-py3-compat.py \
+  > | xargs "$PYTHON" contrib/check-py3-compat.py \
   > | sed 's/[0-9][0-9]*)$/*)/'
 #endif
--- a/tests/test-chg.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-chg.t	Wed Sep 26 20:33:09 2018 +0900
@@ -89,7 +89,7 @@
   > [extensions]
   > pager =
   > [pager]
-  > pager = $PYTHON $TESTTMP/fakepager.py
+  > pager = "$PYTHON" $TESTTMP/fakepager.py
   > EOF
   $ chg version > /dev/null
   $ touch foo
--- a/tests/test-clone-cgi.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-clone-cgi.t	Wed Sep 26 20:33:09 2018 +0900
@@ -26,13 +26,13 @@
 
   $ . "$TESTDIR/cgienv"
   $ QUERY_STRING="cmd=changegroup&roots=0000000000000000000000000000000000000000"; export QUERY_STRING
-  $ $PYTHON hgweb.cgi >page1 2>&1
-  $ $PYTHON "$TESTDIR/md5sum.py" page1
+  $ "$PYTHON" hgweb.cgi >page1 2>&1
+  $ "$PYTHON" "$TESTDIR/md5sum.py" page1
   1f424bb22ec05c3c6bc866b6e67efe43  page1
 
 make sure headers are sent even when there is no body
 
-  $ QUERY_STRING="cmd=listkeys&namespace=nosuchnamespace" $PYTHON hgweb.cgi
+  $ QUERY_STRING="cmd=listkeys&namespace=nosuchnamespace" "$PYTHON" hgweb.cgi
   Status: 200 Script output follows\r (esc)
   Content-Type: application/mercurial-0.1\r (esc)
   Content-Length: 0\r (esc)
--- a/tests/test-clone-pull-corruption.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-clone-pull-corruption.t	Wed Sep 26 20:33:09 2018 +0900
@@ -48,6 +48,6 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
 
   $ cd ..
--- a/tests/test-clone-r.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-clone-r.t	Wed Sep 26 20:33:09 2018 +0900
@@ -37,7 +37,7 @@
   $ hg mv afile anotherfile
   $ hg commit -m "0.3m"
 
-  $ hg debugindex -f 1 afile
+  $ hg debugrevlogindex -f 1 afile
      rev flag     size   link     p1     p2       nodeid
        0 0000        2      0     -1     -1 362fef284ce2
        1 0000        4      1      0     -1 125144f7e028
@@ -71,7 +71,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
 
   $ cd ..
 
@@ -96,7 +96,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   
   ---- hg clone -r 1 test test-1
   adding changesets
@@ -110,7 +110,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   
   ---- hg clone -r 2 test test-2
   adding changesets
@@ -124,7 +124,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   
   ---- hg clone -r 3 test test-3
   adding changesets
@@ -138,7 +138,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 4 changesets, 4 total revisions
+  checked 4 changesets with 4 changes to 1 files
   
   ---- hg clone -r 4 test test-4
   adding changesets
@@ -152,7 +152,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   
   ---- hg clone -r 5 test test-5
   adding changesets
@@ -166,7 +166,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   
   ---- hg clone -r 6 test test-6
   adding changesets
@@ -180,7 +180,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 4 changesets, 5 total revisions
+  checked 4 changesets with 5 changes to 2 files
   
   ---- hg clone -r 7 test test-7
   adding changesets
@@ -194,7 +194,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 5 changesets, 6 total revisions
+  checked 5 changesets with 6 changes to 3 files
   
   ---- hg clone -r 8 test test-8
   adding changesets
@@ -208,7 +208,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
 
   $ cd test-8
   $ hg pull ../test-7
@@ -225,7 +225,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
   $ cd ..
 
   $ hg clone test test-9
--- a/tests/test-clone-uncompressed.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-clone-uncompressed.t	Wed Sep 26 20:33:09 2018 +0900
@@ -2,10 +2,10 @@
 
 #testcases stream-legacy stream-bundle2
 
-#if stream-bundle2
+#if stream-legacy
   $ cat << EOF >> $HGRCPATH
-  > [experimental]
-  > bundle2.stream = yes
+  > [server]
+  > bundle2.stream = no
   > EOF
 #endif
 
@@ -247,6 +247,7 @@
   sending stream_out command
   1027 files to transfer, 96.3 KB of data
   starting 4 threads for background file closing
+  updating the branch cache
   transferred 96.3 KB in * seconds (*/sec) (glob)
   query 1; heads
   sending batch command
@@ -275,6 +276,7 @@
   1030 files to transfer, 96.4 KB of data
   starting 4 threads for background file closing
   starting 4 threads for background file closing
+  updating the branch cache
   transferred 96.4 KB in * seconds (* */sec) (glob)
   bundle2-input-part: total payload size 112077
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
--- a/tests/test-clone.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-clone.t	Wed Sep 26 20:33:09 2018 +0900
@@ -20,7 +20,7 @@
 
 Create a non-inlined filelog:
 
-  $ $PYTHON -c 'open("data1", "wb").write(b"".join(b"%d\n" % x for x in range(10000)))'
+  $ "$PYTHON" -c 'open("data1", "wb").write(b"".join(b"%d\n" % x for x in range(10000)))'
   $ for j in 0 1 2 3 4 5 6 7 8 9; do
   >   cat data1 >> b
   >   hg commit -m test
@@ -47,6 +47,7 @@
   checklink (symlink !)
   checklink-target (symlink !)
   checknoexec (execbit !)
+  manifestfulltextcache (reporevlogstore !)
   rbc-names-v1
   rbc-revs-v1
 
@@ -74,7 +75,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 11 changesets, 11 total revisions
+  checked 11 changesets with 11 changes to 2 files
 
 Invalid dest '' must abort:
 
@@ -145,7 +146,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 11 changesets, 11 total revisions
+  checked 11 changesets with 11 changes to 2 files
 
 Default destination:
 
@@ -190,7 +191,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 11 changesets, 11 total revisions
+  checked 11 changesets with 11 changes to 2 files
 
 Invalid dest '' with --pull must abort (issue2528):
 
@@ -563,7 +564,7 @@
   > hg.clone(myui, {}, repo, dest=b"ua")
   > EOF
 
-  $ $PYTHON simpleclone.py
+  $ "$PYTHON" simpleclone.py
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -577,7 +578,7 @@
   > hg.clone(myui, {}, repo, dest=b"ua", branch=[b"stable",])
   > EOF
 
-  $ $PYTHON branchclone.py
+  $ "$PYTHON" branchclone.py
   adding changesets
   adding manifests
   adding file changes
@@ -641,7 +642,7 @@
   $ mkdir a
   $ chmod 000 a
   $ hg clone a b
-  abort: repository a not found!
+  abort: Permission denied: '$TESTTMP/fail/a/.hg'
   [255]
 
 Inaccessible destination
@@ -664,7 +665,7 @@
 
   $ mkfifo a
   $ hg clone a b
-  abort: repository a not found!
+  abort: $ENOTDIR$: '$TESTTMP/fail/a/.hg'
   [255]
   $ rm a
 
@@ -1176,14 +1177,14 @@
 #if windows
   $ hg clone "ssh://%26touch%20owned%20/" --debug
   running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio"
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob) (sshv2 !)
   sending hello command
   sending between command
   abort: no suitable response from remote hg!
   [255]
   $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug
   running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio"
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob) (sshv2 !)
   sending hello command
   sending between command
   abort: no suitable response from remote hg!
@@ -1191,14 +1192,14 @@
 #else
   $ hg clone "ssh://%3btouch%20owned%20/" --debug
   running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio'
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob) (sshv2 !)
   sending hello command
   sending between command
   abort: no suitable response from remote hg!
   [255]
   $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug
   running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio'
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob) (sshv2 !)
   sending hello command
   sending between command
   abort: no suitable response from remote hg!
@@ -1207,7 +1208,7 @@
 
   $ hg clone "ssh://v-alid.example.com/" --debug
   running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re)
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob) (sshv2 !)
   sending hello command
   sending between command
   abort: no suitable response from remote hg!
--- a/tests/test-clonebundles.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-clonebundles.t	Wed Sep 26 20:33:09 2018 +0900
@@ -465,10 +465,8 @@
   no compatible clone bundles available on server; falling back to regular clone
   (you may want to report this to the server operator)
   streaming all changes
-  4 files to transfer, 613 bytes of data
-  transferred 613 bytes in * seconds (*) (glob)
-  searching for changes
-  no changes found
+  9 files to transfer, 816 bytes of data
+  transferred 816 bytes in * seconds (*) (glob)
 
 A manifest with a stream clone but no BUNDLESPEC
 
@@ -480,10 +478,8 @@
   no compatible clone bundles available on server; falling back to regular clone
   (you may want to report this to the server operator)
   streaming all changes
-  4 files to transfer, 613 bytes of data
-  transferred 613 bytes in * seconds (*) (glob)
-  searching for changes
-  no changes found
+  9 files to transfer, 816 bytes of data
+  transferred 816 bytes in * seconds (*) (glob)
 
 A manifest with a gzip bundle and a stream clone
 
@@ -526,10 +522,8 @@
   no compatible clone bundles available on server; falling back to regular clone
   (you may want to report this to the server operator)
   streaming all changes
-  4 files to transfer, 613 bytes of data
-  transferred 613 bytes in * seconds (*) (glob)
-  searching for changes
-  no changes found
+  9 files to transfer, 816 bytes of data
+  transferred 816 bytes in * seconds (*) (glob)
 
 Test clone bundle retrieved through bundle2
 
--- a/tests/test-commandserver.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-commandserver.t	Wed Sep 26 20:33:09 2018 +0900
@@ -523,7 +523,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   $ hg revert --no-backup -aq
 
   $ cat >> .hg/hgrc << EOF
--- a/tests/test-commit-amend.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-commit-amend.t	Wed Sep 26 20:33:09 2018 +0900
@@ -824,7 +824,8 @@
   $ hg merge -q bar --config ui.interactive=True << EOF
   > c
   > EOF
-  local [working copy] changed aa which other [merge rev] deleted
+  file 'aa' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? c
   $ hg ci -m 'merge bar (with conflicts)'
   $ hg log --config diff.git=1 -pr .
--- a/tests/test-commit-interactive-curses.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-commit-interactive-curses.t	Wed Sep 26 20:33:09 2018 +0900
@@ -350,7 +350,7 @@
 The default interface is text
   $ cp $HGRCPATH.pretest $HGRCPATH
   $ chunkselectorinterface() {
-  > $PYTHON <<EOF
+  > "$PYTHON" <<EOF
   > from mercurial import hg, ui;\
   > repo = hg.repository(ui.ui.load(), ".");\
   > print(repo.ui.interface("chunkselector"))
--- a/tests/test-commit-interactive.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-commit-interactive.t	Wed Sep 26 20:33:09 2018 +0900
@@ -915,7 +915,7 @@
   >         b''.join(escape(c) for c in pycompat.iterbytestr(l)))
   > EOF
 
-  $ hg commit -i --encoding cp932 2>&1 <<EOF | $PYTHON $TESTTMP/escape.py | grep '^y - '
+  $ hg commit -i --encoding cp932 2>&1 <<EOF | "$PYTHON" $TESTTMP/escape.py | grep '^y - '
   > ?
   > q
   > EOF
--- a/tests/test-commit-multiple.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-commit-multiple.t	Wed Sep 26 20:33:09 2018 +0900
@@ -115,7 +115,7 @@
   > printfiles(repo, 6)
   > printfiles(repo, 7)
   > __EOF__
-  $ $PYTHON $TESTTMP/committwice.py
+  $ "$PYTHON" $TESTTMP/committwice.py
   PRE: len(repo): 6
   POST: len(repo): 8
   revision 6 files: ['bugfix', 'file1']
--- a/tests/test-commit.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-commit.t	Wed Sep 26 20:33:09 2018 +0900
@@ -654,7 +654,7 @@
   >                    b'evil', [notrc], filectxfn, 0)
   > r.commitctx(c)
   > EOF
-  $ $PYTHON evil-commit.py
+  $ "$PYTHON" evil-commit.py
 #if windows
   $ hg co --clean tip
   abort: path contains illegal component: .h\xe2\x80\x8cg\\hgrc (esc)
@@ -680,7 +680,7 @@
   >                    b'evil', [notrc], filectxfn, 0)
   > r.commitctx(c)
   > EOF
-  $ $PYTHON evil-commit.py
+  $ "$PYTHON" evil-commit.py
   $ hg co --clean tip
   abort: path contains illegal component: HG~1/hgrc
   [255]
@@ -700,7 +700,7 @@
   >                    b'evil', [notrc], filectxfn, 0)
   > r.commitctx(c)
   > EOF
-  $ $PYTHON evil-commit.py
+  $ "$PYTHON" evil-commit.py
   $ hg co --clean tip
   abort: path contains illegal component: HG8B6C~2/hgrc
   [255]
--- a/tests/test-completion.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-completion.t	Wed Sep 26 20:33:09 2018 +0900
@@ -98,6 +98,7 @@
   debugknown
   debuglabelcomplete
   debuglocks
+  debugmanifestfulltextcache
   debugmergestate
   debugnamecomplete
   debugobsolete
@@ -110,6 +111,7 @@
   debugrebuildfncache
   debugrename
   debugrevlog
+  debugrevlogindex
   debugrevspec
   debugserve
   debugsetparents
@@ -248,7 +250,7 @@
   archive: no-decode, prefix, rev, type, subrepos, include, exclude
   backout: merge, commit, no-commit, parent, rev, edit, tool, include, exclude, message, logfile, date, user
   bisect: reset, good, bad, skip, extend, command, noupdate
-  bookmarks: force, rev, delete, rename, inactive, template
+  bookmarks: force, rev, delete, rename, inactive, list, template
   branch: force, clean, rev
   branches: active, closed, template
   bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
@@ -269,21 +271,22 @@
   debugdata: changelog, manifest, dir
   debugdate: extended
   debugdeltachain: changelog, manifest, dir, template
-  debugdirstate: nodates, datesort
+  debugdirstate: nodates, dates, datesort
   debugdiscovery: old, nonheads, rev, ssh, remotecmd, insecure
   debugdownload: output
   debugextensions: template
-  debugfileset: rev, all-files
+  debugfileset: rev, all-files, show-matcher, show-stage
   debugformat: template
   debugfsinfo: 
   debuggetbundle: head, common, type
   debugignore: 
-  debugindex: changelog, manifest, dir, format
+  debugindex: changelog, manifest, dir, template
   debugindexdot: changelog, manifest, dir
   debuginstall: template
   debugknown: 
   debuglabelcomplete: 
   debuglocks: force-lock, force-wlock, set-lock, set-wlock
+  debugmanifestfulltextcache: clear, add
   debugmergestate: 
   debugnamecomplete: 
   debugobsolete: flags, record-parents, rev, exclusive, index, delete, date, user, template
@@ -296,6 +299,7 @@
   debugrebuildfncache: 
   debugrename: rev
   debugrevlog: changelog, manifest, dir, dump
+  debugrevlogindex: changelog, manifest, dir, format
   debugrevspec: optimize, show-revs, show-set, show-stage, no-optimized, verify-optimized
   debugserve: sshstdio, logiofd, logiofile
   debugsetparents: 
@@ -327,7 +331,7 @@
   phase: public, draft, secret, force, rev
   recover: 
   rename: after, force, include, exclude, dry-run
-  resolve: all, list, mark, unmark, no-status, tool, include, exclude, template
+  resolve: all, list, mark, unmark, no-status, re-merge, tool, include, exclude, template
   revert: all, date, rev, no-backup, interactive, include, exclude, dry-run
   rollback: dry-run, force
   root: 
--- a/tests/test-conflict.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-conflict.t	Wed Sep 26 20:33:09 2018 +0900
@@ -58,8 +58,19 @@
   # To mark files as resolved:  hg resolve --mark FILE
   
   # To continue:    hg commit
-  # To abort:       hg update --clean . (warning: this will discard uncommitted changes)
+  # To abort:       hg merge --abort
   
+  $ hg status -Tjson
+  [
+   {
+    "path": "a",
+    "status": "M"
+   },
+   {
+    "path": "a.orig",
+    "status": "?"
+   }
+  ]
 
   $ cat a
   Small Mathematical Series.
@@ -137,7 +148,7 @@
 Verify line trimming of custom conflict marker using multi-byte characters
 
   $ hg up -q --clean .
-  $ $PYTHON <<EOF
+  $ "$PYTHON" <<EOF
   > fp = open('logfile', 'wb')
   > fp.write(b'12345678901234567890123456789012345678901234567890' +
   >          b'1234567890') # there are 5 more columns for 80 columns
--- a/tests/test-confused-revert.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-confused-revert.t	Wed Sep 26 20:33:09 2018 +0900
@@ -14,8 +14,8 @@
   R a
 
   $ hg revert --all
+  forgetting b
   undeleting a
-  forgetting b
 
 Should show b unknown and a back to normal:
 
@@ -66,8 +66,8 @@
 Revert should be ok now:
 
   $ hg revert -r2 --all
+  forgetting b
   undeleting a
-  forgetting b
 
 Should show b unknown and a marked modified (merged):
 
--- a/tests/test-contrib-dumprevlog.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-contrib-dumprevlog.t	Wed Sep 26 20:33:09 2018 +0900
@@ -19,10 +19,10 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
 
 Dumping revlog of file a to stdout:
-  $ $PYTHON "$CONTRIBDIR/dumprevlog" .hg/store/data/a.i
+  $ "$PYTHON" "$CONTRIBDIR/dumprevlog" .hg/store/data/a.i
   file: .hg/store/data/a.i
   node: 183d2312b35066fb6b3b449b84efc370d50993d0
   linkrev: 0
@@ -54,14 +54,14 @@
 
 Dump all revlogs to file repo.dump:
 
-  $ find .hg/store -name "*.i" | sort | xargs $PYTHON "$CONTRIBDIR/dumprevlog" > ../repo.dump
+  $ find .hg/store -name "*.i" | sort | xargs "$PYTHON" "$CONTRIBDIR/dumprevlog" > ../repo.dump
   $ cd ..
 
 Undumping into repo-b:
 
   $ hg init repo-b
   $ cd repo-b
-  $ $PYTHON "$CONTRIBDIR/undumprevlog" < ../repo.dump
+  $ "$PYTHON" "$CONTRIBDIR/undumprevlog" < ../repo.dump
   .hg/store/00changelog.i
   .hg/store/00manifest.i
   .hg/store/data/a.i
@@ -84,7 +84,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
 
 Compare repos:
 
--- a/tests/test-contrib-perf.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-contrib-perf.t	Wed Sep 26 20:33:09 2018 +0900
@@ -55,6 +55,8 @@
                  benchmark parsing bookmarks from disk to memory
    perfbranchmap
                  benchmark the update of a branchmap
+   perfbranchmapload
+                 benchmark reading the branchmap
    perfbundleread
                  Benchmark reading of bundle files.
    perfcca       (no help text available)
@@ -82,6 +84,8 @@
                  (no help text available)
    perfheads     (no help text available)
    perfindex     (no help text available)
+   perflinelogedits
+                 (no help text available)
    perfloadmarkers
                  benchmark the time to parse the on-disk markers for a repo
    perflog       (no help text available)
@@ -156,11 +160,16 @@
 #endif
   $ hg perfheads
   $ hg perfindex
+  $ hg perflinelogedits -n 1
   $ hg perfloadmarkers
   $ hg perflog
   $ hg perflookup 2
   $ hg perflrucache
   $ hg perfmanifest 2
+  $ hg perfmanifest -m 44fe2c8352bb3a478ffd7d8350bbc721920134d1
+  $ hg perfmanifest -m 44fe2c8352bb
+  abort: manifest revision must be integer or full node
+  [255]
   $ hg perfmergecalculate -r 3
   $ hg perfmoonwalk
   $ hg perfnodelookup 2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-contrib-relnotes.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,291 @@
+#require test-repo py3exe
+  $ . "$TESTDIR/helpers-testrepo.sh"
+
+  $ cd $TESTDIR/..
+  $ python3 contrib/relnotes 4.4 --stoprev 4.5
+  changeset 3398603c5621: unexpected block in release notes directive feature
+  New Features
+  ============
+  
+  revert --interactive
+  --------------------
+  
+  The revert command now accepts the flag --interactive to allow reverting only
+  some of the changes to the specified files.
+  
+  Rebase with different destination per source revision
+  -----------------------------------------------------
+  
+  Previously, rebase only supports one unique destination. Now "SRC" and
+  "ALLSRC" can be used in rebase destination revset to precisely define
+  destination per each individual source revision.
+  
+  For example, the following command could move some orphaned changesets to
+  reasonable new places so they become no longer orphaned:
+  
+  hg rebase   -r 'orphan()-obsolete()'   -d 'max((successors(max(roots(ALLSRC) &
+  ::SRC)^)-obsolete())::)'
+  
+  Accessing hidden changesets
+  ---------------------------
+  
+  Set config option 'experimental.directaccess = True' to access hidden
+  changesets from read only commands.
+  
+  githelp extension
+  -----------------
+  
+  The "githelp" extension provides the "hg githelp" command. This command
+  attempts to convert a "git" command to its Mercurial equivalent. The extension
+  can be useful to Git users new to Mercurial.
+  
+  Other Changes
+  -------------
+  
+  * When interactive revert is run against a revision other than the working
+    directory parent, the diff shown is the diff to *apply* to the working
+    directory, rather than the diff to *discard* from the working copy. This is
+    in line with related user experiences with 'git' and appears to be less
+    confusing with 'ui.interface=curses'.
+  
+  * Let 'hg rebase' avoid content-divergence by skipping obsolete changesets
+    (and their descendants) when they are present in the rebase set along with
+    one of their successors but none of their successors is in destination.
+  
+  * hgweb now displays phases of non-public changesets
+  
+  * The "HGPLAINEXCEPT" environment variable can now include "color" to allow
+    automatic output colorization in otherwise automated environments.
+  
+  * A new unamend command in uncommit extension which undoes the effect of the
+    amend command by creating a new changeset which was there before amend and
+    moving the changes that were amended to the working directory.
+  
+  * A '--abort' flag to merge command to abort the ongoing merge.
+  
+  * An experimental flag '--rev' to 'hg branch' which can be used to change
+    branch of changesets.
+  
+  Backwards Compatibility Changes
+  ===============================
+  
+  * "log --follow-first -rREV", which is deprecated, now follows the first
+    parent of merge revisions from the specified "REV" just like "log --follow
+    -rREV".
+  
+  * "log --follow -rREV FILE.." now follows file history across copies and
+    renames.
+  
+  Bug Fixes
+  =========
+  
+  Issue 5165
+  ----------
+  
+  Bookmark, whose name is longer than 255, can again be exchanged again between
+  4.4+ client and servers.
+  
+  Performance Improvements
+  ========================
+  
+  * bundle2 read I/O throughput significantly increased.
+  
+  * Significant memory use reductions when reading from bundle2 bundles.
+  
+    On the BSD repository, peak RSS during changegroup application decreased by
+    ~185 MB from ~752 MB to ~567 MB.
+  
+  API Changes
+  ===========
+  
+  * bundlerepo.bundlerepository.bundle and
+    bundlerepo.bundlerepository.bundlefile are now prefixed with an underscore.
+  
+  * Rename bundlerepo.bundlerepository.bundlefilespos to _cgfilespos.
+  
+  * dirstate no longer provides a 'dirs()' method.  To test for the existence of
+    a directory in the dirstate, use 'dirstate.hasdir(dirname)'.
+  
+  * bundle2 parts are no longer seekable by default.
+  
+  * mapping does not contain all template resources. use context.resource() in
+    template functions.
+  
+  * "text=False|True" option is dropped from the vfs interface because of Python
+    3 compatibility issue. Use "util.tonativeeol/fromnativeeol()" to convert EOL
+    manually.
+  
+  * wireproto.streamres.__init__ no longer accepts a "reader" argument. Use the
+    "gen" argument instead.
+  
+  * exchange.getbundlechunks() now returns a 2-tuple instead of just an
+    iterator.
+  
+  
+  === commands ===
+   * amend: do not drop missing files (Bts:issue5732)
+   * amend: do not take untracked files as modified or clean (Bts:issue5732)
+   * amend: update .hgsubstate before committing a memctx (Bts:issue5677)
+   * annotate: add support to specify hidden revs if directaccess config is set
+   * bookmark: add methods to binary encode and decode bookmark values
+   * bookmark: deprecate direct update of a bookmark value
+   * bookmark: introduce a 'bookmarks' part
+   * bookmark: introduce in advance a variant of the exchange test
+   * bookmark: run 'pushkey' hooks after bookmark move, not 'prepushkey'
+   * bookmarks: add bookmarks to hidden revs if directaccess config is set
+   * bookmarks: calculate visibility exceptions only once
+   * bookmarks: display the obsfate of hidden revision we create a bookmark on
+   * bookmarks: fix pushkey compatibility mode (Bts:issue5777)
+   * bookmarks: use context managers for lock and transaction in update()
+   * bookmarks: use context managers for locks and transaction in pushbookmark()
+   * branch: allow changing branch name to existing name if possible
+   * clone: add support for storing remotenames while cloning
+   * clone: use utility function to write hgrc
+   * clonebundle: make it possible to retrieve the initial bundle through largefile
+   * commandserver: restore cwd in case of exception
+   * commandserver: unblock SIGCHLD
+   * help: deprecate ui.slash in favor of slashpath template filter (Bts:issue5572)
+   * log: allow matchfn to be non-null even if both --patch/--stat are off
+   * log: build follow-log filematcher at once
+   * log: don't expand aliases in revset built from command options
+   * log: make "slowpath" condition slightly more readable
+   * log: make opt2revset table a module constant
+   * log: merge getlogrevs() and getgraphlogrevs()
+   * log: remove temporary variable 'date' used only once
+   * log: resolve --follow thoroughly in getlogrevs()
+   * log: resolve --follow with -rREV in cmdutil.getlogrevs()
+   * log: simplify 'x or ancestors(x)' expression
+   * log: translate column labels at once (Bts:issue5750)
+   * log: use revsetlang.formatspec() thoroughly
+   * log: use revsetlang.formatspec() to concatenate list expression
+   * log: use smartset.slice() to limit number of revisions to be displayed
+   * merge: cache unknown dir checks (Bts:issue5716)
+   * merge: check created file dirs for path conflicts only once (Bts:issue5716)
+   * patch: add within-line color diff capacity
+   * patch: catch unexpected case in _inlinediff
+   * patch: do not break up multibyte character when highlighting word
+   * patch: improve heuristics to not take the word "diff" as header (Bts:issue1879)
+   * patch: reverse _inlinediff output for consistency
+   * pull: clarify that -u only updates linearly
+   * pull: hold wlock for the full operation when --update is used
+   * pull: retrieve bookmarks through the binary part when possible
+   * pull: store binary node in pullop.remotebookmarks
+   * push: include a 'check:bookmarks' part when possible
+   * push: restrict common discovery to the pushed set
+   * revert: support reverting to hidden cset if directaccess config is set
+  
+  === core ===
+   * filelog: add the ability to report the user facing name
+   * revlog: choose between ifh and dfh once for all
+   * revlog: don't use slicing to return parents
+   * revlog: group delta computation methods under _deltacomputer object
+   * revlog: group revision info into a dedicated structure
+   * revlog: introduce 'deltainfo' to distinguish from 'delta'
+   * revlog: rename 'rev' to 'base', as it is the base revision
+   * revlog: separate diff computation from the collection of other info
+   * revset: evaluate filesets against each revision for 'file()' (Bts:issue5778)
+   * revset: parse x^:: as (x^):: (Bts:issue5764)
+   * templater: look up symbols/resources as if they were separated (Bts:issue5699)
+   * transaction: register summary callbacks only at start of transaction (BC)
+   * util: whitelist NTFS for hardlink creation (Bts:issue4580)
+  
+  === extensions ===
+   * convert: restore the ability to use bzr < 2.6.0 (Bts:issue5733)
+   * histedit: add support to output nodechanges using formatter
+   * largefiles: add a 'debuglfput' command to put largefile into the store
+   * largefiles: add support for 'largefiles://' url scheme
+   * largefiles: allow to run 'debugupgraderepo' on repo with largefiles
+   * largefiles: convert EOL of hgrc before appending to bytes IO
+   * largefiles: explicitly set the source and sink types to 'hg' for lfconvert
+   * largefiles: modernize how capabilities are added to the wire protocol
+   * largefiles: pay attention to dropped standin files when updating largefiles
+   * rebase: add concludememorynode(), and call it when rebasing in-memory
+   * rebase: add the --inmemory option flag; assign a wctx object for the rebase
+   * rebase: add ui.log calls for whether IMM used, whether rebasing WCP
+   * rebase: disable 'inmemory' if the rebaseset contains the working copy
+   * rebase: do not bail on uncomitted changes if rebasing in-memory
+   * rebase: do not update if IMM; instead, set the overlaywctx's parents
+   * rebase: don't run IMM if running rebase in a transaction
+   * rebase: don't take out a dirstate guard for in-memory rebase
+   * rebase: drop --style option
+   * rebase: fix for hgsubversion
+   * rebase: pass the wctx object (IMM or on-disk) to merge.update
+   * rebase: pass wctx to rebasenode()
+   * rebase: rerun a rebase on-disk if IMM merge conflicts arise
+   * rebase: switch ui.log calls to common style
+   * rebase: use fm.formatlist() and fm.formatdict() to support user template
+  
+  === hgweb ===
+   * hgweb: disable diff.noprefix option for diffstat
+   * hgweb: drop support of browsers that don't understand <canvas> (BC)
+   * hgweb: only include graph-related data in jsdata variable on /graph pages (BC)
+   * hgweb: stop adding strings to innerHTML of #graphnodes and #nodebgs (BC)
+  
+  === unsorted ===
+   * archive: add support to specify hidden revs if directaccess config is set
+   * atomicupdate: add an experimental option to use atomictemp when updating
+   * bundle: allow bundlerepo to support alternative manifest implementations
+   * changelog: introduce a 'tiprev' method
+   * changelog: use 'tiprev()' in 'tip()'
+   * completion: add support for new "amend" command
+   * debugssl: convert port number to int (Bts:issue5757)
+   * diff: disable diff.noprefix option for diffstat (Bts:issue5759)
+   * dispatch: abort if early boolean options can't be parsed
+   * dispatch: add HGPLAIN=+strictflags to restrict early parsing of global options
+   * dispatch: add option to not strip command args parsed by _earlygetopt()
+   * dispatch: alias --repo to --repository while parsing early options
+   * dispatch: convert non-list option parsed by _earlygetopt() to string
+   * dispatch: fix early parsing of short option with value like -R=foo
+   * dispatch: handle IOError when writing to stderr
+   * dispatch: stop parsing of early boolean option at "--"
+   * dispatch: verify result of early command parsing
+   * evolution: make reporting of new unstable changesets optional
+   * extdata: abort if external command exits with non-zero status (BC)
+   * fancyopts: add early-options parser compatible with getopt()
+   * graphlog: add another graph node type, unstable, using character "*" (BC)
+   * hgdemandimport: use correct hyperlink to python-bug in comments (Bts:issue5765)
+   * httppeer: add support for tracing all http request made by the peer
+   * identify: document -r. explicitly how to disable wdir scanning (Bts:issue5622)
+   * lfs: register config options
+   * localrepo: specify optional callback parameter to pathauditor as a keyword
+   * match: do not weirdly include explicit files excluded by -X option
+   * memfilectx: make changectx argument mandatory in constructor (API)
+   * morestatus: don't crash with different drive letters for repo.root and CWD
+   * outgoing: respect ":pushurl" paths (Bts:issue5365)
+   * remove: print message for each file in verbose mode only while using '-A' (BC)
+   * rewriteutil: use precheck() in uncommit and amend commands
+   * scmutil: don't try to delete origbackup symlinks to directories (Bts:issue5731)
+   * sshpeer: add support for request tracing
+   * streamclone: add support for bundle2 based stream clone
+   * streamclone: add support for cloning non append-only file
+   * streamclone: also stream caches to the client
+   * streamclone: define first iteration of version 2 of stream format
+   * streamclone: move wire protocol status code from wireproto command
+   * streamclone: rework canperformstreamclone
+   * streamclone: tests phase exchange during stream clone
+   * streamclone: use readexactly when reading stream v2
+   * subrepo: add config option to reject any subrepo operations (SEC)
+   * subrepo: disable git and svn subrepos by default (BC) (SEC)
+   * subrepo: extend config option to disable subrepos by type (SEC)
+   * subrepo: handle 'C:' style paths on the command line (Bts:issue5770)
+   * subrepo: use per-type config options to enable subrepos
+   * svnsubrepo: check if subrepo is missing when checking dirty state (Bts:issue5657)
+   * tr-summary: keep a weakref to the unfiltered repository
+   * unamend: fix command summary line
+   * uncommit: unify functions _uncommitdirstate and _unamenddirstate to one
+   * update: support updating to hidden cset if directaccess config is set
+  
+  === BC ===
+  
+   * extdata: abort if external command exits with non-zero status (BC)
+   * graphlog: add another graph node type, unstable, using character "*" (BC)
+   * hgweb: drop support of browsers that don't understand <canvas> (BC)
+   * hgweb: only include graph-related data in jsdata variable on /graph pages (BC)
+   * hgweb: stop adding strings to innerHTML of #graphnodes and #nodebgs (BC)
+   * remove: print message for each file in verbose mode only while using '-A' (BC)
+   * subrepo: disable git and svn subrepos by default (BC) (SEC)
+   * transaction: register summary callbacks only at start of transaction (BC)
+  
+  === API Changes ===
+  
+   * memfilectx: make changectx argument mandatory in constructor (API)
--- a/tests/test-contrib.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-contrib.t	Wed Sep 26 20:33:09 2018 +0900
@@ -14,7 +14,7 @@
 
 changing local directly
 
-  $ $PYTHON simplemerge local base other && echo "merge succeeded"
+  $ "$PYTHON" simplemerge local base other && echo "merge succeeded"
   merge succeeded
   $ cat local
   local
@@ -24,7 +24,7 @@
 
 printing to stdout
 
-  $ $PYTHON simplemerge -p local base other
+  $ "$PYTHON" simplemerge -p local base other
   local
   base
   other
@@ -43,7 +43,7 @@
   $ echo end >> conflict-local
   $ echo end >> conflict-other
 
-  $ $PYTHON simplemerge -p conflict-local base conflict-other
+  $ "$PYTHON" simplemerge -p conflict-local base conflict-other
   base
   <<<<<<< conflict-local
   not other
@@ -55,7 +55,7 @@
 
 1 label
 
-  $ $PYTHON simplemerge -p -L foo conflict-local base conflict-other
+  $ "$PYTHON" simplemerge -p -L foo conflict-local base conflict-other
   base
   <<<<<<< foo
   not other
@@ -67,7 +67,7 @@
 
 2 labels
 
-  $ $PYTHON simplemerge -p -L foo -L bar conflict-local base conflict-other
+  $ "$PYTHON" simplemerge -p -L foo -L bar conflict-local base conflict-other
   base
   <<<<<<< foo
   not other
@@ -79,7 +79,7 @@
 
 3 labels
 
-  $ $PYTHON simplemerge -p -L foo -L bar -L base conflict-local base conflict-other
+  $ "$PYTHON" simplemerge -p -L foo -L bar -L base conflict-local base conflict-other
   base
   <<<<<<< foo
   not other
@@ -93,21 +93,21 @@
 
 too many labels
 
-  $ $PYTHON simplemerge -p -L foo -L bar -L baz -L buz conflict-local base conflict-other
+  $ "$PYTHON" simplemerge -p -L foo -L bar -L baz -L buz conflict-local base conflict-other
   abort: can only specify three labels.
   [255]
 
 binary file
 
-  $ $PYTHON -c "f = open('binary-local', 'w'); f.write('\x00'); f.close()"
+  $ "$PYTHON" -c "f = open('binary-local', 'w'); f.write('\x00'); f.close()"
   $ cat orig >> binary-local
-  $ $PYTHON simplemerge -p binary-local base other
+  $ "$PYTHON" simplemerge -p binary-local base other
   warning: binary-local looks like a binary file.
   [1]
 
 binary file --text
 
-  $ $PYTHON simplemerge -a -p binary-local base other 2>&1
+  $ "$PYTHON" simplemerge -a -p binary-local base other 2>&1
   warning: binary-local looks like a binary file.
   \x00local (esc)
   base
@@ -115,7 +115,7 @@
 
 help
 
-  $ $PYTHON simplemerge --help
+  $ "$PYTHON" simplemerge --help
   simplemerge [OPTS] LOCAL BASE OTHER
   
       Simple three-way file merge utility with a minimal feature set.
@@ -134,7 +134,7 @@
 
 wrong number of arguments
 
-  $ $PYTHON simplemerge
+  $ "$PYTHON" simplemerge
   simplemerge: wrong number of arguments
   simplemerge [OPTS] LOCAL BASE OTHER
   
@@ -155,7 +155,7 @@
 
 bad option
 
-  $ $PYTHON simplemerge --foo -p local base other
+  $ "$PYTHON" simplemerge --foo -p local base other
   simplemerge: option --foo not recognized
   simplemerge [OPTS] LOCAL BASE OTHER
   
--- a/tests/test-convert-bzr-ghosts.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-bzr-ghosts.t	Wed Sep 26 20:33:09 2018 +0900
@@ -21,7 +21,7 @@
   $ bzr add -q somefile
   $ bzr commit -q -m 'Initial layout setup'
   $ echo morecontent >> somefile
-  $ $PYTHON ../../ghostcreator.py 'Commit with ghost revision' ghostrev
+  $ "$PYTHON" ../../ghostcreator.py 'Commit with ghost revision' ghostrev
   $ cd ..
   $ hg convert source source-hg
   initializing destination source-hg repository
@@ -31,9 +31,9 @@
   1 Initial layout setup
   0 Commit with ghost revision
   $ glog -R source-hg
-  o  1@source "Commit with ghost revision" files: somefile
+  o  1@source "Commit with ghost revision" files+: [], files-: [], files: [somefile]
   |
-  o  0@source "Initial layout setup" files: somefile
+  o  0@source "Initial layout setup" files+: [somefile], files-: [], files: []
   
 
   $ cd ..
--- a/tests/test-convert-bzr-merges.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-bzr-merges.t	Wed Sep 26 20:33:09 2018 +0900
@@ -13,7 +13,8 @@
   $ bzr init -q source
   $ cd source
   $ echo content > file
-  $ bzr add -q file
+  $ echo text > rename_me
+  $ bzr add -q file rename_me
   $ bzr commit -q -m 'Initial add' '--commit-time=2009-10-10 08:00:00 +0100'
   $ cd ..
   $ bzr branch -q source source-branch1
@@ -32,6 +33,8 @@
   $ cd source-branch2
   $ echo somecontent > file-branch2
   $ bzr add -q file-branch2
+  $ bzr mv -q rename_me renamed
+  $ echo change > renamed
   $ bzr commit -q -m 'Added brach2 file' '--commit-time=2009-10-10 08:00:03 +0100'
   $ sleep 1
   $ cd ../source
@@ -39,6 +42,9 @@
   $ bzr merge -q --force ../source-branch2
   $ bzr commit -q -m 'Merged branches' '--commit-time=2009-10-10 08:00:04 +0100'
   $ cd ..
+
+BUG: file-branch2 should not be added in rev 4, and the rename_me -> renamed
+move should be recorded in the fixup merge.
   $ hg convert --datesort --config convert.bzr.saverev=False source source-hg
   initializing destination source-hg repository
   scanning source...
@@ -49,18 +55,19 @@
   2 Added parent file
   1 Added brach2 file
   0 Merged branches
+  warning: can't find ancestor for 'renamed' copied from 'rename_me'!
   $ glog -R source-hg
-  o    5@source "(octopus merge fixup)" files:
+  o    5@source "(octopus merge fixup)" files+: [], files-: [], files: [renamed]
   |\
-  | o    4@source "Merged branches" files: file-branch2
+  | o    4@source "Merged branches" files+: [file-branch1 file-branch2 renamed], files-: [rename_me], files: [file]
   | |\
-  o---+  3@source-branch2 "Added brach2 file" files: file-branch2
+  o---+  3@source-branch2 "Added brach2 file" files+: [file-branch2 renamed], files-: [rename_me], files: []
    / /
-  | o  2@source "Added parent file" files: file-parent
+  | o  2@source "Added parent file" files+: [file-parent], files-: [], files: []
   | |
-  o |  1@source-branch1 "Added branch1 file" files: file file-branch1
+  o |  1@source-branch1 "Added branch1 file" files+: [file-branch1], files-: [], files: [file]
   |/
-  o  0@source "Initial add" files: file
+  o  0@source "Initial add" files+: [file rename_me], files-: [], files: []
   
   $ manifest source-hg tip
   % manifest of tip
@@ -68,6 +75,7 @@
   644   file-branch1
   644   file-branch2
   644   file-parent
+  644   renamed
 
   $ hg convert source-hg hg2hg
   initializing destination hg2hg repository
@@ -80,38 +88,107 @@
   2 Added brach2 file
   1 Merged branches
   0 (octopus merge fixup)
+
+BUG: The manifest entries should be the same for matching revisions, and
+nothing should be outgoing
+
+  $ hg -R source-hg manifest --debug -r tip | grep renamed
+  67109fdebf6c556eb0a9d5696dd98c8420520405 644   renamed
+  $ hg -R hg2hg manifest --debug -r tip | grep renamed
+  27c968376d7c3afd095ecb9c7697919b933448c8 644   renamed
+  $ hg -R source-hg manifest --debug -r 'tip^' | grep renamed
+  27c968376d7c3afd095ecb9c7697919b933448c8 644   renamed
+  $ hg -R hg2hg manifest --debug -r 'tip^' | grep renamed
+  27c968376d7c3afd095ecb9c7697919b933448c8 644   renamed
+
+BUG: The revisions found should be the same in both repos
+
+  $ hg --cwd source-hg log -r 'file("renamed")' -G -Tcompact
+  o    5[tip]:4,3   6652429c300a   2009-10-10 08:00 +0100   foo
+  |\     (octopus merge fixup)
+  | |
+  | o    4:2,1   e0ae8af3503a   2009-10-10 08:00 +0100   foo
+  | |\     Merged branches
+  | ~ ~
+  o  3   138bed2e14be   2009-10-10 08:00 +0100   foo
+  |    Added brach2 file
+  ~
+  $ hg --cwd hg2hg log -r 'file("renamed")' -G -Tcompact
+  o    4:2,1   e0ae8af3503a   2009-10-10 08:00 +0100   foo
+  |\     Merged branches
+  ~ ~
+  o  3   138bed2e14be   2009-10-10 08:00 +0100   foo
+  |    Added brach2 file
+  ~
+
+BUG(?): The move seems to be recorded in rev 4, so it should probably show up
+there.  It's not recorded as a move in rev 5, even in source-hg.
+
+  $ hg -R source-hg up -q tip
+  $ hg -R hg2hg up -q tip
+  $ hg --cwd source-hg log -r 'follow("renamed")' -G -Tcompact
+  @    5[tip]:4,3   6652429c300a   2009-10-10 08:00 +0100   foo
+  |\     (octopus merge fixup)
+  | :
+  o :  3   138bed2e14be   2009-10-10 08:00 +0100   foo
+  :/     Added brach2 file
+  :
+  o  0   18b86f5df51b   2009-10-10 08:00 +0100   foo
+       Initial add
+  
+  $ hg --cwd hg2hg log -r 'follow("renamed")' -G -Tcompact
+  o  3   138bed2e14be   2009-10-10 08:00 +0100   foo
+  :    Added brach2 file
+  :
+  o  0   18b86f5df51b   2009-10-10 08:00 +0100   foo
+       Initial add
+  
+
   $ hg -R hg2hg out source-hg -T compact
   comparing with source-hg
   searching for changes
-  5[tip]:4,3   6bd55e826939   2009-10-10 08:00 +0100   foo
+  5[tip]:4,3   3be2299ccd31   2009-10-10 08:00 +0100   foo
     (octopus merge fixup)
   
-XXX: The manifest lines should probably agree, to avoid changing the hash when
-converting hg -> hg
+
+  $ glog -R hg2hg
+  @    5@source "(octopus merge fixup)" files+: [], files-: [], files: []
+  |\
+  | o    4@source "Merged branches" files+: [file-branch1 file-branch2 renamed], files-: [rename_me], files: [file]
+  | |\
+  o---+  3@source-branch2 "Added brach2 file" files+: [file-branch2 renamed], files-: [rename_me], files: []
+   / /
+  | o  2@source "Added parent file" files+: [file-parent], files-: [], files: []
+  | |
+  o |  1@source-branch1 "Added branch1 file" files+: [file-branch1], files-: [], files: [file]
+  |/
+  o  0@source "Initial add" files+: [file rename_me], files-: [], files: []
+  
 
   $ hg -R source-hg log --debug -r tip
-  changeset:   5:b209510f11b2c987f920749cd8e352aa4b3230f2
+  changeset:   5:6652429c300ab66fdeaf2e730945676a00b53231
   branch:      source
   tag:         tip
   phase:       draft
-  parent:      4:1dc38c377bb35eeea4fa955056fbe4440d54a743
-  parent:      3:4aaba1bfb426b8941bbf63f9dd52301152695164
-  manifest:    5:1109e42bdcbd1f51baa69bc91079011d77057dbb
+  parent:      4:e0ae8af3503af9bbffb0b29268a02744cc61a561
+  parent:      3:138bed2e14be415a2692b02e41405b2864f758b4
+  manifest:    5:1eabd5f5d4b985784cf2c45c717ff053eca14b0d
   user:        Foo Bar <foo.bar@example.com>
   date:        Sat Oct 10 08:00:04 2009 +0100
+  files:       renamed
   extra:       branch=source
   description:
   (octopus merge fixup)
   
   
   $ hg -R hg2hg log --debug -r tip
-  changeset:   5:6bd55e8269392769783345686faf7ff7b3b0215d
+  changeset:   5:3be2299ccd315ff9aab2b49bdb0d14e3244435e8
   branch:      source
   tag:         tip
   phase:       draft
-  parent:      4:1dc38c377bb35eeea4fa955056fbe4440d54a743
-  parent:      3:4aaba1bfb426b8941bbf63f9dd52301152695164
-  manifest:    4:daa315d56a98ba20811fdd0d9d575861f65cfa8c
+  parent:      4:e0ae8af3503af9bbffb0b29268a02744cc61a561
+  parent:      3:138bed2e14be415a2692b02e41405b2864f758b4
+  manifest:    4:3ece3c7f2cc6df15b3cbbf3273c69869fc7c3ab0
   user:        Foo Bar <foo.bar@example.com>
   date:        Sat Oct 10 08:00:04 2009 +0100
   extra:       branch=source
@@ -124,21 +201,25 @@
   5108144f585149b29779d7c7e51d61dd22303ffe 644   file-branch1
   80753c4a9ac3806858405b96b24a907b309e3616 644   file-branch2
   7108421418404a937c684d2479a34a24d2ce4757 644   file-parent
+  67109fdebf6c556eb0a9d5696dd98c8420520405 644   renamed
   $ hg -R source-hg manifest --debug -r 'tip^'
   cdf31ed9242b209cd94697112160e2c5b37a667d 644   file
   5108144f585149b29779d7c7e51d61dd22303ffe 644   file-branch1
   80753c4a9ac3806858405b96b24a907b309e3616 644   file-branch2
   7108421418404a937c684d2479a34a24d2ce4757 644   file-parent
+  27c968376d7c3afd095ecb9c7697919b933448c8 644   renamed
 
   $ hg -R hg2hg manifest --debug -r tip
   cdf31ed9242b209cd94697112160e2c5b37a667d 644   file
   5108144f585149b29779d7c7e51d61dd22303ffe 644   file-branch1
   80753c4a9ac3806858405b96b24a907b309e3616 644   file-branch2
   7108421418404a937c684d2479a34a24d2ce4757 644   file-parent
+  27c968376d7c3afd095ecb9c7697919b933448c8 644   renamed
   $ hg -R hg2hg manifest --debug -r 'tip^'
   cdf31ed9242b209cd94697112160e2c5b37a667d 644   file
   5108144f585149b29779d7c7e51d61dd22303ffe 644   file-branch1
   80753c4a9ac3806858405b96b24a907b309e3616 644   file-branch2
   7108421418404a937c684d2479a34a24d2ce4757 644   file-parent
+  27c968376d7c3afd095ecb9c7697919b933448c8 644   renamed
 
   $ cd ..
--- a/tests/test-convert-bzr-treeroot.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-bzr-treeroot.t	Wed Sep 26 20:33:09 2018 +0900
@@ -20,7 +20,7 @@
   $ echo content > file
   $ bzr add -q file
   $ bzr commit -q -m 'Initial add'
-  $ $PYTHON ../../treeset.py 'Changed root' new
+  $ "$PYTHON" ../../treeset.py 'Changed root' new
   $ cd ..
   $ hg convert source source-hg
   initializing destination source-hg repository
--- a/tests/test-convert-bzr.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-bzr.t	Wed Sep 26 20:33:09 2018 +0900
@@ -42,9 +42,9 @@
   1 Initial add: a, c, e
   0 rename a into b, create a, rename c into d
   $ glog -R source-hg
-  o  1@source "rename a into b, create a, rename c into d" files: a b c d e f
+  o  1@source "rename a into b, create a, rename c into d" files+: [b d f], files-: [c e], files: [a]
   |
-  o  0@source "Initial add: a, c, e" files: a c e
+  o  0@source "Initial add: a, c, e" files+: [a c e], files-: [], files: []
   
 
 manifest
@@ -64,7 +64,7 @@
   converting...
   0 Initial add: a, c, e
   $ glog -R source-1-hg
-  o  0@source "Initial add: a, c, e" files: a c e
+  o  0@source "Initial add: a, c, e" files+: [a c e], files-: [], files: []
   
 
 test with filemap
@@ -129,10 +129,10 @@
   $ bzr branch -q source source-improve
   $ cd source
   $ echo more >> a
-  $ $PYTHON ../helper.py 'Editing a' 100
+  $ "$PYTHON" ../helper.py 'Editing a' 100
   $ cd ../source-improve
   $ echo content3 >> b
-  $ $PYTHON ../helper.py 'Editing b' 200
+  $ "$PYTHON" ../helper.py 'Editing b' 200
   $ cd ../source
   $ bzr merge -q ../source-improve
   $ bzr commit -q -m 'Merged improve branch'
@@ -147,13 +147,13 @@
   1 Editing b
   0 Merged improve branch
   $ glog -R source-hg
-  o    3@source "Merged improve branch" files:
+  o    3@source "Merged improve branch" files+: [], files-: [], files: [b]
   |\
-  | o  2@source-improve "Editing b" files: b
+  | o  2@source-improve "Editing b" files+: [], files-: [], files: [b]
   | |
-  o |  1@source "Editing a" files: a
+  o |  1@source "Editing a" files+: [], files-: [], files: [a]
   |/
-  o  0@source "Initial add" files: a b
+  o  0@source "Initial add" files+: [a b], files-: [], files: []
   
   $ cd ..
 
@@ -250,13 +250,13 @@
   0 changea
   updating tags
   $ (cd repo-bzr; glog)
-  o  3@default "update tags" files: .hgtags
+  o  3@default "update tags" files+: [.hgtags], files-: [], files: []
   |
-  o  2@default "changea" files: a
+  o  2@default "changea" files+: [], files-: [], files: [a]
   |
-  | o  1@branch "addb" files: b
+  | o  1@branch "addb" files+: [b], files-: [], files: []
   |/
-  o  0@default "adda" files: a
+  o  0@default "adda" files+: [a], files-: [], files: []
   
 
 Test tags (converted identifiers are not stable because bzr ones are
--- a/tests/test-convert-clonebranches.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-clonebranches.t	Wed Sep 26 20:33:09 2018 +0900
@@ -42,7 +42,7 @@
 convert
 
   $ hg convert -v --config convert.hg.clonebranches=1 source dest |
-  >     $PYTHON filter.py
+  >     "$PYTHON" filter.py
   3 adda
   2 changea
   1 addb
@@ -75,7 +75,7 @@
 incremental conversion
 
   $ hg convert -v --config convert.hg.clonebranches=1 source dest |
-  >     $PYTHON filter.py
+  >     "$PYTHON" filter.py
   2 c1
   pulling from branch0 into branch1
   4 changesets found
--- a/tests/test-convert-filemap.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-filemap.t	Wed Sep 26 20:33:09 2018 +0900
@@ -317,7 +317,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 5 changesets, 7 total revisions
+  checked 5 changesets with 7 changes to 4 files
 
   $ hg -R renames.repo manifest --debug
   d43feacba7a4f1f2080dde4a4b985bd8a0236d46 644   copied2
@@ -780,7 +780,7 @@
   converting...
   0 3
   $ hg -R .-hg log -G -T '{shortest(node)} {desc}\n{files % "- {file}\n"}\n'
-  o    e9ed 3
+  o    bbfe 3
   |\
   | o  33a0 2
   | |  - f
--- a/tests/test-convert-git.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-git.t	Wed Sep 26 20:33:09 2018 +0900
@@ -420,7 +420,7 @@
   $ mkdir git-repo3
   $ cd git-repo3
   $ git init-db >/dev/null 2>/dev/null
-  $ $PYTHON -c 'import struct; open("b", "wb").write(b"".join([struct.Struct(">B").pack(i) for i in range(256)])*16)'
+  $ "$PYTHON" -c 'import struct; open("b", "wb").write(b"".join([struct.Struct(">B").pack(i) for i in range(256)])*16)'
   $ git add b
   $ commit -a -m addbinary
   $ cd ..
@@ -437,7 +437,7 @@
   $ cd git-repo3-hg
   $ hg up -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ $PYTHON -c 'from __future__ import print_function; print(len(open("b", "rb").read()))'
+  $ "$PYTHON" -c 'from __future__ import print_function; print(len(open("b", "rb").read()))'
   4096
   $ cd ..
 
--- a/tests/test-convert-hg-source.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-hg-source.t	Wed Sep 26 20:33:09 2018 +0900
@@ -130,7 +130,7 @@
   >          for i, l in enumerate(open(sys.argv[1], 'rb'))]
   > open(sys.argv[1], 'wb').write(b''.join(lines))
   > EOF
-  $ $PYTHON rewrite.py new/.hg/shamap
+  $ "$PYTHON" rewrite.py new/.hg/shamap
   $ cd orig
   $ hg up -qC 1
   $ echo foo >> foo
@@ -193,7 +193,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 3 files
 
 manifest -r 0
 
--- a/tests/test-convert-hg-svn.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-hg-svn.t	Wed Sep 26 20:33:09 2018 +0900
@@ -12,9 +12,9 @@
 
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
-  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
 
   $ svnadmin create "$SVNREPOPATH"
--- a/tests/test-convert-mtn.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-mtn.t	Wed Sep 26 20:33:09 2018 +0900
@@ -43,7 +43,7 @@
   $ mkdir dir
   $ echo b > dir/b
   $ echo d > dir/d
-  $ $PYTHON -c 'open("bin", "wb").write(b"a\\x00b")'
+  $ "$PYTHON" -c 'open("bin", "wb").write(b"a\\x00b") and None'
   $ echo c > c
   $ mtn add a dir/b dir/d c bin
   mtn: adding 'a' to workspace manifest
@@ -65,7 +65,7 @@
   $ echo b >> dir/b
   $ mtn drop c
   mtn: dropping 'c' from workspace manifest
-  $ $PYTHON -c 'open("bin", "wb").write(b"b\\x00c")'
+  $ "$PYTHON" -c 'open("bin", "wb").write(b"b\\x00c") and None'
   $ mtn ci -m update1
   mtn: beginning commit on branch 'com.selenic.test'
   mtn: committed revision 51d0a982464573a2a2cf5ee2c9219c652aaebeff
@@ -218,7 +218,7 @@
 test large file support (> 32kB)
 
   >>> fp = open('large-file', 'wb')
-  >>> for x in range(10000): fp.write(b'%d\n' % x)
+  >>> for x in range(10000): fp.write(b'%d\n' % x) and None
   >>> fp.close()
   $ md5sum.py large-file
   5d6de8a95c3b6bf9e0ffb808ba5299c1  large-file
--- a/tests/test-convert-p4-filetypes.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-p4-filetypes.t	Wed Sep 26 20:33:09 2018 +0900
@@ -52,7 +52,7 @@
   >          p4 add -t $T file_$T2
   >          ;;
   >       binary*)
-  >          $PYTHON -c "open('file_$T2', 'wb').write(b'this is $T')"
+  >          "$PYTHON" -c "open('file_$T2', 'wb').write(b'this is $T')"
   >          p4 add -t $T file_$T2
   >          ;;
   >       *)
--- a/tests/test-convert-svn-branches.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-svn-branches.t	Wed Sep 26 20:33:09 2018 +0900
@@ -85,8 +85,8 @@
   $ hg branches
   newbranch                     11:a6d7cc050ad1
   default                       10:6e2b33404495
-  old                            9:93c4b0f99529
-  old2                           8:b52884d7bead (inactive)
+  old                            9:1b494af68c0b
+  old2                           8:5be40b8dcbf6 (inactive)
   $ hg tags -q
   tip
   $ cd ..
--- a/tests/test-convert-svn-encoding.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-svn-encoding.t	Wed Sep 26 20:33:09 2018 +0900
@@ -52,6 +52,7 @@
   5 init projA
   source: svn:afeb9c47-92ff-4c0c-9f72-e1f6eb8ac9af/trunk@1
   converting: 0/6 revisions (0.00%)
+  reusing manifest from p1 (no file change)
   committing changelog
   updating the branch cache
   4 hello
@@ -118,6 +119,7 @@
   converting: 4/6 revisions (66.67%)
   reparent to file:/*/$TESTTMP/svn-repo/branches/branch%C3%A9 (glob)
   scanning paths: /branches/branch\xc3\xa9 0/1 paths (0.00%) (esc)
+  reusing manifest from p1 (no file change)
   committing changelog
   updating the branch cache
   0 branch to branch?e
@@ -125,6 +127,7 @@
   converting: 5/6 revisions (83.33%)
   reparent to file:/*/$TESTTMP/svn-repo/branches/branch%C3%A9e (glob)
   scanning paths: /branches/branch\xc3\xa9e 0/1 paths (0.00%) (esc)
+  reusing manifest from p1 (no file change)
   committing changelog
   updating the branch cache
   reparent to file:/*/$TESTTMP/svn-repo (glob)
--- a/tests/test-convert-svn-move.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-svn-move.t	Wed Sep 26 20:33:09 2018 +0900
@@ -9,9 +9,9 @@
   $ svnadmin load -q svn-repo < "$TESTDIR/svn/move.svndump"
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
-  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
 
 Convert trunk and branches
--- a/tests/test-convert-svn-sink.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-svn-sink.t	Wed Sep 26 20:33:09 2018 +0900
@@ -10,7 +10,7 @@
   >        if [ $2 -gt 0 ]; then
   >            limit="--limit=$2"
   >        fi
-  >        svn log --xml -v $limit | $PYTHON "$TESTDIR/svnxml.py"
+  >        svn log --xml -v $limit | "$PYTHON" "$TESTDIR/svnxml.py"
   >     )
   > }
 
--- a/tests/test-convert-svn-source.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert-svn-source.t	Wed Sep 26 20:33:09 2018 +0900
@@ -14,9 +14,9 @@
   $ svnadmin create svn-repo
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
-  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
   $ INVALIDREVISIONID=svn:x2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk@1
   $ VALIDREVISIONID=svn:a2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk/mytrunk@1
--- a/tests/test-convert.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-convert.t	Wed Sep 26 20:33:09 2018 +0900
@@ -482,7 +482,7 @@
 override $PATH to ensure p4 not visible; use $PYTHON in case we're
 running from a devel copy, not a temp installation
 
-  $ PATH="$BINDIR" $PYTHON "$BINDIR"/hg convert emptydir
+  $ PATH="$BINDIR" "$PYTHON" "$BINDIR"/hg convert emptydir
   assuming destination emptydir-hg
   initializing destination emptydir-hg repository
   emptydir does not look like a CVS checkout
@@ -533,9 +533,11 @@
 
 test bogus URL
 
+#if no-msys
   $ hg convert -q bzr+ssh://foobar@selenic.com/baz baz
   abort: bzr+ssh://foobar@selenic.com/baz: missing or unsupported repository
   [255]
+#endif
 
 test revset converted() lookup
 
--- a/tests/test-copy-move-merge.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-copy-move-merge.t	Wed Sep 26 20:33:09 2018 +0900
@@ -88,7 +88,8 @@
   > c
   > EOF
   rebasing 2:add3f11052fa "other" (tip)
-  other [source] changed a which local [dest] deleted
+  file 'a' was deleted in local [dest] but was modified in other [source].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
 
   $ cat b
--- a/tests/test-copy.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-copy.t	Wed Sep 26 20:33:09 2018 +0900
@@ -101,7 +101,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
 
   $ cd ..
 
@@ -148,6 +148,7 @@
 copy --after to a nonexistent target filename
   $ hg cp -A foo dummy
   foo: not recording copy - dummy does not exist
+  [1]
 
 dry-run; should show that foo is clean
   $ hg copy --dry-run foo bar
@@ -224,12 +225,14 @@
 Trying to copy on top of an existing file fails,
   $ hg copy -A bar foo
   foo: not overwriting - file already committed
-  (hg copy --after --force to replace the file by recording a copy)
+  ('hg copy --after --force' to replace the file by recording a copy)
+  [1]
 same error without the --after, so the user doesn't have to go through
 two hints:
   $ hg copy bar foo
   foo: not overwriting - file already committed
-  (hg copy --force to replace the file by recording a copy)
+  ('hg copy --force' to replace the file by recording a copy)
+  [1]
 but it's considered modified after a copy --after --force
   $ hg copy -Af bar foo
   $ hg st -AC foo
@@ -240,6 +243,7 @@
   $ touch xyzzy
   $ hg cp bar xyzzy
   xyzzy: not overwriting - file exists
-  (hg copy --after to record the copy)
+  ('hg copy --after' to record the copy)
+  [1]
 
   $ cd ..
--- a/tests/test-copytrace-heuristics.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-copytrace-heuristics.t	Wed Sep 26 20:33:09 2018 +0900
@@ -86,7 +86,8 @@
 
   $ hg rebase -s . -d 1
   rebasing 2:d526312210b9 "mode a" (tip)
-  other [source] changed a which local [dest] deleted
+  file 'a' was deleted in local [dest] but was modified in other [source].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
@@ -242,7 +243,8 @@
   $ hg rebase -s 2 -d 1 --config experimental.copytrace.movecandidateslimit=0
   rebasing 2:ef716627c70b "mod a" (tip)
   skipping copytracing for 'a', more candidates than the limit: 7
-  other [source] changed a which local [dest] deleted
+  file 'a' was deleted in local [dest] but was modified in other [source].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
@@ -697,7 +699,8 @@
 
   $ hg rebase -s 8b6e13696 -d .
   rebasing 1:8b6e13696c38 "added more things to a"
-  other [source] changed a which local [dest] deleted
+  file 'a' was deleted in local [dest] but was modified in other [source].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
--- a/tests/test-debugcommands.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-debugcommands.t	Wed Sep 26 20:33:09 2018 +0900
@@ -15,6 +15,39 @@
   adding a
   $ hg ci -Am make-it-full
 #if reporevlogstore
+  $ hg debugrevlog -c
+  format : 1
+  flags  : inline
+  
+  revisions     :   3
+      merges    :   0 ( 0.00%)
+      normal    :   3 (100.00%)
+  revisions     :   3
+      empty     :   0 ( 0.00%)
+                     text  :   0 (100.00%)
+                     delta :   0 (100.00%)
+      snapshot  :   3 (100.00%)
+        lvl-0   :         3 (100.00%)
+      deltas    :   0 ( 0.00%)
+  revision size : 191
+      snapshot  : 191 (100.00%)
+        lvl-0   :       191 (100.00%)
+      deltas    :   0 ( 0.00%)
+  
+  chunks        :   3
+      0x75 (u)  :   3 (100.00%)
+  chunks size   : 191
+      0x75 (u)  : 191 (100.00%)
+  
+  avg chain length  :  0
+  max chain length  :  0
+  max chain reach   : 67
+  compression ratio :  0
+  
+  uncompressed data size (min/max/avg) : 57 / 66 / 62
+  full revision size (min/max/avg)     : 58 / 67 / 63
+  inter-snapshot size (min/max/avg)    : 0 / 0 / 0
+  delta size (min/max/avg)             : 0 / 0 / 0
   $ hg debugrevlog -m
   format : 1
   flags  : inline, generaldelta
@@ -23,10 +56,15 @@
       merges    :  0 ( 0.00%)
       normal    :  3 (100.00%)
   revisions     :  3
-      full      :  3 (100.00%)
+      empty     :  1 (33.33%)
+                     text  :  1 (100.00%)
+                     delta :  0 ( 0.00%)
+      snapshot  :  2 (66.67%)
+        lvl-0   :        2 (66.67%)
       deltas    :  0 ( 0.00%)
   revision size : 88
-      full      : 88 (100.00%)
+      snapshot  : 88 (100.00%)
+        lvl-0   :       88 (100.00%)
       deltas    :  0 ( 0.00%)
   
   chunks        :  3
@@ -42,39 +80,100 @@
   compression ratio :  0
   
   uncompressed data size (min/max/avg) : 0 / 43 / 28
-  full revision size (min/max/avg)     : 0 / 44 / 29
+  full revision size (min/max/avg)     : 44 / 44 / 44
+  inter-snapshot size (min/max/avg)    : 0 / 0 / 0
+  delta size (min/max/avg)             : 0 / 0 / 0
+  $ hg debugrevlog a
+  format : 1
+  flags  : inline, generaldelta
+  
+  revisions     : 1
+      merges    : 0 ( 0.00%)
+      normal    : 1 (100.00%)
+  revisions     : 1
+      empty     : 0 ( 0.00%)
+                     text  : 0 (100.00%)
+                     delta : 0 (100.00%)
+      snapshot  : 1 (100.00%)
+        lvl-0   :       1 (100.00%)
+      deltas    : 0 ( 0.00%)
+  revision size : 3
+      snapshot  : 3 (100.00%)
+        lvl-0   :       3 (100.00%)
+      deltas    : 0 ( 0.00%)
+  
+  chunks        : 1
+      0x75 (u)  : 1 (100.00%)
+  chunks size   : 3
+      0x75 (u)  : 3 (100.00%)
+  
+  avg chain length  : 0
+  max chain length  : 0
+  max chain reach   : 3
+  compression ratio : 0
+  
+  uncompressed data size (min/max/avg) : 2 / 2 / 2
+  full revision size (min/max/avg)     : 3 / 3 / 3
+  inter-snapshot size (min/max/avg)    : 0 / 0 / 0
   delta size (min/max/avg)             : 0 / 0 / 0
 #endif
 
 Test debugindex, with and without the --verbose/--debug flag
-  $ hg debugindex a
+  $ hg debugrevlogindex a
      rev linkrev nodeid       p1           p2
        0       0 b789fdd96dc2 000000000000 000000000000
 
 #if no-reposimplestore
-  $ hg --verbose debugindex a
+  $ hg --verbose debugrevlogindex a
      rev    offset  length linkrev nodeid       p1           p2
        0         0       3       0 b789fdd96dc2 000000000000 000000000000
 
-  $ hg --debug debugindex a
+  $ hg --debug debugrevlogindex a
      rev    offset  length linkrev nodeid                                   p1                                       p2
        0         0       3       0 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
 #endif
 
-  $ hg debugindex -f 1 a
+  $ hg debugrevlogindex -f 1 a
      rev flag     size   link     p1     p2       nodeid
        0 0000        2      0     -1     -1 b789fdd96dc2
 
 #if no-reposimplestore
-  $ hg --verbose debugindex -f 1 a
+  $ hg --verbose debugrevlogindex -f 1 a
      rev flag   offset   length     size   link     p1     p2       nodeid
        0 0000        0        3        2      0     -1     -1 b789fdd96dc2
 
-  $ hg --debug debugindex -f 1 a
+  $ hg --debug debugrevlogindex -f 1 a
      rev flag   offset   length     size   link     p1     p2                                   nodeid
        0 0000        0        3        2      0     -1     -1 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
 #endif
 
+  $ hg debugindex -c
+     rev linkrev nodeid       p1           p2
+       0       0 07f494440405 000000000000 000000000000
+       1       1 8cccb4b5fec2 07f494440405 000000000000
+       2       2 b1e228c512c5 8cccb4b5fec2 000000000000
+  $ hg debugindex -c --debug
+     rev linkrev nodeid                                   p1                                       p2
+       0       0 07f4944404050f47db2e5c5071e0e84e7a27bba9 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+       1       1 8cccb4b5fec20cafeb99dd01c26d4dee8ea4388a 07f4944404050f47db2e5c5071e0e84e7a27bba9 0000000000000000000000000000000000000000
+       2       2 b1e228c512c5d7066d70562ed839c3323a62d6d2 8cccb4b5fec20cafeb99dd01c26d4dee8ea4388a 0000000000000000000000000000000000000000
+  $ hg debugindex -m
+     rev linkrev nodeid       p1           p2
+       0       0 a0c8bcbbb45c 000000000000 000000000000
+       1       1 57faf8a737ae a0c8bcbbb45c 000000000000
+       2       2 a35b10320954 57faf8a737ae 000000000000
+  $ hg debugindex -m --debug
+     rev linkrev nodeid                                   p1                                       p2
+       0       0 a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+       1       1 57faf8a737ae7faf490582941a82319ba6529dca a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 0000000000000000000000000000000000000000
+       2       2 a35b103209548032201c16c7688cb2657f037a38 57faf8a737ae7faf490582941a82319ba6529dca 0000000000000000000000000000000000000000
+  $ hg debugindex a
+     rev linkrev nodeid       p1           p2
+       0       0 b789fdd96dc2 000000000000 000000000000
+  $ hg debugindex --debug a
+     rev linkrev nodeid                                   p1                                       p2
+       0       0 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+
 debugdelta chain basic output
 
 #if reporevlogstore
@@ -411,6 +510,7 @@
   $ ls -r .hg/cache/*
   .hg/cache/rbc-revs-v1
   .hg/cache/rbc-names-v1
+  .hg/cache/manifestfulltextcache (reporevlogstore !)
   .hg/cache/branch2-served
 
 Test debugcolor
@@ -444,7 +544,7 @@
   >     util.dst('hi ...\\nfrom h hidden in g', 1, depth=2)
   > f()
   > EOF
-  $ $PYTHON debugstacktrace.py
+  $ "$PYTHON" debugstacktrace.py
   stacktrace at:
    debugstacktrace.py:12 in * (glob)
    debugstacktrace.py:5  in f
@@ -508,8 +608,8 @@
   devel-peer-request:   pairs: 81 bytes
   sending hello command
   sending between command
-  remote: 413
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 427
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
--- a/tests/test-debugindexdot.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-debugindexdot.t	Wed Sep 26 20:33:09 2018 +0900
@@ -13,6 +13,24 @@
   $ HGMERGE=true hg merge -q
   $ hg ci -m merge -d '3 0'
 
+  $ hg debugindexdot -c
+  digraph G {
+  	-1 -> 0
+  	0 -> 1
+  	0 -> 2
+  	2 -> 3
+  	1 -> 3
+  }
+
+  $ hg debugindexdot -m
+  digraph G {
+  	-1 -> 0
+  	0 -> 1
+  	0 -> 2
+  	2 -> 3
+  	1 -> 3
+  }
+
   $ hg debugindexdot a
   digraph G {
   	-1 -> 0
--- a/tests/test-diff-binary-file.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-diff-binary-file.t	Wed Sep 26 20:33:09 2018 +0900
@@ -83,7 +83,7 @@
   > path = sys.argv[1]
   > open(path, 'wb').write(b'\x00\x01\x02\x03')
   > EOF
-  $ $PYTHON writebin.py binfile.bin
+  $ "$PYTHON" writebin.py binfile.bin
   $ hg add binfile.bin
   $ hg ci -m 'add binfile.bin'
 
--- a/tests/test-diff-color.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-diff-color.t	Wed Sep 26 20:33:09 2018 +0900
@@ -22,7 +22,7 @@
   > c
   > EOF
   $ hg ci -Am adda
-  adding a
+  \x1b[0;32madding a\x1b[0m (esc)
   $ cat > a <<EOF
   > c
   > c
@@ -57,7 +57,7 @@
   >>> with open('a', 'rb') as f:
   ...     data = f.read()
   >>> with open('a', 'wb') as f:
-  ...     f.write(data.replace('dd', 'dd \r'))
+  ...     f.write(data.replace(b'dd', b'dd \r')) and None
   $ hg diff --nodates
   \x1b[0;1mdiff -r cf9f4ba66af2 a\x1b[0m (esc)
   \x1b[0;31;1m--- a/a\x1b[0m (esc)
@@ -218,7 +218,7 @@
   $ hg init sub
   $ echo b > sub/b
   $ hg -R sub commit -Am 'create sub'
-  adding b
+  \x1b[0;32madding b\x1b[0m (esc)
   $ echo 'sub = sub' > .hgsub
   $ hg add .hgsub
   $ hg commit -m 'add subrepo sub'
@@ -396,12 +396,12 @@
 
 multibyte character shouldn't be broken up in word diff:
 
-  $ $PYTHON <<'EOF'
+  $ "$PYTHON" <<'EOF'
   > with open("utf8", "wb") as f:
   >     f.write(b"blah \xe3\x82\xa2 blah\n")
   > EOF
   $ hg ci -Am 'add utf8 char' utf8
-  $ $PYTHON <<'EOF'
+  $ "$PYTHON" <<'EOF'
   > with open("utf8", "wb") as f:
   >     f.write(b"blah \xe3\x82\xa4 blah\n")
   > EOF
--- a/tests/test-diff-newlines.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-diff-newlines.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1,6 +1,6 @@
   $ hg init
 
-  $ $PYTHON -c 'open("a", "wb").write(b"confuse str.splitlines\nembedded\rnewline\n")'
+  $ "$PYTHON" -c 'open("a", "wb").write(b"confuse str.splitlines\nembedded\rnewline\n")'
   $ hg ci -Ama -d '1 0'
   adding a
 
--- a/tests/test-diff-upgrade.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-diff-upgrade.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,7 +16,7 @@
 
   $ echo regular > regular
   $ echo rmregular > rmregular
-  $ $PYTHON -c "open('bintoregular', 'wb').write(b'\0')"
+  $ "$PYTHON" -c "open('bintoregular', 'wb').write(b'\0')"
   $ touch rmempty
   $ echo exec > exec
   $ chmod +x exec
@@ -26,7 +26,7 @@
   $ echo unsetexec > unsetexec
   $ chmod +x unsetexec
   $ echo binary > binary
-  $ $PYTHON -c "open('rmbinary', 'wb').write(b'\0')"
+  $ "$PYTHON" -c "open('rmbinary', 'wb').write(b'\0')"
   $ hg ci -Am addfiles
   adding binary
   adding bintoregular
@@ -50,8 +50,8 @@
   $ rm rmexec
   $ chmod +x setexec
   $ chmod -x unsetexec
-  $ $PYTHON -c "open('binary', 'wb').write(b'\0\0')"
-  $ $PYTHON -c "open('newbinary', 'wb').write(b'\0')"
+  $ "$PYTHON" -c "open('binary', 'wb').write(b'\0\0')"
+  $ "$PYTHON" -c "open('newbinary', 'wb').write(b'\0')"
   $ rm rmbinary
   $ hg addremove -s 0
   adding newbinary
--- a/tests/test-empty.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-empty.t	Wed Sep 26 20:33:09 2018 +0900
@@ -14,7 +14,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  0 files, 0 changesets, 0 total revisions
+  checked 0 changesets with 0 changes to 0 files
 
 Check the basic files created:
 
@@ -39,7 +39,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  0 files, 0 changesets, 0 total revisions
+  checked 0 changesets with 0 changes to 0 files
   $ ls .hg
   00changelog.i
   hgrc
--- a/tests/test-encoding-align.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-encoding-align.t	Wed Sep 26 20:33:09 2018 +0900
@@ -4,7 +4,7 @@
   $ export HGENCODING
   $ hg init t
   $ cd t
-  $ $PYTHON << EOF
+  $ "$PYTHON" << EOF
   > # (byte, width) = (6, 4)
   > s = b"\xe7\x9f\xad\xe5\x90\x8d"
   > # (byte, width) = (7, 7): odd width is good for alignment test
--- a/tests/test-encoding.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-encoding.t	Wed Sep 26 20:33:09 2018 +0900
@@ -10,11 +10,11 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
-  new changesets 1e78a93102a3:0e5b7e3f9c4a
+  new changesets 1e78a93102a3:0e5b7e3f9c4a (2 drafts)
   (run 'hg update' to get a working copy)
   $ hg co
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ $PYTHON << EOF
+  $ "$PYTHON" << EOF
   > f = open('latin-1', 'wb'); f.write(b"latin-1 e' encoded: \xe9"); f.close()
   > f = open('utf-8', 'wb'); f.write(b"utf-8 e' encoded: \xc3\xa9"); f.close()
   > f = open('latin-1-tag', 'wb'); f.write(b"\xe9"); f.close()
--- a/tests/test-eol.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-eol.t	Wed Sep 26 20:33:09 2018 +0900
@@ -73,7 +73,7 @@
   >     echo '% a.txt'
   >     cat a.txt
   >     hg diff
-  >     $PYTHON ../switch-eol.py $1 a.txt
+  >     "$PYTHON" ../switch-eol.py $1 a.txt
   >     echo '% hg diff only reports a single changed line:'
   >     hg diff
   >     echo "% reverting back to $1 format"
--- a/tests/test-excessive-merge.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-excessive-merge.t	Wed Sep 26 20:33:09 2018 +0900
@@ -98,4 +98,4 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 4 total revisions
+  checked 5 changesets with 4 changes to 2 files
--- a/tests/test-exchange-obsmarkers-case-A1.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-A1.t	Wed Sep 26 20:33:09 2018 +0900
@@ -103,7 +103,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
-  new changesets f5bc6836db60
+  new changesets f5bc6836db60 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -145,7 +145,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
-  new changesets f5bc6836db60
+  new changesets f5bc6836db60 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -246,7 +246,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
-  new changesets f5bc6836db60:f6fbb35d8ac9
+  new changesets f5bc6836db60:f6fbb35d8ac9 (2 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -288,7 +288,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
-  new changesets f5bc6836db60:f6fbb35d8ac9
+  new changesets f5bc6836db60:f6fbb35d8ac9 (2 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-A2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-A2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -111,7 +111,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
-  new changesets f5bc6836db60
+  new changesets f5bc6836db60 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-A3.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-A3.t	Wed Sep 26 20:33:09 2018 +0900
@@ -131,7 +131,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
-  new changesets e5ea8f9c7314
+  new changesets e5ea8f9c7314 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -238,7 +238,7 @@
   1 new obsolescence markers
   obsoleted 1 changesets
   1 new orphan changesets
-  new changesets e5ea8f9c7314
+  new changesets e5ea8f9c7314 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-A4.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-A4.t	Wed Sep 26 20:33:09 2018 +0900
@@ -118,7 +118,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
-  new changesets 28b51eb45704:06055a7959d4
+  new changesets 28b51eb45704:06055a7959d4 (2 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-A5.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-A5.t	Wed Sep 26 20:33:09 2018 +0900
@@ -126,7 +126,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
-  new changesets f6298a8ac3a4
+  new changesets f6298a8ac3a4 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-B3.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-B3.t	Wed Sep 26 20:33:09 2018 +0900
@@ -106,7 +106,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets f5bc6836db60
+  new changesets f5bc6836db60 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-B5.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-B5.t	Wed Sep 26 20:33:09 2018 +0900
@@ -138,7 +138,7 @@
   adding file changes
   added 3 changesets with 3 changes to 3 files
   1 new obsolescence markers
-  new changesets 28b51eb45704:1d0f3cd25300
+  new changesets 28b51eb45704:1d0f3cd25300 (3 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-C2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-C2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -119,7 +119,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
-  new changesets e5ea8f9c7314
+  new changesets e5ea8f9c7314 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -167,7 +167,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
-  new changesets e5ea8f9c7314
+  new changesets e5ea8f9c7314 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-D1.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-D1.t	Wed Sep 26 20:33:09 2018 +0900
@@ -118,7 +118,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
-  new changesets e5ea8f9c7314
+  new changesets e5ea8f9c7314 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -166,7 +166,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
-  new changesets e5ea8f9c7314
+  new changesets e5ea8f9c7314 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-D4.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-exchange-obsmarkers-case-D4.t	Wed Sep 26 20:33:09 2018 +0900
@@ -126,7 +126,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
-  new changesets e5ea8f9c7314
+  new changesets e5ea8f9c7314 (1 drafts)
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-extdiff.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-extdiff.t	Wed Sep 26 20:33:09 2018 +0900
@@ -265,7 +265,7 @@
 
 #if windows
   $ cat > 'diff tool.bat' << EOF
-  > @$PYTHON "`pwd`/diff tool.py"
+  > @"$PYTHON" "`pwd`/diff tool.py"
   > EOF
   $ hg extdiff -p "`pwd`/diff tool.bat"
   [1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-extension-timing.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,96 @@
+Test basic extension support
+
+  $ cat > foobar.py <<EOF
+  > import os
+  > from mercurial import commands, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem(b'tests', b'foo', default=b"Foo")
+  > def uisetup(ui):
+  >     ui.debug(b"uisetup called [debug]\\n")
+  >     ui.write(b"uisetup called\\n")
+  >     ui.status(b"uisetup called [status]\\n")
+  >     ui.flush()
+  > def reposetup(ui, repo):
+  >     ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
+  >     ui.write(b"ui %s= repo.ui\\n" % (ui == repo.ui and b"=" or b"!"))
+  >     ui.flush()
+  > @command(b'foo', [], b'hg foo')
+  > def foo(ui, *args, **kwargs):
+  >     foo = ui.config(b'tests', b'foo')
+  >     ui.write(foo)
+  >     ui.write(b"\\n")
+  > @command(b'bar', [], b'hg bar', norepo=True)
+  > def bar(ui, *args, **kwargs):
+  >     ui.write(b"Bar\\n")
+  > EOF
+  $ abspath=`pwd`/foobar.py
+
+  $ mkdir barfoo
+  $ cp foobar.py barfoo/__init__.py
+  $ barfoopath=`pwd`/barfoo
+
+  $ hg init a
+  $ cd a
+  $ echo foo > file
+  $ hg add file
+  $ hg commit -m 'add file'
+
+  $ echo '[extensions]' >> $HGRCPATH
+  $ echo "foobar = $abspath" >> $HGRCPATH
+
+Test extension setup timings
+
+  $ hg foo --traceback --config devel.debug.extensions=yes --debug 2>&1
+  debug.extensions: loading extensions
+  debug.extensions: - processing 1 entries
+  debug.extensions:   - loading extension: 'foobar'
+  debug.extensions:   > 'foobar' extension loaded in * (glob)
+  debug.extensions:     - validating extension tables: 'foobar'
+  debug.extensions:     - invoking registered callbacks: 'foobar'
+  debug.extensions:     > callbacks completed in * (glob)
+  debug.extensions: > loaded 1 extensions, total time * (glob)
+  debug.extensions: - loading configtable attributes
+  debug.extensions: - executing uisetup hooks
+  debug.extensions:   - running uisetup for 'foobar'
+  uisetup called [debug]
+  uisetup called
+  uisetup called [status]
+  debug.extensions:   > uisetup for 'foobar' took * (glob)
+  debug.extensions: > all uisetup took * (glob)
+  debug.extensions: - executing extsetup hooks
+  debug.extensions:   - running extsetup for 'foobar'
+  debug.extensions:   > extsetup for 'foobar' took * (glob)
+  debug.extensions: > all extsetup took * (glob)
+  debug.extensions: - executing remaining aftercallbacks
+  debug.extensions: > remaining aftercallbacks completed in * (glob)
+  debug.extensions: - loading extension registration objects
+  debug.extensions: > extension registration object loading took * (glob)
+  debug.extensions: > extension foobar take a total of * to load (glob)
+  debug.extensions: extension loading complete
+  debug.extensions: loading additional extensions
+  debug.extensions: - processing 1 entries
+  debug.extensions: > loaded 0 extensions, total time * (glob)
+  debug.extensions: - loading configtable attributes
+  debug.extensions: - executing uisetup hooks
+  debug.extensions: > all uisetup took * (glob)
+  debug.extensions: - executing extsetup hooks
+  debug.extensions: > all extsetup took * (glob)
+  debug.extensions: - executing remaining aftercallbacks
+  debug.extensions: > remaining aftercallbacks completed in * (glob)
+  debug.extensions: - loading extension registration objects
+  debug.extensions: > extension registration object loading took * (glob)
+  debug.extensions: extension loading complete
+  debug.extensions: - executing reposetup hooks
+  debug.extensions:   - running reposetup for foobar
+  reposetup called for a
+  ui == repo.ui
+  debug.extensions:   > reposetup for 'foobar' took * (glob)
+  debug.extensions: > all reposetup took * (glob)
+  Foo
+
+  $ cd ..
+
+  $ echo 'foobar = !' >> $HGRCPATH
--- a/tests/test-extension.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-extension.t	Wed Sep 26 20:33:09 2018 +0900
@@ -105,13 +105,14 @@
   > def reposetup(ui, repo):
   >    print("4) %s reposetup" % name)
   > 
+  > bytesname = name.encode('utf-8')
   > # custom predicate to check registration of functions at loading
   > from mercurial import (
   >     registrar,
   >     smartset,
   > )
   > revsetpredicate = registrar.revsetpredicate()
-  > @revsetpredicate(name, safe=True) # safe=True for query via hgweb
+  > @revsetpredicate(bytesname, safe=True) # safe=True for query via hgweb
   > def custompredicate(repo, subset, x):
   >     return smartset.baseset([r for r in subset if r in {0}])
   > EOF
@@ -140,12 +141,12 @@
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
-  > application = hgweb('.', 'test repo')
+  > application = hgweb(b'.', b'test repo')
   > wsgicgi.launch(application)
   > EOF
   $ . "$TESTDIR/cgienv"
 
-  $ PATH_INFO='/' SCRIPT_NAME='' $PYTHON hgweb.cgi \
+  $ PATH_INFO='/' SCRIPT_NAME='' "$PYTHON" hgweb.cgi \
   >    | grep '^[0-9]) ' # ignores HTML output
   1) foo imported
   1) bar imported
@@ -164,7 +165,7 @@
   $ PATH_INFO='/shortlog'
 #endif
   $ export PATH_INFO
-  $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' $PYTHON hgweb.cgi \
+  $ SCRIPT_NAME='' QUERY_STRING='rev=foo() and bar()' "$PYTHON" hgweb.cgi \
   >     | grep '<a href="/rev/[0-9a-z]*">'
      <a href="/rev/c24b9ac61126">add file</a>
 
@@ -322,13 +323,13 @@
 
   $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<EOF
   > def func():
-  >     return "this is extlibroot.lsub1.lsub2.called.func()"
+  >     return b"this is extlibroot.lsub1.lsub2.called.func()"
   > EOF
   $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<EOF
   > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
   > EOF
   $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<EOF
-  > detail = "this is extlibroot.lsub1.lsub2.used"
+  > detail = b"this is extlibroot.lsub1.lsub2.used"
   > EOF
 
 Setup sub-package of "external library", which causes instantiation of
@@ -338,7 +339,7 @@
 
   $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
   $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<EOF
-  > detail = "this is extlibroot.recursedown.abs.used"
+  > detail = b"this is extlibroot.recursedown.abs.used"
   > EOF
   $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<EOF
   > from __future__ import absolute_import
@@ -347,7 +348,7 @@
 
   $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
   $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<EOF
-  > detail = "this is extlibroot.recursedown.legacy.used"
+  > detail = b"this is extlibroot.recursedown.legacy.used"
   > EOF
   $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<EOF
   > # legacy style (level == -1) import
@@ -366,7 +367,7 @@
 
   $ mkdir -p $TESTTMP/extlibroot/shadowing
   $ cat > $TESTTMP/extlibroot/shadowing/used.py <<EOF
-  > detail = "this is extlibroot.shadowing.used"
+  > detail = b"this is extlibroot.shadowing.used"
   > EOF
   $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<EOF
   > from __future__ import absolute_import
@@ -386,25 +387,25 @@
 
   $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<EOF
   > def func():
-  >     return "this is absextroot.xsub1.xsub2.called.func()"
+  >     return b"this is absextroot.xsub1.xsub2.called.func()"
   > EOF
   $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<EOF
   > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
   > EOF
   $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<EOF
-  > detail = "this is absextroot.xsub1.xsub2.used"
+  > detail = b"this is absextroot.xsub1.xsub2.used"
   > EOF
 
 Setup extension local modules to examine whether demand importing
 works as expected in "level > 1" case.
 
   $ cat > $TESTTMP/absextroot/relimportee.py <<EOF
-  > detail = "this is absextroot.relimportee"
+  > detail = b"this is absextroot.relimportee"
   > EOF
   $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<EOF
   > from __future__ import absolute_import
   > from ... import relimportee
-  > detail = "this relimporter imports %r" % (relimportee.detail)
+  > detail = b"this relimporter imports %r" % (relimportee.detail)
   > EOF
 
 Setup modules, which actually import extension local modules at
@@ -1254,7 +1255,7 @@
   > def g():
   >     pass
   > EOF
-  $ hg --config extensions.path=./path.py help foo > /dev/null
+  $ hg --config extensions.path=./path.py help foo
   abort: no such help topic: foo
   (try 'hg help --keyword foo')
   [255]
@@ -1540,6 +1541,7 @@
   reposetup() for $TESTTMP/reposetup-test/src
   reposetup() for $TESTTMP/reposetup-test/src (chg !)
 
+#if no-extraextensions
   $ hg --cwd src debugextensions
   reposetup() for $TESTTMP/reposetup-test/src
   dodo (untested!)
@@ -1547,6 +1549,7 @@
   mq
   reposetuptest (untested!)
   strip
+#endif
 
   $ hg clone -U src clone-dst1
   reposetup() for $TESTTMP/reposetup-test/src
@@ -1683,6 +1686,7 @@
   *** failed to import extension deprecatedcmd from $TESTTMP/deprecated/deprecatedcmd.py: missing attributes: norepo, optionalrepo, inferrepo
   *** (use @command decorator to register 'deprecatedcmd')
   hg: unknown command 'deprecatedcmd'
+  (use 'hg help' for a list of commands)
   [255]
 
  the extension shouldn't be loaded at all so the mq works:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fastannotate-corrupt.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,83 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > fastannotate=
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ for i in 0 1 2 3 4; do
+  >   echo $i >> a
+  >   echo $i >> b
+  >   hg commit -A -m $i a b
+  > done
+
+use the "debugbuildannotatecache" command to build annotate cache at rev 0
+
+  $ hg debugbuildannotatecache --debug --config fastannotate.mainbranch=0
+  fastannotate: a: 1 new changesets in the main branch
+  fastannotate: b: 1 new changesets in the main branch
+
+"debugbuildannotatecache" should work with broken cache (and other files would
+be built without being affected). note: linelog being broken is only noticed
+when we try to append to it.
+
+  $ echo 'CORRUPT!' >> .hg/fastannotate/default/a.m
+  $ hg debugbuildannotatecache --debug --config fastannotate.mainbranch=1
+  fastannotate: a: rebuilding broken cache
+  fastannotate: a: 2 new changesets in the main branch
+  fastannotate: b: 1 new changesets in the main branch
+
+  $ echo 'CANNOT REUSE!' > .hg/fastannotate/default/a.l
+  $ hg debugbuildannotatecache --debug --config fastannotate.mainbranch=2
+  fastannotate: a: rebuilding broken cache
+  fastannotate: a: 3 new changesets in the main branch
+  fastannotate: b: 1 new changesets in the main branch
+
+  $ rm .hg/fastannotate/default/a.m
+  $ hg debugbuildannotatecache --debug --config fastannotate.mainbranch=3
+  fastannotate: a: rebuilding broken cache
+  fastannotate: a: 4 new changesets in the main branch
+  fastannotate: b: 1 new changesets in the main branch
+
+  $ rm .hg/fastannotate/default/a.l
+  $ hg debugbuildannotatecache --debug --config fastannotate.mainbranch=3
+  $ hg debugbuildannotatecache --debug --config fastannotate.mainbranch=4
+  fastannotate: a: rebuilding broken cache
+  fastannotate: a: 5 new changesets in the main branch
+  fastannotate: b: 1 new changesets in the main branch
+
+"fastannotate" should deal with file corruption as well
+
+  $ rm -rf .hg/fastannotate
+  $ hg fastannotate --debug -r 0 a
+  fastannotate: a: 1 new changesets in the main branch
+  0: 0
+
+  $ echo 'CORRUPT!' >> .hg/fastannotate/default/a.m
+  $ hg fastannotate --debug -r 0 a
+  fastannotate: a: cache broken and deleted
+  fastannotate: a: 1 new changesets in the main branch
+  0: 0
+
+  $ echo 'CORRUPT!' > .hg/fastannotate/default/a.l
+  $ hg fastannotate --debug -r 1 a
+  fastannotate: a: cache broken and deleted
+  fastannotate: a: 2 new changesets in the main branch
+  0: 0
+  1: 1
+
+  $ rm .hg/fastannotate/default/a.l
+  $ hg fastannotate --debug -r 1 a
+  fastannotate: a: using fast path (resolved fctx: True)
+  fastannotate: a: cache broken and deleted
+  fastannotate: a: 2 new changesets in the main branch
+  0: 0
+  1: 1
+
+  $ rm .hg/fastannotate/default/a.m
+  $ hg fastannotate --debug -r 2 a
+  fastannotate: a: cache broken and deleted
+  fastannotate: a: 3 new changesets in the main branch
+  0: 0
+  1: 1
+  2: 2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fastannotate-diffopts.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,33 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > fastannotate=
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+
+changes to whitespaces
+
+  $ cat >> a << EOF
+  > 1
+  > 
+  >  
+  >  2
+  > EOF
+  $ hg commit -qAm '1'
+  $ cat > a << EOF
+  >  1
+  > 
+  > 2
+  > 
+  > 
+  > 3
+  > EOF
+  $ hg commit -m 2
+  $ hg fastannotate -wB a
+  0:  1
+  0: 
+  1: 2
+  0: 
+  1: 
+  1: 3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fastannotate-hg.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,764 @@
+(this file is backported from core hg tests/test-annotate.t)
+
+  $ cat >> $HGRCPATH << EOF
+  > [diff]
+  > git=1
+  > [extensions]
+  > fastannotate=
+  > [fastannotate]
+  > modes=fctx
+  > forcefollow=False
+  > mainbranch=.
+  > EOF
+
+  $ HGMERGE=true; export HGMERGE
+
+init
+
+  $ hg init repo
+  $ cd repo
+
+commit
+
+  $ echo 'a' > a
+  $ hg ci -A -m test -u nobody -d '1 0'
+  adding a
+
+annotate -c
+
+  $ hg annotate -c a
+  8435f90966e4: a
+
+annotate -cl
+
+  $ hg annotate -cl a
+  8435f90966e4:1: a
+
+annotate -d
+
+  $ hg annotate -d a
+  Thu Jan 01 00:00:01 1970 +0000: a
+
+annotate -n
+
+  $ hg annotate -n a
+  0: a
+
+annotate -nl
+
+  $ hg annotate -nl a
+  0:1: a
+
+annotate -u
+
+  $ hg annotate -u a
+  nobody: a
+
+annotate -cdnu
+
+  $ hg annotate -cdnu a
+  nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000: a
+
+annotate -cdnul
+
+  $ hg annotate -cdnul a
+  nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000:1: a
+
+annotate (JSON)
+
+  $ hg annotate -Tjson a
+  [
+   {
+    "lines": [{"line": "a\n", "rev": 0}],
+    "path": "a"
+   }
+  ]
+
+  $ hg annotate -Tjson -cdfnul a
+  [
+   {
+    "lines": [{"date": [1.0, 0], "line": "a\n", "line_number": 1, "node": "8435f90966e442695d2ded29fdade2bac5ad8065", "path": "a", "rev": 0, "user": "nobody"}],
+    "path": "a"
+   }
+  ]
+
+  $ cat <<EOF >>a
+  > a
+  > a
+  > EOF
+  $ hg ci -ma1 -d '1 0'
+  $ hg cp a b
+  $ hg ci -mb -d '1 0'
+  $ cat <<EOF >> b
+  > b4
+  > b5
+  > b6
+  > EOF
+  $ hg ci -mb2 -d '2 0'
+
+annotate -n b
+
+  $ hg annotate -n b
+  0: a
+  1: a
+  1: a
+  3: b4
+  3: b5
+  3: b6
+
+annotate --no-follow b
+
+  $ hg annotate --no-follow b
+  2: a
+  2: a
+  2: a
+  3: b4
+  3: b5
+  3: b6
+
+annotate -nl b
+
+  $ hg annotate -nl b
+  0:1: a
+  1:2: a
+  1:3: a
+  3:4: b4
+  3:5: b5
+  3:6: b6
+
+annotate -nf b
+
+  $ hg annotate -nf b
+  0 a: a
+  1 a: a
+  1 a: a
+  3 b: b4
+  3 b: b5
+  3 b: b6
+
+annotate -nlf b
+
+  $ hg annotate -nlf b
+  0 a:1: a
+  1 a:2: a
+  1 a:3: a
+  3 b:4: b4
+  3 b:5: b5
+  3 b:6: b6
+
+  $ hg up -C 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat <<EOF >> b
+  > b4
+  > c
+  > b5
+  > EOF
+  $ hg ci -mb2.1 -d '2 0'
+  created new head
+  $ hg merge
+  merging b
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -mmergeb -d '3 0'
+
+annotate after merge
+(note: the first one falls back to the vanilla annotate which does not use linelog)
+
+  $ hg annotate -nf b --debug
+  fastannotate: b: rebuilding broken cache
+  fastannotate: b: 5 new changesets in the main branch
+  0 a: a
+  1 a: a
+  1 a: a
+  3 b: b4
+  4 b: c
+  3 b: b5
+
+(difference explained below)
+
+  $ hg annotate -nf b --debug
+  fastannotate: b: using fast path (resolved fctx: False)
+  0 a: a
+  1 a: a
+  1 a: a
+  4 b: b4
+  4 b: c
+  4 b: b5
+
+annotate after merge with -l
+(fastannotate differs from annotate)
+
+  $ hg log -Gp -T '{rev}:{node}' -r '2..5'
+  @    5:64afcdf8e29e063c635be123d8d2fb160af00f7e
+  |\
+  | o  4:5fbdc1152d97597717021ad9e063061b200f146bdiff --git a/b b/b
+  | |  --- a/b
+  | |  +++ b/b
+  | |  @@ -1,3 +1,6 @@
+  | |   a
+  | |   a
+  | |   a
+  | |  +b4
+  | |  +c
+  | |  +b5
+  | |
+  o |  3:37ec9f5c3d1f99572d7075971cb4876e2139b52fdiff --git a/b b/b
+  |/   --- a/b
+  |    +++ b/b
+  |    @@ -1,3 +1,6 @@
+  |     a
+  |     a
+  |     a
+  |    +b4
+  |    +b5
+  |    +b6
+  |
+  o  2:3086dbafde1ce745abfc8d2d367847280aabae9ddiff --git a/a b/b
+  |  copy from a
+  ~  copy to b
+  
+
+(in this case, "b4", "b5" could be considered introduced by either rev 3, or rev 4.
+ and that causes the rev number difference)
+
+  $ hg annotate -nlf b --config fastannotate.modes=
+  0 a:1: a
+  1 a:2: a
+  1 a:3: a
+  3 b:4: b4
+  4 b:5: c
+  3 b:5: b5
+
+  $ hg annotate -nlf b
+  0 a:1: a
+  1 a:2: a
+  1 a:3: a
+  4 b:4: b4
+  4 b:5: c
+  4 b:6: b5
+
+  $ hg up -C 1
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg cp a b
+  $ cat <<EOF > b
+  > a
+  > z
+  > a
+  > EOF
+  $ hg ci -mc -d '3 0'
+  created new head
+  $ hg merge
+  merging b
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat <<EOF >> b
+  > b4
+  > c
+  > b5
+  > EOF
+  $ echo d >> b
+  $ hg ci -mmerge2 -d '4 0'
+
+annotate after rename merge
+
+  $ hg annotate -nf b
+  0 a: a
+  6 b: z
+  1 a: a
+  3 b: b4
+  4 b: c
+  3 b: b5
+  7 b: d
+
+annotate after rename merge with -l
+(fastannotate differs from annotate)
+
+  $ hg log -Gp -T '{rev}:{node}' -r '0+1+6+7'
+  @    7:6284bb6c38fef984a929862a53bbc71ce9eafa81diff --git a/b b/b
+  |\   --- a/b
+  | :  +++ b/b
+  | :  @@ -1,3 +1,7 @@
+  | :   a
+  | :   z
+  | :   a
+  | :  +b4
+  | :  +c
+  | :  +b5
+  | :  +d
+  | :
+  o :  6:b80e3e32f75a6a67cd4ac85496a11511e9112816diff --git a/a b/b
+  :/   copy from a
+  :    copy to b
+  :    --- a/a
+  :    +++ b/b
+  :    @@ -1,3 +1,3 @@
+  :    -a (?)
+  :     a
+  :    +z
+  :     a
+  :    -a (?)
+  :
+  o  1:762f04898e6684ff713415f7b8a8d53d33f96c92diff --git a/a b/a
+  |  --- a/a
+  |  +++ b/a
+  |  @@ -1,1 +1,3 @@
+  |   a
+  |  +a
+  |  +a
+  |
+  o  0:8435f90966e442695d2ded29fdade2bac5ad8065diff --git a/a b/a
+     new file mode 100644
+     --- /dev/null
+     +++ b/a
+     @@ -0,0 +1,1 @@
+     +a
+  
+
+(note on question marks:
+ the upstream bdiff change (96f2f50d923f+3633403888ae+8c0c75aa3ff4+5c4e2636c1a9
+ +38ed54888617) alters the output so deletion is not always at the end of the
+ output. for example:
+ | a | b | old | new | # old: e1d6aa0e4c3a, new: 8836f13e3c5b
+ |-------------------|
+ | a | a |  a  | -a  |
+ | a | z | +z  |  a  |
+ | a | a |  a  | +z  |
+ |   |   | -a  |  a  |
+ |-------------------|
+ | a | a |     a     |
+ | a | a |     a     |
+ | a |   |    -a     |
+ this leads to more question marks below)
+
+(rev 1 adds two "a"s and rev 6 deletes one "a".
+ the "a" that rev 6 deletes could be either the first or the second "a" of those two "a"s added by rev 1.
+ and that causes the line number difference)
+
+  $ hg annotate -nlf b --config fastannotate.modes=
+  0 a:1: a
+  6 b:2: z
+  1 a:3: a
+  3 b:4: b4
+  4 b:5: c
+  3 b:5: b5
+  7 b:7: d
+
+  $ hg annotate -nlf b
+  0 a:1: a (?)
+  1 a:2: a (?)
+  6 b:2: z
+  1 a:2: a (?)
+  1 a:3: a (?)
+  3 b:4: b4
+  4 b:5: c
+  3 b:5: b5
+  7 b:7: d
+
+Issue2807: alignment of line numbers with -l
+(fastannotate differs from annotate, same reason as above)
+
+  $ echo more >> b
+  $ hg ci -mmore -d '5 0'
+  $ echo more >> b
+  $ hg ci -mmore -d '6 0'
+  $ echo more >> b
+  $ hg ci -mmore -d '7 0'
+  $ hg annotate -nlf b
+   0 a: 1: a (?)
+   1 a: 2: a (?)
+   6 b: 2: z
+   1 a: 2: a (?)
+   1 a: 3: a (?)
+   3 b: 4: b4
+   4 b: 5: c
+   3 b: 5: b5
+   7 b: 7: d
+   8 b: 8: more
+   9 b: 9: more
+  10 b:10: more
+
+linkrev vs rev
+
+  $ hg annotate -r tip -n a
+  0: a
+  1: a
+  1: a
+
+linkrev vs rev with -l
+
+  $ hg annotate -r tip -nl a
+  0:1: a
+  1:2: a
+  1:3: a
+
+Issue589: "undelete" sequence leads to crash
+
+annotate was crashing when trying to --follow something
+
+like A -> B -> A
+
+generate ABA rename configuration
+
+  $ echo foo > foo
+  $ hg add foo
+  $ hg ci -m addfoo
+  $ hg rename foo bar
+  $ hg ci -m renamefoo
+  $ hg rename bar foo
+  $ hg ci -m renamebar
+
+annotate after ABA with follow
+
+  $ hg annotate --follow foo
+  foo: foo
+
+missing file
+
+  $ hg ann nosuchfile
+  abort: nosuchfile: no such file in rev e9e6b4fa872f
+  [255]
+
+annotate file without '\n' on last line
+
+  $ printf "" > c
+  $ hg ci -A -m test -u nobody -d '1 0'
+  adding c
+  $ hg annotate c
+  $ printf "a\nb" > c
+  $ hg ci -m test
+  $ hg annotate c
+  [0-9]+: a (re)
+  [0-9]+: b (re)
+
+Issue3841: check annotation of the file of which filelog includes
+merging between the revision and its ancestor
+
+to reproduce the situation with recent Mercurial, this script uses (1)
+"hg debugsetparents" to merge without ancestor check by "hg merge",
+and (2) the extension to allow filelog merging between the revision
+and its ancestor by overriding "repo._filecommit".
+
+  $ cat > ../legacyrepo.py <<EOF
+  > from mercurial import error, node
+  > def reposetup(ui, repo):
+  >     class legacyrepo(repo.__class__):
+  >         def _filecommit(self, fctx, manifest1, manifest2,
+  >                         linkrev, tr, changelist):
+  >             fname = fctx.path()
+  >             text = fctx.data()
+  >             flog = self.file(fname)
+  >             fparent1 = manifest1.get(fname, node.nullid)
+  >             fparent2 = manifest2.get(fname, node.nullid)
+  >             meta = {}
+  >             copy = fctx.renamed()
+  >             if copy and copy[0] != fname:
+  >                 raise error.Abort('copying is not supported')
+  >             if fparent2 != node.nullid:
+  >                 changelist.append(fname)
+  >                 return flog.add(text, meta, tr, linkrev,
+  >                                 fparent1, fparent2)
+  >             raise error.Abort('only merging is supported')
+  >     repo.__class__ = legacyrepo
+  > EOF
+
+  $ cat > baz <<EOF
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > EOF
+  $ hg add baz
+  $ hg commit -m "baz:0"
+
+  $ cat > baz <<EOF
+  > 1 baz:1
+  > 2
+  > 3
+  > 4
+  > 5
+  > EOF
+  $ hg commit -m "baz:1"
+
+  $ cat > baz <<EOF
+  > 1 baz:1
+  > 2 baz:2
+  > 3
+  > 4
+  > 5
+  > EOF
+  $ hg debugsetparents 17 17
+  $ hg --config extensions.legacyrepo=../legacyrepo.py  commit -m "baz:2"
+  $ hg debugindexdot baz
+  digraph G {
+  	-1 -> 0
+  	0 -> 1
+  	1 -> 2
+  	1 -> 2
+  }
+  $ hg annotate baz
+  17: 1 baz:1
+  18: 2 baz:2
+  16: 3
+  16: 4
+  16: 5
+
+  $ cat > baz <<EOF
+  > 1 baz:1
+  > 2 baz:2
+  > 3 baz:3
+  > 4
+  > 5
+  > EOF
+  $ hg commit -m "baz:3"
+
+  $ cat > baz <<EOF
+  > 1 baz:1
+  > 2 baz:2
+  > 3 baz:3
+  > 4 baz:4
+  > 5
+  > EOF
+  $ hg debugsetparents 19 18
+  $ hg --config extensions.legacyrepo=../legacyrepo.py  commit -m "baz:4"
+  $ hg debugindexdot baz
+  digraph G {
+  	-1 -> 0
+  	0 -> 1
+  	1 -> 2
+  	1 -> 2
+  	2 -> 3
+  	3 -> 4
+  	2 -> 4
+  }
+  $ hg annotate baz
+  17: 1 baz:1
+  18: 2 baz:2
+  19: 3 baz:3
+  20: 4 baz:4
+  16: 5
+
+annotate clean file
+
+  $ hg annotate -ncr "wdir()" foo
+  11 472b18db256d : foo
+
+annotate modified file
+
+  $ echo foofoo >> foo
+  $ hg annotate -r "wdir()" foo
+  11 : foo
+  20+: foofoo
+
+  $ hg annotate -cr "wdir()" foo
+  472b18db256d : foo
+  b6bedd5477e7+: foofoo
+
+  $ hg annotate -ncr "wdir()" foo
+  11 472b18db256d : foo
+  20 b6bedd5477e7+: foofoo
+
+  $ hg annotate --debug -ncr "wdir()" foo
+  11 472b18db256d1e8282064eab4bfdaf48cbfe83cd : foo
+  20 b6bedd5477e797f25e568a6402d4697f3f895a72+: foofoo
+
+  $ hg annotate -udr "wdir()" foo
+  test Thu Jan 01 00:00:00 1970 +0000: foo
+  test [A-Za-z0-9:+ ]+: foofoo (re)
+
+  $ hg annotate -ncr "wdir()" -Tjson foo
+  [
+   {
+    "lines": [{"line": "foo\n", "node": "472b18db256d1e8282064eab4bfdaf48cbfe83cd", "rev": 11}, {"line": "foofoo\n", "node": "ffffffffffffffffffffffffffffffffffffffff", "rev": 2147483647}],
+    "path": "foo"
+   }
+  ]
+
+annotate added file
+
+  $ echo bar > bar
+  $ hg add bar
+  $ hg annotate -ncr "wdir()" bar
+  20 b6bedd5477e7+: bar
+
+annotate renamed file
+
+  $ hg rename foo renamefoo2
+  $ hg annotate -ncr "wdir()" renamefoo2
+  11 472b18db256d : foo
+  20 b6bedd5477e7+: foofoo
+
+annotate missing file
+
+  $ rm baz
+  $ hg annotate -ncr "wdir()" baz
+  abort: $TESTTMP/repo/baz: $ENOENT$ (windows !)
+  abort: $ENOENT$: $TESTTMP/repo/baz (no-windows !)
+  [255]
+
+annotate removed file
+
+  $ hg rm baz
+  $ hg annotate -ncr "wdir()" baz
+  abort: $TESTTMP/repo/baz: $ENOENT$ (windows !)
+  abort: $ENOENT$: $TESTTMP/repo/baz (no-windows !)
+  [255]
+
+Test annotate with whitespace options
+
+  $ cd ..
+  $ hg init repo-ws
+  $ cd repo-ws
+  $ cat > a <<EOF
+  > aa
+  > 
+  > b b
+  > EOF
+  $ hg ci -Am "adda"
+  adding a
+  $ sed 's/EOL$//g' > a <<EOF
+  > a  a
+  > 
+  >  EOL
+  > b  b
+  > EOF
+  $ hg ci -m "changea"
+
+Annotate with no option
+
+  $ hg annotate a
+  1: a  a
+  0: 
+  1:  
+  1: b  b
+
+Annotate with --ignore-space-change
+
+  $ hg annotate --ignore-space-change a
+  1: a  a
+  1: 
+  0:  
+  0: b  b
+
+Annotate with --ignore-all-space
+
+  $ hg annotate --ignore-all-space a
+  0: a  a
+  0: 
+  1:  
+  0: b  b
+
+Annotate with --ignore-blank-lines (similar to no options case)
+
+  $ hg annotate --ignore-blank-lines a
+  1: a  a
+  0: 
+  1:  
+  1: b  b
+
+  $ cd ..
+
+Annotate with linkrev pointing to another branch
+------------------------------------------------
+
+create history with a filerev whose linkrev points to another branch
+
+  $ hg init branchedlinkrev
+  $ cd branchedlinkrev
+  $ echo A > a
+  $ hg commit -Am 'contentA'
+  adding a
+  $ echo B >> a
+  $ hg commit -m 'contentB'
+  $ hg up --rev 'desc(contentA)'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo unrelated > unrelated
+  $ hg commit -Am 'unrelated'
+  adding unrelated
+  created new head
+  $ hg graft -r 'desc(contentB)'
+  grafting 1:fd27c222e3e6 "contentB"
+  $ echo C >> a
+  $ hg commit -m 'contentC'
+  $ echo W >> a
+  $ hg log -G
+  @  changeset:   4:072f1e8df249
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     contentC
+  |
+  o  changeset:   3:ff38df03cc4b
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     contentB
+  |
+  o  changeset:   2:62aaf3f6fc06
+  |  parent:      0:f0932f74827e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     unrelated
+  |
+  | o  changeset:   1:fd27c222e3e6
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     contentB
+  |
+  o  changeset:   0:f0932f74827e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     contentA
+  
+
+Annotate should list ancestor of starting revision only
+
+  $ hg annotate a
+  0: A
+  3: B
+  4: C
+
+  $ hg annotate a -r 'wdir()'
+  0 : A
+  3 : B
+  4 : C
+  4+: W
+
+Even when the starting revision is the linkrev-shadowed one:
+
+  $ hg annotate a -r 3
+  0: A
+  3: B
+
+  $ cd ..
+
+Issue5360: Deleted chunk in p1 of a merge changeset
+
+  $ hg init repo-5360
+  $ cd repo-5360
+  $ echo 1 > a
+  $ hg commit -A a -m 1
+  $ echo 2 >> a
+  $ hg commit -m 2
+  $ echo a > a
+  $ hg commit -m a
+  $ hg update '.^' -q
+  $ echo 3 >> a
+  $ hg commit -m 3 -q
+  $ hg merge 2 -q
+  $ cat > a << EOF
+  > b
+  > 1
+  > 2
+  > 3
+  > a
+  > EOF
+  $ hg resolve --mark -q
+  $ hg commit -m m
+  $ hg annotate a
+  4: b
+  0: 1
+  1: 2
+  3: 3
+  2: a
+
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fastannotate-perfhack.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,182 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > fastannotate=
+  > [fastannotate]
+  > perfhack=1
+  > EOF
+
+  $ HGMERGE=true; export HGMERGE
+
+  $ hg init repo
+  $ cd repo
+
+a simple merge case
+
+  $ echo 1 > a
+  $ hg commit -qAm 'append 1'
+  $ echo 2 >> a
+  $ hg commit -m 'append 2'
+  $ echo 3 >> a
+  $ hg commit -m 'append 3'
+  $ hg up 1 -q
+  $ cat > a << EOF
+  > 0
+  > 1
+  > 2
+  > EOF
+  $ hg commit -qm 'insert 0'
+  $ hg merge 2 -q
+  $ echo 4 >> a
+  $ hg commit -m merge
+  $ hg log -G -T '{rev}: {desc}'
+  @    4: merge
+  |\
+  | o  3: insert 0
+  | |
+  o |  2: append 3
+  |/
+  o  1: append 2
+  |
+  o  0: append 1
+  
+  $ hg fastannotate a
+  3: 0
+  0: 1
+  1: 2
+  2: 3
+  4: 4
+  $ hg fastannotate -r 0 a
+  0: 1
+  $ hg fastannotate -r 1 a
+  0: 1
+  1: 2
+  $ hg fastannotate -udnclf a
+  test 3 d641cb51f61e Thu Jan 01 00:00:00 1970 +0000 a:1: 0
+  test 0 4994017376d3 Thu Jan 01 00:00:00 1970 +0000 a:1: 1
+  test 1 e940cb6d9a06 Thu Jan 01 00:00:00 1970 +0000 a:2: 2
+  test 2 26162a884ba6 Thu Jan 01 00:00:00 1970 +0000 a:3: 3
+  test 4 3ad7bcd2815f Thu Jan 01 00:00:00 1970 +0000 a:5: 4
+  $ hg fastannotate --linear a
+  3: 0
+  0: 1
+  1: 2
+  4: 3
+  4: 4
+
+incrementally updating
+
+  $ hg fastannotate -r 0 a --debug
+  fastannotate: a: using fast path (resolved fctx: True)
+  0: 1
+  $ hg fastannotate -r 0 a --debug --rebuild
+  fastannotate: a: 1 new changesets in the main branch
+  0: 1
+  $ hg fastannotate -r 1 a --debug
+  fastannotate: a: 1 new changesets in the main branch
+  0: 1
+  1: 2
+  $ hg fastannotate -r 3 a --debug
+  fastannotate: a: 1 new changesets in the main branch
+  3: 0
+  0: 1
+  1: 2
+  $ hg fastannotate -r 4 a --debug
+  fastannotate: a: 1 new changesets in the main branch
+  3: 0
+  0: 1
+  1: 2
+  2: 3
+  4: 4
+  $ hg fastannotate -r 1 a --debug
+  fastannotate: a: using fast path (resolved fctx: True)
+  0: 1
+  1: 2
+
+rebuild happens automatically if unable to update
+
+  $ hg fastannotate -r 2 a --debug
+  fastannotate: a: cache broken and deleted
+  fastannotate: a: 3 new changesets in the main branch
+  0: 1
+  1: 2
+  2: 3
+
+config option "fastannotate.mainbranch"
+
+  $ hg fastannotate -r 1 --rebuild --config fastannotate.mainbranch=tip a --debug
+  fastannotate: a: 4 new changesets in the main branch
+  0: 1
+  1: 2
+  $ hg fastannotate -r 4 a --debug
+  fastannotate: a: using fast path (resolved fctx: True)
+  3: 0
+  0: 1
+  1: 2
+  2: 3
+  4: 4
+
+rename
+
+  $ hg mv a b
+  $ cat > b << EOF
+  > 0
+  > 11
+  > 3
+  > 44
+  > EOF
+  $ hg commit -m b -q
+  $ hg fastannotate -ncf --long-hash b
+  3 d641cb51f61e331c44654104301f8154d7865c89 a: 0
+  5 d44dade239915bc82b91e4556b1257323f8e5824 b: 11
+  2 26162a884ba60e8c87bf4e0d6bb8efcc6f711a4e a: 3
+  5 d44dade239915bc82b91e4556b1257323f8e5824 b: 44
+  $ hg fastannotate -r 26162a884ba60e8c87bf4e0d6bb8efcc6f711a4e a
+  0: 1
+  1: 2
+  2: 3
+
+fastannotate --deleted
+
+  $ hg fastannotate --deleted -nf b
+  3 a:  0
+  5 b:  11
+  0 a: -1
+  1 a: -2
+  2 a:  3
+  5 b:  44
+  4 a: -4
+  $ hg fastannotate --deleted -r 3 -nf a
+  3 a:  0
+  0 a:  1
+  1 a:  2
+
+file and directories with ".l", ".m" suffixes
+
+  $ cd ..
+  $ hg init repo2
+  $ cd repo2
+
+  $ mkdir a.l b.m c.lock a.l.hg b.hg
+  $ for i in a b c d d.l d.m a.l/a b.m/a c.lock/a a.l.hg/a b.hg/a; do
+  >   echo $i > $i
+  > done
+  $ hg add . -q
+  $ hg commit -m init
+  $ hg fastannotate a.l/a b.m/a c.lock/a a.l.hg/a b.hg/a d.l d.m a b c d
+  0: a
+  0: a.l.hg/a
+  0: a.l/a
+  0: b
+  0: b.hg/a
+  0: b.m/a
+  0: c
+  0: c.lock/a
+  0: d
+  0: d.l
+  0: d.m
+
+empty file
+
+  $ touch empty
+  $ hg commit -A empty -m empty
+  $ hg fastannotate empty
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fastannotate-protocol.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,211 @@
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > ssh = "$PYTHON" "$TESTDIR/dummyssh"
+  > [extensions]
+  > fastannotate=
+  > [fastannotate]
+  > mainbranch=@
+  > EOF
+
+  $ HGMERGE=true; export HGMERGE
+
+setup the server repo
+
+  $ hg init repo-server
+  $ cd repo-server
+  $ cat >> .hg/hgrc << EOF
+  > [fastannotate]
+  > server=1
+  > EOF
+  $ for i in 1 2 3 4; do
+  >   echo $i >> a
+  >   hg commit -A -m $i a
+  > done
+  $ [ -d .hg/fastannotate ]
+  [1]
+  $ hg bookmark @
+  $ cd ..
+
+setup the local repo
+
+  $ hg clone 'ssh://user@dummy/repo-server' repo-local -q
+  $ cd repo-local
+  $ cat >> .hg/hgrc << EOF
+  > [fastannotate]
+  > client=1
+  > clientfetchthreshold=0
+  > EOF
+  $ [ -d .hg/fastannotate ]
+  [1]
+  $ hg fastannotate a --debug
+  running * (glob)
+  sending hello command
+  sending between command
+  remote: * (glob) (?)
+  remote: capabilities: * (glob)
+  remote: * (glob) (?)
+  sending protocaps command
+  fastannotate: requesting 1 files
+  sending getannotate command
+  fastannotate: writing 112 bytes to fastannotate/default/a.l
+  fastannotate: writing 94 bytes to fastannotate/default/a.m
+  fastannotate: a: using fast path (resolved fctx: True)
+  0: 1
+  1: 2
+  2: 3
+  3: 4
+
+the cache could be reused and no download is necessary
+
+  $ hg fastannotate a --debug
+  fastannotate: a: using fast path (resolved fctx: True)
+  0: 1
+  1: 2
+  2: 3
+  3: 4
+
+if the client agrees where the head of the master branch is, no re-download
+happens even if the client has more commits
+
+  $ echo 5 >> a
+  $ hg commit -m 5
+  $ hg bookmark -r 3 @ -f
+  $ hg fastannotate a --debug
+  0: 1
+  1: 2
+  2: 3
+  3: 4
+  4: 5
+
+if the client has a different "@" (head of the master branch) and "@" is ahead
+of the server, the server can detect things are unchanged and does not return
+full contents (not that there is no "writing ... to fastannotate"), but the
+client can also build things up on its own (causing diverge)
+
+  $ hg bookmark -r 4 @ -f
+  $ hg fastannotate a --debug
+  running * (glob)
+  sending hello command
+  sending between command
+  remote: * (glob) (?)
+  remote: capabilities: * (glob)
+  remote: * (glob) (?)
+  sending protocaps command
+  fastannotate: requesting 1 files
+  sending getannotate command
+  fastannotate: a: 1 new changesets in the main branch
+  0: 1
+  1: 2
+  2: 3
+  3: 4
+  4: 5
+
+if the client has a different "@" which is behind the server. no download is
+necessary
+
+  $ hg fastannotate a --debug --config fastannotate.mainbranch=2
+  fastannotate: a: using fast path (resolved fctx: True)
+  0: 1
+  1: 2
+  2: 3
+  3: 4
+  4: 5
+
+define fastannotate on-disk paths
+
+  $ p1=.hg/fastannotate/default
+  $ p2=../repo-server/.hg/fastannotate/default
+
+revert bookmark change so the client is behind the server
+
+  $ hg bookmark -r 2 @ -f
+
+in the "fctx" mode with the "annotate" command, the client also downloads the
+cache. but not in the (default) "fastannotate" mode.
+
+  $ rm $p1/a.l $p1/a.m
+  $ hg annotate a --debug | grep 'fastannotate: writing'
+  [1]
+  $ hg annotate a --config fastannotate.modes=fctx --debug | grep 'fastannotate: writing' | sort
+  fastannotate: writing 112 bytes to fastannotate/default/a.l
+  fastannotate: writing 94 bytes to fastannotate/default/a.m
+
+the fastannotate cache (built server-side, downloaded client-side) in two repos
+have the same content (because the client downloads from the server)
+
+  $ diff $p1/a.l $p2/a.l
+  $ diff $p1/a.m $p2/a.m
+
+in the "fctx" mode, the client could also build the cache locally
+
+  $ hg annotate a --config fastannotate.modes=fctx --debug --config fastannotate.mainbranch=4 | grep fastannotate
+  fastannotate: requesting 1 files
+  fastannotate: a: 1 new changesets in the main branch
+
+the server would rebuild broken cache automatically
+
+  $ cp $p2/a.m $p2/a.m.bak
+  $ echo BROKEN1 > $p1/a.m
+  $ echo BROKEN2 > $p2/a.m
+  $ hg fastannotate a --debug | grep 'fastannotate: writing' | sort
+  fastannotate: writing 112 bytes to fastannotate/default/a.l
+  fastannotate: writing 94 bytes to fastannotate/default/a.m
+  $ diff $p1/a.m $p2/a.m
+  $ diff $p2/a.m $p2/a.m.bak
+
+use the "debugbuildannotatecache" command to build annotate cache
+
+  $ rm -rf $p1 $p2
+  $ hg --cwd ../repo-server debugbuildannotatecache a --debug
+  fastannotate: a: 4 new changesets in the main branch
+  $ hg --cwd ../repo-local debugbuildannotatecache a --debug
+  running * (glob)
+  sending hello command
+  sending between command
+  remote: * (glob) (?)
+  remote: capabilities: * (glob)
+  remote: * (glob) (?)
+  sending protocaps command
+  fastannotate: requesting 1 files
+  sending getannotate command
+  fastannotate: writing * (glob)
+  fastannotate: writing * (glob)
+  $ diff $p1/a.l $p2/a.l
+  $ diff $p1/a.m $p2/a.m
+
+with the clientfetchthreshold config option, the client can build up the cache
+without downloading from the server
+
+  $ rm -rf $p1
+  $ hg fastannotate a --debug --config fastannotate.clientfetchthreshold=10
+  fastannotate: a: 3 new changesets in the main branch
+  0: 1
+  1: 2
+  2: 3
+  3: 4
+  4: 5
+
+if the fastannotate directory is not writable, the fctx mode still works
+
+  $ rm -rf $p1
+  $ touch $p1
+  $ hg annotate a --debug --traceback --config fastannotate.modes=fctx
+  fastannotate: a: cache broken and deleted
+  fastannotate: prefetch failed: * (glob)
+  fastannotate: a: cache broken and deleted
+  fastannotate: falling back to the vanilla annotate: * (glob)
+  0: 1
+  1: 2
+  2: 3
+  3: 4
+  4: 5
+
+with serverbuildondemand=False, the server will not build anything
+
+  $ cat >> ../repo-server/.hg/hgrc <<EOF
+  > [fastannotate]
+  > serverbuildondemand=False
+  > EOF
+  $ rm -rf $p1 $p2
+  $ hg fastannotate a --debug | grep 'fastannotate: writing'
+  [1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fastannotate-renames.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,168 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > fastannotate=
+  > [fastannotate]
+  > mainbranch=main
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+
+add or rename files on top of the master branch
+
+  $ echo a1 > a
+  $ echo b1 > b
+  $ hg commit -qAm 1
+  $ hg bookmark -i main
+  $ hg fastannotate --debug -nf b
+  fastannotate: b: 1 new changesets in the main branch
+  0 b: b1
+  $ hg fastannotate --debug -nf a
+  fastannotate: a: 1 new changesets in the main branch
+  0 a: a1
+  $ echo a2 >> a
+  $ cat > b << EOF
+  > b0
+  > b1
+  > EOF
+  $ hg mv a t
+  $ hg mv b a
+  $ hg mv t b
+  $ hg commit -m 'swap names'
+
+existing linelogs are not helpful with such renames in side branches
+
+  $ hg fastannotate --debug -nf a
+  fastannotate: a: linelog cannot help in annotating this revision
+  1 a: b0
+  0 b: b1
+  $ hg fastannotate --debug -nf b
+  fastannotate: b: linelog cannot help in annotating this revision
+  0 a: a1
+  1 b: a2
+
+move main branch forward, rebuild should happen
+
+  $ hg bookmark -i main -r . -q
+  $ hg fastannotate --debug -nf b
+  fastannotate: b: cache broken and deleted
+  fastannotate: b: 2 new changesets in the main branch
+  0 a: a1
+  1 b: a2
+  $ hg fastannotate --debug -nf b
+  fastannotate: b: using fast path (resolved fctx: True)
+  0 a: a1
+  1 b: a2
+
+for rev 0, the existing linelog is still useful for a, but not for b
+
+  $ hg fastannotate --debug -nf a -r 0
+  fastannotate: a: using fast path (resolved fctx: True)
+  0 a: a1
+  $ hg fastannotate --debug -nf b -r 0
+  fastannotate: b: linelog cannot help in annotating this revision
+  0 b: b1
+
+a rebuild can also be triggered if "the main branch last time" mismatches
+
+  $ echo a3 >> a
+  $ hg commit -m a3
+  $ cat >> b << EOF
+  > b3
+  > b4
+  > EOF
+  $ hg commit -m b4
+  $ hg bookmark -i main -q
+  $ hg fastannotate --debug -nf a
+  fastannotate: a: cache broken and deleted
+  fastannotate: a: 3 new changesets in the main branch
+  1 a: b0
+  0 b: b1
+  2 a: a3
+  $ hg fastannotate --debug -nf a
+  fastannotate: a: using fast path (resolved fctx: True)
+  1 a: b0
+  0 b: b1
+  2 a: a3
+
+linelog can be updated without being helpful
+
+  $ hg mv a t
+  $ hg mv b a
+  $ hg mv t b
+  $ hg commit -m 'swap names again'
+  $ hg fastannotate --debug -nf b
+  fastannotate: b: 1 new changesets in the main branch
+  1 a: b0
+  0 b: b1
+  2 a: a3
+  $ hg fastannotate --debug -nf b
+  fastannotate: b: linelog cannot help in annotating this revision
+  1 a: b0
+  0 b: b1
+  2 a: a3
+
+move main branch forward again, rebuilds are one-time
+
+  $ hg bookmark -i main -q
+  $ hg fastannotate --debug -nf a
+  fastannotate: a: cache broken and deleted
+  fastannotate: a: 4 new changesets in the main branch
+  0 a: a1
+  1 b: a2
+  3 b: b3
+  3 b: b4
+  $ hg fastannotate --debug -nf b
+  fastannotate: b: cache broken and deleted
+  fastannotate: b: 4 new changesets in the main branch
+  1 a: b0
+  0 b: b1
+  2 a: a3
+  $ hg fastannotate --debug -nf a
+  fastannotate: a: using fast path (resolved fctx: True)
+  0 a: a1
+  1 b: a2
+  3 b: b3
+  3 b: b4
+  $ hg fastannotate --debug -nf b
+  fastannotate: b: using fast path (resolved fctx: True)
+  1 a: b0
+  0 b: b1
+  2 a: a3
+
+list changeset hashes to improve readability
+
+  $ hg log -T '{rev}:{node}\n'
+  4:980e1ab8c516350172928fba95b49ede3b643dca
+  3:14e123fedad9f491f5dde0beca2a767625a0a93a
+  2:96495c41e4c12218766f78cdf244e768d7718b0f
+  1:35c2b781234c994896aba36bd3245d3104e023df
+  0:653e95416ebb5dbcc25bbc7f75568c9e01f7bd2f
+
+annotate a revision not in the linelog. linelog cannot be used, but does not get rebuilt either
+
+  $ hg fastannotate --debug -nf a -r 96495c41e4c12218766f78cdf244e768d7718b0f
+  fastannotate: a: linelog cannot help in annotating this revision
+  1 a: b0
+  0 b: b1
+  2 a: a3
+  $ hg fastannotate --debug -nf a -r 2
+  fastannotate: a: linelog cannot help in annotating this revision
+  1 a: b0
+  0 b: b1
+  2 a: a3
+  $ hg fastannotate --debug -nf a -r .
+  fastannotate: a: using fast path (resolved fctx: True)
+  0 a: a1
+  1 b: a2
+  3 b: b3
+  3 b: b4
+
+annotate an ancient revision where the path matches. linelog can be used
+
+  $ hg fastannotate --debug -nf a -r 0
+  fastannotate: a: using fast path (resolved fctx: True)
+  0 a: a1
+  $ hg fastannotate --debug -nf a -r 653e95416ebb5dbcc25bbc7f75568c9e01f7bd2f
+  fastannotate: a: using fast path (resolved fctx: False)
+  0 a: a1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fastannotate-revmap.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,198 @@
+from __future__ import absolute_import, print_function
+
+import os
+import tempfile
+
+from mercurial import (
+    pycompat,
+    util,
+)
+
+from hgext.fastannotate import error, revmap
+
+if pycompat.ispy3:
+    xrange = range
+
+def genhsh(i):
+    return chr(i) + b'\0' * 19
+
+def gettemppath():
+    fd, path = tempfile.mkstemp()
+    os.close(fd)
+    os.unlink(path)
+    return path
+
+def ensure(condition):
+    if not condition:
+        raise RuntimeError('Unexpected')
+
+def testbasicreadwrite():
+    path = gettemppath()
+
+    rm = revmap.revmap(path)
+    ensure(rm.maxrev == 0)
+    for i in xrange(5):
+        ensure(rm.rev2hsh(i) is None)
+    ensure(rm.hsh2rev(b'\0' * 20) is None)
+
+    paths = ['', 'a', None, 'b', 'b', 'c', 'c', None, 'a', 'b', 'a', 'a']
+    for i in xrange(1, 5):
+        ensure(rm.append(genhsh(i), sidebranch=(i & 1), path=paths[i]) == i)
+
+    ensure(rm.maxrev == 4)
+    for i in xrange(1, 5):
+        ensure(rm.hsh2rev(genhsh(i)) == i)
+        ensure(rm.rev2hsh(i) == genhsh(i))
+
+    # re-load and verify
+    rm.flush()
+    rm = revmap.revmap(path)
+    ensure(rm.maxrev == 4)
+    for i in xrange(1, 5):
+        ensure(rm.hsh2rev(genhsh(i)) == i)
+        ensure(rm.rev2hsh(i) == genhsh(i))
+        ensure(bool(rm.rev2flag(i) & revmap.sidebranchflag) == bool(i & 1))
+
+    # append without calling save() explicitly
+    for i in xrange(5, 12):
+        ensure(rm.append(genhsh(i), sidebranch=(i & 1), path=paths[i],
+                         flush=True) == i)
+
+    # re-load and verify
+    rm = revmap.revmap(path)
+    ensure(rm.maxrev == 11)
+    for i in xrange(1, 12):
+        ensure(rm.hsh2rev(genhsh(i)) == i)
+        ensure(rm.rev2hsh(i) == genhsh(i))
+        ensure(rm.rev2path(i) == paths[i] or paths[i - 1])
+        ensure(bool(rm.rev2flag(i) & revmap.sidebranchflag) == bool(i & 1))
+
+    os.unlink(path)
+
+    # missing keys
+    ensure(rm.rev2hsh(12) is None)
+    ensure(rm.rev2hsh(0) is None)
+    ensure(rm.rev2hsh(-1) is None)
+    ensure(rm.rev2flag(12) is None)
+    ensure(rm.rev2path(12) is None)
+    ensure(rm.hsh2rev(b'\1' * 20) is None)
+
+    # illformed hash (not 20 bytes)
+    try:
+        rm.append(b'\0')
+        ensure(False)
+    except Exception:
+        pass
+
+def testcorruptformat():
+    path = gettemppath()
+
+    # incorrect header
+    with open(path, 'w') as f:
+        f.write(b'NOT A VALID HEADER')
+    try:
+        revmap.revmap(path)
+        ensure(False)
+    except error.CorruptedFileError:
+        pass
+
+    # rewrite the file
+    os.unlink(path)
+    rm = revmap.revmap(path)
+    rm.append(genhsh(0), flush=True)
+
+    rm = revmap.revmap(path)
+    ensure(rm.maxrev == 1)
+
+    # corrupt the file by appending a byte
+    size = os.stat(path).st_size
+    with open(path, 'a') as f:
+        f.write('\xff')
+    try:
+        revmap.revmap(path)
+        ensure(False)
+    except error.CorruptedFileError:
+        pass
+
+    # corrupt the file by removing the last byte
+    ensure(size > 0)
+    with open(path, 'w') as f:
+        f.truncate(size - 1)
+    try:
+        revmap.revmap(path)
+        ensure(False)
+    except error.CorruptedFileError:
+        pass
+
+    os.unlink(path)
+
+def testcopyfrom():
+    path = gettemppath()
+    rm = revmap.revmap(path)
+    for i in xrange(1, 10):
+        ensure(rm.append(genhsh(i), sidebranch=(i & 1), path=str(i // 3)) == i)
+    rm.flush()
+
+    # copy rm to rm2
+    rm2 = revmap.revmap()
+    rm2.copyfrom(rm)
+    path2 = gettemppath()
+    rm2.path = path2
+    rm2.flush()
+
+    # two files should be the same
+    ensure(len(set(util.readfile(p) for p in [path, path2])) == 1)
+
+    os.unlink(path)
+    os.unlink(path2)
+
+class fakefctx(object):
+    def __init__(self, node, path=None):
+        self._node = node
+        self._path = path
+
+    def node(self):
+        return self._node
+
+    def path(self):
+        return self._path
+
+def testcontains():
+    path = gettemppath()
+
+    rm = revmap.revmap(path)
+    for i in xrange(1, 5):
+        ensure(rm.append(genhsh(i), sidebranch=(i & 1)) == i)
+
+    for i in xrange(1, 5):
+        ensure(((genhsh(i), None) in rm) == ((i & 1) == 0))
+        ensure((fakefctx(genhsh(i)) in rm) == ((i & 1) == 0))
+    for i in xrange(5, 10):
+        ensure(fakefctx(genhsh(i)) not in rm)
+        ensure((genhsh(i), None) not in rm)
+
+    # "contains" checks paths
+    rm = revmap.revmap()
+    for i in xrange(1, 5):
+        ensure(rm.append(genhsh(i), path=str(i // 2)) == i)
+    for i in xrange(1, 5):
+        ensure(fakefctx(genhsh(i), path=str(i // 2)) in rm)
+        ensure(fakefctx(genhsh(i), path='a') not in rm)
+
+def testlastnode():
+    path = gettemppath()
+    ensure(revmap.getlastnode(path) is None)
+    rm = revmap.revmap(path)
+    ensure(revmap.getlastnode(path) is None)
+    for i in xrange(1, 10):
+        hsh = genhsh(i)
+        rm.append(hsh, path=str(i // 2), flush=True)
+        ensure(revmap.getlastnode(path) == hsh)
+        rm2 = revmap.revmap(path)
+        ensure(rm2.rev2hsh(rm2.maxrev) == hsh)
+
+testbasicreadwrite()
+testcorruptformat()
+testcopyfrom()
+testcontains()
+testlastnode()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-fastannotate.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,263 @@
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > fastannotate=
+  > EOF
+
+  $ HGMERGE=true; export HGMERGE
+
+  $ hg init repo
+  $ cd repo
+
+a simple merge case
+
+  $ echo 1 > a
+  $ hg commit -qAm 'append 1'
+  $ echo 2 >> a
+  $ hg commit -m 'append 2'
+  $ echo 3 >> a
+  $ hg commit -m 'append 3'
+  $ hg up 1 -q
+  $ cat > a << EOF
+  > 0
+  > 1
+  > 2
+  > EOF
+  $ hg commit -qm 'insert 0'
+  $ hg merge 2 -q
+  $ echo 4 >> a
+  $ hg commit -m merge
+  $ hg log -G -T '{rev}: {desc}'
+  @    4: merge
+  |\
+  | o  3: insert 0
+  | |
+  o |  2: append 3
+  |/
+  o  1: append 2
+  |
+  o  0: append 1
+  
+  $ hg fastannotate a
+  3: 0
+  0: 1
+  1: 2
+  2: 3
+  4: 4
+  $ hg fastannotate -r 0 a
+  0: 1
+  $ hg fastannotate -r 1 a
+  0: 1
+  1: 2
+  $ hg fastannotate -udnclf a
+  test 3 d641cb51f61e Thu Jan 01 00:00:00 1970 +0000 a:1: 0
+  test 0 4994017376d3 Thu Jan 01 00:00:00 1970 +0000 a:1: 1
+  test 1 e940cb6d9a06 Thu Jan 01 00:00:00 1970 +0000 a:2: 2
+  test 2 26162a884ba6 Thu Jan 01 00:00:00 1970 +0000 a:3: 3
+  test 4 3ad7bcd2815f Thu Jan 01 00:00:00 1970 +0000 a:5: 4
+  $ hg fastannotate --linear a
+  3: 0
+  0: 1
+  1: 2
+  4: 3
+  4: 4
+
+incrementally updating
+
+  $ hg fastannotate -r 0 a --debug
+  fastannotate: a: using fast path (resolved fctx: True)
+  0: 1
+  $ hg fastannotate -r 0 a --debug --rebuild
+  fastannotate: a: 1 new changesets in the main branch
+  0: 1
+  $ hg fastannotate -r 1 a --debug
+  fastannotate: a: 1 new changesets in the main branch
+  0: 1
+  1: 2
+  $ hg fastannotate -r 3 a --debug
+  fastannotate: a: 1 new changesets in the main branch
+  3: 0
+  0: 1
+  1: 2
+  $ hg fastannotate -r 4 a --debug
+  fastannotate: a: 1 new changesets in the main branch
+  3: 0
+  0: 1
+  1: 2
+  2: 3
+  4: 4
+  $ hg fastannotate -r 1 a --debug
+  fastannotate: a: using fast path (resolved fctx: True)
+  0: 1
+  1: 2
+
+rebuild happens automatically if unable to update
+
+  $ hg fastannotate -r 2 a --debug
+  fastannotate: a: cache broken and deleted
+  fastannotate: a: 3 new changesets in the main branch
+  0: 1
+  1: 2
+  2: 3
+
+config option "fastannotate.mainbranch"
+
+  $ hg fastannotate -r 1 --rebuild --config fastannotate.mainbranch=tip a --debug
+  fastannotate: a: 4 new changesets in the main branch
+  0: 1
+  1: 2
+  $ hg fastannotate -r 4 a --debug
+  fastannotate: a: using fast path (resolved fctx: True)
+  3: 0
+  0: 1
+  1: 2
+  2: 3
+  4: 4
+
+config option "fastannotate.modes"
+
+  $ hg annotate -r 1 --debug a
+  0: 1
+  1: 2
+  $ hg annotate --config fastannotate.modes=fctx -r 1 --debug a
+  fastannotate: a: using fast path (resolved fctx: False)
+  0: 1
+  1: 2
+  $ hg fastannotate --config fastannotate.modes=fctx -h -q
+  hg: unknown command 'fastannotate'
+  (did you mean *) (glob)
+  [255]
+
+rename
+
+  $ hg mv a b
+  $ cat > b << EOF
+  > 0
+  > 11
+  > 3
+  > 44
+  > EOF
+  $ hg commit -m b -q
+  $ hg fastannotate -ncf --long-hash b
+  3 d641cb51f61e331c44654104301f8154d7865c89 a: 0
+  5 d44dade239915bc82b91e4556b1257323f8e5824 b: 11
+  2 26162a884ba60e8c87bf4e0d6bb8efcc6f711a4e a: 3
+  5 d44dade239915bc82b91e4556b1257323f8e5824 b: 44
+  $ hg fastannotate -r 26162a884ba60e8c87bf4e0d6bb8efcc6f711a4e a
+  0: 1
+  1: 2
+  2: 3
+
+fastannotate --deleted
+
+  $ hg fastannotate --deleted -nf b
+  3 a:  0
+  5 b:  11
+  0 a: -1
+  1 a: -2
+  2 a:  3
+  5 b:  44
+  4 a: -4
+  $ hg fastannotate --deleted -r 3 -nf a
+  3 a:  0
+  0 a:  1
+  1 a:  2
+
+file and directories with ".l", ".m" suffixes
+
+  $ cd ..
+  $ hg init repo2
+  $ cd repo2
+
+  $ mkdir a.l b.m c.lock a.l.hg b.hg
+  $ for i in a b c d d.l d.m a.l/a b.m/a c.lock/a a.l.hg/a b.hg/a; do
+  >   echo $i > $i
+  > done
+  $ hg add . -q
+  $ hg commit -m init
+  $ hg fastannotate a.l/a b.m/a c.lock/a a.l.hg/a b.hg/a d.l d.m a b c d
+  0: a
+  0: a.l.hg/a
+  0: a.l/a
+  0: b
+  0: b.hg/a
+  0: b.m/a
+  0: c
+  0: c.lock/a
+  0: d
+  0: d.l
+  0: d.m
+
+empty file
+
+  $ touch empty
+  $ hg commit -A empty -m empty
+  $ hg fastannotate empty
+
+json format
+
+  $ hg fastannotate -Tjson -cludn b a empty
+  [
+   {
+    "date": [0.0, 0],
+    "line": "a\n",
+    "line_number": 1,
+    "node": "1fd620b16252aecb54c6aa530dff5ed6e6ec3d21",
+    "rev": 0,
+    "user": "test"
+   },
+   {
+    "date": [0.0, 0],
+    "line": "b\n",
+    "line_number": 1,
+    "node": "1fd620b16252aecb54c6aa530dff5ed6e6ec3d21",
+    "rev": 0,
+    "user": "test"
+   }
+  ]
+
+  $ hg fastannotate -Tjson -cludn empty
+  [
+  ]
+  $ hg fastannotate -Tjson --no-content -n a
+  [
+   {
+    "rev": 0
+   }
+  ]
+
+working copy
+
+  $ echo a >> a
+  $ hg fastannotate -r 'wdir()' a
+  abort: cannot update linelog to wdir()
+  (set fastannotate.mainbranch)
+  [255]
+  $ cat >> $HGRCPATH << EOF
+  > [fastannotate]
+  > mainbranch = .
+  > EOF
+  $ hg fastannotate -r 'wdir()' a
+  0 : a
+  1+: a
+  $ hg fastannotate -cludn -r 'wdir()' a
+  test 0 1fd620b16252  Thu Jan 01 00:00:00 1970 +0000:1: a
+  test 1 720582f5bdb6+ *:2: a (glob)
+  $ hg fastannotate -cludn -r 'wdir()' -Tjson a
+  [
+   {
+    "date": [0.0, 0],
+    "line": "a\n",
+    "line_number": 1,
+    "node": "1fd620b16252aecb54c6aa530dff5ed6e6ec3d21",
+    "rev": 0,
+    "user": "test"
+   },
+   {
+    "date": [*, 0], (glob)
+    "line": "a\n",
+    "line_number": 2,
+    "node": null,
+    "rev": null,
+    "user": "test"
+   }
+  ]
--- a/tests/test-filebranch.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-filebranch.t	Wed Sep 26 20:33:09 2018 +0900
@@ -41,7 +41,7 @@
 
 We shouldn't have anything but n state here:
 
-  $ hg debugstate --nodates | grep -v "^n"
+  $ hg debugstate --no-dates | grep -v "^n"
   [1]
 
 Merging:
@@ -141,6 +141,6 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 4 changesets, 10 total revisions
+  checked 4 changesets with 10 changes to 4 files
 
   $ cd ..
--- a/tests/test-fileset-generated.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-fileset-generated.t	Wed Sep 26 20:33:09 2018 +0900
@@ -2,15 +2,15 @@
 
 Set up history and working copy
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 1
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 1
   $ hg addremove -q --similarity 0
   $ hg commit -m first
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 2
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 2
   $ hg addremove -q --similarity 0
   $ hg commit -m second
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 wc
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 wc
   $ hg addremove -q --similarity 0
   $ hg forget *_*_*-untracked
   $ rm *_*_missing-*
@@ -187,11 +187,11 @@
   undeleting missing_content2_missing-untracked
 
   $ hg revert 'set:deleted()'
+  forgetting content1_missing_missing-tracked
+  forgetting missing_missing_missing-tracked
   reverting content1_content1_missing-tracked
   reverting content1_content2_missing-tracked
-  forgetting content1_missing_missing-tracked
   reverting missing_content2_missing-tracked
-  forgetting missing_missing_missing-tracked
 
   $ hg revert 'set:unknown()'
 
--- a/tests/test-fileset.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-fileset.t	Wed Sep 26 20:33:09 2018 +0900
@@ -18,13 +18,19 @@
 
   $ fileset -v a1
   (symbol 'a1')
+  * matcher:
+  <patternmatcher patterns='(?:a1$)'>
   a1
   $ fileset -v 'a*'
   (symbol 'a*')
+  * matcher:
+  <patternmatcher patterns='(?:a[^/]*$)'>
   a1
   a2
   $ fileset -v '"re:a\d"'
   (string 're:a\\d')
+  * matcher:
+  <patternmatcher patterns='(?:a\\d)'>
   a1
   a2
   $ fileset -v '!re:"a\d"'
@@ -32,6 +38,10 @@
     (kindpat
       (symbol 're')
       (string 'a\\d')))
+  * matcher:
+  <predicatenmatcher
+    pred=<not
+      <patternmatcher patterns='(?:a\\d)'>>>
   b1
   b2
   $ fileset -v 'path:a1 or glob:b?'
@@ -42,10 +52,12 @@
     (kindpat
       (symbol 'glob')
       (symbol 'b?')))
+  * matcher:
+  <patternmatcher patterns='(?:a1(?:/|$)|b.$)'>
   a1
   b1
   b2
-  $ fileset -v 'a1 or a2'
+  $ fileset -v --no-show-matcher 'a1 or a2'
   (or
     (symbol 'a1')
     (symbol 'a2'))
@@ -97,6 +109,15 @@
       None))
   hg: parse error: can't use negate operator in this context
   [255]
+  $ fileset -p parsed 'a, b, c'
+  * parsed:
+  (list
+    (symbol 'a')
+    (symbol 'b')
+    (symbol 'c'))
+  hg: parse error: can't use a list in this context
+  (see 'hg help "filesets.x or y"')
+  [255]
 
   $ fileset '"path":.'
   hg: parse error: not a symbol
@@ -114,6 +135,183 @@
   hg: parse error: invalid pattern kind: foo
   [255]
 
+Show parsed tree at stages:
+
+  $ fileset -p unknown a
+  abort: invalid stage name: unknown
+  [255]
+
+  $ fileset -p parsed 'path:a1 or glob:b?'
+  * parsed:
+  (or
+    (kindpat
+      (symbol 'path')
+      (symbol 'a1'))
+    (kindpat
+      (symbol 'glob')
+      (symbol 'b?')))
+  a1
+  b1
+  b2
+
+  $ fileset -p all -s 'a1 or a2 or (grep("b") & clean())'
+  * parsed:
+  (or
+    (symbol 'a1')
+    (symbol 'a2')
+    (group
+      (and
+        (func
+          (symbol 'grep')
+          (string 'b'))
+        (func
+          (symbol 'clean')
+          None))))
+  * analyzed:
+  (or
+    (symbol 'a1')
+    (symbol 'a2')
+    (and
+      (func
+        (symbol 'grep')
+        (string 'b'))
+      (withstatus
+        (func
+          (symbol 'clean')
+          None)
+        (string 'clean'))))
+  * optimized:
+  (or
+    (patterns
+      (symbol 'a1')
+      (symbol 'a2'))
+    (and
+      (withstatus
+        (func
+          (symbol 'clean')
+          None)
+        (string 'clean'))
+      (func
+        (symbol 'grep')
+        (string 'b'))))
+  * matcher:
+  <unionmatcher matchers=[
+    <patternmatcher patterns='(?:a1$|a2$)'>,
+    <intersectionmatcher
+      m1=<predicatenmatcher pred=clean>,
+      m2=<predicatenmatcher pred=grep('b')>>]>
+  a1
+  a2
+  b1
+  b2
+
+Union of basic patterns:
+
+  $ fileset -p optimized -s -r. 'a1 or a2 or path:b1'
+  * optimized:
+  (patterns
+    (symbol 'a1')
+    (symbol 'a2')
+    (kindpat
+      (symbol 'path')
+      (symbol 'b1')))
+  * matcher:
+  <patternmatcher patterns='(?:a1$|a2$|b1(?:/|$))'>
+  a1
+  a2
+  b1
+
+OR expression should be reordered by weight:
+
+  $ fileset -p optimized -s -r. 'grep("a") or a1 or grep("b") or b2'
+  * optimized:
+  (or
+    (patterns
+      (symbol 'a1')
+      (symbol 'b2'))
+    (func
+      (symbol 'grep')
+      (string 'a'))
+    (func
+      (symbol 'grep')
+      (string 'b')))
+  * matcher:
+  <unionmatcher matchers=[
+    <patternmatcher patterns='(?:a1$|b2$)'>,
+    <predicatenmatcher pred=grep('a')>,
+    <predicatenmatcher pred=grep('b')>]>
+  a1
+  a2
+  b1
+  b2
+
+Use differencematcher for 'x and not y':
+
+  $ fileset -p optimized -s 'a* and not a1'
+  * optimized:
+  (minus
+    (symbol 'a*')
+    (symbol 'a1'))
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:a[^/]*$)'>,
+    m2=<patternmatcher patterns='(?:a1$)'>>
+  a2
+
+  $ fileset -p optimized -s '!binary() and a*'
+  * optimized:
+  (minus
+    (symbol 'a*')
+    (func
+      (symbol 'binary')
+      None))
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:a[^/]*$)'>,
+    m2=<predicatenmatcher pred=binary>>
+  a1
+  a2
+
+'x - y' is rewritten to 'x and not y' first so the operands can be reordered:
+
+  $ fileset -p analyzed -p optimized -s 'a* - a1'
+  * analyzed:
+  (and
+    (symbol 'a*')
+    (not
+      (symbol 'a1')))
+  * optimized:
+  (minus
+    (symbol 'a*')
+    (symbol 'a1'))
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:a[^/]*$)'>,
+    m2=<patternmatcher patterns='(?:a1$)'>>
+  a2
+
+  $ fileset -p analyzed -p optimized -s 'binary() - a*'
+  * analyzed:
+  (and
+    (func
+      (symbol 'binary')
+      None)
+    (not
+      (symbol 'a*')))
+  * optimized:
+  (and
+    (not
+      (symbol 'a*'))
+    (func
+      (symbol 'binary')
+      None))
+  * matcher:
+  <intersectionmatcher
+    m1=<predicatenmatcher
+      pred=<not
+        <patternmatcher patterns='(?:a[^/]*$)'>>>,
+    m2=<predicatenmatcher pred=binary>>
+
 Test files status
 
   $ rm a1
@@ -180,6 +378,156 @@
   b2
   c1
 
+Test insertion of status hints
+
+  $ fileset -p optimized 'added()'
+  * optimized:
+  (withstatus
+    (func
+      (symbol 'added')
+      None)
+    (string 'added'))
+  c1
+
+  $ fileset -p optimized 'a* & removed()'
+  * optimized:
+  (and
+    (symbol 'a*')
+    (withstatus
+      (func
+        (symbol 'removed')
+        None)
+      (string 'removed')))
+  a2
+
+  $ fileset -p optimized 'a* - removed()'
+  * optimized:
+  (minus
+    (symbol 'a*')
+    (withstatus
+      (func
+        (symbol 'removed')
+        None)
+      (string 'removed')))
+  a1
+
+  $ fileset -p analyzed -p optimized '(added() + removed()) - a*'
+  * analyzed:
+  (and
+    (withstatus
+      (or
+        (func
+          (symbol 'added')
+          None)
+        (func
+          (symbol 'removed')
+          None))
+      (string 'added removed'))
+    (not
+      (symbol 'a*')))
+  * optimized:
+  (and
+    (not
+      (symbol 'a*'))
+    (withstatus
+      (or
+        (func
+          (symbol 'added')
+          None)
+        (func
+          (symbol 'removed')
+          None))
+      (string 'added removed')))
+  c1
+
+  $ fileset -p optimized 'a* + b* + added() + unknown()'
+  * optimized:
+  (withstatus
+    (or
+      (patterns
+        (symbol 'a*')
+        (symbol 'b*'))
+      (func
+        (symbol 'added')
+        None)
+      (func
+        (symbol 'unknown')
+        None))
+    (string 'added unknown'))
+  a1
+  a2
+  b1
+  b2
+  c1
+  c3
+
+  $ fileset -p analyzed -p optimized 'removed() & missing() & a*'
+  * analyzed:
+  (and
+    (withstatus
+      (and
+        (func
+          (symbol 'removed')
+          None)
+        (func
+          (symbol 'missing')
+          None))
+      (string 'removed missing'))
+    (symbol 'a*'))
+  * optimized:
+  (and
+    (symbol 'a*')
+    (withstatus
+      (and
+        (func
+          (symbol 'removed')
+          None)
+        (func
+          (symbol 'missing')
+          None))
+      (string 'removed missing')))
+
+  $ fileset -p optimized 'clean() & revs(0, added())'
+  * optimized:
+  (and
+    (withstatus
+      (func
+        (symbol 'clean')
+        None)
+      (string 'clean'))
+    (func
+      (symbol 'revs')
+      (list
+        (symbol '0')
+        (withstatus
+          (func
+            (symbol 'added')
+            None)
+          (string 'added')))))
+  b1
+
+  $ fileset -p optimized 'clean() & status(null, 0, b* & added())'
+  * optimized:
+  (and
+    (withstatus
+      (func
+        (symbol 'clean')
+        None)
+      (string 'clean'))
+    (func
+      (symbol 'status')
+      (list
+        (symbol 'null')
+        (symbol '0')
+        (and
+          (symbol 'b*')
+          (withstatus
+            (func
+              (symbol 'added')
+              None)
+            (string 'added'))))))
+  b1
+
 Test files properties
 
   >>> open('bin', 'wb').write(b'\0a') and None
@@ -194,6 +542,19 @@
   $ fileset 'binary()'
   bin
 
+  $ fileset -p optimized -s 'binary() and b*'
+  * optimized:
+  (and
+    (symbol 'b*')
+    (func
+      (symbol 'binary')
+      None))
+  * matcher:
+  <intersectionmatcher
+    m1=<patternmatcher patterns='(?:b[^/]*$)'>,
+    m2=<predicatenmatcher pred=binary>>
+  bin
+
   $ fileset 'grep("b{1}")'
   .hgignore
   b1
@@ -231,7 +592,7 @@
   [255]
   $ fileset '(1k, 2k)'
   hg: parse error: can't use a list in this context
-  (see hg help "filesets.x or y")
+  (see 'hg help "filesets.x or y"')
   [255]
   $ fileset 'size(1k)'
   1k
--- a/tests/test-fix-topology.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-fix-topology.t	Wed Sep 26 20:33:09 2018 +0900
@@ -9,7 +9,7 @@
   > sys.stdout.write(sys.stdin.read().upper())
   > EOF
   $ TESTLINES="foo\nbar\nbaz\n"
-  $ printf $TESTLINES | $PYTHON $UPPERCASEPY
+  $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY
   FOO
   BAR
   BAZ
@@ -22,7 +22,7 @@
   > [extensions]
   > fix =
   > [fix]
-  > uppercase-whole-file:command=$PYTHON $UPPERCASEPY
+  > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY
   > uppercase-whole-file:fileset=set:**
   > EOF
 
--- a/tests/test-fix.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-fix.t	Wed Sep 26 20:33:09 2018 +0900
@@ -22,32 +22,32 @@
   >     sys.stdout.write(line)
   > EOF
   $ TESTLINES="foo\nbar\nbaz\nqux\n"
-  $ printf $TESTLINES | $PYTHON $UPPERCASEPY
+  $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY
   foo
   bar
   baz
   qux
-  $ printf $TESTLINES | $PYTHON $UPPERCASEPY all
+  $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all
   FOO
   BAR
   BAZ
   QUX
-  $ printf $TESTLINES | $PYTHON $UPPERCASEPY 1-1
+  $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1
   FOO
   bar
   baz
   qux
-  $ printf $TESTLINES | $PYTHON $UPPERCASEPY 1-2
+  $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2
   FOO
   BAR
   baz
   qux
-  $ printf $TESTLINES | $PYTHON $UPPERCASEPY 2-3
+  $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3
   foo
   BAR
   BAZ
   qux
-  $ printf $TESTLINES | $PYTHON $UPPERCASEPY 2-2 4-4
+  $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4
   foo
   BAR
   baz
@@ -65,9 +65,9 @@
   > evolution.createmarkers=True
   > evolution.allowunstable=True
   > [fix]
-  > uppercase-whole-file:command=$PYTHON $UPPERCASEPY all
+  > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all
   > uppercase-whole-file:fileset=set:**.whole
-  > uppercase-changed-lines:command=$PYTHON $UPPERCASEPY
+  > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY
   > uppercase-changed-lines:linerange={first}-{last}
   > uppercase-changed-lines:fileset=set:**.changed
   > EOF
@@ -502,12 +502,13 @@
 
   $ cd ..
 
-When a fixer prints to stderr, we assume that it has failed. We should show the
-error messages to the user, and we should not let the failing fixer affect the
-file it was fixing (many code formatters might emit error messages on stderr
-and nothing on stdout, which would cause us the clear the file). We show the
-user which fixer failed and which revision, but we assume that the fixer will
-print the filename if it is relevant.
+When a fixer prints to stderr, we don't assume that it has failed. We show the
+error messages to the user, and we still let the fixer affect the file it was
+fixing if its exit code is zero. Some code formatters might emit error messages
+on stderr and nothing on stdout, which would cause us the clear the file,
+except that they also exit with a non-zero code. We show the user which fixer
+emitted the stderr, and which revision, but we assume that the fixer will print
+the filename if it is relevant (since the issue may be non-specific).
 
   $ hg init showstderr
   $ cd showstderr
@@ -515,17 +516,37 @@
   $ printf "hello\n" > hello.txt
   $ hg add
   adding hello.txt
-  $ cat >> $TESTTMP/cmd.sh <<'EOF'
+  $ cat > $TESTTMP/fail.sh <<'EOF'
   > printf 'HELLO\n'
   > printf "$@: some\nerror" >&2
+  > exit 0 # success despite the stderr output
   > EOF
-  $ hg --config "fix.fail:command=sh $TESTTMP/cmd.sh {rootpath}" \
+  $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
   >    --config "fix.fail:fileset=hello.txt" \
   >    fix --working-dir
   [wdir] fail: hello.txt: some
   [wdir] fail: error
   $ cat hello.txt
-  hello
+  HELLO
+
+  $ printf "goodbye\n" > hello.txt
+  $ cat > $TESTTMP/work.sh <<'EOF'
+  > printf 'GOODBYE\n'
+  > printf "$@: some\nerror\n" >&2
+  > exit 42 # success despite the stdout output
+  > EOF
+  $ hg --config "fix.fail:command=sh $TESTTMP/work.sh {rootpath}" \
+  >    --config "fix.fail:fileset=hello.txt" \
+  >    fix --working-dir
+  [wdir] fail: hello.txt: some
+  [wdir] fail: error
+  $ cat hello.txt
+  goodbye
+
+  $ hg --config "fix.fail:command=exit 42" \
+  >    --config "fix.fail:fileset=hello.txt" \
+  >    fix --working-dir
+  [wdir] fail: exited with status 42
 
   $ cd ..
 
@@ -830,9 +851,9 @@
   
   $ hg fix -r 0:2
   $ hg log --graph --template '{node|shortest} {files}'
-  o  3801 bar.whole
+  o  b4e2 bar.whole
   |
-  o  38cc
+  o  59f4
   |
   | @  bc05 bar.whole
   | |
--- a/tests/test-fncache.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-fncache.t	Wed Sep 26 20:33:09 2018 +0900
@@ -41,7 +41,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 3 files
 
   $ rm .hg/store/fncache
 
@@ -53,7 +53,7 @@
    warning: revlog 'data/a.i' not in fncache!
    warning: revlog 'data/a.i.hg/c.i' not in fncache!
    warning: revlog 'data/a.i/b.i' not in fncache!
-  3 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 3 files
   3 warnings encountered!
   hint: run "hg debugrebuildfncache" to recover from corrupt fncache
 
@@ -70,7 +70,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 3 files
 
   $ cd ..
 
@@ -88,6 +88,7 @@
   .hg/00manifest.i
   .hg/cache
   .hg/cache/branch2-served
+  .hg/cache/manifestfulltextcache (reporevlogstore !)
   .hg/cache/rbc-names-v1
   .hg/cache/rbc-revs-v1
   .hg/data
@@ -121,6 +122,7 @@
   .hg/00changelog.i
   .hg/cache
   .hg/cache/branch2-served
+  .hg/cache/manifestfulltextcache (reporevlogstore !)
   .hg/cache/rbc-names-v1
   .hg/cache/rbc-revs-v1
   .hg/dirstate
@@ -338,7 +340,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   $ cat .hg/store/fncache
   data/y.i
 
@@ -446,7 +448,7 @@
 
   $ cat > fncacheloadwarn.py << EOF
   > from __future__ import absolute_import
-  > from mercurial import extensions, store
+  > from mercurial import extensions, localrepo
   > 
   > def extsetup(ui):
   >     def wrapstore(orig, requirements, *args):
@@ -454,7 +456,7 @@
   >         if 'store' in requirements and 'fncache' in requirements:
   >             instrumentfncachestore(store, ui)
   >         return store
-  >     extensions.wrapfunction(store, 'store', wrapstore)
+  >     extensions.wrapfunction(localrepo, 'makestore', wrapstore)
   > 
   > def instrumentfncachestore(fncachestore, ui):
   >     class instrumentedfncache(type(fncachestore.fncache)):
--- a/tests/test-gendoc.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-gendoc.t	Wed Sep 26 20:33:09 2018 +0900
@@ -8,7 +8,7 @@
   $ { echo C; ls "$TESTDIR/../i18n"/*.po | sort; } | while read PO; do
   >     LOCALE=`basename "$PO" .po`
   >     echo "% extracting documentation from $LOCALE"
-  >     LANGUAGE=$LOCALE $PYTHON "$TESTDIR/../doc/gendoc.py" >> gendoc-$LOCALE.txt 2> /dev/null || exit
+  >     LANGUAGE=$LOCALE "$PYTHON" "$TESTDIR/../doc/gendoc.py" >> gendoc-$LOCALE.txt 2> /dev/null || exit
   > 
   >     if [ $LOCALE != C ]; then
   >         if [ ! -f $TESTDIR/test-gendoc-$LOCALE.t ]; then
--- a/tests/test-generaldelta.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-generaldelta.t	Wed Sep 26 20:33:09 2018 +0900
@@ -267,7 +267,7 @@
        51       4        3       50    prev        356        594        611   1.02862       611         0    0.00000
        52       4        4       51      p1         58        640        669   1.04531       669         0    0.00000
        53       5        1       -1    base          0          0          0   0.00000         0         0    0.00000
-       54       5        2       53      p1        376        640        376   0.58750       376         0    0.00000
+       54       6        1       -1    base        369        640        369   0.57656       369         0    0.00000
   $ hg clone --pull source-repo --config experimental.maxdeltachainspan=2800 relax-chain --config format.generaldelta=yes
   requesting all changes
   adding changesets
@@ -333,7 +333,7 @@
        51       2       13       17      p1         58        594        739   1.24411      2781      2042    2.76319
        52       5        1       -1    base        369        640        369   0.57656       369         0    0.00000
        53       6        1       -1    base          0          0          0   0.00000         0         0    0.00000
-       54       6        2       53      p1        376        640        376   0.58750       376         0    0.00000
+       54       7        1       -1    base        369        640        369   0.57656       369         0    0.00000
   $ hg clone --pull source-repo --config experimental.maxdeltachainspan=0 noconst-chain --config format.generaldelta=yes
   requesting all changes
   adding changesets
@@ -399,4 +399,4 @@
        51       2       13       17      p1         58        594        739   1.24411      2642      1903    2.57510
        52       2       14       51      p1         58        640        797   1.24531      2700      1903    2.38770
        53       4        1       -1    base          0          0          0   0.00000         0         0    0.00000
-       54       4        2       53      p1        376        640        376   0.58750       376         0    0.00000
+       54       5        1       -1    base        369        640        369   0.57656       369         0    0.00000
--- a/tests/test-glog-beautifygraph.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-glog-beautifygraph.t	Wed Sep 26 20:33:09 2018 +0900
@@ -80,52 +80,8 @@
   >   hg commit -Aqd "$rev 0" -m "($rev) $msg"
   > }
 
-  $ cat > printrevset.py <<EOF
-  > from __future__ import absolute_import
-  > from mercurial import (
-  >   cmdutil,
-  >   commands,
-  >   extensions,
-  >   logcmdutil,
-  >   revsetlang,
-  >   smartset,
-  > )
-  > 
-  > from mercurial.utils import (
-  >   stringutil,
-  > )
-  > 
-  > def logrevset(repo, pats, opts):
-  >     revs = logcmdutil._initialrevs(repo, opts)
-  >     if not revs:
-  >         return None
-  >     match, pats, slowpath = logcmdutil._makematcher(repo, revs, pats, opts)
-  >     return logcmdutil._makerevset(repo, match, pats, slowpath, opts)
-  > 
-  > def uisetup(ui):
-  >     def printrevset(orig, repo, pats, opts):
-  >         revs, filematcher = orig(repo, pats, opts)
-  >         if opts.get(b'print_revset'):
-  >             expr = logrevset(repo, pats, opts)
-  >             if expr:
-  >                 tree = revsetlang.parse(expr)
-  >                 tree = revsetlang.analyze(tree)
-  >             else:
-  >                 tree = []
-  >             ui = repo.ui
-  >             ui.write(b'%r\n' % (opts.get(b'rev', []),))
-  >             ui.write(revsetlang.prettyformat(tree) + b'\n')
-  >             ui.write(stringutil.prettyrepr(revs) + b'\n')
-  >             revs = smartset.baseset()  # display no revisions
-  >         return revs, filematcher
-  >     extensions.wrapfunction(logcmdutil, 'getrevs', printrevset)
-  >     aliases, entry = cmdutil.findcmd(b'log', commands.table)
-  >     entry[1].append((b'', b'print-revset', False,
-  >                      b'print generated revset and exit (DEPRECATED)'))
-  > EOF
-
   $ echo "[extensions]" >> $HGRCPATH
-  $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
+  $ echo "printrevset=$TESTDIR/printrevset.py" >> $HGRCPATH
   $ echo "beautifygraph=" >> $HGRCPATH
 
 Set a default of narrow-text UTF-8.
@@ -1853,7 +1809,7 @@
 
 Test glob expansion of pats
 
-  $ expandglobs=`$PYTHON -c "import mercurial.util; \
+  $ expandglobs=`"$PYTHON" -c "import mercurial.util; \
   >   print(mercurial.util.expandglobs and 'true' or 'false')"`
   $ if [ $expandglobs = "true" ]; then
   >    testlog 'a*';
@@ -2043,7 +1999,7 @@
     <spanset- 0:7>,
     <matchfiles patterns=[], include=['set:copied()'] exclude=[], default='relpath', rev=2147483647>>
   $ testlog -r "sort(file('set:copied()'), -rev)"
-  ["sort(file('set:copied()'), -rev)"]
+  ['sort(file(\'set:copied()\'), -rev)']
   []
   <filteredset
     <fullreposet- 0:7>,
--- a/tests/test-glog-topological.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-glog-topological.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,7 +16,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets bfaf4b5cbf01:916f1afdef90
+  new changesets bfaf4b5cbf01:916f1afdef90 (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg log -G
--- a/tests/test-glog.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-glog.t	Wed Sep 26 20:33:09 2018 +0900
@@ -81,49 +81,8 @@
   >   hg commit -Aqd "$rev 0" -m "($rev) $msg"
   > }
 
-  $ cat > printrevset.py <<EOF
-  > from __future__ import absolute_import
-  > from mercurial import (
-  >   cmdutil,
-  >   commands,
-  >   extensions,
-  >   logcmdutil,
-  >   revsetlang,
-  >   smartset,
-  > )
-  > from mercurial.utils import stringutil
-  > 
-  > def logrevset(repo, pats, opts):
-  >     revs = logcmdutil._initialrevs(repo, opts)
-  >     if not revs:
-  >         return None
-  >     match, pats, slowpath = logcmdutil._makematcher(repo, revs, pats, opts)
-  >     return logcmdutil._makerevset(repo, match, pats, slowpath, opts)
-  > 
-  > def uisetup(ui):
-  >     def printrevset(orig, repo, pats, opts):
-  >         revs, filematcher = orig(repo, pats, opts)
-  >         if opts.get(b'print_revset'):
-  >             expr = logrevset(repo, pats, opts)
-  >             if expr:
-  >                 tree = revsetlang.parse(expr)
-  >                 tree = revsetlang.analyze(tree)
-  >             else:
-  >                 tree = []
-  >             ui = repo.ui
-  >             ui.write(b'%r\n' % (opts.get(b'rev', []),))
-  >             ui.write(revsetlang.prettyformat(tree) + b'\n')
-  >             ui.write(stringutil.prettyrepr(revs) + b'\n')
-  >             revs = smartset.baseset()  # display no revisions
-  >         return revs, filematcher
-  >     extensions.wrapfunction(logcmdutil, 'getrevs', printrevset)
-  >     aliases, entry = cmdutil.findcmd(b'log', commands.table)
-  >     entry[1].append((b'', b'print-revset', False,
-  >                      b'print generated revset and exit (DEPRECATED)'))
-  > EOF
-
   $ echo "[extensions]" >> $HGRCPATH
-  $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
+  $ echo "printrevset=$TESTDIR/printrevset.py" >> $HGRCPATH
 
   $ hg init repo
   $ cd repo
@@ -1700,7 +1659,7 @@
 
 Test glob expansion of pats
 
-  $ expandglobs=`$PYTHON -c "import mercurial.util; \
+  $ expandglobs=`"$PYTHON" -c "import mercurial.util; \
   >   print(mercurial.util.expandglobs and 'true' or 'false')"`
   $ if [ $expandglobs = "true" ]; then
   >    testlog 'a*';
@@ -1890,7 +1849,7 @@
     <spanset- 0:7>,
     <matchfiles patterns=[], include=['set:copied()'] exclude=[], default='relpath', rev=2147483647>>
   $ testlog -r "sort(file('set:copied()'), -rev)"
-  ["sort(file('set:copied()'), -rev)"]
+  ['sort(file(\'set:copied()\'), -rev)']
   []
   <filteredset
     <fullreposet- 0:7>,
--- a/tests/test-graft.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-graft.t	Wed Sep 26 20:33:09 2018 +0900
@@ -237,7 +237,7 @@
   # To mark files as resolved:  hg resolve --mark FILE
   
   # To continue:    hg graft --continue
-  # To abort:       hg update --clean . (warning: this will discard uncommitted changes)
+  # To abort:       hg graft --abort
   
 
 Commit while interrupted should fail:
@@ -699,8 +699,24 @@
   summary:     2
   
 ... grafts of grafts unfortunately can't
-  $ hg graft -q 13
+  $ hg graft -q 13 --debug
+  scanning for duplicate grafts
+  grafting 13:7a4785234d87 "2"
+    searching for copies back to rev 12
+    unmatched files in other (from topological common ancestor):
+     g
+    unmatched files new in both:
+     b
+  resolving manifests
+   branchmerge: True, force: True, partial: False
+   ancestor: b592ea63bb0c, local: 7e61b508e709+, remote: 7a4785234d87
+  starting 4 threads for background file closing (?)
+  committing files:
+  b
   warning: can't find ancestor for 'b' copied from 'a'!
+  reusing manifest form p1 (listed files actually unchanged)
+  committing changelog
+  updating the branch cache
   $ hg log -r 'destination(13)'
 All copies of a cset
   $ hg log -r 'origin(13) or destination(origin(13))'
@@ -731,7 +747,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     2
   
-  changeset:   22:d1cb6591fa4b
+  changeset:   22:3a4e92d81b97
   branch:      dev
   tag:         tip
   user:        foo
@@ -743,8 +759,8 @@
 
   $ hg graft 'origin(13) or destination(origin(13))'
   skipping ancestor revision 21:7e61b508e709
-  skipping ancestor revision 22:d1cb6591fa4b
-  skipping revision 2:5c095ad7e90f (already grafted to 22:d1cb6591fa4b)
+  skipping ancestor revision 22:3a4e92d81b97
+  skipping revision 2:5c095ad7e90f (already grafted to 22:3a4e92d81b97)
   grafting 7:ef0ef43d49e7 "2"
   warning: can't find ancestor for 'b' copied from 'a'!
   grafting 13:7a4785234d87 "2"
@@ -758,7 +774,7 @@
   $ hg graft 19 0 6
   skipping ungraftable merge revision 6
   skipping ancestor revision 0:68795b066622
-  skipping already grafted revision 19:9627f653b421 (22:d1cb6591fa4b also has origin 2:5c095ad7e90f)
+  skipping already grafted revision 19:9627f653b421 (22:3a4e92d81b97 also has origin 2:5c095ad7e90f)
   [255]
   $ hg graft 19 0 6 --force
   skipping ungraftable merge revision 6
@@ -773,12 +789,12 @@
   $ hg ci -m 28
   $ hg backout 28
   reverting a
-  changeset 29:53177ba928f6 backs out changeset 28:50a516bb8b57
+  changeset 29:9d95e865b00c backs out changeset 28:cc20d29aec8d
   $ hg graft 28
-  skipping ancestor revision 28:50a516bb8b57
+  skipping ancestor revision 28:cc20d29aec8d
   [255]
   $ hg graft 28 --force
-  grafting 28:50a516bb8b57 "28"
+  grafting 28:cc20d29aec8d "28"
   merging a
   $ cat a
   abc
@@ -788,7 +804,7 @@
   $ echo def > a
   $ hg ci -m 31
   $ hg graft 28 --force --tool internal:fail
-  grafting 28:50a516bb8b57 "28"
+  grafting 28:cc20d29aec8d "28"
   abort: unresolved conflicts, can't continue
   (use 'hg resolve' and 'hg graft --continue')
   [255]
@@ -801,7 +817,7 @@
   (no more unresolved files)
   continue: hg graft --continue
   $ hg graft -c
-  grafting 28:50a516bb8b57 "28"
+  grafting 28:cc20d29aec8d "28"
   $ cat a
   abc
 
@@ -822,8 +838,8 @@
   $ hg tag -f something
   $ hg graft -qr 27
   $ hg graft -f 27
-  grafting 27:ed6c7e54e319 "28"
-  note: graft of 27:ed6c7e54e319 created no changes to commit
+  grafting 27:17d42b8f5d50 "28"
+  note: graft of 27:17d42b8f5d50 created no changes to commit
 
   $ cd ..
 
@@ -1863,7 +1879,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 9 changes to 8 files (+4 heads)
-  new changesets 9092f1db7931:6b98ff0062dd
+  new changesets 9092f1db7931:6b98ff0062dd (6 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 9
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1878,7 +1894,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets 311dfc6cf3bf
+  new changesets 311dfc6cf3bf (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg graft --abort
--- a/tests/test-grep.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-grep.t	Wed Sep 26 20:33:09 2018 +0900
@@ -18,7 +18,7 @@
 pattern error
 
   $ hg grep '**test**'
-  grep: invalid match pattern: nothing to repeat
+  grep: invalid match pattern: nothing to repeat* (glob)
   [1]
 
 simple
@@ -43,17 +43,17 @@
 simple templated
 
   $ hg grep port -r tip:0 \
-  > -T '{file}:{rev}:{node|short}:{texts % "{if(matched, text|upper, text)}"}\n'
+  > -T '{path}:{rev}:{node|short}:{texts % "{if(matched, text|upper, text)}"}\n'
   port:4:914fa752cdea:exPORT
   port:4:914fa752cdea:vaPORTight
   port:4:914fa752cdea:imPORT/exPORT
 
-  $ hg grep port -r tip:0 -T '{file}:{rev}:{texts}\n'
+  $ hg grep port -r tip:0 -T '{path}:{rev}:{texts}\n'
   port:4:export
   port:4:vaportight
   port:4:import/export
 
-  $ hg grep port -r tip:0 -T '{file}:{tags}:{texts}\n'
+  $ hg grep port -r tip:0 -T '{path}:{tags}:{texts}\n'
   port:tip:export
   port:tip:vaportight
   port:tip:import/export
@@ -64,27 +64,27 @@
   [
    {
     "date": [4, 0],
-    "file": "port",
     "line_number": 1,
     "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c",
+    "path": "port",
     "rev": 4,
     "texts": [{"matched": false, "text": "ex"}, {"matched": true, "text": "port"}],
     "user": "spam"
    },
    {
     "date": [4, 0],
-    "file": "port",
     "line_number": 2,
     "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c",
+    "path": "port",
     "rev": 4,
     "texts": [{"matched": false, "text": "va"}, {"matched": true, "text": "port"}, {"matched": false, "text": "ight"}],
     "user": "spam"
    },
    {
     "date": [4, 0],
-    "file": "port",
     "line_number": 3,
     "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c",
+    "path": "port",
     "rev": 4,
     "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}, {"matched": false, "text": "/ex"}, {"matched": true, "text": "port"}],
     "user": "spam"
@@ -97,9 +97,9 @@
   [
    {
     "date": [4, 0],
-    "file": "port",
     "line_number": 1,
     "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c",
+    "path": "port",
     "rev": 4,
     "user": "spam"
    }
@@ -125,9 +125,9 @@
    {
     "change": "-",
     "date": [4, 0],
-    "file": "port",
     "line_number": 4,
     "node": "914fa752cdea87777ac1a8d5c858b0c736218f6c",
+    "path": "port",
     "rev": 4,
     "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}, {"matched": false, "text": "/ex"}, {"matched": true, "text": "port"}],
     "user": "spam"
@@ -135,9 +135,9 @@
    {
     "change": "+",
     "date": [3, 0],
-    "file": "port",
     "line_number": 4,
     "node": "95040cfd017d658c536071c6290230a613c4c2a6",
+    "path": "port",
     "rev": 3,
     "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}, {"matched": false, "text": "/ex"}, {"matched": true, "text": "port"}],
     "user": "eggs"
@@ -145,9 +145,9 @@
    {
     "change": "-",
     "date": [2, 0],
-    "file": "port",
     "line_number": 1,
     "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47",
+    "path": "port",
     "rev": 2,
     "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}],
     "user": "spam"
@@ -155,9 +155,9 @@
    {
     "change": "-",
     "date": [2, 0],
-    "file": "port",
     "line_number": 2,
     "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47",
+    "path": "port",
     "rev": 2,
     "texts": [{"matched": false, "text": "ex"}, {"matched": true, "text": "port"}],
     "user": "spam"
@@ -165,9 +165,9 @@
    {
     "change": "+",
     "date": [2, 0],
-    "file": "port",
     "line_number": 1,
     "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47",
+    "path": "port",
     "rev": 2,
     "texts": [{"matched": false, "text": "ex"}, {"matched": true, "text": "port"}],
     "user": "spam"
@@ -175,9 +175,9 @@
    {
     "change": "+",
     "date": [2, 0],
-    "file": "port",
     "line_number": 2,
     "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47",
+    "path": "port",
     "rev": 2,
     "texts": [{"matched": false, "text": "va"}, {"matched": true, "text": "port"}, {"matched": false, "text": "ight"}],
     "user": "spam"
@@ -185,9 +185,9 @@
    {
     "change": "+",
     "date": [2, 0],
-    "file": "port",
     "line_number": 3,
     "node": "3b325e3481a1f07435d81dfdbfa434d9a0245b47",
+    "path": "port",
     "rev": 2,
     "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}, {"matched": false, "text": "/ex"}, {"matched": true, "text": "port"}],
     "user": "spam"
@@ -195,9 +195,9 @@
    {
     "change": "+",
     "date": [1, 0],
-    "file": "port",
     "line_number": 2,
     "node": "8b20f75c158513ff5ac80bd0e5219bfb6f0eb587",
+    "path": "port",
     "rev": 1,
     "texts": [{"matched": false, "text": "ex"}, {"matched": true, "text": "port"}],
     "user": "eggs"
@@ -205,9 +205,9 @@
    {
     "change": "+",
     "date": [0, 0],
-    "file": "port",
     "line_number": 1,
     "node": "f31323c9217050ba245ee8b537c713ec2e8ab226",
+    "path": "port",
     "rev": 0,
     "texts": [{"matched": false, "text": "im"}, {"matched": true, "text": "port"}],
     "user": "spam"
@@ -300,7 +300,7 @@
 
 match in last "line" without newline
 
-  $ $PYTHON -c 'fp = open("noeol", "wb"); fp.write(b"no infinite loop"); fp.close();'
+  $ "$PYTHON" -c 'fp = open("noeol", "wb"); fp.write(b"no infinite loop"); fp.close();'
   $ hg ci -Amnoeol
   adding noeol
   $ hg grep -r tip:0 loop
@@ -481,9 +481,9 @@
   [
    {
     "date": [0, 0],
-    "file": "file2",
     "line_number": 1,
     "node": "ffffffffffffffffffffffffffffffffffffffff",
+    "path": "file2",
     "rev": 2147483647,
     "texts": [{"matched": true, "text": "some"}, {"matched": false, "text": " text"}],
     "user": "test"
@@ -491,3 +491,17 @@
   ]
 
   $ cd ..
+
+test -rMULTIREV with --all-files
+
+  $ cd sng
+  $ hg rm um
+  $ hg commit -m "deletes um"
+  $ hg grep -r "0:2" "unmod" --all-files
+  um:0:unmod
+  um:1:unmod
+  $ hg grep -r "0:2" "unmod" --all-files um
+  um:0:unmod
+  um:1:unmod
+  $ cd ..
+
--- a/tests/test-hardlinks.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hardlinks.t	Wed Sep 26 20:33:09 2018 +0900
@@ -11,7 +11,7 @@
 
   $ nlinksdir()
   > {
-  >     find "$@" -type f | $PYTHON $TESTTMP/nlinks.py
+  >     find "$@" -type f | "$PYTHON" $TESTTMP/nlinks.py
   > }
 
 Some implementations of cp can't create hardlinks (replaces 'cp -al' on Linux):
@@ -25,7 +25,7 @@
 
   $ linkcp()
   > {
-  >     $PYTHON $TESTTMP/linkcp.py $1 $2
+  >     "$PYTHON" $TESTTMP/linkcp.py $1 $2
   > }
 
 Prepare repo r1:
@@ -151,7 +151,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
 
   $ cd r3
   $ hg push
@@ -181,7 +181,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
 
 
   $ cd r1
@@ -241,6 +241,7 @@
   2 r4/.hg/cache/checkisexec (execbit !)
   ? r4/.hg/cache/checklink-target (glob) (symlink !)
   2 r4/.hg/cache/checknoexec (execbit !)
+  2 r4/.hg/cache/manifestfulltextcache (reporevlogstore !)
   2 r4/.hg/cache/rbc-names-v1
   2 r4/.hg/cache/rbc-revs-v1
   2 r4/.hg/dirstate
@@ -291,6 +292,7 @@
   2 r4/.hg/cache/checkisexec (execbit !)
   2 r4/.hg/cache/checklink-target (symlink !)
   2 r4/.hg/cache/checknoexec (execbit !)
+  2 r4/.hg/cache/manifestfulltextcache (reporevlogstore !)
   2 r4/.hg/cache/rbc-names-v1
   2 r4/.hg/cache/rbc-revs-v1
   1 r4/.hg/dirstate
--- a/tests/test-help.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-help.t	Wed Sep 26 20:33:09 2018 +0900
@@ -652,29 +652,7 @@
 
   $ hg skjdfks
   hg: unknown command 'skjdfks'
-  Mercurial Distributed SCM
-  
-  basic commands:
-  
-   add           add the specified files on the next commit
-   annotate      show changeset information by line for each file
-   clone         make a copy of an existing repository
-   commit        commit the specified files or all outstanding changes
-   diff          diff repository (or selected files)
-   export        dump the header and diffs for one or more changesets
-   forget        forget the specified files on the next commit
-   init          create a new repository in the given directory
-   log           show revision history of entire repository or files
-   merge         merge another revision into working directory
-   pull          pull changes from the specified source
-   push          push changes to the specified destination
-   remove        remove the specified files on the next commit
-   serve         start stand-alone webserver
-   status        show changed files in the working directory
-   summary       summarize working directory state
-   update        update working directory (or switch revisions)
-  
-  (use 'hg help' for the full list of commands or 'hg -v' for details)
+  (use 'hg help' for a list of commands)
   [255]
 
 Typoed command gives suggestion
@@ -960,12 +938,15 @@
                  retrieves a bundle from a repo
    debugignore   display the combined ignore pattern and information about
                  ignored files
-   debugindex    dump the contents of an index file
+   debugindex    dump index data for a storage primitive
    debugindexdot
                  dump an index DAG as a graphviz dot file
    debuginstall  test Mercurial installation
    debugknown    test whether node ids are known to a repo
    debuglocks    show or modify state of locks
+   debugmanifestfulltextcache
+                 show, clear or amend the contents of the manifest fulltext
+                 cache
    debugmergestate
                  print merge state
    debugnamecomplete
@@ -989,6 +970,8 @@
                  rebuild the fncache file
    debugrename   dump rename information
    debugrevlog   show data and statistics about a revlog
+   debugrevlogindex
+                 dump the contents of a revlog index
    debugrevspec  parse and apply a revision specification
    debugserve    run a server with advanced settings
    debugsetparents
@@ -1027,12 +1010,17 @@
   
        bundle2       Bundle2
        bundles       Bundles
+       cbor          CBOR
        censor        Censor
        changegroups  Changegroups
        config        Config Registrar
        requirements  Repository Requirements
        revlogs       Revision Logs
        wireprotocol  Wire Protocol
+       wireprotocolrpc
+                     Wire Protocol RPC
+       wireprotocolv2
+                     Wire Protocol Version 2
 
 sub-topics can be accessed
 
@@ -1357,6 +1345,55 @@
       "smtp.host"
           Host name of mail server, e.g. "mail.example.com".
   
+
+Test section name with dot
+
+  $ hg help config.ui.username
+      "ui.username"
+          The committer of a changeset created when running "commit". Typically
+          a person's name and email address, e.g. "Fred Widget
+          <fred@example.com>". Environment variables in the username are
+          expanded.
+  
+          (default: "$EMAIL" or "username@hostname". If the username in hgrc is
+          empty, e.g. if the system admin set "username =" in the system hgrc,
+          it has to be specified manually or in a different hgrc file)
+  
+
+  $ hg help config.annotate.git
+  abort: help section not found: config.annotate.git
+  [255]
+
+  $ hg help config.update.check
+      "commands.update.check"
+          Determines what level of checking 'hg update' will perform before
+          moving to a destination revision. Valid values are "abort", "none",
+          "linear", and "noconflict". "abort" always fails if the working
+          directory has uncommitted changes. "none" performs no checking, and
+          may result in a merge with uncommitted changes. "linear" allows any
+          update as long as it follows a straight line in the revision history,
+          and may trigger a merge with uncommitted changes. "noconflict" will
+          allow any update which would not trigger a merge with uncommitted
+          changes, if any are present. (default: "linear")
+  
+
+  $ hg help config.commands.update.check
+      "commands.update.check"
+          Determines what level of checking 'hg update' will perform before
+          moving to a destination revision. Valid values are "abort", "none",
+          "linear", and "noconflict". "abort" always fails if the working
+          directory has uncommitted changes. "none" performs no checking, and
+          may result in a merge with uncommitted changes. "linear" allows any
+          update as long as it follows a straight line in the revision history,
+          and may trigger a merge with uncommitted changes. "noconflict" will
+          allow any update which would not trigger a merge with uncommitted
+          changes, if any are present. (default: "linear")
+  
+
+  $ hg help config.ommands.update.check
+  abort: help section not found: config.ommands.update.check
+  [255]
+
 Unrelated trailing paragraphs shouldn't be included
 
   $ hg help config.extramsg | grep '^$'
@@ -1377,6 +1414,14 @@
   $ hg help config.type | egrep '^$'|wc -l
   \s*3 (re)
 
+  $ hg help config.profiling.type.ls
+          "profiling.type.ls"
+            Use Python's built-in instrumenting profiler. This profiler works on
+            all platforms, but each line number it reports is the first line of
+            a function. This restriction makes it difficult to identify the
+            expensive parts of a non-trivial function.
+  
+
 Separate sections from subsections
 
   $ hg help config.format | egrep '^    ("|-)|^\s*$' | uniq
@@ -1499,7 +1544,7 @@
   Commands:
   $ hg help -c commit > /dev/null
   $ hg help -e -c commit > /dev/null
-  $ hg help -e commit > /dev/null
+  $ hg help -e commit
   abort: no such help topic: commit
   (try 'hg help --keyword commit')
   [255]
@@ -1727,7 +1772,7 @@
 This tests that section lookup by translated string isn't broken by
 such str.lower().
 
-  $ $PYTHON <<EOF
+  $ "$PYTHON" <<EOF
   > def escape(s):
   >     return ''.join('\u%x' % ord(uc) for uc in s.decode('cp932'))
   > # translation of "record" in ja_JP.cp932
@@ -1761,7 +1806,7 @@
   > ambiguous = ./ambiguous.py
   > EOF
 
-  $ $PYTHON <<EOF | sh
+  $ "$PYTHON" <<EOF | sh
   > upper = "\x8bL\x98^"
   > print("hg --encoding cp932 help -e ambiguous.%s" % upper)
   > EOF
@@ -1771,7 +1816,7 @@
   Upper name should show only this message
   
 
-  $ $PYTHON <<EOF | sh
+  $ "$PYTHON" <<EOF | sh
   > lower = "\x8bl\x98^"
   > print("hg --encoding cp932 help -e ambiguous.%s" % lower)
   > EOF
@@ -1848,18 +1893,26 @@
         This implies premerge. Therefore, files aren't dumped, if premerge runs
         successfully. Use :forcedump to forcibly write files out.
   
+        (actual capabilities: binary, symlink)
+  
       ":fail"
         Rather than attempting to merge files that were modified on both
         branches, it marks them as unresolved. The resolve command must be used
         to resolve these conflicts.
   
+        (actual capabilities: binary, symlink)
+  
       ":forcedump"
         Creates three versions of the files as same as :dump, but omits
         premerge.
   
+        (actual capabilities: binary, symlink)
+  
       ":local"
         Uses the local 'p1()' version of files as the merged version.
   
+        (actual capabilities: binary, symlink)
+  
       ":merge"
         Uses the internal non-interactive simple merge algorithm for merging
         files. It will fail if there are any conflicts and leave markers in the
@@ -1883,10 +1936,14 @@
       ":other"
         Uses the other 'p2()' version of files as the merged version.
   
+        (actual capabilities: binary, symlink)
+  
       ":prompt"
         Asks the user which of the local 'p1()' or the other 'p2()' version to
         keep as the merged version.
   
+        (actual capabilities: binary, symlink)
+  
       ":tagmerge"
         Uses the internal tag merge algorithm (experimental).
   
@@ -1896,7 +1953,8 @@
         markers are inserted.
   
       Internal tools are always available and do not require a GUI but will by
-      default not handle symlinks or binary files.
+      default not handle symlinks or binary files. See next section for detail
+      about "actual capabilities" described above.
   
       Choosing a merge tool
       =====================
@@ -1911,8 +1969,7 @@
          must be executable by the shell.
       3. If the filename of the file to be merged matches any of the patterns in
          the merge-patterns configuration section, the first usable merge tool
-         corresponding to a matching pattern is used. Here, binary capabilities
-         of the merge tool are not considered.
+         corresponding to a matching pattern is used.
       4. If ui.merge is set it will be considered next. If the value is not the
          name of a configured tool, the specified value is used and must be
          executable by the shell. Otherwise the named tool is used if it is
@@ -1925,6 +1982,27 @@
          internal ":merge" is used.
       8. Otherwise, ":prompt" is used.
   
+      For historical reason, Mercurial treats merge tools as below while
+      examining rules above.
+  
+      step specified via  binary symlink
+      ----------------------------------
+      1.   --tool         o/o    o/o
+      2.   HGMERGE        o/o    o/o
+      3.   merge-patterns o/o(*) x/?(*)
+      4.   ui.merge       x/?(*) x/?(*)
+  
+      Each capability column indicates Mercurial behavior for internal/external
+      merge tools at examining each rule.
+  
+      - "o": "assume that a tool has capability"
+      - "x": "assume that a tool does not have capability"
+      - "?": "check actual capability of a tool"
+  
+      If "merge.strict-capability-check" configuration is true, Mercurial checks
+      capabilities of merge tools strictly in (*) cases above (= each capability
+      column becomes "?/?"). It is false by default for backward compatibility.
+  
       Note:
          After selecting a merge program, Mercurial will by default attempt to
          merge the files using a simple merge algorithm first. Only if it
@@ -1948,7 +2026,7 @@
 Test usage of section marks in help documents
 
   $ cd "$TESTDIR"/../doc
-  $ $PYTHON check-seclevel.py
+  $ "$PYTHON" check-seclevel.py
   $ cd $TESTTMP
 
 #if serve
@@ -3221,6 +3299,13 @@
   Bundles
   </td></tr>
   <tr><td>
+  <a href="/help/internals.cbor">
+  cbor
+  </a>
+  </td><td>
+  CBOR
+  </td></tr>
+  <tr><td>
   <a href="/help/internals.censor">
   censor
   </a>
@@ -3262,6 +3347,20 @@
   </td><td>
   Wire Protocol
   </td></tr>
+  <tr><td>
+  <a href="/help/internals.wireprotocolrpc">
+  wireprotocolrpc
+  </a>
+  </td><td>
+  Wire Protocol RPC
+  </td></tr>
+  <tr><td>
+  <a href="/help/internals.wireprotocolv2">
+  wireprotocolv2
+  </a>
+  </td><td>
+  Wire Protocol Version 2
+  </td></tr>
   
   
   
--- a/tests/test-hghave.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hghave.t	Wed Sep 26 20:33:09 2018 +0900
@@ -22,7 +22,7 @@
   > EOF
   $ ( \
   > testrepohgenv; \
-  > $PYTHON $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE test-hghaveaddon.t \
+  > "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE test-hghaveaddon.t \
   > )
   .
   # Ran 1 tests, 0 skipped, 0 failed.
--- a/tests/test-hgignore.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgignore.t	Wed Sep 26 20:33:09 2018 +0900
@@ -19,7 +19,7 @@
   > f.close()
   > EOF
 
-  $ $PYTHON makeignore.py
+  $ "$PYTHON" makeignore.py
 
 Should display baz only:
 
--- a/tests/test-hgrc.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgrc.t	Wed Sep 26 20:33:09 2018 +0900
@@ -58,7 +58,7 @@
   unexpected leading whitespace
   [255]
 
-  $ $PYTHON -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n  de\n fg \nbaz = bif cb \n')" \
+  $ "$PYTHON" -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n  de\n fg \nbaz = bif cb \n')" \
   > > $HGRC
   $ hg showconfig foo
   foo.bar=a\nb\nc\nde\nfg
--- a/tests/test-hgweb-commands.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgweb-commands.t	Wed Sep 26 20:33:09 2018 +0900
@@ -26,7 +26,7 @@
   $ hg ci -Ambranch
   $ hg branch unstable
   marked working directory as branch unstable
-  >>> open('msg', 'wb').write('branch commit with null character: \0\n')
+  >>> open('msg', 'wb').write(b'branch commit with null character: \0\n') and None
   $ hg ci -l msg
   $ rm msg
 
@@ -2183,7 +2183,7 @@
   
   batch
   branchmap
-  $USUAL_BUNDLE2_CAPS_SERVER$
+  $USUAL_BUNDLE2_CAPS$
   changegroupsubset
   compression=$BUNDLE2_COMPRESSIONS$
   getbundle
@@ -2276,13 +2276,13 @@
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
-  > app = hgweb('.', 'test')
+  > app = hgweb(b'.', b'test')
   > wsgicgi.launch(app)
   > HGWEB
   $ . "$TESTDIR/cgienv"
   $ PATH_INFO=/bookmarks; export PATH_INFO
   $ QUERY_STRING='style=raw'
-  $ $PYTHON hgweb.cgi | grep -v ETag:
+  $ "$PYTHON" hgweb.cgi | grep -v ETag:
   Status: 200 Script output follows\r (esc)
   Content-Type: text/plain; charset=ascii\r (esc)
   \r (esc)
@@ -2291,7 +2291,7 @@
 
   $ PATH_INFO=/; export PATH_INFO
   $ QUERY_STRING='cmd=listkeys&namespace=bookmarks'
-  $ $PYTHON hgweb.cgi
+  $ "$PYTHON" hgweb.cgi
   Status: 200 Script output follows\r (esc)
   Content-Type: application/mercurial-0.1\r (esc)
   Content-Length: 0\r (esc)
@@ -2301,7 +2301,7 @@
 
   $ PATH_INFO=/log; export PATH_INFO
   $ QUERY_STRING='rev=babar'
-  $ $PYTHON hgweb.cgi > search
+  $ "$PYTHON" hgweb.cgi > search
   $ grep Status search
   Status: 200 Script output follows\r (esc)
 
@@ -2309,7 +2309,7 @@
 
   $ PATH_INFO=/summary; export PATH_INFO
   $ QUERY_STRING='style=monoblue'; export QUERY_STRING
-  $ $PYTHON hgweb.cgi > summary.out
+  $ "$PYTHON" hgweb.cgi > summary.out
   $ grep "^Status" summary.out
   Status: 200 Script output follows\r (esc)
 
@@ -2320,7 +2320,7 @@
 
   $ PATH_INFO=/rev/5; export PATH_INFO
   $ QUERY_STRING='style=raw'
-  $ $PYTHON hgweb.cgi #> search
+  $ "$PYTHON" hgweb.cgi #> search
   Status: 404 Not Found\r (esc)
   ETag: W/"*"\r (glob) (esc)
   Content-Type: text/plain; charset=ascii\r (esc)
@@ -2334,7 +2334,7 @@
 
   $ PATH_INFO=/rev/4; export PATH_INFO
   $ QUERY_STRING='style=raw'
-  $ $PYTHON hgweb.cgi #> search
+  $ "$PYTHON" hgweb.cgi #> search
   Status: 404 Not Found\r (esc)
   ETag: W/"*"\r (glob) (esc)
   Content-Type: text/plain; charset=ascii\r (esc)
@@ -2362,11 +2362,11 @@
   $ hg phase --force --secret 0
   $ PATH_INFO=/graph/; export PATH_INFO
   $ QUERY_STRING=''
-  $ $PYTHON hgweb.cgi | grep Status
+  $ "$PYTHON" hgweb.cgi | grep Status
   Status: 200 Script output follows\r (esc)
 (check rendered revision)
   $ QUERY_STRING='style=raw'
-  $ $PYTHON hgweb.cgi | grep -v ETag
+  $ "$PYTHON" hgweb.cgi | grep -v ETag
   Status: 200 Script output follows\r (esc)
   Content-Type: text/plain; charset=ascii\r (esc)
   \r (esc)
--- a/tests/test-hgweb-json.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgweb-json.t	Wed Sep 26 20:33:09 2018 +0900
@@ -588,6 +588,187 @@
     "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7"
   }
 
+shortlog is displayed by default (issue5978)
+
+  $ request '?style=json'
+  200 Script output follows
+  
+  {
+    "changeset_count": 10,
+    "changesets": [
+      {
+        "bookmarks": [],
+        "branch": "default",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "merge test-branch into default",
+        "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
+        "parents": [
+          "ceed296fe500c3fac9541e31dad860cb49c89e45",
+          "ed66c30e87eb65337c05a4229efaa5f1d5285a90"
+        ],
+        "phase": "draft",
+        "tags": [
+          "tip"
+        ],
+        "user": "test"
+      },
+      {
+        "bookmarks": [],
+        "branch": "test-branch",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "another commit in test-branch",
+        "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
+        "parents": [
+          "6ab967a8ab3489227a83f80e920faa039a71819f"
+        ],
+        "phase": "draft",
+        "tags": [],
+        "user": "test"
+      },
+      {
+        "bookmarks": [],
+        "branch": "test-branch",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "create test branch",
+        "node": "6ab967a8ab3489227a83f80e920faa039a71819f",
+        "parents": [
+          "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
+        ],
+        "phase": "draft",
+        "tags": [],
+        "user": "test"
+      },
+      {
+        "bookmarks": [
+          "bookmark2"
+        ],
+        "branch": "default",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "create tag2",
+        "node": "ceed296fe500c3fac9541e31dad860cb49c89e45",
+        "parents": [
+          "f2890a05fea49bfaf9fb27ed5490894eba32da78"
+        ],
+        "phase": "draft",
+        "tags": [],
+        "user": "test"
+      },
+      {
+        "bookmarks": [],
+        "branch": "default",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "another commit to da/foo",
+        "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
+        "parents": [
+          "93a8ce14f89156426b7fa981af8042da53f03aa0"
+        ],
+        "phase": "draft",
+        "tags": [
+          "tag2"
+        ],
+        "user": "test"
+      },
+      {
+        "bookmarks": [],
+        "branch": "default",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "create tag",
+        "node": "93a8ce14f89156426b7fa981af8042da53f03aa0",
+        "parents": [
+          "78896eb0e102174ce9278438a95e12543e4367a7"
+        ],
+        "phase": "public",
+        "tags": [],
+        "user": "test"
+      },
+      {
+        "bookmarks": [],
+        "branch": "default",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "move foo",
+        "node": "78896eb0e102174ce9278438a95e12543e4367a7",
+        "parents": [
+          "8d7c456572acf3557e8ed8a07286b10c408bcec5"
+        ],
+        "phase": "public",
+        "tags": [
+          "tag1"
+        ],
+        "user": "test"
+      },
+      {
+        "bookmarks": [
+          "bookmark1"
+        ],
+        "branch": "default",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "modify da/foo",
+        "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
+        "parents": [
+          "f8bbb9024b10f93cdbb8d940337398291d40dea8"
+        ],
+        "phase": "public",
+        "tags": [],
+        "user": "test"
+      },
+      {
+        "bookmarks": [],
+        "branch": "default",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "modify foo",
+        "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
+        "parents": [
+          "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
+        ],
+        "phase": "public",
+        "tags": [],
+        "user": "test"
+      },
+      {
+        "bookmarks": [],
+        "branch": "default",
+        "date": [
+          0.0,
+          0
+        ],
+        "desc": "initial",
+        "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
+        "parents": [],
+        "phase": "public",
+        "tags": [],
+        "user": "test"
+      }
+    ],
+    "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7"
+  }
+
 changeset/ renders the tip changeset
 
   $ request json-rev
@@ -2002,10 +2183,20 @@
     "topic": "phases"
   }
 
+Error page shouldn't crash
+
+  $ request json-changeset/deadbeef
+  404 Not Found
+  
+  {
+    "error": "unknown revision 'deadbeef'"
+  }
+  [1]
+
 Commit message with Japanese Kanji 'Noh', which ends with '\x5c'
 
   $ echo foo >> da/foo
-  $ HGENCODING=cp932 hg ci -m `$PYTHON -c 'print("\x94\x5c")'`
+  $ HGENCODING=cp932 hg ci -m `"$PYTHON" -c 'print("\x94\x5c")'`
 
 Commit message with null character
 
--- a/tests/test-hgweb-no-path-info.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgweb-no-path-info.t	Wed Sep 26 20:33:09 2018 +0900
@@ -64,13 +64,13 @@
   > 
   > output = stringio()
   > env['QUERY_STRING'] = 'style=atom'
-  > process(hgweb('.', name='repo'))
+  > process(hgweb(b'.', name=b'repo'))
   > 
   > output = stringio()
   > env['QUERY_STRING'] = 'style=raw'
-  > process(hgwebdir({'repo': '.'}))
+  > process(hgwebdir({b'repo': b'.'}))
   > EOF
-  $ $PYTHON request.py
+  $ "$PYTHON" request.py
   ---- STATUS
   200 Script output follows
   ---- HEADERS
--- a/tests/test-hgweb-no-request-uri.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgweb-no-request-uri.t	Wed Sep 26 20:33:09 2018 +0900
@@ -64,24 +64,24 @@
   > output = stringio()
   > env['PATH_INFO'] = '/'
   > env['QUERY_STRING'] = 'style=atom'
-  > process(hgweb('.', name = 'repo'))
+  > process(hgweb(b'.', name = b'repo'))
   > 
   > output = stringio()
   > env['PATH_INFO'] = '/file/tip/'
   > env['QUERY_STRING'] = 'style=raw'
-  > process(hgweb('.', name = 'repo'))
+  > process(hgweb(b'.', name = b'repo'))
   > 
   > output = stringio()
   > env['PATH_INFO'] = '/'
   > env['QUERY_STRING'] = 'style=raw'
-  > process(hgwebdir({'repo': '.'}))
+  > process(hgwebdir({b'repo': b'.'}))
   > 
   > output = stringio()
   > env['PATH_INFO'] = '/repo/file/tip/'
   > env['QUERY_STRING'] = 'style=raw'
-  > process(hgwebdir({'repo': '.'}))
+  > process(hgwebdir({b'repo': b'.'}))
   > EOF
-  $ $PYTHON request.py
+  $ "$PYTHON" request.py
   ---- STATUS
   200 Script output follows
   ---- HEADERS
--- a/tests/test-hgweb-non-interactive.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgweb-non-interactive.t	Wed Sep 26 20:33:09 2018 +0900
@@ -65,7 +65,7 @@
   >     'SERVER_PROTOCOL': 'HTTP/1.0'
   > }
   > 
-  > i = hgweb('.')
+  > i = hgweb(b'.')
   > for c in i(env, startrsp):
   >     pass
   > print('---- ERRORS')
@@ -74,9 +74,9 @@
   > print(sorted([x for x in os.environ if x.startswith('wsgi')]))
   > print('---- request.ENVIRON wsgi variables')
   > with i._obtainrepo() as repo:
-  >     print(sorted([x for x in repo.ui.environ if x.startswith('wsgi')]))
+  >     print(sorted([x for x in repo.ui.environ if x.startswith(b'wsgi')]))
   > EOF
-  $ $PYTHON request.py
+  $ "$PYTHON" request.py
   ---- STATUS
   200 Script output follows
   ---- HEADERS
--- a/tests/test-hgweb.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgweb.t	Wed Sep 26 20:33:09 2018 +0900
@@ -329,7 +329,7 @@
 
 Test the access/error files are opened in append mode
 
-  $ $PYTHON -c "print len(open('access.log', 'rb').readlines()), 'log lines written'"
+  $ "$PYTHON" -c "from __future__ import print_function; print(len(open('access.log', 'rb').readlines()), 'log lines written')"
   14 log lines written
 
 static file
--- a/tests/test-hgwebdir.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-hgwebdir.t	Wed Sep 26 20:33:09 2018 +0900
@@ -66,6 +66,20 @@
   > EOF
   $ cd ..
 
+add file under the directory which could be shadowed by another repository
+
+  $ mkdir notrepo/f/f3
+  $ echo f3/file > notrepo/f/f3/file
+  $ hg -R notrepo/f ci -Am 'f3/file'
+  adding f3/file
+  $ hg -R notrepo/f update null
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ hg init notrepo/f/f3
+  $ cat <<'EOF' > notrepo/f/f3/.hg/hgrc
+  > [web]
+  > hidden = true
+  > EOF
+
 create repository without .hg/store
 
   $ hg init nostore
@@ -1217,6 +1231,39 @@
   
   f2
 
+Test accessing file that could be shadowed by another repository if the URL
+path were audited as a working-directory path:
+
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/f/file/tip/f3/file?style=raw'
+  200 Script output follows
+  
+  f3/file
+
+Test accessing working-directory file that is shadowed by another repository
+
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/f/file/ffffffffffff/f3/file?style=raw'
+  403 Forbidden
+  
+  
+  error: path 'f3/file' is inside nested repo 'f3'
+  [1]
+
+Test accessing invalid paths:
+
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/f/file/tip/..?style=raw'
+  403 Forbidden
+  
+  
+  error: .. not under root '$TESTTMP/dir/webdir/notrepo/f'
+  [1]
+
+  $ get-with-headers.py localhost:$HGPORT1 'rcoll/notrepo/f/file/tip/.hg/hgrc?style=raw'
+  403 Forbidden
+  
+  
+  error: path contains illegal component: .hg/hgrc
+  [1]
+
 Test descend = False
 
   $ killdaemons.py
--- a/tests/test-highlight.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-highlight.t	Wed Sep 26 20:33:09 2018 +0900
@@ -947,7 +947,7 @@
   $ cd ..
   $ hg init eucjp
   $ cd eucjp
-  $ $PYTHON -c 'print("\265\376")' >> eucjp.txt  # Japanese kanji "Kyo"
+  $ "$PYTHON" -c 'print("\265\376")' >> eucjp.txt  # Japanese kanji "Kyo"
   $ hg ci -Ama
   adding eucjp.txt
   $ hgserveget () {
--- a/tests/test-histedit-arguments.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-histedit-arguments.t	Wed Sep 26 20:33:09 2018 +0900
@@ -279,7 +279,7 @@
 Test that trimming description using multi-byte characters
 --------------------------------------------------------------------
 
-  $ $PYTHON <<EOF
+  $ "$PYTHON" <<EOF
   > fp = open('logfile', 'wb')
   > fp.write(b'12345678901234567890123456789012345678901234567890' +
   >          b'12345') # there are 5 more columns for 80 columns
--- a/tests/test-histedit-base.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-histedit-base.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,7 +16,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-histedit-commute.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-histedit-commute.t	Wed Sep 26 20:33:09 2018 +0900
@@ -364,7 +364,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
-  new changesets 141947992243:bd22688093b3
+  new changesets 141947992243:bd22688093b3 (3 drafts)
   (run 'hg update' to get a working copy)
   $ hg co tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-http-api-httpv2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http-api-httpv2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -18,7 +18,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     GET /api/exp-http-v2-0001 HTTP/1.1\r\n
+  s>     GET /api/exp-http-v2-0002 HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -30,7 +30,7 @@
   s>     Content-Type: text/plain\r\n
   s>     Content-Length: 33\r\n
   s>     \r\n
-  s>     API exp-http-v2-0001 not enabled\n
+  s>     API exp-http-v2-0002 not enabled\n
 
 Restart server with support for HTTP v2 API
 
@@ -46,7 +46,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/badcommand HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/badcommand HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -67,7 +67,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     GET /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
+  s>     GET /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -88,7 +88,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -110,7 +110,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: invalid\r\n
   s>     user-agent: test\r\n
@@ -134,7 +134,7 @@
   >     content-type: badmedia
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: badmedia\r\n
@@ -160,7 +160,7 @@
   >     frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     *\r\n (glob)
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -176,8 +176,14 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     32\r\n
-  s>     *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
+  s>     \r\n
+  s>     27\r\n
+  s>     \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
+  s>     \r\n
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
   s>     \r\n
   s>     0\r\n
   s>     \r\n
@@ -187,7 +193,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending customreadonly command
-  s>     POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -203,14 +209,25 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     32\r\n
-  s>     *\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBokX\x1dcustomreadonly bytes response
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=42; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     27\r\n
+  s>     \x1f\x00\x00\x01\x00\x02\x001
+  s>     X\x1dcustomreadonly bytes response
+  s>     \r\n
+  received frame(size=31; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: [{b'status': b'ok'}, b'customreadonly bytes response']
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    b'customreadonly bytes response'
+  ]
 
 Request to read-write command fails because server is read-only by default
 
@@ -221,7 +238,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     GET /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
+  s>     GET /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -242,7 +259,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     GET /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
+  s>     GET /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -263,7 +280,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -301,7 +318,7 @@
   >     frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/rw/customreadonly HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/rw/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -317,8 +334,14 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     32\r\n
-  s>     *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
+  s>     \r\n
+  s>     27\r\n
+  s>     \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
+  s>     \r\n
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
   s>     \r\n
   s>     0\r\n
   s>     \r\n
@@ -331,7 +354,7 @@
   >     accept: $MEDIATYPE
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/rw/badcommand HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/rw/badcommand HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     user-agent: test\r\n
@@ -353,7 +376,7 @@
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -393,7 +416,7 @@
   >     frame 1 1 stream-begin command-request new cbor:{b'name': b'command1', b'args': {b'foo': b'val1', b'bar1': b'val'}}
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/debugreflect HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/debugreflect HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -401,7 +424,7 @@
   s>     content-length: 47\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
   s>     \r\n
-  s>     \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1
+  s>     \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1
   s> makefile('rb', None)
   s>     HTTP/1.1 200 OK\r\n
   s>     Server: testing stub value\r\n
@@ -409,7 +432,7 @@
   s>     Content-Type: text/plain\r\n
   s>     Content-Length: 205\r\n
   s>     \r\n
-  s>     received: 1 1 1 \xa2Dargs\xa2CfooDval1Dbar1CvalDnameHcommand1\n
+  s>     received: 1 1 1 \xa2Dargs\xa2Dbar1CvalCfooDval1DnameHcommand1\n
   s>     ["runcommand", {"args": {"bar1": "val", "foo": "val1"}, "command": "command1", "data": null, "requestid": 1}]\n
   s>     received: <no frame>\n
   s>     {"action": "noop"}
@@ -424,7 +447,7 @@
   >     frame 1 1 stream-begin command-request new cbor:{b'name': b'customreadonly'}
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/customreadonly HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/customreadonly HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -440,8 +463,14 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     32\r\n
-  s>     *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
+  s>     \r\n
+  s>     27\r\n
+  s>     \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
+  s>     \r\n
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
   s>     \r\n
   s>     0\r\n
   s>     \r\n
@@ -457,7 +486,7 @@
   >     frame 3 1 0 command-request new cbor:{b'name': b'customreadonly'}
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     *\r\n (glob)
   s>     *\r\n (glob)
@@ -473,11 +502,23 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     32\r\n
-  s>     *\x00\x00\x01\x00\x02\x012\xa1FstatusBokX\x1dcustomreadonly bytes response
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011\xa1FstatusBok
+  s>     \r\n
+  s>     27\r\n
+  s>     \x1f\x00\x00\x01\x00\x02\x001X\x1dcustomreadonly bytes response
+  s>     \r\n
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
   s>     \r\n
-  s>     32\r\n
-  s>     *\x00\x00\x03\x00\x02\x002\xa1FstatusBokX\x1dcustomreadonly bytes response
+  s>     13\r\n
+  s>     \x0b\x00\x00\x03\x00\x02\x001\xa1FstatusBok
+  s>     \r\n
+  s>     27\r\n
+  s>     \x1f\x00\x00\x03\x00\x02\x001X\x1dcustomreadonly bytes response
+  s>     \r\n
+  s>     8\r\n
+  s>     \x00\x00\x00\x03\x00\x02\x002
   s>     \r\n
   s>     0\r\n
   s>     \r\n
@@ -495,7 +536,7 @@
   >     frame 1 1 0 command-request continuation IbookmarksDnameHlistkeys
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -511,11 +552,23 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     33\r\n
-  s>     +\x00\x00\x03\x00\x02\x012\xa1FstatusBok\xa3Fphases@Ibookmarks@Jnamespaces@
+  s>     13\r\n
+  s>     \x0b\x00\x00\x03\x00\x02\x011\xa1FstatusBok
+  s>     \r\n
+  s>     28\r\n
+  s>      \x00\x00\x03\x00\x02\x001\xa3Ibookmarks@Jnamespaces@Fphases@
+  s>     \r\n
+  s>     8\r\n
+  s>     \x00\x00\x00\x03\x00\x02\x002
   s>     \r\n
-  s>     14\r\n
-  s>     \x0c\x00\x00\x01\x00\x02\x002\xa1FstatusBok\xa0
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x001\xa1FstatusBok
+  s>     \r\n
+  s>     9\r\n
+  s>     \x01\x00\x00\x01\x00\x02\x001\xa0
+  s>     \r\n
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
   s>     \r\n
   s>     0\r\n
   s>     \r\n
@@ -545,7 +598,7 @@
   >     frame 1 1 stream-begin command-request new cbor:{b'name': b'pushkey'}
   > EOF
   using raw connection to peer
-  s>     POST /api/exp-http-v2-0001/ro/multirequest HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/multirequest HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
--- a/tests/test-http-api.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http-api.t	Wed Sep 26 20:33:09 2018 +0900
@@ -218,11 +218,11 @@
 Accessing a known but not enabled API yields a different error
 
   $ send << EOF
-  > httprequest GET api/exp-http-v2-0001
+  > httprequest GET api/exp-http-v2-0002
   >     user-agent: test
   > EOF
   using raw connection to peer
-  s>     GET /api/exp-http-v2-0001 HTTP/1.1\r\n
+  s>     GET /api/exp-http-v2-0002 HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -234,7 +234,7 @@
   s>     Content-Type: text/plain\r\n
   s>     Content-Length: 33\r\n
   s>     \r\n
-  s>     API exp-http-v2-0001 not enabled\n
+  s>     API exp-http-v2-0002 not enabled\n
 
 Restart server with support for HTTP v2 API
 
@@ -269,7 +269,7 @@
   s>     \r\n
   s>     APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
   s>     \n
-  s>     exp-http-v2-0001
+  s>     exp-http-v2-0002
 
   $ send << EOF
   > httprequest GET api/
@@ -290,4 +290,4 @@
   s>     \r\n
   s>     APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
   s>     \n
-  s>     exp-http-v2-0001
+  s>     exp-http-v2-0002
--- a/tests/test-http-bad-server.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http-bad-server.t	Wed Sep 26 20:33:09 2018 +0900
@@ -118,9 +118,9 @@
   write(23) -> Server: badhttpserver\r\n
   write(37) -> Date: $HTTP_DATE$\r\n
   write(41) -> Content-Type: application/mercurial-0.1\r\n
-  write(21) -> Content-Length: 436\r\n
+  write(21) -> Content-Length: 450\r\n
   write(2) -> \r\n
-  write(436) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(450) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(4? from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob)
   readline(1? from -1) -> (1?) Accept-Encoding* (glob)
   read limit reached; closing socket
@@ -159,9 +159,9 @@
   write(23) -> Server: badhttpserver\r\n
   write(37) -> Date: $HTTP_DATE$\r\n
   write(41) -> Content-Type: application/mercurial-0.1\r\n
-  write(21) -> Content-Length: 436\r\n
+  write(21) -> Content-Length: 450\r\n
   write(2) -> \r\n
-  write(436) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(450) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(13? from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob)
   readline(1?? from -1) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(8? from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -216,9 +216,9 @@
   write(23) -> Server: badhttpserver\r\n
   write(37) -> Date: $HTTP_DATE$\r\n
   write(41) -> Content-Type: application/mercurial-0.1\r\n
-  write(21) -> Content-Length: 449\r\n
+  write(21) -> Content-Length: 463\r\n
   write(2) -> \r\n
-  write(449) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(463) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(1?? from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n (glob)
   readline(1?? from -1) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(1?? from -1) -> (41) content-type: application/mercurial-0.1\r\n (glob)
@@ -275,7 +275,7 @@
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: HTTP request error (incomplete response; expected 416 bytes got 20)
+  abort: HTTP request error (incomplete response; expected 450 bytes got 20)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
@@ -292,16 +292,16 @@
   write(23 from 23) -> (121) Server: badhttpserver\r\n
   write(37 from 37) -> (84) Date: $HTTP_DATE$\r\n
   write(41 from 41) -> (43) Content-Type: application/mercurial-0.1\r\n
-  write(21 from 21) -> (22) Content-Length: 436\r\n
+  write(21 from 21) -> (22) Content-Length: 450\r\n
   write(2 from 2) -> (20) \r\n
-  write(20 from 436) -> (0) batch branchmap bund
+  write(20 from 450) -> (0) batch branchmap bund
   write limit reached; closing socket
 
   $ rm -f error.log
 
 Server sends incomplete headers for batch request
 
-  $ hg serve --config badserver.closeaftersendbytes=714 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=728 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO this output is horrible
@@ -323,13 +323,13 @@
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
   readline(-1) -> (2) \r\n
-  write(36 from 36) -> (678) HTTP/1.1 200 Script output follows\r\n
-  write(23 from 23) -> (655) Server: badhttpserver\r\n
-  write(37 from 37) -> (618) Date: $HTTP_DATE$\r\n
-  write(41 from 41) -> (577) Content-Type: application/mercurial-0.1\r\n
-  write(21 from 21) -> (556) Content-Length: 436\r\n
-  write(2 from 2) -> (554) \r\n
-  write(436 from 436) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(36 from 36) -> (692) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (669) Server: badhttpserver\r\n
+  write(37 from 37) -> (632) Date: $HTTP_DATE$\r\n
+  write(41 from 41) -> (591) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (570) Content-Length: 450\r\n
+  write(2 from 2) -> (568) \r\n
+  write(450 from 450) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -350,7 +350,7 @@
 
 Server sends an incomplete HTTP response body to batch request
 
-  $ hg serve --config badserver.closeaftersendbytes=779 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=793 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO client spews a stack due to uncaught ValueError in batch.results()
@@ -371,13 +371,13 @@
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
   readline(-1) -> (2) \r\n
-  write(36 from 36) -> (743) HTTP/1.1 200 Script output follows\r\n
-  write(23 from 23) -> (720) Server: badhttpserver\r\n
-  write(37 from 37) -> (683) Date: $HTTP_DATE$\r\n
-  write(41 from 41) -> (642) Content-Type: application/mercurial-0.1\r\n
-  write(21 from 21) -> (621) Content-Length: 436\r\n
-  write(2 from 2) -> (619) \r\n
-  write(436 from 436) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(36 from 36) -> (757) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (734) Server: badhttpserver\r\n
+  write(37 from 37) -> (697) Date: $HTTP_DATE$\r\n
+  write(41 from 41) -> (656) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (635) Content-Length: 450\r\n
+  write(2 from 2) -> (633) \r\n
+  write(450 from 450) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -400,7 +400,7 @@
 
 Server sends incomplete headers for getbundle response
 
-  $ hg serve --config badserver.closeaftersendbytes=926 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=940 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO this output is terrible
@@ -423,13 +423,13 @@
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
   readline(-1) -> (2) \r\n
-  write(36 from 36) -> (890) HTTP/1.1 200 Script output follows\r\n
-  write(23 from 23) -> (867) Server: badhttpserver\r\n
-  write(37 from 37) -> (830) Date: $HTTP_DATE$\r\n
-  write(41 from 41) -> (789) Content-Type: application/mercurial-0.1\r\n
-  write(21 from 21) -> (768) Content-Length: 436\r\n
-  write(2 from 2) -> (766) \r\n
-  write(436 from 436) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(36 from 36) -> (904) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (881) Server: badhttpserver\r\n
+  write(37 from 37) -> (844) Date: $HTTP_DATE$\r\n
+  write(41 from 41) -> (803) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (782) Content-Length: 450\r\n
+  write(2 from 2) -> (780) \r\n
+  write(450 from 450) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -464,9 +464,29 @@
 
   $ rm -f error.log
 
+Server stops before it sends transfer encoding
+
+  $ hg serve --config badserver.closeaftersendbytes=973 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  abort: stream ended unexpectedly (got 0 bytes, expected 1)
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -4 error.log
+  write(41 from 41) -> (25) Content-Type: application/mercurial-0.2\r\n
+  write(25 from 28) -> (0) Transfer-Encoding: chunke
+  write limit reached; closing socket
+  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
+
+  $ rm -f error.log
+
 Server sends empty HTTP body for getbundle
 
-  $ hg serve --config badserver.closeaftersendbytes=964 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=978 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -484,13 +504,13 @@
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
   readline(-1) -> (2) \r\n
-  write(36 from 36) -> (928) HTTP/1.1 200 Script output follows\r\n
-  write(23 from 23) -> (905) Server: badhttpserver\r\n
-  write(37 from 37) -> (868) Date: $HTTP_DATE$\r\n
-  write(41 from 41) -> (827) Content-Type: application/mercurial-0.1\r\n
-  write(21 from 21) -> (806) Content-Length: 436\r\n
-  write(2 from 2) -> (804) \r\n
-  write(436 from 436) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(36 from 36) -> (942) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (919) Server: badhttpserver\r\n
+  write(37 from 37) -> (882) Date: $HTTP_DATE$\r\n
+  write(41 from 41) -> (841) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (820) Content-Length: 450\r\n
+  write(2 from 2) -> (818) \r\n
+  write(450 from 450) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -529,7 +549,7 @@
 
 Server sends partial compression string
 
-  $ hg serve --config badserver.closeaftersendbytes=988 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1002 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -547,13 +567,13 @@
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
   readline(-1) -> (2) \r\n
-  write(36 from 36) -> (952) HTTP/1.1 200 Script output follows\r\n
-  write(23 from 23) -> (929) Server: badhttpserver\r\n
-  write(37 from 37) -> (892) Date: $HTTP_DATE$\r\n
-  write(41 from 41) -> (851) Content-Type: application/mercurial-0.1\r\n
-  write(21 from 21) -> (830) Content-Length: 436\r\n
-  write(2 from 2) -> (828) \r\n
-  write(436 from 436) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(36 from 36) -> (966) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (943) Server: badhttpserver\r\n
+  write(37 from 37) -> (906) Date: $HTTP_DATE$\r\n
+  write(41 from 41) -> (865) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (844) Content-Length: 450\r\n
+  write(2 from 2) -> (842) \r\n
+  write(450 from 450) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -595,12 +615,12 @@
 
 Server sends partial bundle2 header magic
 
-  $ hg serve --config badserver.closeaftersendbytes=985 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=999 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
   requesting all changes
-  abort: HTTP request error (incomplete response; expected 1 bytes got 3)
+  abort: HTTP request error (incomplete response; expected 4 bytes got 3)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
@@ -619,12 +639,12 @@
 
 Server sends incomplete bundle2 stream params length
 
-  $ hg serve --config badserver.closeaftersendbytes=994 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1008 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
   requesting all changes
-  abort: HTTP request error (incomplete response; expected 1 bytes got 3)
+  abort: HTTP request error (incomplete response; expected 4 bytes got 3)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
@@ -644,7 +664,7 @@
 
 Servers stops after bundle2 stream params header
 
-  $ hg serve --config badserver.closeaftersendbytes=997 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1011 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -669,7 +689,7 @@
 
 Server stops sending after bundle2 part header length
 
-  $ hg serve --config badserver.closeaftersendbytes=1006 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1020 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -695,7 +715,7 @@
 
 Server stops sending after bundle2 part header
 
-  $ hg serve --config badserver.closeaftersendbytes=1053 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1067 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -725,7 +745,7 @@
 
 Server stops after bundle2 part payload chunk size
 
-  $ hg serve --config badserver.closeaftersendbytes=1074 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1088 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -733,7 +753,7 @@
   adding changesets
   transaction abort!
   rollback completed
-  abort: HTTP request error (incomplete response; expected 459 bytes got 7)
+  abort: HTTP request error (incomplete response; expected 466 bytes got 7)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
@@ -756,7 +776,7 @@
 
 Server stops sending in middle of bundle2 payload chunk
 
-  $ hg serve --config badserver.closeaftersendbytes=1535 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1549 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -788,7 +808,7 @@
 
 Server stops sending after 0 length payload chunk size
 
-  $ hg serve --config badserver.closeaftersendbytes=1566 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1580 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -799,7 +819,7 @@
   added 1 changesets with 1 changes to 1 files
   transaction abort!
   rollback completed
-  abort: HTTP request error (incomplete response; expected 23 bytes got 9)
+  abort: HTTP request error (incomplete response; expected 32 bytes got 9)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
@@ -825,8 +845,8 @@
 Server stops sending after 0 part bundle part header (indicating end of bundle2 payload)
 This is before the 0 size chunked transfer part that signals end of HTTP response.
 
-#  $ hg serve --config badserver.closeaftersendbytes=1741 -p $HGPORT -d --pid-file=hg.pid -E error.log
-  $ hg serve --config badserver.closeaftersendbytes=1848 -p $HGPORT -d --pid-file=hg.pid -E error.log
+#  $ hg serve --config badserver.closeaftersendbytes=1755 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1862 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -870,7 +890,7 @@
 
 Server sends a size 0 chunked-transfer size without terminating \r\n
 
-  $ hg serve --config badserver.closeaftersendbytes=1851 -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1865 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
--- a/tests/test-http-bundle1.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http-bundle1.t	Wed Sep 26 20:33:09 2018 +0900
@@ -49,7 +49,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 1 changesets, 4 total revisions
+  checked 1 changesets with 4 changes to 4 files
 #endif
 
 try to clone via stream, should use pull instead
@@ -101,7 +101,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 1 changesets, 4 total revisions
+  checked 1 changesets with 4 changes to 4 files
   $ cd test
   $ echo bar > bar
   $ hg commit -A -d '1 0' -m 2
--- a/tests/test-http-clone-r.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http-clone-r.t	Wed Sep 26 20:33:09 2018 +0900
@@ -9,7 +9,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets bfaf4b5cbf01:916f1afdef90
+  new changesets bfaf4b5cbf01:916f1afdef90 (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -40,7 +40,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -52,7 +52,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -64,7 +64,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -76,7 +76,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 4 changesets, 4 total revisions
+  checked 4 changesets with 4 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -88,7 +88,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -100,7 +100,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -112,7 +112,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 4 changesets, 5 total revisions
+  checked 4 changesets with 5 changes to 2 files
   adding changesets
   adding manifests
   adding file changes
@@ -124,7 +124,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 5 changesets, 6 total revisions
+  checked 5 changesets with 6 changes to 3 files
   adding changesets
   adding manifests
   adding file changes
@@ -136,7 +136,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
   $ cd test-8
   $ hg pull ../test-7
   pulling from ../test-7
@@ -152,7 +152,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
   $ cd ..
   $ cd test-1
   $ hg pull -r 4 http://localhost:$HGPORT/
@@ -169,7 +169,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 2 total revisions
+  checked 3 changesets with 2 changes to 1 files
   $ hg pull http://localhost:$HGPORT/
   pulling from http://localhost:$HGPORT/
   searching for changes
@@ -195,7 +195,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 5 changesets, 3 total revisions
+  checked 5 changesets with 3 changes to 1 files
   $ hg pull http://localhost:$HGPORT/
   pulling from http://localhost:$HGPORT/
   searching for changes
@@ -210,7 +210,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
   $ cd ..
 
 no default destination if url has no path:
--- a/tests/test-http-permissions.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http-permissions.t	Wed Sep 26 20:33:09 2018 +0900
@@ -13,16 +13,16 @@
   >         return super(testenvhgweb, self).__call__(env, respond)
   > hgweb_mod.hgweb = testenvhgweb
   > 
-  > @wireprotov1server.wireprotocommand('customreadnoperm')
+  > @wireprotov1server.wireprotocommand(b'customreadnoperm')
   > def customread(repo, proto):
   >     return b'read-only command no defined permissions\n'
-  > @wireprotov1server.wireprotocommand('customwritenoperm')
+  > @wireprotov1server.wireprotocommand(b'customwritenoperm')
   > def customwritenoperm(repo, proto):
   >     return b'write command no defined permissions\n'
-  > @wireprotov1server.wireprotocommand('customreadwithperm', permission='pull')
+  > @wireprotov1server.wireprotocommand(b'customreadwithperm', permission=b'pull')
   > def customreadwithperm(repo, proto):
   >     return b'read-only command w/ defined permissions\n'
-  > @wireprotov1server.wireprotocommand('customwritewithperm', permission='push')
+  > @wireprotov1server.wireprotocommand(b'customwritewithperm', permission=b'push')
   > def customwritewithperm(repo, proto):
   >     return b'write command w/ defined permissions\n'
   > EOF
--- a/tests/test-http-protocol.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http-protocol.t	Wed Sep 26 20:33:09 2018 +0900
@@ -192,7 +192,7 @@
   s>     Content-Type: application/mercurial-0.1\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   sending listkeys command
   s>     GET /?cmd=listkeys HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
@@ -213,7 +213,11 @@
   s>     bookmarks\t\n
   s>     namespaces\t\n
   s>     phases\t
-  response: {b'bookmarks': b'', b'namespaces': b'', b'phases': b''}
+  response: {
+    b'bookmarks': b'',
+    b'namespaces': b'',
+    b'phases': b''
+  }
 
 Same thing, but with "httprequest" command
 
@@ -249,7 +253,7 @@
   s>     Accept-Encoding: identity\r\n
   s>     vary: X-HgProto-1,X-HgUpgrade-1\r\n
   s>     x-hgproto-1: cbor\r\n
-  s>     x-hgupgrade-1: exp-http-v2-0001\r\n
+  s>     x-hgupgrade-1: exp-http-v2-0002\r\n
   s>     accept: application/mercurial-0.1\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
   s>     user-agent: Mercurial debugwireproto\r\n
@@ -261,7 +265,7 @@
   s>     Content-Type: application/mercurial-0.1\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   sending heads command
   s>     GET /?cmd=heads HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
@@ -279,7 +283,9 @@
   s>     Content-Length: 41\r\n
   s>     \r\n
   s>     0000000000000000000000000000000000000000\n
-  response: [b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00']
+  response: [
+    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+  ]
 
   $ killdaemons.py
   $ enablehttpv2 empty
@@ -295,7 +301,7 @@
   s>     Accept-Encoding: identity\r\n
   s>     vary: X-HgProto-1,X-HgUpgrade-1\r\n
   s>     x-hgproto-1: cbor\r\n
-  s>     x-hgupgrade-1: exp-http-v2-0001\r\n
+  s>     x-hgupgrade-1: exp-http-v2-0002\r\n
   s>     accept: application/mercurial-0.1\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
   s>     user-agent: Mercurial debugwireproto\r\n
@@ -307,9 +313,9 @@
   s>     Content-Type: application/mercurial-cbor\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x81\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0005GapibaseDapi/Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   sending heads command
-  s>     POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -325,14 +331,25 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     29\r\n
-  s>     !\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=33; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     1e\r\n
+  s>     \x16\x00\x00\x01\x00\x02\x001
+  s>     \x81T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  s>     \r\n
+  received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: [b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00']
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: [
+    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+  ]
 
   $ killdaemons.py
 
@@ -407,9 +424,9 @@
   s>     Server: testing stub value\r\n
   s>     Date: $HTTP_DATE$\r\n
   s>     Content-Type: application/mercurial-0.1\r\n
-  s>     Content-Length: 453\r\n
+  s>     Content-Length: 467\r\n
   s>     \r\n
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
 
 Test with the HTTP peer
 
@@ -442,10 +459,10 @@
   s>     Server: testing stub value\r\n
   s>     Date: $HTTP_DATE$\r\n
   s>     Content-Type: application/mercurial-0.1\r\n
-  s>     Content-Length: 453\r\n
+  s>     Content-Length: 467\r\n
   s>     \r\n
   real URL is http://$LOCALIP:$HGPORT/redirected (glob)
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   sending heads command
   s>     GET /redirected?cmd=heads HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
@@ -463,7 +480,9 @@
   s>     Content-Length: 41\r\n
   s>     \r\n
   s>     96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
-  response: [b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL']
+  response: [
+    b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
+  ]
 
   $ killdaemons.py
 
@@ -704,10 +723,10 @@
   s>     Server: testing stub value\r\n
   s>     Date: $HTTP_DATE$\r\n
   s>     Content-Type: application/mercurial-0.1\r\n
-  s>     Content-Length: 453\r\n
+  s>     Content-Length: 467\r\n
   s>     \r\n
   real URL is http://$LOCALIP:$HGPORT/redirected (glob)
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   sending heads command
   s>     GET /redirected?cmd=heads HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
@@ -725,4 +744,6 @@
   s>     Content-Length: 41\r\n
   s>     \r\n
   s>     96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n
-  response: [b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL']
+  response: [
+    b'\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL'
+  ]
--- a/tests/test-http-proxy.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http-proxy.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,11 +16,9 @@
 
   $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --stream http://localhost:$HGPORT/ b
   streaming all changes
-  3 files to transfer, 303 bytes of data (reporevlogstore !)
+  6 files to transfer, 412 bytes of data (reporevlogstore !)
   4 files to transfer, 330 bytes of data (reposimplestore !)
   transferred * bytes in * seconds (*/sec) (glob)
-  searching for changes
-  no changes found
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd b
@@ -29,7 +27,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   $ cd ..
 
 url for proxy, pull
@@ -49,7 +47,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   $ cd ..
 
 host:port for proxy
@@ -108,10 +106,8 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat proxy.log
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=branchmap HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
-  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
-  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D83180e7845de420a1bb46896fd5fe05294f8d629 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
-  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&stream=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
   $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (glob)
--- a/tests/test-http.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-http.t	Wed Sep 26 20:33:09 2018 +0900
@@ -29,10 +29,8 @@
 #if no-reposimplestore
   $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
   streaming all changes
-  6 files to transfer, 606 bytes of data
+  9 files to transfer, 715 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
-  searching for changes
-  no changes found
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy
@@ -40,7 +38,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 1 changesets, 4 total revisions
+  checked 1 changesets with 4 changes to 4 files
 #endif
 
 try to clone via stream, should use pull instead
@@ -92,7 +90,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 1 changesets, 4 total revisions
+  checked 1 changesets with 4 changes to 4 files
   $ cd test
   $ echo bar > bar
   $ hg commit -A -d '1 0' -m 2
@@ -219,10 +217,8 @@
 #if no-reposimplestore
   $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
   streaming all changes
-  7 files to transfer, 916 bytes of data
-  transferred * bytes in * seconds (*/sec) (glob)
-  searching for changes
-  no changes found
+  10 files to transfer, 1.01 KB of data
+  transferred * KB in * seconds (*/sec) (glob)
   updating to branch default
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
 #endif
@@ -373,10 +369,8 @@
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
   "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
   "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
-  "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
-  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
+  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&stream=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
   "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
   "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
@@ -470,13 +464,11 @@
   streaming all changes
   * files to transfer, * of data (glob)
   transferred * in * seconds (*/sec) (glob)
-  searching for changes
-  no changes found
   $ cat error.log
 #endif
 
 ... and also keep partial clones and pulls working
-  $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
+  $ hg clone http://localhost:$HGPORT1 --rev 0 test/partial/clone
   adding changesets
   adding manifests
   adding file changes
@@ -484,7 +476,7 @@
   new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg pull -R test-partial-clone
+  $ hg pull -R test/partial/clone
   pulling from http://localhost:$HGPORT1/
   searching for changes
   adding changesets
@@ -494,6 +486,13 @@
   new changesets 5fed3813f7f5:56f9bc90cce6
   (run 'hg update' to get a working copy)
 
+  $ hg clone -U -r 0 test/partial/clone test/another/clone
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
+
 corrupt cookies file should yield a warning
 
   $ cat > $TESTTMP/cookies.txt << EOF
--- a/tests/test-https.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-https.t	Wed Sep 26 20:33:09 2018 +0900
@@ -195,7 +195,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 1 changesets, 4 total revisions
+  checked 1 changesets with 4 changes to 4 files
   $ cd test
   $ echo bar > bar
   $ hg commit -A -d '1 0' -m 2
--- a/tests/test-i18n.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-i18n.t	Wed Sep 26 20:33:09 2018 +0900
@@ -45,8 +45,8 @@
 tool itself by doctest
 
   $ cd "$TESTDIR"/../i18n
-  $ $PYTHON check-translation.py *.po
-  $ $PYTHON check-translation.py --doctest
+  $ "$PYTHON" check-translation.py *.po
+  $ "$PYTHON" check-translation.py --doctest
   $ cd $TESTTMP
 
 #if gettext
--- a/tests/test-identify.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-identify.t	Wed Sep 26 20:33:09 2018 +0900
@@ -49,9 +49,9 @@
     "bookmarks": [],
     "branch": "default",
     "dirty": "",
-    "id": "cb9a9f314b8b",
+    "id": "cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b",
     "node": "ffffffffffffffffffffffffffffffffffffffff",
-    "parents": [{"node": "cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b", "rev": 0}],
+    "parents": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
     "tags": ["tip"]
    }
   ]
@@ -63,7 +63,7 @@
   $ hg id -T '{parents % "{rev} {node|shortest} {desc}\n"}'
   0 cb9a a
   $ hg id -T '{parents}\n'
-  0:cb9a9f314b8b
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
 
 test nested template: '{tags}'/'{node}' constants shouldn't override the
 default keywords, but '{id}' persists because there's no default keyword
@@ -71,7 +71,7 @@
 
   $ hg id -T '{tags}\n'
   tip
-  $ hg id -T '{revset("null:.") % "{rev}:{node|short} {tags} {id}\n"}'
+  $ hg id -T '{revset("null:.") % "{rev}:{node|short} {tags} {id|short}\n"}'
   -1:000000000000  cb9a9f314b8b
   0:cb9a9f314b8b tip cb9a9f314b8b
 
@@ -86,9 +86,9 @@
     "bookmarks": [],
     "branch": "default",
     "dirty": "+",
-    "id": "cb9a9f314b8b+",
+    "id": "cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b+",
     "node": "ffffffffffffffffffffffffffffffffffffffff",
-    "parents": [{"node": "cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b", "rev": 0}],
+    "parents": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
     "tags": ["tip"]
    }
   ]
--- a/tests/test-impexp-branch.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-impexp-branch.t	Wed Sep 26 20:33:09 2018 +0900
@@ -32,12 +32,12 @@
   $ hg export 1 > ../r1.patch
   $ cd ..
 
-  $ if $PYTHON findbranch.py < r0.patch; then
+  $ if "$PYTHON" findbranch.py < r0.patch; then
   >     echo "Export of default branch revision has Branch header" 1>&2
   >     exit 1
   > fi
 
-  $ if $PYTHON findbranch.py < r1.patch; then
+  $ if "$PYTHON" findbranch.py < r1.patch; then
   >     :  # Do nothing
   > else
   >     echo "Export of branch revision is missing Branch header" 1>&2
--- a/tests/test-import-bypass.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-import-bypass.t	Wed Sep 26 20:33:09 2018 +0900
@@ -227,7 +227,7 @@
 (this also tests that editor is not invoked for '--bypass', if the
 commit message is explicitly specified, regardless of '--edit')
 
-  $ $PYTHON -c 'open("a", "wb").write(b"a\r\n")'
+  $ "$PYTHON" -c 'open("a", "wb").write(b"a\r\n")'
   $ hg ci -m makeacrlf
   $ HGEDITOR=cat hg import -m 'should fail because of eol' --edit --bypass ../test.diff
   applying ../test.diff
--- a/tests/test-import-context.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-import-context.t	Wed Sep 26 20:33:09 2018 +0900
@@ -26,10 +26,10 @@
 
   $ hg init repo
   $ cd repo
-  $ $PYTHON ../writepatterns.py a 0 5A 1B 5C 1D
-  $ $PYTHON ../writepatterns.py b 1 1A 1B
-  $ $PYTHON ../writepatterns.py c 1 5A
-  $ $PYTHON ../writepatterns.py d 1 5A 1B
+  $ "$PYTHON" ../writepatterns.py a 0 5A 1B 5C 1D
+  $ "$PYTHON" ../writepatterns.py b 1 1A 1B
+  $ "$PYTHON" ../writepatterns.py c 1 5A
+  $ "$PYTHON" ../writepatterns.py d 1 5A 1B
   $ hg add
   adding a
   adding b
@@ -114,13 +114,13 @@
 
 What's in a
 
-  $ $PYTHON ../cat.py a
+  $ "$PYTHON" ../cat.py a
   'A\nA\nA\nA\nA\nE\nC\nC\nC\nC\nC\nF\nF\n'
-  $ $PYTHON ../cat.py newnoeol
+  $ "$PYTHON" ../cat.py newnoeol
   'a\nb'
-  $ $PYTHON ../cat.py c
+  $ "$PYTHON" ../cat.py c
   'A\nA\nA\nA\nA\nB\nB\n'
-  $ $PYTHON ../cat.py d
+  $ "$PYTHON" ../cat.py d
   'A\nA\nA\nA\n'
 
   $ cd ..
--- a/tests/test-import-eol.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-import-eol.t	Wed Sep 26 20:33:09 2018 +0900
@@ -29,14 +29,14 @@
 
 Test different --eol values
 
-  $ $PYTHON -c 'open("a", "wb").write(b"a\nbbb\ncc\n\nd\ne")'
+  $ "$PYTHON" -c 'open("a", "wb").write(b"a\nbbb\ncc\n\nd\ne")'
   $ hg ci -Am adda
   adding .hgignore
   adding a
-  $ $PYTHON ../makepatch.py empty:lf eol.diff
-  $ $PYTHON ../makepatch.py empty:crlf eol-empty-crlf.diff
-  $ $PYTHON ../makepatch.py empty:stripped-lf eol-empty-stripped-lf.diff
-  $ $PYTHON ../makepatch.py empty:stripped-crlf eol-empty-stripped-crlf.diff
+  $ "$PYTHON" ../makepatch.py empty:lf eol.diff
+  $ "$PYTHON" ../makepatch.py empty:crlf eol-empty-crlf.diff
+  $ "$PYTHON" ../makepatch.py empty:stripped-lf eol-empty-stripped-lf.diff
+  $ "$PYTHON" ../makepatch.py empty:stripped-crlf eol-empty-stripped-crlf.diff
 
 invalid eol
 
@@ -116,7 +116,7 @@
 
 auto EOL on CRLF file
 
-  $ $PYTHON -c 'open("a", "wb").write(b"a\r\nbbb\r\ncc\r\n\r\nd\r\ne")'
+  $ "$PYTHON" -c 'open("a", "wb").write(b"a\r\nbbb\r\ncc\r\n\r\nd\r\ne")'
   $ hg commit -m 'switch EOLs in a'
   $ hg --traceback --config patch.eol='auto' import eol.diff
   applying eol.diff
@@ -132,11 +132,11 @@
 
 auto EOL on new file or source without any EOL
 
-  $ $PYTHON -c 'open("noeol", "wb").write(b"noeol")'
+  $ "$PYTHON" -c 'open("noeol", "wb").write(b"noeol")'
   $ hg add noeol
   $ hg commit -m 'add noeol'
-  $ $PYTHON -c 'open("noeol", "wb").write(b"noeol\r\nnoeol\n")'
-  $ $PYTHON -c 'open("neweol", "wb").write(b"neweol\nneweol\r\n")'
+  $ "$PYTHON" -c 'open("noeol", "wb").write(b"noeol\r\nnoeol\n")'
+  $ "$PYTHON" -c 'open("neweol", "wb").write(b"neweol\nneweol\r\n")'
   $ hg add neweol
   $ hg diff --git > noeol.diff
   $ hg revert --no-backup noeol neweol
@@ -154,10 +154,10 @@
 
 Test --eol and binary patches
 
-  $ $PYTHON -c 'open("b", "wb").write(b"a\x00\nb\r\nd")'
+  $ "$PYTHON" -c 'open("b", "wb").write(b"a\x00\nb\r\nd")'
   $ hg ci -Am addb
   adding b
-  $ $PYTHON -c 'open("b", "wb").write(b"a\x00\nc\r\nd")'
+  $ "$PYTHON" -c 'open("b", "wb").write(b"a\x00\nc\r\nd")'
   $ hg diff --git > bin.diff
   $ hg revert --no-backup b
 
--- a/tests/test-import-git.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-import-git.t	Wed Sep 26 20:33:09 2018 +0900
@@ -615,8 +615,8 @@
 Prefix with strip, renames, creates etc
 
   $ hg revert -aC
+  forgetting b
   undeleting a
-  forgetting b
   $ rm b
   $ mkdir -p dir/dir2
   $ echo b > dir/dir2/b
@@ -715,10 +715,10 @@
 
   $ hg revert -aC
   forgetting dir/a
+  forgetting dir/dir2/b2
+  reverting dir/dir2/c
   undeleting dir/d
   undeleting dir/dir2/b
-  forgetting dir/dir2/b2
-  reverting dir/dir2/c
   $ rm dir/a dir/dir2/b2
   $ hg import --similarity 90 --no-commit - <<EOF
   > diff --git a/a b/b
--- a/tests/test-import-merge.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-import-merge.t	Wed Sep 26 20:33:09 2018 +0900
@@ -164,4 +164,4 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
--- a/tests/test-import.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-import.t	Wed Sep 26 20:33:09 2018 +0900
@@ -305,7 +305,7 @@
   new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ $PYTHON mkmsg.py diffed-tip.patch msg.patch
+  $ "$PYTHON" mkmsg.py diffed-tip.patch msg.patch
   $ hg --cwd b import ../msg.patch
   applying ../msg.patch
   $ hg --cwd b tip | grep email
@@ -371,7 +371,7 @@
   new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ $PYTHON mkmsg.py exported-tip.patch msg.patch
+  $ "$PYTHON" mkmsg.py exported-tip.patch msg.patch
   $ cat msg.patch | hg --cwd b import -
   applying patch from stdin
   $ hg --cwd b tip | grep second
@@ -403,7 +403,7 @@
   new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ $PYTHON mkmsg2.py diffed-tip.patch msg.patch
+  $ "$PYTHON" mkmsg2.py diffed-tip.patch msg.patch
   $ cat msg.patch | hg --cwd b import -
   applying patch from stdin
   $ hg --cwd b tip --template '{desc}\n'
@@ -865,7 +865,7 @@
   $ hg init binaryremoval
   $ cd binaryremoval
   $ echo a > a
-  $ $PYTHON -c "open('b', 'wb').write(b'a\x00b')"
+  $ "$PYTHON" -c "open('b', 'wb').write(b'a\x00b')"
   $ hg ci -Am addall
   adding a
   adding b
@@ -1014,8 +1014,8 @@
     a
   R a
   $ hg revert -a
+  forgetting b
   undeleting a
-  forgetting b
   $ cat b
   mod b
   $ rm b
--- a/tests/test-imports-checker.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-imports-checker.t	Wed Sep 26 20:33:09 2018 +0900
@@ -8,7 +8,7 @@
 it's working correctly.
   $ TERM=dumb
   $ export TERM
-  $ $PYTHON -m doctest $import_checker
+  $ "$PYTHON" -m doctest $import_checker
 
 Run additional tests for the import checker
 
@@ -136,7 +136,7 @@
   > from . import errors
   > EOF
 
-  $ $PYTHON "$import_checker" testpackage*/*.py testpackage/subpackage/*.py \
+  $ "$PYTHON" "$import_checker" testpackage*/*.py testpackage/subpackage/*.py \
   >   email/*.py
   testpackage/importalias.py:2: ui module must be "as" aliased to uimod
   testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod
--- a/tests/test-incoming-outgoing.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-incoming-outgoing.t	Wed Sep 26 20:33:09 2018 +0900
@@ -12,7 +12,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 9 changesets, 9 total revisions
+  checked 9 changesets with 9 changes to 1 files
   $ hg serve -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
   $ cd ..
@@ -329,14 +329,14 @@
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 1 files
-  new changesets 00a43fa82f62:e4feb4ac9035
+  new changesets 00a43fa82f62:e4feb4ac9035 (9 drafts)
   (run 'hg update' to get a working copy)
   $ hg -R temp2 unbundle test2.hg
   adding changesets
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 1 files
-  new changesets 00a43fa82f62:e4feb4ac9035
+  new changesets 00a43fa82f62:e4feb4ac9035 (9 drafts)
   (run 'hg update' to get a working copy)
   $ hg -R temp tip
   changeset:   8:e4feb4ac9035
@@ -370,7 +370,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 14 changesets, 14 total revisions
+  checked 14 changesets with 14 changes to 1 files
   $ cd ..
   $ hg -R test-dev outgoing test
   comparing with test
--- a/tests/test-infinitepush-bundlestore.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-infinitepush-bundlestore.t	Wed Sep 26 20:33:09 2018 +0900
@@ -77,7 +77,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 20759b6926ce
+  new changesets 20759b6926ce (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg log -G
   o  changeset:   1:20759b6926ce
@@ -170,7 +170,7 @@
   adding file changes
   added 1 changesets with 1 changes to 2 files
   adding remote bookmark newbook
-  new changesets 1de1d7d92f89
+  new changesets 1de1d7d92f89 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg log -G -T '{desc} {phase} {bookmarks}'
   o  new scratch commit draft scratch/mybranch
--- a/tests/test-infinitepush-ci.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-infinitepush-ci.t	Wed Sep 26 20:33:09 2018 +0900
@@ -321,7 +321,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files
-  new changesets eaba929e866c:9b42578d4447
+  new changesets eaba929e866c:9b42578d4447 (1 drafts)
   (run 'hg update' to get a working copy)
 
   $ hg glog
@@ -423,7 +423,7 @@
   added 1 changesets with 0 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
-  new changesets 99949238d9ac
+  new changesets 99949238d9ac (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg glog
--- a/tests/test-infinitepush.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-infinitepush.t	Wed Sep 26 20:33:09 2018 +0900
@@ -112,7 +112,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 33910bfe6ffe
+  new changesets 33910bfe6ffe (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg log -G -T '{desc} {phase} {bookmarks}'
   o  testpullbycommithash1 draft
@@ -163,7 +163,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets a79b6597f322:c70aee6da07d
+  new changesets a79b6597f322:c70aee6da07d (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg log -r scratch/scratchontopofpublic -T '{phase}'
   draft (no-eol)
--- a/tests/test-inherit-mode.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-inherit-mode.t	Wed Sep 26 20:33:09 2018 +0900
@@ -48,7 +48,7 @@
 store can be written by the group, other files cannot
 store is setgid
 
-  $ $PYTHON ../printmodes.py .
+  $ "$PYTHON" ../printmodes.py .
   00700 ./.hg/
   00600 ./.hg/00changelog.i
   00600 ./.hg/requires
@@ -64,11 +64,12 @@
 (in particular, store/**, dirstate, branch cache file, undo files)
 new directories are setgid
 
-  $ $PYTHON ../printmodes.py .
+  $ "$PYTHON" ../printmodes.py .
   00700 ./.hg/
   00600 ./.hg/00changelog.i
   00770 ./.hg/cache/
   00660 ./.hg/cache/branch2-served
+  00660 ./.hg/cache/manifestfulltextcache (reporevlogstore !)
   00660 ./.hg/cache/rbc-names-v1
   00660 ./.hg/cache/rbc-revs-v1
   00660 ./.hg/dirstate
@@ -108,7 +109,7 @@
 before push
 group can write everything
 
-  $ $PYTHON ../printmodes.py ../push
+  $ "$PYTHON" ../printmodes.py ../push
   00770 ../push/.hg/
   00660 ../push/.hg/00changelog.i
   00660 ../push/.hg/requires
@@ -120,7 +121,7 @@
 after push
 group can still write everything
 
-  $ $PYTHON ../printmodes.py ../push
+  $ "$PYTHON" ../printmodes.py ../push
   00770 ../push/.hg/
   00660 ../push/.hg/00changelog.i
   00770 ../push/.hg/cache/
@@ -162,8 +163,8 @@
   $ mkdir dir
   $ touch dir/file
   $ hg ci -qAm 'add dir/file'
-  $ storemode=`$PYTHON ../mode.py .hg/store`
-  $ dirmode=`$PYTHON ../mode.py .hg/store/data/dir`
+  $ storemode=`"$PYTHON" ../mode.py .hg/store`
+  $ dirmode=`"$PYTHON" ../mode.py .hg/store/data/dir`
   $ if [ "$storemode" != "$dirmode" ]; then
   >  echo "$storemode != $dirmode"
   > fi
--- a/tests/test-install.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-install.t	Wed Sep 26 20:33:09 2018 +0900
@@ -205,7 +205,7 @@
   >     print('  %s' % f)
   > EOF
 
-  $ ( testrepohgenv; $PYTHON wixxml.py help )
+  $ ( testrepohgenv; "$PYTHON" wixxml.py help )
   Not installed:
     help/common.txt
     help/hg-ssh.8.txt
@@ -214,7 +214,7 @@
     help/hgrc.5.txt
   Not tracked:
 
-  $ ( testrepohgenv; $PYTHON wixxml.py templates )
+  $ ( testrepohgenv; "$PYTHON" wixxml.py templates )
   Not installed:
   Not tracked:
 
@@ -231,7 +231,7 @@
 ancient virtualenv from their linux distro or similar and it's not yet
 the default for them.
   $ unset PYTHONPATH
-  $ $PYTHON -m virtualenv --no-site-packages --never-download installenv >> pip.log
+  $ "$PYTHON" -m virtualenv --no-site-packages --never-download installenv >> pip.log
 Note: we use this weird path to run pip and hg to avoid platform differences,
 since it's bin on most platforms but Scripts on Windows.
   $ ./installenv/*/pip install --no-index $TESTDIR/.. >> pip.log
--- a/tests/test-issue1175.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-issue1175.t	Wed Sep 26 20:33:09 2018 +0900
@@ -41,7 +41,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 6 changesets, 4 total revisions
+  checked 6 changesets with 4 changes to 4 files
 
   $ hg export --git tip
   # HG changeset patch
--- a/tests/test-issue4074.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-issue4074.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,12 +16,12 @@
 
 Check in a big file:
 
-  $ $PYTHON ../s.py > a
+  $ "$PYTHON" ../s.py > a
   $ hg ci -qAm0
 
 Modify it:
 
-  $ $PYTHON ../s.py > a
+  $ "$PYTHON" ../s.py > a
 
 Time a check-in, should never take more than 10 seconds user time:
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-issue5979.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,34 @@
+  $ hg init r1
+  $ cd r1
+  $ hg ci --config ui.allowemptycommit=true -m c0
+  $ hg ci --config ui.allowemptycommit=true -m c1
+  $ hg ci --config ui.allowemptycommit=true -m c2
+  $ hg co -q 0
+  $ hg ci --config ui.allowemptycommit=true -m c3
+  created new head
+  $ hg co -q 3
+  $ hg merge --quiet
+  $ hg ci --config ui.allowemptycommit=true -m c4
+
+  $ hg log -G -T'{desc}'
+  @    c4
+  |\
+  | o  c3
+  | |
+  o |  c2
+  | |
+  o |  c1
+  |/
+  o  c0
+  
+
+  >>> from mercurial.ui import ui
+  >>> from mercurial.hg import repository
+  >>> repo = repository(ui())
+  >>> for anc in repo.changelog.ancestors([4], inclusive=True):
+  ...   print(anc)
+  4
+  3
+  2
+  1
+  0
--- a/tests/test-issue660.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-issue660.t	Wed Sep 26 20:33:09 2018 +0900
@@ -66,9 +66,9 @@
 Revert all - should succeed:
 
   $ hg revert --all
-  undeleting a
   forgetting a/a
   forgetting b
+  undeleting a
   undeleting b/b
 
   $ hg st
--- a/tests/test-journal-exists.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-journal-exists.t	Wed Sep 26 20:33:09 2018 +0900
@@ -19,7 +19,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
 Check that zero-size journals are correctly aborted:
 
--- a/tests/test-journal.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-journal.t	Wed Sep 26 20:33:09 2018 +0900
@@ -149,44 +149,44 @@
     "command": "up",
     "date": [5, 0],
     "name": ".",
-    "newhashes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
-    "oldhashes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
+    "newnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
+    "oldnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
     "user": "foobar"
    },
    {
     "command": "up 0",
     "date": [2, 0],
     "name": ".",
-    "newhashes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
-    "oldhashes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
+    "newnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
+    "oldnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
     "user": "foobar"
    },
    {
     "command": "commit -Aqm b",
     "date": [1, 0],
     "name": ".",
-    "newhashes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
-    "oldhashes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
+    "newnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
+    "oldnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
     "user": "foobar"
    },
    {
     "command": "commit -Aqm a",
     "date": [0, 0],
     "name": ".",
-    "newhashes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
-    "oldhashes": ["0000000000000000000000000000000000000000"],
+    "newnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
+    "oldnodes": ["0000000000000000000000000000000000000000"],
     "user": "foobar"
    }
   ]
 
   $ cat <<EOF >> $HGRCPATH
   > [templates]
-  > j = "{oldhashes % '{node|upper}'} -> {newhashes % '{node|upper}'}
+  > j = "{oldnodes % '{node|upper}'} -> {newnodes % '{node|upper}'}
   >      - user: {user}
   >      - command: {command}
   >      - date: {date|rfc3339date}
-  >      - newhashes: {newhashes}
-  >      - oldhashes: {oldhashes}
+  >      - newnodes: {newnodes}
+  >      - oldnodes: {oldnodes}
   >      "
   > EOF
   $ hg journal -Tj -l1
@@ -195,8 +195,8 @@
   - user: foobar
   - command: up
   - date: 1970-01-01T00:00:05+00:00
-  - newhashes: 1e6c11564562b4ed919baca798bc4338bd299d6a
-  - oldhashes: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
+  - newnodes: 1e6c11564562b4ed919baca798bc4338bd299d6a
+  - oldnodes: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
 
   $ hg journal --commit
   previous locations of '.':
@@ -231,6 +231,62 @@
   summary:     a
   
 
+  $ hg journal --commit -Tjson
+  [
+   {
+    "changesets": [{"bookmarks": ["bar", "baz"], "branch": "default", "date": [0, 0], "desc": "b", "node": "1e6c11564562b4ed919baca798bc4338bd299d6a", "parents": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"], "phase": "draft", "rev": 1, "tags": ["tip"], "user": "test"}],
+    "command": "up",
+    "date": [5, 0],
+    "name": ".",
+    "newnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
+    "oldnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
+    "user": "foobar"
+   },
+   {
+    "changesets": [{"bookmarks": [], "branch": "default", "date": [0, 0], "desc": "a", "node": "cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b", "parents": ["0000000000000000000000000000000000000000"], "phase": "draft", "rev": 0, "tags": [], "user": "test"}],
+    "command": "up 0",
+    "date": [2, 0],
+    "name": ".",
+    "newnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
+    "oldnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
+    "user": "foobar"
+   },
+   {
+    "changesets": [{"bookmarks": ["bar", "baz"], "branch": "default", "date": [0, 0], "desc": "b", "node": "1e6c11564562b4ed919baca798bc4338bd299d6a", "parents": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"], "phase": "draft", "rev": 1, "tags": ["tip"], "user": "test"}],
+    "command": "commit -Aqm b",
+    "date": [1, 0],
+    "name": ".",
+    "newnodes": ["1e6c11564562b4ed919baca798bc4338bd299d6a"],
+    "oldnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
+    "user": "foobar"
+   },
+   {
+    "changesets": [{"bookmarks": [], "branch": "default", "date": [0, 0], "desc": "a", "node": "cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b", "parents": ["0000000000000000000000000000000000000000"], "phase": "draft", "rev": 0, "tags": [], "user": "test"}],
+    "command": "commit -Aqm a",
+    "date": [0, 0],
+    "name": ".",
+    "newnodes": ["cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b"],
+    "oldnodes": ["0000000000000000000000000000000000000000"],
+    "user": "foobar"
+   }
+  ]
+
+  $ hg journal --commit \
+  > -T'command: {command}\n{changesets % " rev: {rev}\n children: {children}\n"}'
+  previous locations of '.':
+  command: up
+   rev: 1
+   children: 
+  command: up 0
+   rev: 0
+   children: 1:1e6c11564562
+  command: commit -Aqm b
+   rev: 1
+   children: 
+  command: commit -Aqm a
+   rev: 0
+   children: 1:1e6c11564562
+
 Test for behaviour on unexpected storage version information
 
   $ printf '42\0' > .hg/namejournal
--- a/tests/test-keyword.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-keyword.t	Wed Sep 26 20:33:09 2018 +0900
@@ -125,7 +125,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets a2392c293916
+  new changesets a2392c293916 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up a2392c293916
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -263,7 +263,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
-  new changesets a2392c293916:ef63ca68695b
+  new changesets a2392c293916:ef63ca68695b (2 drafts)
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
@@ -842,7 +842,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 3 changesets, 4 total revisions
+  checked 3 changesets with 4 changes to 3 files
   $ cat a b
   expand $Id: a bb948857c743 Thu, 01 Jan 1970 00:00:02 +0000 user $
   do not process $Id:
--- a/tests/test-largefiles-misc.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-largefiles-misc.t	Wed Sep 26 20:33:09 2018 +0900
@@ -111,7 +111,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 09a186cfa6da
+  new changesets 09a186cfa6da (1 drafts)
   updating to branch default
   getting changed largefiles
   1 largefiles updated, 0 removed
@@ -1095,7 +1095,7 @@
 Move (and then undo) a directory move with only largefiles.
 
   $ cd subrepo-root
-  $ $PYTHON $TESTDIR/list-tree.py .hglf dir* large*
+  $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
   .hglf/
   .hglf/dir/
   .hglf/dir/subdir/
@@ -1110,7 +1110,7 @@
   $ hg mv dir/subdir dir/subdir2
   moving .hglf/dir/subdir/large.bin to .hglf/dir/subdir2/large.bin
 
-  $ $PYTHON $TESTDIR/list-tree.py .hglf dir* large*
+  $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
   .hglf/
   .hglf/dir/
   .hglf/dir/subdir2/
@@ -1135,8 +1135,8 @@
   ? large.orig
 
   $ hg revert --all
+  forgetting .hglf/dir/subdir2/large.bin
   undeleting .hglf/dir/subdir/large.bin
-  forgetting .hglf/dir/subdir2/large.bin
   reverting subrepo no-largefiles
 
   $ hg status -C
@@ -1150,7 +1150,7 @@
 
 The standin for subdir2 should be deleted, not just dropped
 
-  $ $PYTHON $TESTDIR/list-tree.py .hglf dir* large*
+  $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
   .hglf/
   .hglf/dir/
   .hglf/dir/subdir/
@@ -1177,7 +1177,7 @@
   R dir/subdir/large.bin
   ? large.orig
 
-  $ $PYTHON $TESTDIR/list-tree.py .hglf dir* large*
+  $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
   .hglf/
   .hglf/dir/
   .hglf/dir/subdir2/
@@ -1202,7 +1202,7 @@
     dir/subdir/large.bin
   R dir/subdir/large.bin
 
-  $ $PYTHON $TESTDIR/list-tree.py .hglf dir* large*
+  $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
   .hglf/
   .hglf/dir2/
   .hglf/dir2/subdir/
@@ -1214,14 +1214,14 @@
   large
 
   $ hg revert --all
+  forgetting .hglf/dir2/subdir/large.bin
   undeleting .hglf/dir/subdir/large.bin
-  forgetting .hglf/dir2/subdir/large.bin
   reverting subrepo no-largefiles
 
   $ hg status -C
   ? dir2/subdir/large.bin
 
-  $ $PYTHON $TESTDIR/list-tree.py .hglf dir* large*
+  $ "$PYTHON" $TESTDIR/list-tree.py .hglf dir* large*
   .hglf/
   .hglf/dir/
   .hglf/dir/subdir/
@@ -1263,7 +1263,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets bf5e395ced2c
+  new changesets bf5e395ced2c (1 drafts)
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-largefiles-update.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-largefiles-update.t	Wed Sep 26 20:33:09 2018 +0900
@@ -412,7 +412,7 @@
   $ hg update -q 5
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
-  $ hg debugdirstate --nodates | grep large2
+  $ hg debugdirstate --no-dates | grep large2
   a   0         -1 unset               .hglf/large2
   r   0          0 set                 large2
   $ hg status -A large2
@@ -428,7 +428,7 @@
   $ hg update -q 5
   remote turned local largefile large3 into a normal file
   keep (l)argefile or use (n)ormal file? l
-  $ hg debugdirstate --nodates | grep large3
+  $ hg debugdirstate --no-dates | grep large3
   a   0         -1 unset               .hglf/large3
   r   0          0 set                 large3
   $ hg status -A large3
@@ -456,7 +456,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 5 files
-  new changesets 9530e27857f7:d65e59e952a9
+  new changesets 9530e27857f7:d65e59e952a9 (3 drafts)
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
   largefile large1 has a merge conflict
@@ -492,7 +492,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 5 files
-  new changesets 9530e27857f7:d65e59e952a9
+  new changesets 9530e27857f7:d65e59e952a9 (3 drafts)
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
   largefile large1 has a merge conflict
@@ -611,7 +611,8 @@
   > EOF
   rebasing 1:72518492caa6 "#1"
   rebasing 4:07d6153b5c04 "#4"
-  local [dest] changed .hglf/large1 which other [source] deleted
+  file '.hglf/large1' was deleted in other [source] but was modified in local [dest].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? c
 
   $ hg diff -c "tip~1" --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
--- a/tests/test-largefiles-wireproto.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-largefiles-wireproto.t	Wed Sep 26 20:33:09 2018 +0900
@@ -51,7 +51,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets b6eb3a2e2efe
+  new changesets b6eb3a2e2efe (1 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -64,7 +64,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets b6eb3a2e2efe
+  new changesets b6eb3a2e2efe (1 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 #endif
@@ -166,7 +166,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
   searching 1 changesets for largefiles
   verified existence of 1 revisions of 1 largefiles
   $ hg serve --config extensions.largefiles=! -R ../r6 -d -p $HGPORT --pid-file ../hg.pid
@@ -236,7 +236,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets cf03e5bb9936
+  new changesets cf03e5bb9936 (1 drafts)
 
 Archive contains largefiles
   >>> import os
@@ -259,7 +259,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   searching 1 changesets for largefiles
   changeset 0:cf03e5bb9936: f1 missing
   verified existence of 1 revisions of 1 largefiles
@@ -295,7 +295,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   searching 1 changesets for largefiles
   verified contents of 1 revisions of 1 largefiles
   $ hg -R http-clone up -Cqr null
@@ -352,13 +352,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  new changesets 567253b0f523:04d19c27a332
+  new changesets 567253b0f523:04d19c27a332 (2 drafts)
   $ hg -R batchverifyclone verify --large --lfa
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
   searching 2 changesets for largefiles
   verified existence of 2 revisions of 2 largefiles
   $ tail -1 access.log
@@ -389,14 +389,14 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 6bba8cb6935d
+  new changesets 6bba8cb6935d (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg -R batchverifyclone verify --lfa
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 3 files
   searching 3 changesets for largefiles
   verified existence of 3 revisions of 3 largefiles
   $ tail -1 access.log
@@ -452,7 +452,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 567253b0f523
+  new changesets 567253b0f523 (1 drafts)
   updating to branch default
   getting changed largefiles
   1 largefiles updated, 0 removed
--- a/tests/test-largefiles.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-largefiles.t	Wed Sep 26 20:33:09 2018 +0900
@@ -43,12 +43,12 @@
   $ touch large1 sub/large2
   $ sleep 1
   $ hg st
-  $ hg debugstate --nodates
+  $ hg debugstate --no-dates
   n 644         41 set                 .hglf/large1
   n 644         41 set                 .hglf/sub/large2
   n 644          8 set                 normal1
   n 644          8 set                 sub/normal2
-  $ hg debugstate --large --nodates
+  $ hg debugstate --large --no-dates
   n 644          7 set                 large1
   n 644          7 set                 sub/large2
   $ echo normal11 > normal1
@@ -225,7 +225,7 @@
   $ . "$TESTDIR/cgienv"
 
   $ SCRIPT_NAME='' \
-  > $PYTHON "$TESTTMP/hgweb.cgi" > /dev/null
+  > "$PYTHON" "$TESTTMP/hgweb.cgi" > /dev/null
 
 Test archiving the various revisions.  These hit corner cases known with
 archiving.
@@ -961,7 +961,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 10 changes to 4 files
-  new changesets 30d30fe6a5be:9e8fbc4bce62
+  new changesets 30d30fe6a5be:9e8fbc4bce62 (4 drafts)
   updating to branch default
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -1031,7 +1031,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  10 files, 8 changesets, 24 total revisions
+  checked 8 changesets with 24 changes to 10 files
   searching 8 changesets for largefiles
   verified contents of 13 revisions of 6 largefiles
   $ hg -R a-clone1 sum
@@ -1090,7 +1090,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 8 changes to 4 files
-  new changesets 30d30fe6a5be:ce8896473775
+  new changesets 30d30fe6a5be:ce8896473775 (2 drafts)
   updating to branch default
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -1104,7 +1104,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
-  new changesets 51a0ae4d5864:daea875e9014
+  new changesets 51a0ae4d5864:daea875e9014 (6 drafts)
   (run 'hg update' to get a working copy)
   6 largefiles cached
 
@@ -1132,7 +1132,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
-  new changesets 51a0ae4d5864:daea875e9014
+  new changesets 51a0ae4d5864:daea875e9014 (6 drafts)
   calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
   (run 'hg update' to get a working copy)
   pulling largefiles for revision 7
@@ -1205,7 +1205,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
-  new changesets a381d2c8c80e
+  new changesets a381d2c8c80e (1 drafts)
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
   Invoking status precommit hook
   M sub/normal4
@@ -1263,7 +1263,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
-  new changesets a381d2c8c80e
+  new changesets a381d2c8c80e (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg rebase
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
@@ -1513,9 +1513,9 @@
   $ cat sub/large4
   large4-modified
   $ hg revert -a --no-backup
-  undeleting .hglf/sub2/large6
   forgetting .hglf/sub2/large8
   reverting normal3
+  undeleting .hglf/sub2/large6
   $ hg status
   ? sub/large4.orig
   ? sub/normal4.orig
@@ -1549,7 +1549,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  10 files, 10 changesets, 28 total revisions
+  checked 10 changesets with 28 changes to 10 files
   searching 1 changesets for largefiles
   verified existence of 3 revisions of 3 largefiles
 
@@ -1563,7 +1563,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  10 files, 10 changesets, 28 total revisions
+  checked 10 changesets with 28 changes to 10 files
   searching 1 changesets for largefiles
   changeset 9:598410d3eb9a: sub/large4 references missing $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
   verified existence of 3 revisions of 3 largefiles
@@ -1669,7 +1669,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 24 changes to 10 files
-  new changesets 30d30fe6a5be:daea875e9014
+  new changesets 30d30fe6a5be:daea875e9014 (8 drafts)
   updating to branch default
   getting changed largefiles
   3 largefiles updated, 0 removed
@@ -1695,7 +1695,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 4 changes to 4 files (+1 heads)
-  new changesets a381d2c8c80e:598410d3eb9a
+  new changesets a381d2c8c80e:598410d3eb9a (2 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   2 largefiles cached
   $ hg merge
@@ -1771,7 +1771,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 26 changes to 10 files
-  new changesets 30d30fe6a5be:a381d2c8c80e
+  new changesets 30d30fe6a5be:a381d2c8c80e (9 drafts)
   updating to branch default
   getting changed largefiles
   3 largefiles updated, 0 removed
@@ -1784,7 +1784,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
-  new changesets 598410d3eb9a
+  new changesets 598410d3eb9a (1 drafts)
   $ hg log --template '{rev}:{node|short}  {desc|firstline}\n'
   9:598410d3eb9a  modify normal file largefile in repo d
   8:a381d2c8c80e  modify normal file and largefile in repo b
--- a/tests/test-lfconvert.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-lfconvert.t	Wed Sep 26 20:33:09 2018 +0900
@@ -101,6 +101,7 @@
   largefiles
   revlogv1
   store
+  testonly-simplestore (reposimplestore !)
 
 "lfconvert" includes a newline at the end of the standin files.
   $ cat .hglf/large .hglf/sub/maybelarge.dat
@@ -336,7 +337,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  9 files, 8 changesets, 13 total revisions
+  checked 8 changesets with 13 changes to 9 files
   searching 7 changesets for largefiles
   changeset 0:d4892ec57ce2: large references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/2e000fa7e85759c7f4c254d4d9c33ef481e459a7
   changeset 1:334e5237836d: sub/maybelarge.dat references missing $TESTTMP/largefiles-repo-hg/.hg/largefiles/34e163be8e43c5631d8b92e9c43ab0bf0fa62b9c
--- a/tests/test-lfs-largefiles.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-lfs-largefiles.t	Wed Sep 26 20:33:09 2018 +0900
@@ -201,7 +201,7 @@
 commit.  By the time the commit occurs, the tracked file is smaller than the
 threshold (assuming it is > 41, so the standins don't become lfs objects).
 
-  $ $PYTHON -c 'import sys ; sys.stdout.write("y\n" * 1048576)' > large_by_size.bin
+  $ "$PYTHON" -c 'import sys ; sys.stdout.write("y\n" * 1048576)' > large_by_size.bin
   $ hg --config largefiles.minsize=1 ci -Am 'large by size'
   adding large_by_size.bin as a largefile
   $ hg manifest
--- a/tests/test-lfs-serve-access.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-lfs-serve-access.t	Wed Sep 26 20:33:09 2018 +0900
@@ -52,7 +52,7 @@
   abort: LFS HTTP error: HTTP Error 400: no such method: .git (action=download)!
   [255]
 
-  $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
 
   $ cat $TESTTMP/access.log $TESTTMP/errors.log
   $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
@@ -141,7 +141,7 @@
   lfs: found f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e in the local lfs store
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-  $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
 
   $ cat $TESTTMP/access.log $TESTTMP/errors.log
   $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point?cmd=capabilities HTTP/1.1" 200 - (glob)
@@ -150,6 +150,33 @@
   $LOCALIP - - [$LOGDATE$] "POST /subdir/mount/point/.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
   $LOCALIP - - [$LOGDATE$] "GET /subdir/mount/point/.hg/lfs/objects/f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e HTTP/1.1" 200 - (glob)
 
+Blobs that already exist in the usercache are linked into the repo store, even
+though the client doesn't send the blob.
+
+  $ hg init server2
+  $ hg --config "lfs.usercache=$TESTTMP/servercache" -R server2 serve -d \
+  >    -p $HGPORT --pid-file=hg.pid \
+  >    -A $TESTTMP/access.log -E $TESTTMP/errors.log
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ hg --config "lfs.usercache=$TESTTMP/servercache" -R cloned2 --debug \
+  >    push http://localhost:$HGPORT | grep '^[{} ]'
+  {
+    "objects": [
+      {
+        "oid": "f03217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e"
+        "size": 20
+      }
+    ]
+    "transfer": "basic"
+  }
+  $ find server2/.hg/store/lfs/objects | sort
+  server2/.hg/store/lfs/objects
+  server2/.hg/store/lfs/objects/f0
+  server2/.hg/store/lfs/objects/f0/3217a32529a28a42d03b1244fe09b6e0f9fd06d7b966d4d50567be2abe6c0e
+  $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ cat $TESTTMP/errors.log
+
   $ cat >> $TESTTMP/lfsstoreerror.py <<EOF
   > import errno
   > from hgext.lfs import blobstore
@@ -259,7 +286,7 @@
   abort: HTTP error: HTTP Error 422: corrupt blob (oid=276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d, action=download)!
   [255]
 
-  $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
 
   $ cat $TESTTMP/access.log
   $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
@@ -411,7 +438,7 @@
     "transfer": "basic"
   }
 
-  $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
 
   $ cat $TESTTMP/access.log $TESTTMP/errors.log
   $LOCALIP - - [$LOGDATE$] "GET /?cmd=capabilities HTTP/1.1" 401 - (glob)
--- a/tests/test-lfs-serve.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-lfs-serve.t	Wed Sep 26 20:33:09 2018 +0900
@@ -509,7 +509,7 @@
 
 #endif
 
-  $ $PYTHON $TESTDIR/killdaemons.py $DAEMON_PIDS
+  $ "$PYTHON" $TESTDIR/killdaemons.py $DAEMON_PIDS
 
 #if lfsremote-on
   $ cat $TESTTMP/errors.log | grep '^[A-Z]'
--- a/tests/test-lfs-test-server.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-lfs-test-server.t	Wed Sep 26 20:33:09 2018 +0900
@@ -36,7 +36,7 @@
   >             sys.exit(0)
   > sys.exit(1)
   > EOF
-  $ $PYTHON $TESTTMP/spawn.py >> $DAEMON_PIDS
+  $ "$PYTHON" $TESTTMP/spawn.py >> $DAEMON_PIDS
 #endif
 
   $ cat >> $HGRCPATH <<EOF
@@ -694,10 +694,6 @@
   $ rm *
   $ hg revert --all -r 1 --debug
   http auth: user foo, password ***
-  adding a
-  reverting b
-  reverting c
-  reverting d
   http auth: user foo, password ***
   Status: 200
   Content-Length: 905 (git-server !)
@@ -778,9 +774,13 @@
   lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache
   lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998
   lfs: downloaded 3 files (51 bytes)
+  reverting b
   lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
+  reverting c
   lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store
+  reverting d
   lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store
+  adding a
   lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store
 
 Check error message when the remote missed a blob:
@@ -850,7 +850,7 @@
 
 (Restart the server in a different location so it no longer has the content)
 
-  $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
 
 #if hg-server
   $ cat $TESTTMP/access.log $TESTTMP/errors.log
@@ -888,7 +888,7 @@
 #endif
 
 #if windows git-server
-  $ $PYTHON $TESTTMP/spawn.py >> $DAEMON_PIDS
+  $ "$PYTHON" $TESTTMP/spawn.py >> $DAEMON_PIDS
 #endif
 
 #if hg-server
@@ -938,4 +938,4 @@
   abort: LFS server error for "a": The object does not exist!
   [255]
 
-  $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
--- a/tests/test-lfs.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-lfs.t	Wed Sep 26 20:33:09 2018 +0900
@@ -486,7 +486,7 @@
   > [lfs]
   > track=all()
   > EOF
-  $ $PYTHON <<'EOF'
+  $ "$PYTHON" <<'EOF'
   > def write(path, content):
   >     with open(path, 'wb') as f:
   >         f.write(content)
@@ -722,7 +722,7 @@
   checking files
    l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
    large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
-  4 files, 5 changesets, 10 total revisions
+  checked 5 changesets with 10 changes to 4 files
   2 integrity errors encountered!
   (first damaged changeset appears to be 0)
   [1]
@@ -759,7 +759,7 @@
   lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
   lfs: adding b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c to the usercache
   lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
-  4 files, 5 changesets, 10 total revisions
+  checked 5 changesets with 10 changes to 4 files
 
 Verify will not copy/link a corrupted file from the usercache into the local
 store, and poison it.  (The verify with a good remote now works.)
@@ -776,7 +776,7 @@
    large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
   lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
   lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
-  4 files, 5 changesets, 10 total revisions
+  checked 5 changesets with 10 changes to 4 files
   2 integrity errors encountered!
   (first damaged changeset appears to be 0)
   [1]
@@ -791,7 +791,7 @@
   lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store
   lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
   lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
-  4 files, 5 changesets, 10 total revisions
+  checked 5 changesets with 10 changes to 4 files
 
 Damaging a file required by the update destination fails the update.
 
@@ -817,7 +817,7 @@
   checking files
    l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0
    large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
-  4 files, 5 changesets, 10 total revisions
+  checked 5 changesets with 10 changes to 4 files
   2 integrity errors encountered!
   (first damaged changeset appears to be 0)
   [1]
@@ -848,7 +848,7 @@
    large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0
   lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store
   lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store
-  4 files, 5 changesets, 10 total revisions
+  checked 5 changesets with 10 changes to 4 files
   2 integrity errors encountered!
   (first damaged changeset appears to be 0)
   [1]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-linelog.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,193 @@
+from __future__ import absolute_import, print_function
+
+import difflib
+import random
+import unittest
+
+from mercurial import linelog
+
+vecratio = 3 # number of replacelines / number of replacelines_vec
+maxlinenum = 0xffffff
+maxb1 = 0xffffff
+maxdeltaa = 10
+maxdeltab = 10
+
+def _genedits(seed, endrev):
+    lines = []
+    random.seed(seed)
+    rev = 0
+    for rev in range(0, endrev):
+        n = len(lines)
+        a1 = random.randint(0, n)
+        a2 = random.randint(a1, min(n, a1 + maxdeltaa))
+        b1 = random.randint(0, maxb1)
+        b2 = random.randint(b1, b1 + maxdeltab)
+        usevec = not bool(random.randint(0, vecratio))
+        if usevec:
+            blines = [(random.randint(0, rev), random.randint(0, maxlinenum))
+                      for _ in range(b1, b2)]
+        else:
+            blines = [(rev, bidx) for bidx in range(b1, b2)]
+        lines[a1:a2] = blines
+        yield lines, rev, a1, a2, b1, b2, blines, usevec
+
+class linelogtests(unittest.TestCase):
+    def testlinelogencodedecode(self):
+        program = [linelog._eof(0, 0),
+                   linelog._jge(41, 42),
+                   linelog._jump(0, 43),
+                   linelog._eof(0, 0),
+                   linelog._jl(44, 45),
+                   linelog._line(46, 47),
+                   ]
+        ll = linelog.linelog(program, maxrev=100)
+        enc = ll.encode()
+        # round-trips okay
+        self.assertEqual(linelog.linelog.fromdata(enc)._program, ll._program)
+        self.assertEqual(linelog.linelog.fromdata(enc), ll)
+        # This encoding matches the encoding used by hg-experimental's
+        # linelog file, or is supposed to if it doesn't.
+        self.assertEqual(enc, (b'\x00\x00\x01\x90\x00\x00\x00\x06'
+                               b'\x00\x00\x00\xa4\x00\x00\x00*'
+                               b'\x00\x00\x00\x00\x00\x00\x00+'
+                               b'\x00\x00\x00\x00\x00\x00\x00\x00'
+                               b'\x00\x00\x00\xb1\x00\x00\x00-'
+                               b'\x00\x00\x00\xba\x00\x00\x00/'))
+
+    def testsimpleedits(self):
+        ll = linelog.linelog()
+        # Initial revision: add lines 0, 1, and 2
+        ll.replacelines(1, 0, 0, 0, 3)
+        self.assertEqual([(l.rev, l.linenum) for l in ll.annotate(1)],
+                         [(1, 0),
+                          (1, 1),
+                          (1, 2),
+                         ])
+        # Replace line 1 with a new line
+        ll.replacelines(2, 1, 2, 1, 2)
+        self.assertEqual([(l.rev, l.linenum) for l in ll.annotate(2)],
+                         [(1, 0),
+                          (2, 1),
+                          (1, 2),
+                         ])
+        # delete a line out of 2
+        ll.replacelines(3, 1, 2, 0, 0)
+        self.assertEqual([(l.rev, l.linenum) for l in ll.annotate(3)],
+                         [(1, 0),
+                          (1, 2),
+                         ])
+        # annotation of 1 is unchanged
+        self.assertEqual([(l.rev, l.linenum) for l in ll.annotate(1)],
+                         [(1, 0),
+                          (1, 1),
+                          (1, 2),
+                         ])
+        ll.annotate(3) # set internal state to revision 3
+        start = ll.getoffset(0)
+        end = ll.getoffset(1)
+        self.assertEqual(ll.getalllines(start, end), [
+            (1, 0),
+            (2, 1),
+            (1, 1),
+        ])
+        self.assertEqual(ll.getalllines(), [
+            (1, 0),
+            (2, 1),
+            (1, 1),
+            (1, 2),
+        ])
+
+    def testparseclinelogfile(self):
+        # This data is what the replacements in testsimpleedits
+        # produce when fed to the original linelog.c implementation.
+        data = (b'\x00\x00\x00\x0c\x00\x00\x00\x0f'
+                b'\x00\x00\x00\x00\x00\x00\x00\x02'
+                b'\x00\x00\x00\x05\x00\x00\x00\x06'
+                b'\x00\x00\x00\x06\x00\x00\x00\x00'
+                b'\x00\x00\x00\x00\x00\x00\x00\x07'
+                b'\x00\x00\x00\x06\x00\x00\x00\x02'
+                b'\x00\x00\x00\x00\x00\x00\x00\x00'
+                b'\x00\x00\x00\t\x00\x00\x00\t'
+                b'\x00\x00\x00\x00\x00\x00\x00\x0c'
+                b'\x00\x00\x00\x08\x00\x00\x00\x05'
+                b'\x00\x00\x00\x06\x00\x00\x00\x01'
+                b'\x00\x00\x00\x00\x00\x00\x00\x05'
+                b'\x00\x00\x00\x0c\x00\x00\x00\x05'
+                b'\x00\x00\x00\n\x00\x00\x00\x01'
+                b'\x00\x00\x00\x00\x00\x00\x00\t')
+        llc = linelog.linelog.fromdata(data)
+        self.assertEqual([(l.rev, l.linenum) for l in llc.annotate(1)],
+                         [(1, 0),
+                          (1, 1),
+                          (1, 2),
+                         ])
+        self.assertEqual([(l.rev, l.linenum) for l in llc.annotate(2)],
+                         [(1, 0),
+                          (2, 1),
+                          (1, 2),
+                         ])
+        self.assertEqual([(l.rev, l.linenum) for l in llc.annotate(3)],
+                         [(1, 0),
+                          (1, 2),
+                         ])
+        # Check we emit the same bytecode.
+        ll = linelog.linelog()
+        # Initial revision: add lines 0, 1, and 2
+        ll.replacelines(1, 0, 0, 0, 3)
+        # Replace line 1 with a new line
+        ll.replacelines(2, 1, 2, 1, 2)
+        # delete a line out of 2
+        ll.replacelines(3, 1, 2, 0, 0)
+        diff = '\n   ' + '\n   '.join(difflib.unified_diff(
+            ll.debugstr().splitlines(), llc.debugstr().splitlines(),
+            'python', 'c', lineterm=''))
+        self.assertEqual(ll._program, llc._program, 'Program mismatch: ' + diff)
+        # Done as a secondary step so we get a better result if the
+        # program is where the mismatch is.
+        self.assertEqual(ll, llc)
+        self.assertEqual(ll.encode(), data)
+
+    def testanothersimplecase(self):
+        ll = linelog.linelog()
+        ll.replacelines(3, 0, 0, 0, 2)
+        ll.replacelines(4, 0, 2, 0, 0)
+        self.assertEqual([(l.rev, l.linenum) for l in ll.annotate(4)],
+                         [])
+        self.assertEqual([(l.rev, l.linenum) for l in ll.annotate(3)],
+                         [(3, 0), (3, 1)])
+        # rev 2 is empty because contents were only ever introduced in rev 3
+        self.assertEqual([(l.rev, l.linenum) for l in ll.annotate(2)],
+                         [])
+
+    def testrandomedits(self):
+        # Inspired by original linelog tests.
+        seed = random.random()
+        numrevs = 2000
+        ll = linelog.linelog()
+        # Populate linelog
+        for lines, rev, a1, a2, b1, b2, blines, usevec in _genedits(
+                seed, numrevs):
+            if usevec:
+                ll.replacelines_vec(rev, a1, a2, blines)
+            else:
+                ll.replacelines(rev, a1, a2, b1, b2)
+            ar = ll.annotate(rev)
+            self.assertEqual(ll.annotateresult, lines)
+        # Verify we can get back these states by annotating each rev
+        for lines, rev, a1, a2, b1, b2, blines, usevec in _genedits(
+                seed, numrevs):
+            ar = ll.annotate(rev)
+            self.assertEqual([(l.rev, l.linenum) for l in ar], lines)
+
+    def testinfinitebadprogram(self):
+        ll = linelog.linelog.fromdata(
+            b'\x00\x00\x00\x00\x00\x00\x00\x02'  # header
+            b'\x00\x00\x00\x00\x00\x00\x00\x01'  # JUMP to self
+        )
+        with self.assertRaises(linelog.LineLogError):
+            # should not be an infinite loop and raise
+            ll.annotate(1)
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
--- a/tests/test-locate.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-locate.t	Wed Sep 26 20:33:09 2018 +0900
@@ -158,7 +158,7 @@
 
 Convert native path separator to slash (issue5572)
 
-  $ hg files -T '{path|slashpath}\n'
+  $ hg files -T '{path|relpath|slashpath}\n'
   ../b
   ../dir.h/foo
   ../t.h
--- a/tests/test-log-exthook.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-log-exthook.t	Wed Sep 26 20:33:09 2018 +0900
@@ -3,13 +3,14 @@
 
   $ cat > $TESTTMP/logexthook.py <<EOF
   > from __future__ import absolute_import
+  > import codecs
   > from mercurial import (
   >   commands,
   >   logcmdutil,
   >   repair,
   > )
   > def rot13description(self, ctx):
-  >     summary = "summary".encode('rot13')
+  >     summary = codecs.encode("summary", 'rot-13')
   >     description = ctx.description().strip().splitlines()[0].encode('rot13')
   >     self.ui.write("%s:     %s\n" % (summary, description))
   > def reposetup(ui, repo):
--- a/tests/test-log.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-log.t	Wed Sep 26 20:33:09 2018 +0900
@@ -2126,6 +2126,7 @@
   phase:       draft
   parent:      0:65624cd9070a035fa7191a54f2b8af39f16b0c08
   parent:      -1:0000000000000000000000000000000000000000
+  manifest:    2147483647:ffffffffffffffffffffffffffffffffffffffff
   user:        test
   date:        [A-Za-z0-9:+ ]+ (re)
   extra:       branch=default
@@ -2164,6 +2165,7 @@
   phase:       draft
   parent:      0:65624cd9070a035fa7191a54f2b8af39f16b0c08
   parent:      -1:0000000000000000000000000000000000000000
+  manifest:    2147483647:ffffffffffffffffffffffffffffffffffffffff
   user:        test
   date:        [A-Za-z0-9:+ ]+ (re)
   files:       d1/f1
@@ -2208,10 +2210,10 @@
     "branch": "default",
     "date": [*, 0], (glob)
     "desc": "",
-    "node": null,
+    "node": "ffffffffffffffffffffffffffffffffffffffff",
     "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
     "phase": "draft",
-    "rev": null,
+    "rev": 2147483647,
     "tags": [],
     "user": "test"
    }
@@ -2220,8 +2222,8 @@
   $ hg log -r 'wdir()' -Tjson -q
   [
    {
-    "node": null,
-    "rev": null
+    "node": "ffffffffffffffffffffffffffffffffffffffff",
+    "rev": 2147483647
    }
   ]
 
@@ -2234,13 +2236,13 @@
     "date": [*, 0], (glob)
     "desc": "",
     "extra": {"branch": "default"},
-    "manifest": null,
+    "manifest": "ffffffffffffffffffffffffffffffffffffffff",
     "modified": ["d1/f1"],
-    "node": null,
+    "node": "ffffffffffffffffffffffffffffffffffffffff",
     "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
     "phase": "draft",
     "removed": [".d6/f1"],
-    "rev": null,
+    "rev": 2147483647,
     "tags": [],
     "user": "test"
    }
--- a/tests/test-logexchange.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-logexchange.t	Wed Sep 26 20:33:09 2018 +0900
@@ -3,7 +3,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > ssh = $PYTHON "$TESTDIR/dummyssh"
+  > ssh = "$PYTHON" "$TESTDIR/dummyssh"
   > [alias]
   > glog = log -G -T '{rev}:{node|short}  {desc}'
   > [extensions]
@@ -77,10 +77,8 @@
 
   $ hg show work
   o  3e14 (wat) (default/wat) added bar
-  |
   ~
   @  ec24 (default/default) Added h
-  |
   ~
 
   $ hg update "default/wat"
--- a/tests/test-lrucachedict.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-lrucachedict.py	Wed Sep 26 20:33:09 2018 +0900
@@ -1,77 +1,337 @@
 from __future__ import absolute_import, print_function
 
+import unittest
+
+import silenttestrunner
+
 from mercurial import (
     util,
 )
 
-def printifpresent(d, xs, name='d'):
-    for x in xs:
-        present = x in d
-        print("'%s' in %s: %s" % (x, name, present))
-        if present:
-            print("%s['%s']: %s" % (name, x, d[x]))
+class testlrucachedict(unittest.TestCase):
+    def testsimple(self):
+        d = util.lrucachedict(4)
+        self.assertEqual(d.capacity, 4)
+        d.insert('a', 'va', cost=2)
+        d['b'] = 'vb'
+        d['c'] = 'vc'
+        d.insert('d', 'vd', cost=42)
+
+        self.assertEqual(d['a'], 'va')
+        self.assertEqual(d['b'], 'vb')
+        self.assertEqual(d['c'], 'vc')
+        self.assertEqual(d['d'], 'vd')
+
+        self.assertEqual(d.totalcost, 44)
+
+        # 'a' should be dropped because it was least recently used.
+        d['e'] = 've'
+        self.assertNotIn('a', d)
+        self.assertIsNone(d.get('a'))
+        self.assertEqual(d.totalcost, 42)
+
+        self.assertEqual(d['b'], 'vb')
+        self.assertEqual(d['c'], 'vc')
+        self.assertEqual(d['d'], 'vd')
+        self.assertEqual(d['e'], 've')
+
+        # Replacing item with different cost adjusts totalcost.
+        d.insert('e', 've', cost=4)
+        self.assertEqual(d.totalcost, 46)
+
+        # Touch entries in some order (both get and set).
+        d['e']
+        d['c'] = 'vc2'
+        d['d']
+        d['b'] = 'vb2'
 
-def test_lrucachedict():
-    d = util.lrucachedict(4)
-    d['a'] = 'va'
-    d['b'] = 'vb'
-    d['c'] = 'vc'
-    d['d'] = 'vd'
+        # 'e' should be dropped now
+        d['f'] = 'vf'
+        self.assertNotIn('e', d)
+        self.assertEqual(d['b'], 'vb2')
+        self.assertEqual(d['c'], 'vc2')
+        self.assertEqual(d['d'], 'vd')
+        self.assertEqual(d['f'], 'vf')
+
+        d.clear()
+        for key in ('a', 'b', 'c', 'd', 'e', 'f'):
+            self.assertNotIn(key, d)
+
+    def testunfull(self):
+        d = util.lrucachedict(4)
+        d['a'] = 1
+        d['b'] = 2
+        d['a']
+        d['b']
 
-    # all of these should be present
-    printifpresent(d, ['a', 'b', 'c', 'd'])
+        for key in ('a', 'b'):
+            self.assertIn(key, d)
+
+    def testget(self):
+        d = util.lrucachedict(4)
+        d['a'] = 'va'
+        d['b'] = 'vb'
+        d['c'] = 'vc'
+
+        self.assertIsNone(d.get('missing'))
+        self.assertEqual(list(d), ['c', 'b', 'a'])
+
+        self.assertEqual(d.get('a'), 'va')
+        self.assertEqual(list(d), ['a', 'c', 'b'])
+
+    def testcopypartial(self):
+        d = util.lrucachedict(4)
+        d.insert('a', 'va', cost=4)
+        d.insert('b', 'vb', cost=2)
+
+        dc = d.copy()
 
-    # 'a' should be dropped because it was least recently used
-    d['e'] = 've'
-    printifpresent(d, ['a', 'b', 'c', 'd', 'e'])
+        self.assertEqual(len(dc), 2)
+        self.assertEqual(dc.totalcost, 6)
+        for key in ('a', 'b'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
+
+        self.assertEqual(len(d), 2)
+        for key in ('a', 'b'):
+            self.assertIn(key, d)
+            self.assertEqual(d[key], 'v%s' % key)
 
-    assert d.get('a') is None
-    assert d.get('e') == 've'
+        d['c'] = 'vc'
+        del d['b']
+        self.assertEqual(d.totalcost, 4)
+        dc = d.copy()
+        self.assertEqual(len(dc), 2)
+        self.assertEqual(dc.totalcost, 4)
+        for key in ('a', 'c'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
+
+    def testcopyempty(self):
+        d = util.lrucachedict(4)
+        dc = d.copy()
+        self.assertEqual(len(dc), 0)
+
+    def testcopyfull(self):
+        d = util.lrucachedict(4)
+        d.insert('a', 'va', cost=42)
+        d['b'] = 'vb'
+        d['c'] = 'vc'
+        d['d'] = 'vd'
+
+        dc = d.copy()
+
+        for key in ('a', 'b', 'c', 'd'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
 
-    # touch entries in some order (get or set).
-    d['e']
-    d['c'] = 'vc2'
-    d['d']
-    d['b'] = 'vb2'
+        self.assertEqual(d.totalcost, 42)
+        self.assertEqual(dc.totalcost, 42)
+
+        # 'a' should be dropped because it was least recently used.
+        dc['e'] = 've'
+        self.assertNotIn('a', dc)
+        for key in ('b', 'c', 'd', 'e'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
+
+        self.assertEqual(d.totalcost, 42)
+        self.assertEqual(dc.totalcost, 0)
+
+        # Contents and order of original dict should remain unchanged.
+        dc['b'] = 'vb_new'
+
+        self.assertEqual(list(iter(d)), ['d', 'c', 'b', 'a'])
+        for key in ('a', 'b', 'c', 'd'):
+            self.assertEqual(d[key], 'v%s' % key)
 
-    # 'e' should be dropped now
-    d['f'] = 'vf'
-    printifpresent(d, ['b', 'c', 'd', 'e', 'f'])
+        d = util.lrucachedict(4, maxcost=42)
+        d.insert('a', 'va', cost=5)
+        d.insert('b', 'vb', cost=4)
+        d.insert('c', 'vc', cost=3)
+        dc = d.copy()
+        self.assertEqual(dc.maxcost, 42)
+        self.assertEqual(len(dc), 3)
+
+        # Max cost can be lowered as part of copy.
+        dc = d.copy(maxcost=10)
+        self.assertEqual(dc.maxcost, 10)
+        self.assertEqual(len(dc), 2)
+        self.assertEqual(dc.totalcost, 7)
+        self.assertIn('b', dc)
+        self.assertIn('c', dc)
+
+    def testcopydecreasecapacity(self):
+        d = util.lrucachedict(5)
+        d.insert('a', 'va', cost=4)
+        d.insert('b', 'vb', cost=2)
+        d['c'] = 'vc'
+        d['d'] = 'vd'
 
-    d.clear()
-    printifpresent(d, ['b', 'c', 'd', 'e', 'f'])
+        dc = d.copy(2)
+        self.assertEqual(dc.totalcost, 0)
+        for key in ('a', 'b'):
+            self.assertNotIn(key, dc)
+        for key in ('c', 'd'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
+
+        dc.insert('e', 've', cost=7)
+        self.assertEqual(dc.totalcost, 7)
+        self.assertNotIn('c', dc)
+        for key in ('d', 'e'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
+
+        # Original should remain unchanged.
+        self.assertEqual(d.totalcost, 6)
+        for key in ('a', 'b', 'c', 'd'):
+            self.assertIn(key, d)
+            self.assertEqual(d[key], 'v%s' % key)
+
+    def testcopyincreasecapacity(self):
+        d = util.lrucachedict(5)
+        d['a'] = 'va'
+        d['b'] = 'vb'
+        d['c'] = 'vc'
+        d['d'] = 'vd'
+
+        dc = d.copy(6)
+        for key in ('a', 'b', 'c', 'd'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
 
-    # Now test dicts that aren't full.
-    d = util.lrucachedict(4)
-    d['a'] = 1
-    d['b'] = 2
-    d['a']
-    d['b']
-    printifpresent(d, ['a', 'b'])
+        dc['e'] = 've'
+        dc['f'] = 'vf'
+        for key in ('a', 'b', 'c', 'd', 'e', 'f'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
+
+        dc['g'] = 'vg'
+        self.assertNotIn('a', dc)
+        for key in ('b', 'c', 'd', 'e', 'f', 'g'):
+            self.assertIn(key, dc)
+            self.assertEqual(dc[key], 'v%s' % key)
+
+        # Original should remain unchanged.
+        for key in ('a', 'b', 'c', 'd'):
+            self.assertIn(key, d)
+            self.assertEqual(d[key], 'v%s' % key)
+
+    def testpopoldest(self):
+        d = util.lrucachedict(4)
+        d.insert('a', 'va', cost=10)
+        d.insert('b', 'vb', cost=5)
+
+        self.assertEqual(len(d), 2)
+        self.assertEqual(d.popoldest(), ('a', 'va'))
+        self.assertEqual(len(d), 1)
+        self.assertEqual(d.totalcost, 5)
+        self.assertEqual(d.popoldest(), ('b', 'vb'))
+        self.assertEqual(len(d), 0)
+        self.assertEqual(d.totalcost, 0)
+        self.assertIsNone(d.popoldest())
+
+        d['a'] = 'va'
+        d['b'] = 'vb'
+        d['c'] = 'vc'
+        d['d'] = 'vd'
+
+        self.assertEqual(d.popoldest(), ('a', 'va'))
+        self.assertEqual(len(d), 3)
+        for key in ('b', 'c', 'd'):
+            self.assertEqual(d[key], 'v%s' % key)
+
+        d['a'] = 'va'
+        self.assertEqual(d.popoldest(), ('b', 'vb'))
 
-    # test copy method
-    d = util.lrucachedict(4)
-    d['a'] = 'va3'
-    d['b'] = 'vb3'
-    d['c'] = 'vc3'
-    d['d'] = 'vd3'
+    def testmaxcost(self):
+        # Item cost is zero by default.
+        d = util.lrucachedict(6, maxcost=10)
+        d['a'] = 'va'
+        d['b'] = 'vb'
+        d['c'] = 'vc'
+        d['d'] = 'vd'
+        self.assertEqual(len(d), 4)
+        self.assertEqual(d.totalcost, 0)
+
+        d.clear()
+
+        # Insertion to exact cost threshold works without eviction.
+        d.insert('a', 'va', cost=6)
+        d.insert('b', 'vb', cost=4)
+
+        self.assertEqual(len(d), 2)
+        self.assertEqual(d['a'], 'va')
+        self.assertEqual(d['b'], 'vb')
 
-    dc = d.copy()
+        # Inserting a new element with 0 cost works.
+        d['c'] = 'vc'
+        self.assertEqual(len(d), 3)
+
+        # Inserting a new element with cost putting us above high
+        # water mark evicts oldest single item.
+        d.insert('d', 'vd', cost=1)
+        self.assertEqual(len(d), 3)
+        self.assertEqual(d.totalcost, 5)
+        self.assertNotIn('a', d)
+        for key in ('b', 'c', 'd'):
+            self.assertEqual(d[key], 'v%s' % key)
+
+        # Inserting a new element with enough room for just itself
+        # evicts all items before.
+        d.insert('e', 've', cost=10)
+        self.assertEqual(len(d), 1)
+        self.assertEqual(d.totalcost, 10)
+        self.assertIn('e', d)
 
-    # all of these should be present
-    print("\nAll of these should be present:")
-    printifpresent(dc, ['a', 'b', 'c', 'd'], 'dc')
+        # Inserting a new element with cost greater than threshold
+        # still retains that item.
+        d.insert('f', 'vf', cost=11)
+        self.assertEqual(len(d), 1)
+        self.assertEqual(d.totalcost, 11)
+        self.assertIn('f', d)
+
+        # Inserting a new element will evict the last item since it is
+        # too large.
+        d['g'] = 'vg'
+        self.assertEqual(len(d), 1)
+        self.assertEqual(d.totalcost, 0)
+        self.assertIn('g', d)
+
+        d.clear()
+
+        d.insert('a', 'va', cost=7)
+        d.insert('b', 'vb', cost=3)
+        self.assertEqual(len(d), 2)
+
+        # Replacing a value with smaller cost won't result in eviction.
+        d.insert('b', 'vb2', cost=2)
+        self.assertEqual(len(d), 2)
 
-    # 'a' should be dropped because it was least recently used
-    print("\nAll of these except 'a' should be present:")
-    dc['e'] = 've3'
-    printifpresent(dc, ['a', 'b', 'c', 'd', 'e'], 'dc')
+        # Replacing a value with a higher cost will evict when threshold
+        # exceeded.
+        d.insert('b', 'vb3', cost=4)
+        self.assertEqual(len(d), 1)
+        self.assertNotIn('a', d)
 
-    # contents and order of original dict should remain unchanged
-    print("\nThese should be in reverse alphabetical order and read 'v?3':")
-    dc['b'] = 'vb3_new'
-    for k in list(iter(d)):
-        print("d['%s']: %s" % (k, d[k]))
+    def testmaxcostcomplex(self):
+        d = util.lrucachedict(100, maxcost=100)
+        d.insert('a', 'va', cost=9)
+        d.insert('b', 'vb', cost=21)
+        d.insert('c', 'vc', cost=7)
+        d.insert('d', 'vc', cost=50)
+        self.assertEqual(d.totalcost, 87)
+
+        # Inserting new element should free multiple elements so we hit
+        # low water mark.
+        d.insert('e', 'vd', cost=25)
+        self.assertEqual(len(d), 2)
+        self.assertNotIn('a', d)
+        self.assertNotIn('b', d)
+        self.assertNotIn('c', d)
+        self.assertIn('d', d)
+        self.assertIn('e', d)
 
 if __name__ == '__main__':
-    test_lrucachedict()
+    silenttestrunner.main(__name__)
--- a/tests/test-lrucachedict.py.out	Tue Sep 25 16:32:38 2018 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,62 +0,0 @@
-'a' in d: True
-d['a']: va
-'b' in d: True
-d['b']: vb
-'c' in d: True
-d['c']: vc
-'d' in d: True
-d['d']: vd
-'a' in d: False
-'b' in d: True
-d['b']: vb
-'c' in d: True
-d['c']: vc
-'d' in d: True
-d['d']: vd
-'e' in d: True
-d['e']: ve
-'b' in d: True
-d['b']: vb2
-'c' in d: True
-d['c']: vc2
-'d' in d: True
-d['d']: vd
-'e' in d: False
-'f' in d: True
-d['f']: vf
-'b' in d: False
-'c' in d: False
-'d' in d: False
-'e' in d: False
-'f' in d: False
-'a' in d: True
-d['a']: 1
-'b' in d: True
-d['b']: 2
-
-All of these should be present:
-'a' in dc: True
-dc['a']: va3
-'b' in dc: True
-dc['b']: vb3
-'c' in dc: True
-dc['c']: vc3
-'d' in dc: True
-dc['d']: vd3
-
-All of these except 'a' should be present:
-'a' in dc: False
-'b' in dc: True
-dc['b']: vb3
-'c' in dc: True
-dc['c']: vc3
-'d' in dc: True
-dc['d']: vd3
-'e' in dc: True
-dc['e']: ve3
-
-These should be in reverse alphabetical order and read 'v?3':
-d['d']: vd3
-d['c']: vc3
-d['b']: vb3
-d['a']: va3
--- a/tests/test-mactext.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-mactext.t	Wed Sep 26 20:33:09 2018 +0900
@@ -24,7 +24,7 @@
   $ hg add f
   $ hg ci -m 1
 
-  $ $PYTHON unix2mac.py f
+  $ "$PYTHON" unix2mac.py f
   $ hg ci -m 2
   attempt to commit or push text file(s) using CR line endings
   in dea860dc51ec: f
@@ -32,7 +32,7 @@
   rollback completed
   abort: pretxncommit.cr hook failed
   [255]
-  $ hg cat f | $PYTHON print.py
+  $ hg cat f | "$PYTHON" print.py
   hello<LF>
-  $ cat f | $PYTHON print.py
+  $ cat f | "$PYTHON" print.py
   hello<CR>
--- a/tests/test-manifest.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-manifest.t	Wed Sep 26 20:33:09 2018 +0900
@@ -15,7 +15,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
-  new changesets b73562a03cfe:5bdc995175ba
+  new changesets b73562a03cfe:5bdc995175ba (2 drafts)
   (run 'hg update' to get a working copy)
 
 The next call is expected to return nothing:
--- a/tests/test-match.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-match.py	Wed Sep 26 20:33:09 2018 +0900
@@ -6,14 +6,832 @@
 
 from mercurial import (
     match as matchmod,
+    util,
 )
 
+class BaseMatcherTests(unittest.TestCase):
+
+    def testVisitdir(self):
+        m = matchmod.basematcher(b'', b'')
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertTrue(m.visitdir(b'dir'))
+
+    def testVisitchildrenset(self):
+        m = matchmod.basematcher(b'', b'')
+        self.assertEqual(m.visitchildrenset(b'.'), b'this')
+        self.assertEqual(m.visitchildrenset(b'dir'), b'this')
+
+class AlwaysMatcherTests(unittest.TestCase):
+
+    def testVisitdir(self):
+        m = matchmod.alwaysmatcher(b'', b'')
+        self.assertEqual(m.visitdir(b'.'), b'all')
+        self.assertEqual(m.visitdir(b'dir'), b'all')
+
+    def testVisitchildrenset(self):
+        m = matchmod.alwaysmatcher(b'', b'')
+        self.assertEqual(m.visitchildrenset(b'.'), b'all')
+        self.assertEqual(m.visitchildrenset(b'dir'), b'all')
+
 class NeverMatcherTests(unittest.TestCase):
 
     def testVisitdir(self):
-        m = matchmod.nevermatcher('', '')
-        self.assertFalse(m.visitdir('.'))
-        self.assertFalse(m.visitdir('dir'))
+        m = matchmod.nevermatcher(b'', b'')
+        self.assertFalse(m.visitdir(b'.'))
+        self.assertFalse(m.visitdir(b'dir'))
+
+    def testVisitchildrenset(self):
+        m = matchmod.nevermatcher(b'', b'')
+        self.assertEqual(m.visitchildrenset(b'.'), set())
+        self.assertEqual(m.visitchildrenset(b'dir'), set())
+
+class PredicateMatcherTests(unittest.TestCase):
+    # predicatematcher does not currently define either of these methods, so
+    # this is equivalent to BaseMatcherTests.
+
+    def testVisitdir(self):
+        m = matchmod.predicatematcher(b'', b'', lambda *a: False)
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertTrue(m.visitdir(b'dir'))
+
+    def testVisitchildrenset(self):
+        m = matchmod.predicatematcher(b'', b'', lambda *a: False)
+        self.assertEqual(m.visitchildrenset(b'.'), b'this')
+        self.assertEqual(m.visitchildrenset(b'dir'), b'this')
+
+class PatternMatcherTests(unittest.TestCase):
+
+    def testVisitdirPrefix(self):
+        m = matchmod.match(b'x', b'', patterns=[b'path:dir/subdir'])
+        assert isinstance(m, matchmod.patternmatcher)
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertTrue(m.visitdir(b'dir'))
+        self.assertEqual(m.visitdir(b'dir/subdir'), b'all')
+        # OPT: This should probably be 'all' if its parent is?
+        self.assertTrue(m.visitdir(b'dir/subdir/x'))
+        self.assertFalse(m.visitdir(b'folder'))
+
+    def testVisitchildrensetPrefix(self):
+        m = matchmod.match(b'x', b'', patterns=[b'path:dir/subdir'])
+        assert isinstance(m, matchmod.patternmatcher)
+        self.assertEqual(m.visitchildrenset(b'.'), b'this')
+        self.assertEqual(m.visitchildrenset(b'dir'), b'this')
+        self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'all')
+        # OPT: This should probably be 'all' if its parent is?
+        self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
+        self.assertEqual(m.visitchildrenset(b'folder'), set())
+
+    def testVisitdirRootfilesin(self):
+        m = matchmod.match(b'x', b'', patterns=[b'rootfilesin:dir/subdir'])
+        assert isinstance(m, matchmod.patternmatcher)
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertFalse(m.visitdir(b'dir/subdir/x'))
+        self.assertFalse(m.visitdir(b'folder'))
+        # FIXME: These should probably be True.
+        self.assertFalse(m.visitdir(b'dir'))
+        self.assertFalse(m.visitdir(b'dir/subdir'))
+
+    def testVisitchildrensetRootfilesin(self):
+        m = matchmod.match(b'x', b'', patterns=[b'rootfilesin:dir/subdir'])
+        assert isinstance(m, matchmod.patternmatcher)
+        self.assertEqual(m.visitchildrenset(b'.'), b'this')
+        self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
+        self.assertEqual(m.visitchildrenset(b'folder'), set())
+        # FIXME: These should probably be {'subdir'} and 'this', respectively,
+        # or at least 'this' and 'this'.
+        self.assertEqual(m.visitchildrenset(b'dir'), set())
+        self.assertEqual(m.visitchildrenset(b'dir/subdir'), set())
+
+    def testVisitdirGlob(self):
+        m = matchmod.match(b'x', b'', patterns=[b'glob:dir/z*'])
+        assert isinstance(m, matchmod.patternmatcher)
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertTrue(m.visitdir(b'dir'))
+        self.assertFalse(m.visitdir(b'folder'))
+        # OPT: these should probably be False.
+        self.assertTrue(m.visitdir(b'dir/subdir'))
+        self.assertTrue(m.visitdir(b'dir/subdir/x'))
+
+    def testVisitchildrensetGlob(self):
+        m = matchmod.match(b'x', b'', patterns=[b'glob:dir/z*'])
+        assert isinstance(m, matchmod.patternmatcher)
+        self.assertEqual(m.visitchildrenset(b'.'), b'this')
+        self.assertEqual(m.visitchildrenset(b'folder'), set())
+        self.assertEqual(m.visitchildrenset(b'dir'), b'this')
+        # OPT: these should probably be set().
+        self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
+        self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
+
+class IncludeMatcherTests(unittest.TestCase):
+
+    def testVisitdirPrefix(self):
+        m = matchmod.match(b'x', b'', include=[b'path:dir/subdir'])
+        assert isinstance(m, matchmod.includematcher)
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertTrue(m.visitdir(b'dir'))
+        self.assertEqual(m.visitdir(b'dir/subdir'), b'all')
+        # OPT: This should probably be 'all' if its parent is?
+        self.assertTrue(m.visitdir(b'dir/subdir/x'))
+        self.assertFalse(m.visitdir(b'folder'))
+
+    def testVisitchildrensetPrefix(self):
+        m = matchmod.match(b'x', b'', include=[b'path:dir/subdir'])
+        assert isinstance(m, matchmod.includematcher)
+        self.assertEqual(m.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'all')
+        # OPT: This should probably be 'all' if its parent is?
+        self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
+        self.assertEqual(m.visitchildrenset(b'folder'), set())
+
+    def testVisitdirRootfilesin(self):
+        m = matchmod.match(b'x', b'', include=[b'rootfilesin:dir/subdir'])
+        assert isinstance(m, matchmod.includematcher)
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertTrue(m.visitdir(b'dir'))
+        self.assertTrue(m.visitdir(b'dir/subdir'))
+        self.assertFalse(m.visitdir(b'dir/subdir/x'))
+        self.assertFalse(m.visitdir(b'folder'))
+
+    def testVisitchildrensetRootfilesin(self):
+        m = matchmod.match(b'x', b'', include=[b'rootfilesin:dir/subdir'])
+        assert isinstance(m, matchmod.includematcher)
+        self.assertEqual(m.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
+        self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
+        self.assertEqual(m.visitchildrenset(b'folder'), set())
+
+    def testVisitdirGlob(self):
+        m = matchmod.match(b'x', b'', include=[b'glob:dir/z*'])
+        assert isinstance(m, matchmod.includematcher)
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertTrue(m.visitdir(b'dir'))
+        self.assertFalse(m.visitdir(b'folder'))
+        # OPT: these should probably be False.
+        self.assertTrue(m.visitdir(b'dir/subdir'))
+        self.assertTrue(m.visitdir(b'dir/subdir/x'))
+
+    def testVisitchildrensetGlob(self):
+        m = matchmod.match(b'x', b'', include=[b'glob:dir/z*'])
+        assert isinstance(m, matchmod.includematcher)
+        self.assertEqual(m.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(m.visitchildrenset(b'folder'), set())
+        self.assertEqual(m.visitchildrenset(b'dir'), b'this')
+        # OPT: these should probably be set().
+        self.assertEqual(m.visitchildrenset(b'dir/subdir'), b'this')
+        self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), b'this')
+
+class ExactMatcherTests(unittest.TestCase):
+
+    def testVisitdir(self):
+        m = matchmod.match(b'x', b'', patterns=[b'dir/subdir/foo.txt'],
+                           exact=True)
+        assert isinstance(m, matchmod.exactmatcher)
+        self.assertTrue(m.visitdir(b'.'))
+        self.assertTrue(m.visitdir(b'dir'))
+        self.assertTrue(m.visitdir(b'dir/subdir'))
+        self.assertFalse(m.visitdir(b'dir/subdir/foo.txt'))
+        self.assertFalse(m.visitdir(b'dir/foo'))
+        self.assertFalse(m.visitdir(b'dir/subdir/x'))
+        self.assertFalse(m.visitdir(b'folder'))
+
+    def testVisitchildrenset(self):
+        m = matchmod.match(b'x', b'', patterns=[b'dir/subdir/foo.txt'],
+                           exact=True)
+        assert isinstance(m, matchmod.exactmatcher)
+        self.assertEqual(m.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(m.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(m.visitchildrenset(b'dir/subdir'), {b'foo.txt'})
+        self.assertEqual(m.visitchildrenset(b'dir/subdir/x'), set())
+        self.assertEqual(m.visitchildrenset(b'dir/subdir/foo.txt'), set())
+        self.assertEqual(m.visitchildrenset(b'folder'), set())
+
+    def testVisitchildrensetFilesAndDirs(self):
+        m = matchmod.match(b'x', b'', patterns=[b'rootfile.txt',
+                                                b'a/file1.txt',
+                                                b'a/b/file2.txt',
+                                                # no file in a/b/c
+                                                b'a/b/c/d/file4.txt'],
+                           exact=True)
+        assert isinstance(m, matchmod.exactmatcher)
+        self.assertEqual(m.visitchildrenset(b'.'), {b'a', b'rootfile.txt'})
+        self.assertEqual(m.visitchildrenset(b'a'), {b'b', b'file1.txt'})
+        self.assertEqual(m.visitchildrenset(b'a/b'), {b'c', b'file2.txt'})
+        self.assertEqual(m.visitchildrenset(b'a/b/c'), {b'd'})
+        self.assertEqual(m.visitchildrenset(b'a/b/c/d'), {b'file4.txt'})
+        self.assertEqual(m.visitchildrenset(b'a/b/c/d/e'), set())
+        self.assertEqual(m.visitchildrenset(b'folder'), set())
+
+class DifferenceMatcherTests(unittest.TestCase):
+
+    def testVisitdirM2always(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.alwaysmatcher(b'', b'')
+        dm = matchmod.differencematcher(m1, m2)
+        # dm should be equivalent to a nevermatcher.
+        self.assertFalse(dm.visitdir(b'.'))
+        self.assertFalse(dm.visitdir(b'dir'))
+        self.assertFalse(dm.visitdir(b'dir/subdir'))
+        self.assertFalse(dm.visitdir(b'dir/subdir/z'))
+        self.assertFalse(dm.visitdir(b'dir/foo'))
+        self.assertFalse(dm.visitdir(b'dir/subdir/x'))
+        self.assertFalse(dm.visitdir(b'folder'))
+
+    def testVisitchildrensetM2always(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.alwaysmatcher(b'', b'')
+        dm = matchmod.differencematcher(m1, m2)
+        # dm should be equivalent to a nevermatcher.
+        self.assertEqual(dm.visitchildrenset(b'.'), set())
+        self.assertEqual(dm.visitchildrenset(b'dir'), set())
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir'), set())
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), set())
+        self.assertEqual(dm.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), set())
+        self.assertEqual(dm.visitchildrenset(b'folder'), set())
+
+    def testVisitdirM2never(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.nevermatcher(b'', b'')
+        dm = matchmod.differencematcher(m1, m2)
+        # dm should be equivalent to a alwaysmatcher. OPT: if m2 is a
+        # nevermatcher, we could return 'all' for these.
+        #
+        # We're testing Equal-to-True instead of just 'assertTrue' since
+        # assertTrue does NOT verify that it's a bool, just that it's truthy.
+        # While we may want to eventually make these return 'all', they should
+        # not currently do so.
+        self.assertEqual(dm.visitdir(b'.'), True)
+        self.assertEqual(dm.visitdir(b'dir'), True)
+        self.assertEqual(dm.visitdir(b'dir/subdir'), True)
+        self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
+        self.assertEqual(dm.visitdir(b'dir/foo'), True)
+        self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
+        self.assertEqual(dm.visitdir(b'folder'), True)
+
+    def testVisitchildrensetM2never(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.nevermatcher(b'', b'')
+        dm = matchmod.differencematcher(m1, m2)
+        # dm should be equivalent to a alwaysmatcher.
+        self.assertEqual(dm.visitchildrenset(b'.'), b'all')
+        self.assertEqual(dm.visitchildrenset(b'dir'), b'all')
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'all')
+        self.assertEqual(dm.visitchildrenset(b'dir/foo'), b'all')
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'all')
+        self.assertEqual(dm.visitchildrenset(b'folder'), b'all')
+
+    def testVisitdirM2SubdirPrefix(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
+        dm = matchmod.differencematcher(m1, m2)
+        self.assertEqual(dm.visitdir(b'.'), True)
+        self.assertEqual(dm.visitdir(b'dir'), True)
+        self.assertFalse(dm.visitdir(b'dir/subdir'))
+        # OPT: We should probably return False for these; we don't because
+        # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
+        # an 'all' pattern, just True.
+        self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
+        self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
+        # OPT: We could return 'all' for these.
+        self.assertEqual(dm.visitdir(b'dir/foo'), True)
+        self.assertEqual(dm.visitdir(b'folder'), True)
+
+    def testVisitchildrensetM2SubdirPrefix(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
+        dm = matchmod.differencematcher(m1, m2)
+        self.assertEqual(dm.visitchildrenset(b'.'), b'this')
+        self.assertEqual(dm.visitchildrenset(b'dir'), b'this')
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir'), set())
+        self.assertEqual(dm.visitchildrenset(b'dir/foo'), b'all')
+        self.assertEqual(dm.visitchildrenset(b'folder'), b'all')
+        # OPT: We should probably return set() for these; we don't because
+        # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
+        # an 'all' pattern, just 'this'.
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'this')
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'this')
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeIncludfe(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        dm = matchmod.differencematcher(m1, m2)
+        self.assertEqual(dm.visitdir(b'.'), True)
+        self.assertEqual(dm.visitdir(b'dir'), True)
+        self.assertEqual(dm.visitdir(b'dir/subdir'), True)
+        self.assertFalse(dm.visitdir(b'dir/foo'))
+        self.assertFalse(dm.visitdir(b'folder'))
+        # OPT: We should probably return False for these; we don't because
+        # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
+        # an 'all' pattern, just True.
+        self.assertEqual(dm.visitdir(b'dir/subdir/z'), True)
+        self.assertEqual(dm.visitdir(b'dir/subdir/x'), True)
+
+    def testVisitchildrensetIncludeInclude(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        dm = matchmod.differencematcher(m1, m2)
+        self.assertEqual(dm.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(dm.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(dm.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(dm.visitchildrenset(b'folder'), set())
+        # OPT: We should probably return set() for these; we don't because
+        # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
+        # an 'all' pattern, just 'this'.
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir/z'), b'this')
+        self.assertEqual(dm.visitchildrenset(b'dir/subdir/x'), b'this')
+
+class IntersectionMatcherTests(unittest.TestCase):
+
+    def testVisitdirM2always(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.alwaysmatcher(b'', b'')
+        im = matchmod.intersectmatchers(m1, m2)
+        # im should be equivalent to a alwaysmatcher.
+        self.assertEqual(im.visitdir(b'.'), b'all')
+        self.assertEqual(im.visitdir(b'dir'), b'all')
+        self.assertEqual(im.visitdir(b'dir/subdir'), b'all')
+        self.assertEqual(im.visitdir(b'dir/subdir/z'), b'all')
+        self.assertEqual(im.visitdir(b'dir/foo'), b'all')
+        self.assertEqual(im.visitdir(b'dir/subdir/x'), b'all')
+        self.assertEqual(im.visitdir(b'folder'), b'all')
+
+    def testVisitchildrensetM2always(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.alwaysmatcher(b'', b'')
+        im = matchmod.intersectmatchers(m1, m2)
+        # im should be equivalent to a alwaysmatcher.
+        self.assertEqual(im.visitchildrenset(b'.'), b'all')
+        self.assertEqual(im.visitchildrenset(b'dir'), b'all')
+        self.assertEqual(im.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), b'all')
+        self.assertEqual(im.visitchildrenset(b'dir/foo'), b'all')
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'all')
+        self.assertEqual(im.visitchildrenset(b'folder'), b'all')
+
+    def testVisitdirM2never(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.nevermatcher(b'', b'')
+        im = matchmod.intersectmatchers(m1, m2)
+        # im should be equivalent to a nevermatcher.
+        self.assertFalse(im.visitdir(b'.'))
+        self.assertFalse(im.visitdir(b'dir'))
+        self.assertFalse(im.visitdir(b'dir/subdir'))
+        self.assertFalse(im.visitdir(b'dir/subdir/z'))
+        self.assertFalse(im.visitdir(b'dir/foo'))
+        self.assertFalse(im.visitdir(b'dir/subdir/x'))
+        self.assertFalse(im.visitdir(b'folder'))
+
+    def testVisitchildrensetM2never(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.nevermatcher(b'', b'')
+        im = matchmod.intersectmatchers(m1, m2)
+        # im should be equivalent to a nevermqtcher.
+        self.assertEqual(im.visitchildrenset(b'.'), set())
+        self.assertEqual(im.visitchildrenset(b'dir'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
+        self.assertEqual(im.visitchildrenset(b'folder'), set())
+
+    def testVisitdirM2SubdirPrefix(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
+        im = matchmod.intersectmatchers(m1, m2)
+        self.assertEqual(im.visitdir(b'.'), True)
+        self.assertEqual(im.visitdir(b'dir'), True)
+        self.assertEqual(im.visitdir(b'dir/subdir'), b'all')
+        self.assertFalse(im.visitdir(b'dir/foo'))
+        self.assertFalse(im.visitdir(b'folder'))
+        # OPT: We should probably return 'all' for these; we don't because
+        # patternmatcher.visitdir() (our m2) doesn't return 'all' for subdirs of
+        # an 'all' pattern, just True.
+        self.assertEqual(im.visitdir(b'dir/subdir/z'), True)
+        self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
+
+    def testVisitchildrensetM2SubdirPrefix(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        im = matchmod.intersectmatchers(m1, m2)
+        self.assertEqual(im.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(im.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(im.visitchildrenset(b'folder'), set())
+        # OPT: We should probably return 'all' for these
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), b'this')
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'this')
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeIncludfe(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        im = matchmod.intersectmatchers(m1, m2)
+        self.assertEqual(im.visitdir(b'.'), True)
+        self.assertEqual(im.visitdir(b'dir'), True)
+        self.assertFalse(im.visitdir(b'dir/subdir'))
+        self.assertFalse(im.visitdir(b'dir/foo'))
+        self.assertFalse(im.visitdir(b'folder'))
+        self.assertFalse(im.visitdir(b'dir/subdir/z'))
+        self.assertFalse(im.visitdir(b'dir/subdir/x'))
+
+    def testVisitchildrensetIncludeInclude(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        im = matchmod.intersectmatchers(m1, m2)
+        self.assertEqual(im.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(im.visitchildrenset(b'dir'), b'this')
+        self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(im.visitchildrenset(b'folder'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeInclude2(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'path:folder'])
+        im = matchmod.intersectmatchers(m1, m2)
+        # FIXME: is True correct here?
+        self.assertEqual(im.visitdir(b'.'), True)
+        self.assertFalse(im.visitdir(b'dir'))
+        self.assertFalse(im.visitdir(b'dir/subdir'))
+        self.assertFalse(im.visitdir(b'dir/foo'))
+        self.assertFalse(im.visitdir(b'folder'))
+        self.assertFalse(im.visitdir(b'dir/subdir/z'))
+        self.assertFalse(im.visitdir(b'dir/subdir/x'))
+
+    def testVisitchildrensetIncludeInclude2(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'path:folder'])
+        im = matchmod.intersectmatchers(m1, m2)
+        # FIXME: is set() correct here?
+        self.assertEqual(im.visitchildrenset(b'.'), set())
+        self.assertEqual(im.visitchildrenset(b'dir'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(im.visitchildrenset(b'folder'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeInclude3(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        im = matchmod.intersectmatchers(m1, m2)
+        self.assertEqual(im.visitdir(b'.'), True)
+        self.assertEqual(im.visitdir(b'dir'), True)
+        self.assertEqual(im.visitdir(b'dir/subdir'), True)
+        self.assertFalse(im.visitdir(b'dir/foo'))
+        self.assertFalse(im.visitdir(b'folder'))
+        self.assertFalse(im.visitdir(b'dir/subdir/z'))
+        # OPT: this should probably be 'all' not True.
+        self.assertEqual(im.visitdir(b'dir/subdir/x'), True)
+
+    def testVisitchildrensetIncludeInclude3(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        im = matchmod.intersectmatchers(m1, m2)
+        self.assertEqual(im.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(im.visitchildrenset(b'dir/subdir'), {b'x'})
+        self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(im.visitchildrenset(b'folder'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
+        # OPT: this should probably be 'all' not 'this'.
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), b'this')
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeInclude4(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
+        im = matchmod.intersectmatchers(m1, m2)
+        # OPT: these next three could probably be False as well.
+        self.assertEqual(im.visitdir(b'.'), True)
+        self.assertEqual(im.visitdir(b'dir'), True)
+        self.assertEqual(im.visitdir(b'dir/subdir'), True)
+        self.assertFalse(im.visitdir(b'dir/foo'))
+        self.assertFalse(im.visitdir(b'folder'))
+        self.assertFalse(im.visitdir(b'dir/subdir/z'))
+        self.assertFalse(im.visitdir(b'dir/subdir/x'))
+
+    def testVisitchildrensetIncludeInclude4(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
+        im = matchmod.intersectmatchers(m1, m2)
+        # OPT: these next two could probably be set() as well.
+        self.assertEqual(im.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(im.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(im.visitchildrenset(b'dir/subdir'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(im.visitchildrenset(b'folder'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/z'), set())
+        self.assertEqual(im.visitchildrenset(b'dir/subdir/x'), set())
+
+class UnionMatcherTests(unittest.TestCase):
+
+    def testVisitdirM2always(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.alwaysmatcher(b'', b'')
+        um = matchmod.unionmatcher([m1, m2])
+        # um should be equivalent to a alwaysmatcher.
+        self.assertEqual(um.visitdir(b'.'), b'all')
+        self.assertEqual(um.visitdir(b'dir'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitdir(b'dir/foo'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
+        self.assertEqual(um.visitdir(b'folder'), b'all')
+
+    def testVisitchildrensetM2always(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.alwaysmatcher(b'', b'')
+        um = matchmod.unionmatcher([m1, m2])
+        # um should be equivalent to a alwaysmatcher.
+        self.assertEqual(um.visitchildrenset(b'.'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
+        self.assertEqual(um.visitchildrenset(b'folder'), b'all')
+
+    def testVisitdirM1never(self):
+        m1 = matchmod.nevermatcher(b'', b'')
+        m2 = matchmod.alwaysmatcher(b'', b'')
+        um = matchmod.unionmatcher([m1, m2])
+        # um should be equivalent to a alwaysmatcher.
+        self.assertEqual(um.visitdir(b'.'), b'all')
+        self.assertEqual(um.visitdir(b'dir'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitdir(b'dir/foo'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
+        self.assertEqual(um.visitdir(b'folder'), b'all')
+
+    def testVisitchildrensetM1never(self):
+        m1 = matchmod.nevermatcher(b'', b'')
+        m2 = matchmod.alwaysmatcher(b'', b'')
+        um = matchmod.unionmatcher([m1, m2])
+        # um should be equivalent to a alwaysmatcher.
+        self.assertEqual(um.visitchildrenset(b'.'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
+        self.assertEqual(um.visitchildrenset(b'folder'), b'all')
+
+    def testVisitdirM2never(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.nevermatcher(b'', b'')
+        um = matchmod.unionmatcher([m1, m2])
+        # um should be equivalent to a alwaysmatcher.
+        self.assertEqual(um.visitdir(b'.'), b'all')
+        self.assertEqual(um.visitdir(b'dir'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitdir(b'dir/foo'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
+        self.assertEqual(um.visitdir(b'folder'), b'all')
+
+    def testVisitchildrensetM2never(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.nevermatcher(b'', b'')
+        um = matchmod.unionmatcher([m1, m2])
+        # um should be equivalent to a alwaysmatcher.
+        self.assertEqual(um.visitchildrenset(b'.'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
+        self.assertEqual(um.visitchildrenset(b'folder'), b'all')
+
+    def testVisitdirM2SubdirPrefix(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.match(b'', b'', patterns=[b'path:dir/subdir'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitdir(b'.'), b'all')
+        self.assertEqual(um.visitdir(b'dir'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitdir(b'dir/foo'), b'all')
+        self.assertEqual(um.visitdir(b'folder'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
+
+    def testVisitchildrensetM2SubdirPrefix(self):
+        m1 = matchmod.alwaysmatcher(b'', b'')
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitchildrenset(b'.'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/foo'), b'all')
+        self.assertEqual(um.visitchildrenset(b'folder'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeIncludfe(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitdir(b'.'), True)
+        self.assertEqual(um.visitdir(b'dir'), True)
+        self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
+        self.assertFalse(um.visitdir(b'dir/foo'))
+        self.assertFalse(um.visitdir(b'folder'))
+        # OPT: These two should probably be 'all' not True.
+        self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
+        self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
+
+    def testVisitchildrensetIncludeInclude(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'rootfilesin:dir'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(um.visitchildrenset(b'dir'), b'this')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(um.visitchildrenset(b'folder'), set())
+        # OPT: These next two could be 'all' instead of 'this'.
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'this')
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeInclude2(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'path:folder'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitdir(b'.'), True)
+        self.assertEqual(um.visitdir(b'dir'), True)
+        self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
+        self.assertFalse(um.visitdir(b'dir/foo'))
+        self.assertEqual(um.visitdir(b'folder'), b'all')
+        # OPT: These should probably be 'all' not True.
+        self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
+        self.assertEqual(um.visitdir(b'dir/subdir/x'), True)
+
+    def testVisitchildrensetIncludeInclude2(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        m2 = matchmod.match(b'', b'', include=[b'path:folder'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitchildrenset(b'.'), {b'folder', b'dir'})
+        self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(um.visitchildrenset(b'folder'), b'all')
+        # OPT: These next two could be 'all' instead of 'this'.
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'this')
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeInclude3(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitdir(b'.'), True)
+        self.assertEqual(um.visitdir(b'dir'), True)
+        self.assertEqual(um.visitdir(b'dir/subdir'), b'all')
+        self.assertFalse(um.visitdir(b'dir/foo'))
+        self.assertFalse(um.visitdir(b'folder'))
+        self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
+        # OPT: this should probably be 'all' not True.
+        self.assertEqual(um.visitdir(b'dir/subdir/z'), True)
+
+    def testVisitchildrensetIncludeInclude3(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(um.visitchildrenset(b'dir/subdir'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(um.visitchildrenset(b'folder'), set())
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
+        # OPT: this should probably be 'all' not 'this'.
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'this')
+
+    # We're using includematcher instead of patterns because it behaves slightly
+    # better (giving narrower results) than patternmatcher.
+    def testVisitdirIncludeInclude4(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
+        um = matchmod.unionmatcher([m1, m2])
+        # OPT: these next three could probably be False as well.
+        self.assertEqual(um.visitdir(b'.'), True)
+        self.assertEqual(um.visitdir(b'dir'), True)
+        self.assertEqual(um.visitdir(b'dir/subdir'), True)
+        self.assertFalse(um.visitdir(b'dir/foo'))
+        self.assertFalse(um.visitdir(b'folder'))
+        self.assertEqual(um.visitdir(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitdir(b'dir/subdir/x'), b'all')
+
+    def testVisitchildrensetIncludeInclude4(self):
+        m1 = matchmod.match(b'', b'', include=[b'path:dir/subdir/x'])
+        m2 = matchmod.match(b'', b'', include=[b'path:dir/subdir/z'])
+        um = matchmod.unionmatcher([m1, m2])
+        self.assertEqual(um.visitchildrenset(b'.'), {b'dir'})
+        self.assertEqual(um.visitchildrenset(b'dir'), {b'subdir'})
+        self.assertEqual(um.visitchildrenset(b'dir/subdir'), {b'x', b'z'})
+        self.assertEqual(um.visitchildrenset(b'dir/foo'), set())
+        self.assertEqual(um.visitchildrenset(b'folder'), set())
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/z'), b'all')
+        self.assertEqual(um.visitchildrenset(b'dir/subdir/x'), b'all')
+
+class SubdirMatcherTests(unittest.TestCase):
+
+    def testVisitdir(self):
+        m = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        sm = matchmod.subdirmatcher(b'dir', m)
+
+        self.assertEqual(sm.visitdir(b'.'), True)
+        self.assertEqual(sm.visitdir(b'subdir'), b'all')
+        # OPT: These next two should probably be 'all' not True.
+        self.assertEqual(sm.visitdir(b'subdir/x'), True)
+        self.assertEqual(sm.visitdir(b'subdir/z'), True)
+        self.assertFalse(sm.visitdir(b'foo'))
+
+    def testVisitchildrenset(self):
+        m = matchmod.match(b'', b'', include=[b'path:dir/subdir'])
+        sm = matchmod.subdirmatcher(b'dir', m)
+
+        self.assertEqual(sm.visitchildrenset(b'.'), {b'subdir'})
+        self.assertEqual(sm.visitchildrenset(b'subdir'), b'all')
+        # OPT: These next two should probably be 'all' not 'this'.
+        self.assertEqual(sm.visitchildrenset(b'subdir/x'), b'this')
+        self.assertEqual(sm.visitchildrenset(b'subdir/z'), b'this')
+        self.assertEqual(sm.visitchildrenset(b'foo'), set())
+
+class PrefixdirMatcherTests(unittest.TestCase):
+
+    def testVisitdir(self):
+        m = matchmod.match(util.localpath(b'root/d'), b'e/f',
+                [b'../a.txt', b'b.txt'])
+        pm = matchmod.prefixdirmatcher(b'root', b'd/e/f', b'd', m)
+
+        # `m` elides 'd' because it's part of the root, and the rest of the
+        # patterns are relative.
+        self.assertEqual(bool(m(b'a.txt')), False)
+        self.assertEqual(bool(m(b'b.txt')), False)
+        self.assertEqual(bool(m(b'e/a.txt')), True)
+        self.assertEqual(bool(m(b'e/b.txt')), False)
+        self.assertEqual(bool(m(b'e/f/b.txt')), True)
+
+        # The prefix matcher re-adds 'd' to the paths, so they need to be
+        # specified when using the prefixdirmatcher.
+        self.assertEqual(bool(pm(b'a.txt')), False)
+        self.assertEqual(bool(pm(b'b.txt')), False)
+        self.assertEqual(bool(pm(b'd/e/a.txt')), True)
+        self.assertEqual(bool(pm(b'd/e/b.txt')), False)
+        self.assertEqual(bool(pm(b'd/e/f/b.txt')), True)
+
+        self.assertEqual(m.visitdir(b'.'), True)
+        self.assertEqual(m.visitdir(b'e'), True)
+        self.assertEqual(m.visitdir(b'e/f'), True)
+        self.assertEqual(m.visitdir(b'e/f/g'), False)
+
+        self.assertEqual(pm.visitdir(b'.'), True)
+        self.assertEqual(pm.visitdir(b'd'), True)
+        self.assertEqual(pm.visitdir(b'd/e'), True)
+        self.assertEqual(pm.visitdir(b'd/e/f'), True)
+        self.assertEqual(pm.visitdir(b'd/e/f/g'), False)
+
+    def testVisitchildrenset(self):
+        m = matchmod.match(util.localpath(b'root/d'), b'e/f',
+                [b'../a.txt', b'b.txt'])
+        pm = matchmod.prefixdirmatcher(b'root', b'd/e/f', b'd', m)
+
+        # OPT: visitchildrenset could possibly return {'e'} and {'f'} for these
+        # next two, respectively; patternmatcher does not have this
+        # optimization.
+        self.assertEqual(m.visitchildrenset(b'.'), b'this')
+        self.assertEqual(m.visitchildrenset(b'e'), b'this')
+        self.assertEqual(m.visitchildrenset(b'e/f'), b'this')
+        self.assertEqual(m.visitchildrenset(b'e/f/g'), set())
+
+        # OPT: visitchildrenset could possibly return {'d'}, {'e'}, and {'f'}
+        # for these next three, respectively; patternmatcher does not have this
+        # optimization.
+        self.assertEqual(pm.visitchildrenset(b'.'), b'this')
+        self.assertEqual(pm.visitchildrenset(b'd'), b'this')
+        self.assertEqual(pm.visitchildrenset(b'd/e'), b'this')
+        self.assertEqual(pm.visitchildrenset(b'd/e/f'), b'this')
+        self.assertEqual(pm.visitchildrenset(b'd/e/f/g'), set())
 
 if __name__ == '__main__':
     silenttestrunner.main(__name__)
--- a/tests/test-merge-changedelete.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-merge-changedelete.t	Wed Sep 26 20:33:09 2018 +0900
@@ -54,9 +54,11 @@
 Non-interactive merge:
 
   $ hg merge -y
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging file3
   warning: conflicts while merging file3! (edit, then use 'hg resolve --mark')
@@ -121,9 +123,11 @@
   > c
   > d
   > EOF
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? c
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? d
   merging file3
   warning: conflicts while merging file3! (edit, then use 'hg resolve --mark')
@@ -189,18 +193,23 @@
   > baz
   > c
   > EOF
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? foo
   unrecognized response
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? bar
   unrecognized response
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? d
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? baz
   unrecognized response
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
   merging file3
   warning: conflicts while merging file3! (edit, then use 'hg resolve --mark')
@@ -262,9 +271,11 @@
   $ hg merge --config ui.interactive=true <<EOF
   > d
   > EOF
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? d
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   merging file3
   warning: conflicts while merging file3! (edit, then use 'hg resolve --mark')
@@ -473,9 +484,11 @@
   1 other heads for branch "default"
 
   $ hg merge --config ui.interactive=True --tool :prompt
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for file3? 
   0 files updated, 0 files merged, 0 files removed, 3 files unresolved
@@ -532,9 +545,11 @@
   1 other heads for branch "default"
 
   $ hg merge --tool :prompt
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for file3? u
   0 files updated, 0 files merged, 0 files removed, 3 files unresolved
@@ -589,9 +604,11 @@
   1 other heads for branch "default"
 
   $ hg merge --tool :merge3
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging file3
   warning: conflicts while merging file3! (edit, then use 'hg resolve --mark')
@@ -679,9 +696,11 @@
   (status identical)
   
   === :other -> :prompt ===
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for file3? 
   --- diff of status ---
@@ -707,9 +726,11 @@
   (status identical)
   
   === :local -> :prompt ===
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for file3? 
   --- diff of status ---
@@ -725,9 +746,11 @@
   (status identical)
   
   === :fail -> :prompt ===
-  local [working copy] changed file1 which other [merge rev] deleted
+  file 'file1' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  other [merge rev] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for file3? 
   --- diff of status ---
@@ -751,9 +774,11 @@
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 -y
-  local [working copy] changed file1 which other [destination] deleted
+  file 'file1' was deleted in other [destination] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  other [destination] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [destination].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -927,9 +952,11 @@
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 --config ui.interactive=True --tool :prompt
-  local [working copy] changed file1 which other [destination] deleted
+  file 'file1' was deleted in other [destination] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  other [destination] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [destination].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -977,9 +1004,11 @@
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 --tool :merge3
-  local [working copy] changed file1 which other [destination] deleted
+  file 'file1' was deleted in other [destination] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  other [destination] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [destination].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -1033,9 +1062,11 @@
   (status identical)
   
   === :other -> :prompt ===
-  local [working copy] changed file1 which other [destination] deleted
+  file 'file1' was deleted in other [destination] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  other [destination] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [destination].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
@@ -1060,9 +1091,11 @@
   (status identical)
   
   === :local -> :prompt ===
-  local [working copy] changed file1 which other [destination] deleted
+  file 'file1' was deleted in other [destination] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  other [destination] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [destination].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
@@ -1077,9 +1110,11 @@
   (status identical)
   
   === :fail -> :prompt ===
-  local [working copy] changed file1 which other [destination] deleted
+  file 'file1' was deleted in other [destination] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  other [destination] changed file2 which local [working copy] deleted
+  file 'file2' was deleted in local [working copy] but was modified in other [destination].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
--- a/tests/test-merge-default.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-merge-default.t	Wed Sep 26 20:33:09 2018 +0900
@@ -55,9 +55,9 @@
     "bookmarks": [],
     "branch": "default",
     "dirty": "+",
-    "id": "f25cbe84d8b3+2d95304fed5d+",
+    "id": "f25cbe84d8b320e298e7703f18a25a3959518c23+2d95304fed5d89bc9d70b2a0d02f0d567469c3ab+",
     "node": "ffffffffffffffffffffffffffffffffffffffff",
-    "parents": [{"node": "f25cbe84d8b320e298e7703f18a25a3959518c23", "rev": 4}, {"node": "2d95304fed5d89bc9d70b2a0d02f0d567469c3ab", "rev": 2}],
+    "parents": ["f25cbe84d8b320e298e7703f18a25a3959518c23", "2d95304fed5d89bc9d70b2a0d02f0d567469c3ab"],
     "tags": ["tip"]
    }
   ]
@@ -82,7 +82,7 @@
    {
     "bookmarks": [],
     "branch": "default",
-    "id": "1846eede8b68",
+    "id": "1846eede8b6886d8cc8a88c96a687b7fe8f3b9d1",
     "node": "1846eede8b6886d8cc8a88c96a687b7fe8f3b9d1",
     "tags": []
    }
--- a/tests/test-merge-force.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-merge-force.t	Wed Sep 26 20:33:09 2018 +0900
@@ -10,26 +10,26 @@
 
 Create base changeset
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 3 1
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 3 1
   $ hg addremove -q --similarity 0
   $ hg commit -qm 'base'
 
 Create remote changeset
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 3 2
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 3 2
   $ hg addremove -q --similarity 0
   $ hg commit -qm 'remote'
 
 Create local changeset
 
   $ hg update -q 0
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 3 3
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 3 3
   $ hg addremove -q --similarity 0
   $ hg commit -qm 'local'
 
 Set up working directory
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 3 wc
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 3 wc
   $ hg addremove -q --similarity 0
   $ hg forget *_*_*_*-untracked
   $ rm *_*_*_missing-*
@@ -142,55 +142,80 @@
 #   in the same way, so it could potentially be left alone
 
   $ hg merge -f --tool internal:merge3 'desc("remote")' 2>&1 | tee $TESTTMP/merge-output-1
-  local [working copy] changed content1_missing_content1_content4-tracked which other [merge rev] deleted
+  file 'content1_missing_content1_content4-tracked' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  local [working copy] changed content1_missing_content3_content3-tracked which other [merge rev] deleted
+  file 'content1_missing_content3_content3-tracked' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  local [working copy] changed content1_missing_content3_content4-tracked which other [merge rev] deleted
+  file 'content1_missing_content3_content4-tracked' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  local [working copy] changed content1_missing_missing_content4-tracked which other [merge rev] deleted
+  file 'content1_missing_missing_content4-tracked' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content1_content1-untracked which local [working copy] deleted
+  file 'content1_content2_content1_content1-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content1_content2-untracked which local [working copy] deleted
+  file 'content1_content2_content1_content2-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content1_content4-untracked which local [working copy] deleted
+  file 'content1_content2_content1_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content1_missing-tracked which local [working copy] deleted
+  file 'content1_content2_content1_missing-tracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content1_missing-untracked which local [working copy] deleted
+  file 'content1_content2_content1_missing-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content2_content1-untracked which local [working copy] deleted
+  file 'content1_content2_content2_content1-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content2_content2-untracked which local [working copy] deleted
+  file 'content1_content2_content2_content2-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content2_content4-untracked which local [working copy] deleted
+  file 'content1_content2_content2_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content2_missing-tracked which local [working copy] deleted
+  file 'content1_content2_content2_missing-tracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content2_missing-untracked which local [working copy] deleted
+  file 'content1_content2_content2_missing-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_content1-untracked which local [working copy] deleted
+  file 'content1_content2_content3_content1-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_content2-untracked which local [working copy] deleted
+  file 'content1_content2_content3_content2-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_content3-untracked which local [working copy] deleted
+  file 'content1_content2_content3_content3-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_content4-untracked which local [working copy] deleted
+  file 'content1_content2_content3_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_missing-tracked which local [working copy] deleted
+  file 'content1_content2_content3_missing-tracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_missing-untracked which local [working copy] deleted
+  file 'content1_content2_content3_missing-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_missing_content1-untracked which local [working copy] deleted
+  file 'content1_content2_missing_content1-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_missing_content2-untracked which local [working copy] deleted
+  file 'content1_content2_missing_content2-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_missing_content4-untracked which local [working copy] deleted
+  file 'content1_content2_missing_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_missing_missing-tracked which local [working copy] deleted
+  file 'content1_content2_missing_missing-tracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_missing_missing-untracked which local [working copy] deleted
+  file 'content1_content2_missing_missing-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_content1_content4-tracked
   merging content1_content2_content2_content1-tracked
@@ -286,7 +311,7 @@
 the remote side did not touch the file
 
   $ checkstatus() {
-  >   for f in `$PYTHON $TESTDIR/generate-working-copy-states.py filelist 3`
+  >   for f in `"$PYTHON" $TESTDIR/generate-working-copy-states.py filelist 3`
   >   do
   >     echo
   >     hg status -A $f
@@ -667,7 +692,7 @@
   missing_missing_missing_missing-untracked: * (glob)
   <missing>
 
-  $ for f in `$PYTHON $TESTDIR/generate-working-copy-states.py filelist 3`
+  $ for f in `"$PYTHON" $TESTDIR/generate-working-copy-states.py filelist 3`
   > do
   >   if test -f ${f}.orig
   >   then
@@ -703,63 +728,88 @@
   (no more unresolved files)
   $ hg resolve --unmark --all
   $ hg resolve --all --tool internal:merge3
-  other [merge rev] changed content1_content2_content1_content1-untracked which local [working copy] deleted
+  file 'content1_content2_content1_content1-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content1_content2-untracked which local [working copy] deleted
+  file 'content1_content2_content1_content2-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_content1_content4-tracked
-  other [merge rev] changed content1_content2_content1_content4-untracked which local [working copy] deleted
+  file 'content1_content2_content1_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content1_missing-tracked which local [working copy] deleted
+  file 'content1_content2_content1_missing-tracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content1_missing-untracked which local [working copy] deleted
+  file 'content1_content2_content1_missing-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_content2_content1-tracked
-  other [merge rev] changed content1_content2_content2_content1-untracked which local [working copy] deleted
+  file 'content1_content2_content2_content1-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content2_content2-untracked which local [working copy] deleted
+  file 'content1_content2_content2_content2-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_content2_content4-tracked
-  other [merge rev] changed content1_content2_content2_content4-untracked which local [working copy] deleted
+  file 'content1_content2_content2_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content2_missing-tracked which local [working copy] deleted
+  file 'content1_content2_content2_missing-tracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content2_missing-untracked which local [working copy] deleted
+  file 'content1_content2_content2_missing-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_content3_content1-tracked
-  other [merge rev] changed content1_content2_content3_content1-untracked which local [working copy] deleted
+  file 'content1_content2_content3_content1-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_content2-untracked which local [working copy] deleted
+  file 'content1_content2_content3_content2-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_content3_content3-tracked
-  other [merge rev] changed content1_content2_content3_content3-untracked which local [working copy] deleted
+  file 'content1_content2_content3_content3-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_content3_content4-tracked
-  other [merge rev] changed content1_content2_content3_content4-untracked which local [working copy] deleted
+  file 'content1_content2_content3_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_missing-tracked which local [working copy] deleted
+  file 'content1_content2_content3_missing-tracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_content3_missing-untracked which local [working copy] deleted
+  file 'content1_content2_content3_missing-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_missing_content1-tracked
-  other [merge rev] changed content1_content2_missing_content1-untracked which local [working copy] deleted
+  file 'content1_content2_missing_content1-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_missing_content2-untracked which local [working copy] deleted
+  file 'content1_content2_missing_content2-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging content1_content2_missing_content4-tracked
-  other [merge rev] changed content1_content2_missing_content4-untracked which local [working copy] deleted
+  file 'content1_content2_missing_content4-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_missing_missing-tracked which local [working copy] deleted
+  file 'content1_content2_missing_missing-tracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  other [merge rev] changed content1_content2_missing_missing-untracked which local [working copy] deleted
+  file 'content1_content2_missing_missing-untracked' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
-  local [working copy] changed content1_missing_content1_content4-tracked which other [merge rev] deleted
+  file 'content1_missing_content1_content4-tracked' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  local [working copy] changed content1_missing_content3_content3-tracked which other [merge rev] deleted
+  file 'content1_missing_content3_content3-tracked' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  local [working copy] changed content1_missing_content3_content4-tracked which other [merge rev] deleted
+  file 'content1_missing_content3_content4-tracked' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  local [working copy] changed content1_missing_missing_content4-tracked which other [merge rev] deleted
+  file 'content1_missing_missing_content4-tracked' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
   merging missing_content2_content2_content4-tracked
   merging missing_content2_content3_content3-tracked
@@ -784,7 +834,7 @@
 
   $ hg -q update --clean 2
   $ hg --config extensions.purge= purge
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 3 wc
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 3 wc
   $ hg addremove -q --similarity 0
   $ hg forget *_*_*_*-untracked
   $ rm *_*_*_missing-*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-merge-no-file-change.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,379 @@
+  $ cat <<'EOF' >> "$HGRCPATH"
+  > [extensions]
+  > convert =
+  > [templates]
+  > l = '{rev}:{node|short} p={p1rev},{p2rev} m={manifest} f={files|json}'
+  > EOF
+
+  $ check_convert_identity () {
+  >     hg convert -q "$1" "$1.converted"
+  >     hg outgoing -q -R "$1.converted" "$1"
+  >     if [ "$?" != 1 ]; then
+  >         echo '*** BUG: hash changes on convert ***'
+  >         hg log -R "$1.converted" -GTl
+  >     fi
+  > }
+
+Files added at both parents:
+
+  $ hg init added-both
+  $ cd added-both
+  $ touch a b c
+  $ hg ci -qAm0 a
+  $ hg ci -qAm1 b
+  $ hg up -q 0
+  $ hg ci -qAm2 c
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci --debug -m merge
+  committing files:
+  b
+  not reusing manifest (no file change in changelog, but manifest differs)
+  committing manifest
+  committing changelog
+  updating the branch cache
+  committed changeset 3:7aa8a293f5d97377037afc21e871e036e718d659
+  $ hg log -GTl
+  @    3:7aa8a293f5d9 p=2,1 m=3:8667461869a1 f=[]
+  |\
+  | o  2:e0ea47086fce p=0,-1 m=2:b2e5b07f9374 f=["c"]
+  | |
+  o |  1:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"]
+  
+
+  $ cd ..
+  $ check_convert_identity added-both
+
+Files added at both parents, but the other removed at the merge:
+(In this case, ctx.files() after the commit contains the removed file "b", but
+its manifest does not differ from p1.)
+
+  $ hg init added-both-removed-at-merge
+  $ cd added-both-removed-at-merge
+  $ touch a b c
+  $ hg ci -qAm0 a
+  $ hg ci -qAm1 b
+  $ hg up -q 0
+  $ hg ci -qAm2 c
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg rm -f b
+  $ hg ci --debug -m merge
+  committing files:
+  committing manifest
+  committing changelog
+  updating the branch cache
+  committed changeset 3:915745f3ca3d9d699925269474c2d0a9526e8dfa
+  $ hg log -GTl
+  @    3:915745f3ca3d p=2,1 m=3:8e9cf3456921 f=["b"]
+  |\
+  | o  2:e0ea47086fce p=0,-1 m=2:b2e5b07f9374 f=["c"]
+  | |
+  o |  1:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"]
+  
+
+  $ cd ..
+  $ check_convert_identity added-both
+
+An identical file added at both parents:
+
+  $ hg init added-identical
+  $ cd added-identical
+  $ touch a b
+  $ hg ci -qAm0 a
+  $ hg ci -qAm1 b
+  $ hg up -q 0
+  $ touch b
+  $ hg ci -qAm2 b
+
+  $ hg merge
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci --debug -m merge
+  reusing manifest from p1 (no file change)
+  committing changelog
+  updating the branch cache
+  committed changeset 3:de26182cd210f0c3fb175ca7616704ab963d3024
+  $ hg log -GTl
+  @    3:de26182cd210 p=2,1 m=1:686dbf0aeca4 f=[]
+  |\
+  | o  2:f00991f11eca p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  | |
+  o |  1:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"]
+  
+
+  $ cd ..
+  $ check_convert_identity added-identical
+
+#if execbit
+
+An identical file added at both parents, but the flag differs. Take local:
+
+  $ hg init flag-change-take-p1
+  $ cd flag-change-take-p1
+  $ touch a b
+  $ hg ci -qAm0 a
+  $ hg ci -qAm1 b
+  $ hg up -q 0
+  $ touch b
+  $ chmod +x b
+  $ hg ci -qAm2 b
+
+  $ hg merge
+  warning: cannot merge flags for b without common ancestor - keeping local flags
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ chmod +x b
+  $ hg ci --debug -m merge
+  committing files:
+  b
+  reusing manifest form p1 (listed files actually unchanged)
+  committing changelog
+  updating the branch cache
+  committed changeset 3:c8d50407916ef8a5a97cb6e36ca9bc844a6ee13e
+  $ hg log -GTl
+  @    3:c8d50407916e p=2,1 m=2:36b69ba4b24b f=[]
+  |\
+  | o  2:99451f16b3f5 p=0,-1 m=2:36b69ba4b24b f=["b"]
+  | |
+  o |  1:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"]
+  
+  $ hg files -vr3
+           0   a
+           0 x b
+
+  $ cd ..
+  $ check_convert_identity flag-change-take-p1
+
+An identical file added at both parents, but the flag differs. Take other:
+
+  $ hg init flag-change-take-p2
+  $ cd flag-change-take-p2
+  $ touch a b
+  $ hg ci -qAm0 a
+  $ hg ci -qAm1 b
+  $ hg up -q 0
+  $ touch b
+  $ chmod +x b
+  $ hg ci -qAm2 b
+
+  $ hg merge
+  warning: cannot merge flags for b without common ancestor - keeping local flags
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ chmod -x b
+  $ hg ci --debug -m merge
+  committing files:
+  b
+  committing manifest
+  committing changelog
+  updating the branch cache
+  committed changeset 3:06a62a687d87c7d8944743dee1ee9d8c66b3f6e3
+  $ hg log -GTl
+  @    3:06a62a687d87 p=2,1 m=3:2a315ba1aa45 f=["b"]
+  |\
+  | o  2:99451f16b3f5 p=0,-1 m=2:36b69ba4b24b f=["b"]
+  | |
+  o |  1:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"]
+  
+  $ hg files -vr3
+           0   a
+           0   b
+
+  $ cd ..
+  $ check_convert_identity flag-change-take-p2
+
+#endif
+
+An identical file added at both parents, one more file added at p2:
+
+  $ hg init added-some-p2
+  $ cd added-some-p2
+  $ touch a b c
+  $ hg ci -qAm0 a
+  $ hg ci -qAm1 b
+  $ hg ci -qAm2 c
+  $ hg up -q 0
+  $ touch b
+  $ hg ci -qAm3 b
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci --debug -m merge
+  committing files:
+  c
+  not reusing manifest (no file change in changelog, but manifest differs)
+  committing manifest
+  committing changelog
+  updating the branch cache
+  committed changeset 4:f7fbc4e4d9a8fde03ba475adad675578c8bf472d
+  $ hg log -GTl
+  @    4:f7fbc4e4d9a8 p=3,2 m=3:92acd5bfd716 f=[]
+  |\
+  | o  3:e9d9f3cc981f p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  | |
+  o |  2:93c5529a4ec7 p=1,-1 m=2:ae25a31b30b3 f=["c"]
+  | |
+  o |  1:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"]
+  
+
+  $ cd ..
+  $ check_convert_identity added-some-p2
+
+An identical file added at both parents, one more file added at p1:
+(In this case, p1 manifest is reused at the merge commit, which means the
+manifest DAG does not have the same shape as the changelog.)
+
+  $ hg init added-some-p1
+  $ cd added-some-p1
+  $ touch a b
+  $ hg ci -qAm0 a
+  $ hg ci -qAm1 b
+  $ hg up -q 0
+  $ touch b c
+  $ hg ci -qAm2 b
+  $ hg ci -qAm3 c
+
+  $ hg merge
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci --debug -m merge
+  reusing manifest from p1 (no file change)
+  committing changelog
+  updating the branch cache
+  committed changeset 4:a9f0f589a913f5a149dc10dfbd5af726977c36c4
+  $ hg log -GTl
+  @    4:a9f0f589a913 p=3,1 m=2:ae25a31b30b3 f=[]
+  |\
+  | o  3:b8dc385241b5 p=2,-1 m=2:ae25a31b30b3 f=["c"]
+  | |
+  | o  2:f00991f11eca p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  | |
+  o |  1:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"]
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"]
+  
+
+  $ cd ..
+  $ check_convert_identity added-some-p1
+
+A file added at p2, a named branch created at p1:
+
+  $ hg init named-branch-p1
+  $ cd named-branch-p1
+  $ touch a b
+  $ hg ci -qAm0 a
+  $ hg ci -qAm1 b
+  $ hg up -q 0
+  $ hg branch -q foo
+  $ hg ci -m2
+
+  $ hg merge default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci --debug -m merge
+  committing files:
+  b
+  not reusing manifest (no file change in changelog, but manifest differs)
+  committing manifest
+  committing changelog
+  updating the branch cache
+  committed changeset 3:fb97d83b02fd072295cfc2171f21b7d38509bfd7
+  $ hg log -GT'{l} branch={branch}'
+  @    3:fb97d83b02fd p=2,1 m=2:9091c64f4ea1 f=[] branch=foo
+  |\
+  | o  2:a3a9fa6587e5 p=0,-1 m=0:8515d4bfda76 f=[] branch=foo
+  | |
+  o |  1:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"] branch=default
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"] branch=default
+  
+
+  $ cd ..
+  $ check_convert_identity named-branch-p1
+
+A file added at p1, a named branch created at p2:
+(In this case, p1 manifest is reused at the merge commit, which means the
+manifest DAG does not have the same shape as the changelog.)
+
+  $ hg init named-branch-p2
+  $ cd named-branch-p2
+  $ touch a b
+  $ hg ci -qAm0 a
+  $ hg branch -q foo
+  $ hg ci -m1
+  $ hg up -q 0
+  $ hg ci -qAm1 b
+
+  $ hg merge foo
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci --debug -m merge
+  reusing manifest from p1 (no file change)
+  committing changelog
+  updating the branch cache
+  committed changeset 3:036823e24692218324d4af43b07ff89f8a000096
+  $ hg log -GT'{l} branch={branch}'
+  @    3:036823e24692 p=2,1 m=1:686dbf0aeca4 f=[] branch=default
+  |\
+  | o  2:64d01526d4c2 p=0,-1 m=1:686dbf0aeca4 f=["b"] branch=default
+  | |
+  o |  1:da38c8e00727 p=0,-1 m=0:8515d4bfda76 f=[] branch=foo
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"] branch=default
+  
+
+  $ cd ..
+  $ check_convert_identity named-branch-p2
+
+A file changed once at both parents, but amended to have identical content:
+
+  $ hg init amend-p1
+  $ cd amend-p1
+  $ touch a
+  $ hg ci -qAm0 a
+  $ echo foo > a
+  $ hg ci -m1
+  $ hg up -q 0
+  $ echo bar > a
+  $ hg ci -qm2
+  $ echo foo > a
+  $ hg ci -qm3 --amend
+
+  $ hg merge
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci --debug -m merge
+  reusing manifest from p1 (no file change)
+  committing changelog
+  updating the branch cache
+  committed changeset 3:314e5bc5adf5c58ea571efabe33eedba20a201aa
+  $ hg log -GT'{l} branch={branch}'
+  @    3:314e5bc5adf5 p=2,1 m=1:d33ea248bd73 f=[] branch=default
+  |\
+  | o  2:de9c64f226a3 p=0,-1 m=1:d33ea248bd73 f=["a"] branch=default
+  | |
+  o |  1:6a74aec01b3c p=0,-1 m=1:d33ea248bd73 f=["a"] branch=default
+  |/
+  o  0:487a0a245cea p=-1,-1 m=0:8515d4bfda76 f=["a"] branch=default
+  
+
+  $ cd ..
+  $ check_convert_identity amend-p1
--- a/tests/test-merge-remove.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-merge-remove.t	Wed Sep 26 20:33:09 2018 +0900
@@ -20,7 +20,7 @@
   1 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
-  $ hg debugstate --nodates
+  $ hg debugstate --no-dates
   m   0         -2 unset               bar
   m   0         -2 unset               foo1
   copy: foo -> foo1
@@ -36,7 +36,7 @@
   $ cp bar B
   $ hg rm -f foo1 bar
 
-  $ hg debugstate --nodates
+  $ hg debugstate --no-dates
   r   0         -1 set                 bar
   r   0         -1 set                 foo1
   copy: foo -> foo1
@@ -54,7 +54,7 @@
   adding bar
   adding foo1
 
-  $ hg debugstate --nodates
+  $ hg debugstate --no-dates
   n   0         -2 unset               bar
   n   0         -2 unset               foo1
   copy: foo -> foo1
@@ -69,11 +69,11 @@
 
   $ hg revert -vr . foo1 bar
   saving current version of bar as bar.orig
+  saving current version of foo1 as foo1.orig
   reverting bar
-  saving current version of foo1 as foo1.orig
   reverting foo1
 
-  $ hg debugstate --nodates
+  $ hg debugstate --no-dates
   n   0         -2 unset               bar
   n   0         -2 unset               foo1
   copy: foo -> foo1
@@ -102,7 +102,8 @@
 Those who use force will lose
 
   $ hg merge -f
-  other [merge rev] changed bar which local [working copy] deleted
+  file 'bar' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   merging foo1 and foo to foo1
   0 files updated, 1 files merged, 0 files removed, 1 files unresolved
--- a/tests/test-merge-subrepos.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-merge-subrepos.t	Wed Sep 26 20:33:09 2018 +0900
@@ -110,7 +110,8 @@
   $ hg up -r '.^' --config ui.interactive=True << EOF
   > d
   > EOF
-  other [destination] changed b which local [working copy] deleted
+  file 'b' was deleted in local [working copy] but was modified in other [destination].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? d
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-merge-tools.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-merge-tools.t	Wed Sep 26 20:33:09 2018 +0900
@@ -68,7 +68,7 @@
 override $PATH to ensure hgmerge not visible; use $PYTHON in case we're
 running from a devel copy, not a temp installation
 
-  $ PATH="$BINDIR:/usr/sbin" $PYTHON "$BINDIR"/hg merge -r 2
+  $ PATH="$BINDIR:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
   merging f
   warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
@@ -117,7 +117,7 @@
 
   $ echo "echo fail" > false
   $ hg up -qC 1
-  $ PATH="`pwd`:$BINDIR:/usr/sbin" $PYTHON "$BINDIR"/hg merge -r 2
+  $ PATH="`pwd`:$BINDIR:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
   merging f
   warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
@@ -131,7 +131,7 @@
 
   $ mkdir false
   $ hg up -qC 1
-  $ PATH="`pwd`:$BINDIR:/usr/sbin" $PYTHON "$BINDIR"/hg merge -r 2
+  $ PATH="`pwd`:$BINDIR:/usr/sbin" "$PYTHON" "$BINDIR"/hg merge -r 2
   merging f
   warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
@@ -1701,6 +1701,35 @@
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ hg update -C 1 > /dev/null
+
+#else
+
+Match the non-portable filename commits above for test stability
+
+  $ hg import --bypass -q - << EOF
+  > # HG changeset patch
+  > revision 5
+  > 
+  > diff --git a/"; exit 1; echo " b/"; exit 1; echo "
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/"; exit 1; echo "
+  > @@ -0,0 +1,1 @@
+  > +revision 5
+  > EOF
+
+  $ hg import --bypass -q - << EOF
+  > # HG changeset patch
+  > revision 6
+  > 
+  > diff --git a/"; exit 1; echo " b/"; exit 1; echo "
+  > new file mode 100644
+  > --- /dev/null
+  > +++ b/"; exit 1; echo "
+  > @@ -0,0 +1,1 @@
+  > +revision 6
+  > EOF
+
 #endif
 
 Merge post-processing
@@ -1737,14 +1766,64 @@
   # hg resolve --list
   U f
 
-#if symlink
+missingbinary is a merge-tool that doesn't exist:
+
+  $ echo "missingbinary.executable=doesnotexist" >> .hg/hgrc
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  missingbinary.executable=doesnotexist
+  # hg update -C 1
+  $ hg merge -y -r 2 --config ui.merge=missingbinary
+  couldn't find merge tool missingbinary (for pattern f)
+  merging f
+  couldn't find merge tool missingbinary (for pattern f)
+  revision 1
+  space
+  revision 0
+  space
+  revision 2
+  space
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg update -q -C 1
+  $ rm f
 
 internal merge cannot handle symlinks and shouldn't try:
 
-  $ hg update -q -C 1
-  $ rm f
+#if symlink
+
   $ ln -s symlink f
   $ hg commit -qm 'f is symlink'
+
+#else
+
+  $ hg import --bypass -q - << EOF
+  > # HG changeset patch
+  > f is symlink
+  > 
+  > diff --git a/f b/f
+  > old mode 100644
+  > new mode 120000
+  > --- a/f
+  > +++ b/f
+  > @@ -1,2 +1,1 @@
+  > -revision 1
+  > -space
+  > +symlink
+  > \ No newline at end of file
+  > EOF
+
+Resolve 'other [destination] changed f which local [working copy] deleted' prompt
+  $ hg up -q -C --config ui.interactive=True << EOF
+  > c
+  > EOF
+
+#endif
+
   $ hg merge -r 2 --tool internal:merge
   merging f
   warning: internal :merge cannot merge symlinks for f
@@ -1753,8 +1832,6 @@
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
   [1]
 
-#endif
-
 Verify naming of temporary files and that extension is preserved:
 
   $ hg update -q -C 1
@@ -1782,6 +1859,89 @@
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
+Binary files capability checking
+
+  $ hg update -q -C 0
+  $ python <<EOF
+  > with open('b', 'wb') as fp:
+  >     fp.write(b'\x00\x01\x02\x03')
+  > EOF
+  $ hg add b
+  $ hg commit -qm "add binary file (#1)"
+
+  $ hg update -q -C 0
+  $ python <<EOF
+  > with open('b', 'wb') as fp:
+  >     fp.write(b'\x03\x02\x01\x00')
+  > EOF
+  $ hg add b
+  $ hg commit -qm "add binary file (#2)"
+
+By default, binary files capability of internal merge tools is not
+checked strictly.
+
+(for merge-patterns, chosen unintentionally)
+
+  $ hg merge 9 \
+  > --config merge-patterns.b=:merge-other \
+  > --config merge-patterns.re:[a-z]=:other
+  warning: check merge-patterns configurations, if ':merge-other' for binary file 'b' is unintentional
+  (see 'hg help merge-tools' for binary files capability)
+  merging b
+  warning: b looks like a binary file.
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+  [1]
+  $ hg merge --abort -q
+
+(for ui.merge, ignored unintentionally)
+
+  $ hg merge 9 \
+  > --config merge-tools.:other.binary=true \
+  > --config ui.merge=:other
+  tool :other (for pattern b) can't handle binary
+  tool true can't handle binary
+  tool :other can't handle binary
+  tool false can't handle binary
+  no tool found to merge b
+  keep (l)ocal [working copy], take (o)ther [merge rev], or leave (u)nresolved for b? u
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
+  [1]
+  $ hg merge --abort -q
+
+With merge.strict-capability-check=true, binary files capability of
+internal merge tools is checked strictly.
+
+  $ f --hexdump b
+  b:
+  0000: 03 02 01 00                                     |....|
+
+(for merge-patterns)
+
+  $ hg merge 9 --config merge.strict-capability-check=true \
+  > --config merge-tools.:merge-other.binary=true \
+  > --config merge-patterns.b=:merge-other \
+  > --config merge-patterns.re:[a-z]=:other
+  tool :merge-other (for pattern b) can't handle binary
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ f --hexdump b
+  b:
+  0000: 00 01 02 03                                     |....|
+  $ hg merge --abort -q
+
+(for ui.merge)
+
+  $ hg merge 9 --config merge.strict-capability-check=true \
+  > --config ui.merge=:other
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ f --hexdump b
+  b:
+  0000: 00 01 02 03                                     |....|
+  $ hg merge --abort -q
+
 Check that debugpicktool examines which merge tool is chosen for
 specified file as expected
 
@@ -1790,6 +1950,7 @@
   false.whatever=
   true.priority=1
   true.executable=cat
+  missingbinary.executable=doesnotexist
   # hg update -C 1
 
 (default behavior: checking files in the working parent context)
@@ -1812,9 +1973,9 @@
 
 (-r REV causes checking files in specified revision)
 
-  $ hg manifest -r tip
+  $ hg manifest -r 8
   f.txt
-  $ hg debugpickmergetool -r tip
+  $ hg debugpickmergetool -r 8
   f.txt = true
 
 #if symlink
@@ -1824,6 +1985,45 @@
   $ hg debugpickmergetool -r 6d00b3726f6e
   f = :prompt
 
+(by default, it is assumed that no internal merge tools has symlinks
+capability)
+
+  $ hg debugpickmergetool \
+  > -r 6d00b3726f6e \
+  > --config merge-tools.:merge-other.symlink=true \
+  > --config merge-patterns.f=:merge-other \
+  > --config merge-patterns.re:[f]=:merge-local \
+  > --config merge-patterns.re:[a-z]=:other
+  f = :prompt
+
+  $ hg debugpickmergetool \
+  > -r 6d00b3726f6e \
+  > --config merge-tools.:other.symlink=true \
+  > --config ui.merge=:other
+  f = :prompt
+
+(with strict-capability-check=true, actual symlink capabilities are
+checked striclty)
+
+  $ hg debugpickmergetool --config merge.strict-capability-check=true \
+  > -r 6d00b3726f6e \
+  > --config merge-tools.:merge-other.symlink=true \
+  > --config merge-patterns.f=:merge-other \
+  > --config merge-patterns.re:[f]=:merge-local \
+  > --config merge-patterns.re:[a-z]=:other
+  f = :other
+
+  $ hg debugpickmergetool --config merge.strict-capability-check=true \
+  > -r 6d00b3726f6e \
+  > --config ui.merge=:other
+  f = :other
+
+  $ hg debugpickmergetool --config merge.strict-capability-check=true \
+  > -r 6d00b3726f6e \
+  > --config merge-tools.:merge-other.symlink=true \
+  > --config ui.merge=:merge-other
+  f = :prompt
+
 #endif
 
 (--verbose shows some configurations)
--- a/tests/test-merge1.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-merge1.t	Wed Sep 26 20:33:09 2018 +0900
@@ -30,7 +30,8 @@
 
   $ mkdir b && touch b/nonempty
   $ hg up
-  abort: Directory not empty: '$TESTTMP/t/b'
+  abort: Unlinking directory not permitted: *$TESTTMP/t/b* (glob) (windows !)
+  abort: Directory not empty: '?\$TESTTMP/t/b'? (re) (no-windows !)
   [255]
   $ hg ci
   abort: last update was interrupted
--- a/tests/test-minirst.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-minirst.py	Wed Sep 26 20:33:09 2018 +0900
@@ -7,6 +7,7 @@
 )
 
 def debugformat(text, form, **kwargs):
+    blocks, pruned = minirst.parse(text, **kwargs)
     if form == b'html':
         print("html format:")
         out = minirst.format(text, style=form, **kwargs)
@@ -15,12 +16,10 @@
         out = minirst.format(text, width=form, **kwargs)
 
     print("-" * 70)
-    if type(out) == tuple:
-        print(out[0][:-1].decode('utf8'))
+    print(out[:-1].decode('utf8'))
+    if kwargs.get('keep'):
         print("-" * 70)
-        print(stringutil.pprint(out[1]).decode('utf8'))
-    else:
-        print(out[:-1].decode('utf8'))
+        print(stringutil.pprint(pruned).decode('utf8'))
     print("-" * 70)
     print()
 
--- a/tests/test-mq-eol.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-mq-eol.t	Wed Sep 26 20:33:09 2018 +0900
@@ -44,11 +44,11 @@
 
 Test different --eol values
 
-  $ $PYTHON -c 'open("a", "wb").write(b"a\nb\nc\nd\ne")'
+  $ "$PYTHON" -c 'open("a", "wb").write(b"a\nb\nc\nd\ne")'
   $ hg ci -Am adda
   adding .hgignore
   adding a
-  $ $PYTHON ../makepatch.py
+  $ "$PYTHON" ../makepatch.py
   $ hg qimport eol.diff
   adding eol.diff to series file
 
@@ -85,7 +85,7 @@
   applying eol.diff
   now at: eol.diff
   $ hg qrefresh
-  $ $PYTHON ../cateol.py .hg/patches/eol.diff
+  $ "$PYTHON" ../cateol.py .hg/patches/eol.diff
   # HG changeset patch<LF>
   # Parent  0d0bf99a8b7a3842c6f8ef09e34f69156c4bd9d0<LF>
   test message<LF>
@@ -106,7 +106,7 @@
   +d<CR><LF>
   +z<LF>
   \ No newline at end of file<LF>
-  $ $PYTHON ../cateol.py a
+  $ "$PYTHON" ../cateol.py a
   a<CR><LF>
   y<CR><LF>
   c<CR><LF>
@@ -121,7 +121,7 @@
   $ hg --config patch.eol='CRLF' qpush
   applying eol.diff
   now at: eol.diff
-  $ $PYTHON ../cateol.py a
+  $ "$PYTHON" ../cateol.py a
   a<CR><LF>
   y<CR><LF>
   c<CR><LF>
@@ -136,7 +136,7 @@
   $ hg qpush
   applying eol.diff
   now at: eol.diff
-  $ $PYTHON ../cateol.py a
+  $ "$PYTHON" ../cateol.py a
   a<CR><LF>
   y<CR><LF>
   c<CR><LF>
@@ -152,15 +152,15 @@
 
   $ hg init testeol
   $ cd testeol
-  $ $PYTHON -c "open('a', 'wb').write(b'1\r\n2\r\n3\r\n4')"
+  $ "$PYTHON" -c "open('a', 'wb').write(b'1\r\n2\r\n3\r\n4')"
   $ hg ci -Am adda
   adding a
-  $ $PYTHON -c "open('a', 'wb').write(b'1\r\n2\r\n33\r\n4')"
+  $ "$PYTHON" -c "open('a', 'wb').write(b'1\r\n2\r\n33\r\n4')"
   $ hg qnew patch1
   $ hg qpop
   popping patch1
   patch queue now empty
-  $ $PYTHON -c "open('a', 'wb').write(b'1\r\n22\r\n33\r\n4')"
+  $ "$PYTHON" -c "open('a', 'wb').write(b'1\r\n22\r\n33\r\n4')"
   $ hg ci -m changea
 
   $ hg --config 'patch.eol=LF' qpush
--- a/tests/test-mq-missingfiles.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-mq-missingfiles.t	Wed Sep 26 20:33:09 2018 +0900
@@ -23,11 +23,11 @@
 
   $ hg init normal
   $ cd normal
-  $ $PYTHON ../writelines.py b 10 'a\n'
+  $ "$PYTHON" ../writelines.py b 10 'a\n'
   $ hg ci -Am addb
   adding b
   $ echo a > a
-  $ $PYTHON ../writelines.py b 2 'b\n' 10 'a\n' 2 'c\n'
+  $ "$PYTHON" ../writelines.py b 2 'b\n' 10 'a\n' 2 'c\n'
   $ echo c > c
   $ hg add a c
   $ hg qnew -f changeb
@@ -82,7 +82,7 @@
   $ hg up -qC 0
   $ echo a > a
   $ hg mv b bb
-  $ $PYTHON ../writelines.py bb 2 'b\n' 10 'a\n' 2 'c\n'
+  $ "$PYTHON" ../writelines.py bb 2 'b\n' 10 'a\n' 2 'c\n'
   $ echo c > c
   $ hg add a c
   $ hg qnew changebb
@@ -129,11 +129,11 @@
 
   $ hg init git
   $ cd git
-  $ $PYTHON ../writelines.py b 1 '\x00'
+  $ "$PYTHON" ../writelines.py b 1 '\x00'
   $ hg ci -Am addb
   adding b
   $ echo a > a
-  $ $PYTHON ../writelines.py b 1 '\x01' 1 '\x00'
+  $ "$PYTHON" ../writelines.py b 1 '\x01' 1 '\x00'
   $ echo c > c
   $ hg add a c
   $ hg qnew -f changeb
--- a/tests/test-mq-pull-from-bundle.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-mq-pull-from-bundle.t	Wed Sep 26 20:33:09 2018 +0900
@@ -90,7 +90,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
-  new changesets d7553909353d
+  new changesets d7553909353d (1 drafts)
   merging series
   2 files updated, 1 files merged, 0 files removed, 0 files unresolved
   $ test -f .hg/patches/hg-bundle* && echo 'temp. bundle file remained' || true
@@ -122,7 +122,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
-  new changesets d7553909353d
+  new changesets d7553909353d (1 drafts)
   merging series
   2 files updated, 1 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-mq-qimport.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-mq-qimport.t	Wed Sep 26 20:33:09 2018 +0900
@@ -149,10 +149,10 @@
 
 build diff with CRLF
 
-  $ $PYTHON ../writelines.py b 5 'a\n' 5 'a\r\n'
+  $ "$PYTHON" ../writelines.py b 5 'a\n' 5 'a\r\n'
   $ hg ci -Am addb
   adding b
-  $ $PYTHON ../writelines.py b 2 'a\n' 10 'b\n' 2 'a\r\n'
+  $ "$PYTHON" ../writelines.py b 2 'a\n' 10 'b\n' 2 'a\r\n'
   $ hg diff > b.diff
   $ hg up -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-mq-qpush-fail.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-mq-qpush-fail.t	Wed Sep 26 20:33:09 2018 +0900
@@ -31,7 +31,7 @@
   popping patch2
   popping patch1
   patch queue now empty
-  $ $PYTHON -c 'import sys; getattr(sys.stdout, "buffer", sys.stdout).write(b"\xe9\n")' > message
+  $ "$PYTHON" -c 'import sys; getattr(sys.stdout, "buffer", sys.stdout).write(b"\xe9\n")' > message
   $ cat .hg/patches/bad-patch >> message
   $ mv message .hg/patches/bad-patch
   $ cat > $TESTTMP/wrapplayback.py <<EOF
--- a/tests/test-mq-subrepo-svn.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-mq-subrepo-svn.t	Wed Sep 26 20:33:09 2018 +0900
@@ -24,9 +24,9 @@
 
   $ SVNREPOPATH=`pwd`/svn-repo-2499/project
 #if windows
-  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
 
   $ mkdir -p svn-project-2499/trunk
--- a/tests/test-mq.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-mq.t	Wed Sep 26 20:33:09 2018 +0900
@@ -782,7 +782,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 770eb8fce608
+  new changesets 770eb8fce608 (1 drafts)
   (run 'hg update' to get a working copy)
 
 
@@ -1128,9 +1128,9 @@
   > path = sys.argv[1]
   > open(path, 'wb').write(b'BIN\x00ARY')
   > EOF
-  $ $PYTHON writebin.py bucephalus
+  $ "$PYTHON" writebin.py bucephalus
 
-  $ $PYTHON "$TESTDIR/md5sum.py" bucephalus
+  $ "$PYTHON" "$TESTDIR/md5sum.py" bucephalus
   8ba2a2f3e77b55d03051ff9c24ad65e7  bucephalus
   $ hg add bucephalus
   $ hg qnew -f --git addbucephalus
@@ -1149,7 +1149,7 @@
   applying addbucephalus
   now at: addbucephalus
   $ test -f bucephalus
-  $ $PYTHON "$TESTDIR/md5sum.py" bucephalus
+  $ "$PYTHON" "$TESTDIR/md5sum.py" bucephalus
   8ba2a2f3e77b55d03051ff9c24ad65e7  bucephalus
 
 
@@ -1565,7 +1565,7 @@
   > from mercurial.hgweb import wsgicgi
   > import cgitb
   > cgitb.enable()
-  > app = hgweb('.', 'test')
+  > app = hgweb(b'.', b'test')
   > wsgicgi.launch(app)
   > HGWEB
   $ . "$TESTDIR/cgienv"
@@ -1575,7 +1575,7 @@
   $ PATH_INFO=/tags; export PATH_INFO
 #endif
   $ QUERY_STRING='style=raw'
-  $ $PYTHON hgweb.cgi | grep '^tip'
+  $ "$PYTHON" hgweb.cgi | grep '^tip'
   tip	[0-9a-f]{40} (re)
 
   $ cd ..
--- a/tests/test-narrow-clone-no-ellipsis.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-clone-no-ellipsis.t	Wed Sep 26 20:33:09 2018 +0900
@@ -30,10 +30,8 @@
   store
   testonly-simplestore (reposimplestore !)
 
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir/src/f10
-  [excludes]
+  $ hg tracked
+  I path:dir/src/f10
   $ hg update
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ find * | sort
@@ -55,11 +53,9 @@
   added 40 changesets with 19 changes to 19 files
   new changesets *:* (glob)
   $ cd narrowdir
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir/tests
-  [excludes]
-  path:dir/tests/t19
+  $ hg tracked
+  I path:dir/tests
+  X path:dir/tests/t19
   $ hg update
   19 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ find * | sort
@@ -97,11 +93,9 @@
   added 40 changesets with 20 changes to 20 files
   new changesets *:* (glob)
   $ cd narrowroot
-  $ cat .hg/narrowspec
-  [includes]
-  path:.
-  [excludes]
-  path:dir/tests
+  $ hg tracked
+  I path:.
+  X path:dir/tests
   $ hg update
   20 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ find * | sort
@@ -129,3 +123,39 @@
   dir/src/f9
 
   $ cd ..
+
+Testing the --narrowspec flag to clone
+
+  $ cat >> narrowspecs <<EOF
+  > %include foo
+  > [include]
+  > path:dir/tests/
+  > path:dir/src/f12
+  > EOF
+
+  $ hg clone ssh://user@dummy/master specfile --narrowspec narrowspecs
+  reading narrowspec from '$TESTTMP/narrowspecs'
+  abort: cannot specify other files using '%include' in narrowspec
+  [255]
+
+  $ cat > narrowspecs <<EOF
+  > [include]
+  > path:dir/tests/
+  > path:dir/src/f12
+  > EOF
+
+  $ hg clone ssh://user@dummy/master specfile --narrowspec narrowspecs
+  reading narrowspec from '$TESTTMP/narrowspecs'
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 40 changesets with 21 changes to 21 files
+  new changesets 681085829a73:26ce255d5b5d
+  updating to branch default
+  21 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd specfile
+  $ hg tracked
+  I path:dir/src/f12
+  I path:dir/tests
+  $ cd ..
--- a/tests/test-narrow-clone-non-narrow-server.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-clone-non-narrow-server.t	Wed Sep 26 20:33:09 2018 +0900
@@ -31,8 +31,9 @@
   > print(unquote(list(sys.stdin)[1]))
   > EOF
   $ echo hello | hg -R . serve --stdio | \
-  >   $PYTHON unquote.py | grep narrow
+  >   "$PYTHON" unquote.py | tr ' ' '\n' | grep narrow
   narrow=v0
+  exp-narrow-1
 
   $ cd ..
 
--- a/tests/test-narrow-clone.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-clone.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,6 +16,18 @@
   $ for x in `$TESTDIR/seq.py 20`; do echo $x > "t$x"; hg add "t$x"; hg commit -m "Commit test $x"; done
   $ cd ../../..
 
+Only path: and rootfilesin: pattern prefixes are allowed
+
+  $ hg clone --narrow ssh://user@dummy/master badnarrow --noupdate --include 'glob:**'
+  abort: invalid prefix on narrow pattern: glob:**
+  (narrow patterns must begin with one of the following: path:, rootfilesin:)
+  [255]
+
+  $ hg clone --narrow ssh://user@dummy/master badnarrow --noupdate --exclude 'set:ignored'
+  abort: invalid prefix on narrow pattern: set:ignored
+  (narrow patterns must begin with one of the following: path:, rootfilesin:)
+  [255]
+
 narrow clone a file, f10
 
   $ hg clone --narrow ssh://user@dummy/master narrow --noupdate --include "dir/src/f10"
@@ -34,10 +46,8 @@
   store
   testonly-simplestore (reposimplestore !)
 
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir/src/f10
-  [excludes]
+  $ hg tracked
+  I path:dir/src/f10
   $ hg tracked
   I path:dir/src/f10
   $ hg update
@@ -51,11 +61,21 @@
 
   $ cd ..
 
+BUG: local-to-local narrow clones should work, but don't.
+
+  $ hg clone --narrow master narrow-via-localpeer --noupdate --include "dir/src/f10"
+  requesting all changes
+  abort: server doesn't support narrow clones
+  [255]
+  $ hg tracked -R narrow-via-localpeer
+  abort: repository narrow-via-localpeer not found!
+  [255]
+  $ rm -Rf narrow-via-localpeer
+
 narrow clone with a newline should fail
 
   $ hg clone --narrow ssh://user@dummy/master narrow_fail --noupdate --include 'dir/src/f10
   > '
-  requesting all changes
   abort: newlines are not allowed in narrowspec paths
   [255]
 
@@ -69,11 +89,9 @@
   added 21 changesets with 19 changes to 19 files
   new changesets *:* (glob)
   $ cd narrowdir
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir/tests
-  [excludes]
-  path:dir/tests/t19
+  $ hg tracked
+  I path:dir/tests
+  X path:dir/tests/t19
   $ hg tracked
   I path:dir/tests
   X path:dir/tests/t19
@@ -114,11 +132,9 @@
   added 21 changesets with 20 changes to 20 files
   new changesets *:* (glob)
   $ cd narrowroot
-  $ cat .hg/narrowspec
-  [includes]
-  path:.
-  [excludes]
-  path:dir/tests
+  $ hg tracked
+  I path:.
+  X path:dir/tests
   $ hg tracked
   I path:.
   X path:dir/tests
@@ -224,3 +240,52 @@
   dir/tests/t9
 
   $ cd ..
+
+Testing the --narrowspec flag to clone
+
+  $ cat >> narrowspecs <<EOF
+  > %include foo
+  > [include]
+  > path:dir/tests/
+  > path:dir/src/f12
+  > EOF
+
+  $ hg clone ssh://user@dummy/master specfile --narrowspec narrowspecs
+  reading narrowspec from '$TESTTMP/narrowspecs'
+  abort: cannot specify other files using '%include' in narrowspec
+  [255]
+
+  $ cat > narrowspecs <<EOF
+  > [include]
+  > path:dir/tests/
+  > path:dir/src/f12
+  > EOF
+
+  $ hg clone ssh://user@dummy/master specfile --narrowspec narrowspecs
+  reading narrowspec from '$TESTTMP/narrowspecs'
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 23 changesets with 21 changes to 21 files
+  new changesets c13e3773edb4:26ce255d5b5d
+  updating to branch default
+  21 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd specfile
+  $ hg tracked
+  I path:dir/src/f12
+  I path:dir/tests
+  $ cd ..
+
+Narrow spec with invalid patterns is rejected
+
+  $ cat > narrowspecs <<EOF
+  > [include]
+  > glob:**
+  > EOF
+
+  $ hg clone ssh://user@dummy/master badspecfile --narrowspec narrowspecs
+  reading narrowspec from '$TESTTMP/narrowspecs'
+  abort: invalid prefix on narrow pattern: glob:**
+  (narrow patterns must begin with one of the following: path:, rootfilesin:)
+  [255]
--- a/tests/test-narrow-commit.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-commit.t	Wed Sep 26 20:33:09 2018 +0900
@@ -103,5 +103,5 @@
 debugdirstate. If we don't do this, the test can be slightly flaky.
   $ sleep 3
   $ hg status
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   n 644         10 set                 inside/f1
--- a/tests/test-narrow-debugcommands.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-debugcommands.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1,10 +1,10 @@
   $ . "$TESTDIR/narrow-library.sh"
   $ hg init repo
   $ cd repo
-  $ cat << EOF > .hg/narrowspec
-  > [includes]
+  $ cat << EOF > .hg/store/narrowspec
+  > [include]
   > path:foo
-  > [excludes]
+  > [exclude]
   > EOF
   $ echo treemanifest >> .hg/requires
   $ echo narrowhg-experimental >> .hg/requires
--- a/tests/test-narrow-exchange.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-exchange.t	Wed Sep 26 20:33:09 2018 +0900
@@ -161,7 +161,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 8 changesets, 10 total revisions
+  checked 8 changesets with 10 changes to 3 files
 
 Can not push to wider repo if change affects paths in wider repo that are
 not also in narrower repo
--- a/tests/test-narrow-expanddirstate.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-expanddirstate.t	Wed Sep 26 20:33:09 2018 +0900
@@ -27,16 +27,16 @@
 
   $ mkdir outside
   $ echo other_contents > outside/f2
-  $ grep outside .hg/narrowspec
+  $ hg tracked | grep outside
   [1]
-  $ grep outside .hg/dirstate
+  $ hg files | grep outside
   [1]
   $ hg status
 
 `hg status` did not add outside.
-  $ grep outside .hg/narrowspec
+  $ hg tracked | grep outside
   [1]
-  $ grep outside .hg/dirstate
+  $ hg files | grep outside
   [1]
 
 Unfortunately this is not really a candidate for adding to narrowhg proper,
@@ -115,12 +115,12 @@
 `hg status` will now add outside, but not patchdir.
   $ DIRSTATEINCLUDES=path:outside hg status
   M outside/f2
-  $ grep outside .hg/narrowspec
-  path:outside
-  $ grep outside .hg/dirstate > /dev/null
-  $ grep patchdir .hg/narrowspec
+  $ hg tracked | grep outside
+  I path:outside
+  $ hg files | grep outside > /dev/null
+  $ hg tracked | grep patchdir
   [1]
-  $ grep patchdir .hg/dirstate
+  $ hg files | grep patchdir
   [1]
 
 Get rid of the modification to outside/f2.
@@ -142,9 +142,9 @@
   1 out of 1 hunks FAILED -- saving rejects to file patchdir/f3.rej
   abort: patch failed to apply
   [255]
-  $ grep patchdir .hg/narrowspec
+  $ hg tracked | grep patchdir
   [1]
-  $ grep patchdir .hg/dirstate > /dev/null
+  $ hg files | grep patchdir > /dev/null
   [1]
 
 Let's make it apply cleanly and see that it *did* expand properly
@@ -159,6 +159,6 @@
   applying $TESTTMP/foo.patch
   $ cat patchdir/f3
   patched_this
-  $ grep patchdir .hg/narrowspec
-  path:patchdir
-  $ grep patchdir .hg/dirstate > /dev/null
+  $ hg tracked | grep patchdir
+  I path:patchdir
+  $ hg files | grep patchdir > /dev/null
--- a/tests/test-narrow-patterns.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-patterns.t	Wed Sep 26 20:33:09 2018 +0900
@@ -88,15 +88,13 @@
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cd narrow
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir1
-  path:dir2
-  [excludes]
-  path:dir1/dirA
-  path:dir1/dirB
-  path:dir2/dirA
-  path:dir2/dirB
+  $ hg tracked
+  I path:dir1
+  I path:dir2
+  X path:dir1/dirA
+  X path:dir1/dirB
+  X path:dir2/dirA
+  X path:dir2/dirB
   $ hg manifest -r tip
   dir1/bar
   dir1/dirA/bar
@@ -144,14 +142,12 @@
   adding file changes
   added 9 changesets with 6 changes to 6 files
   new changesets *:* (glob)
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir1
-  path:dir2
-  [excludes]
-  path:dir1/dirB
-  path:dir2/dirA
-  path:dir2/dirB
+  $ hg tracked
+  I path:dir1
+  I path:dir2
+  X path:dir1/dirB
+  X path:dir2/dirA
+  X path:dir2/dirB
   $ find * | sort
   dir1
   dir1/bar
@@ -206,14 +202,12 @@
   adding file changes
   added 11 changesets with 7 changes to 7 files
   new changesets *:* (glob)
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir1
-  path:dir2
-  [excludes]
-  path:dir1/dirA/bar
-  path:dir1/dirB
-  path:dir2/dirA
+  $ hg tracked
+  I path:dir1
+  I path:dir2
+  X path:dir1/dirA/bar
+  X path:dir1/dirB
+  X path:dir2/dirA
   $ find * | sort
   dir1
   dir1/bar
@@ -266,14 +260,12 @@
   adding file changes
   added 13 changesets with 8 changes to 8 files
   new changesets *:* (glob)
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir1
-  path:dir2
-  [excludes]
-  path:dir1/dirA
-  path:dir1/dirA/bar
-  path:dir1/dirB
+  $ hg tracked
+  I path:dir1
+  I path:dir2
+  X path:dir1/dirA
+  X path:dir1/dirA/bar
+  X path:dir1/dirB
   $ find * | sort
   dir1
   dir1/bar
@@ -327,13 +319,11 @@
   adding file changes
   added 13 changesets with 9 changes to 9 files
   new changesets *:* (glob)
-  $ cat .hg/narrowspec
-  [includes]
-  path:dir1
-  path:dir2
-  [excludes]
-  path:dir1/dirA/bar
-  path:dir1/dirB
+  $ hg tracked
+  I path:dir1
+  I path:dir2
+  X path:dir1/dirA/bar
+  X path:dir1/dirB
   $ find * | sort
   dir1
   dir1/bar
@@ -437,3 +427,43 @@
   |
   o  0 2a4f0c3b67da... root
   
+
+Illegal patterns are rejected
+
+  $ hg tracked --addinclude glob:**
+  abort: invalid prefix on narrow pattern: glob:**
+  (narrow patterns must begin with one of the following: path:, rootfilesin:)
+  [255]
+
+  $ hg tracked --addexclude set:ignored
+  abort: invalid prefix on narrow pattern: set:ignored
+  (narrow patterns must begin with one of the following: path:, rootfilesin:)
+  [255]
+
+  $ cat .hg/store/narrowspec
+  [include]
+  path:dir1
+  path:dir1/dirA
+  [exclude]
+
+  $ cat > .hg/store/narrowspec << EOF
+  > [include]
+  > glob:**
+  > EOF
+
+  $ hg tracked
+  abort: invalid prefix on narrow pattern: glob:**
+  (narrow patterns must begin with one of the following: path:, rootfilesin:)
+  [255]
+
+  $ cat > .hg/store/narrowspec << EOF
+  > [include]
+  > path:.
+  > [exclude]
+  > set:ignored
+  > EOF
+
+  $ hg tracked
+  abort: invalid prefix on narrow pattern: set:ignored
+  (narrow patterns must begin with one of the following: path:, rootfilesin:)
+  [255]
--- a/tests/test-narrow-pull.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-pull.t	Wed Sep 26 20:33:09 2018 +0900
@@ -166,10 +166,9 @@
 
 We should also be able to unshare without breaking everything:
   $ hg unshare
-  devel-warn: write with no wlock: "narrowspec" at: */hgext/narrow/narrowrepo.py:* (unsharenarrowspec) (glob)
   $ hg verify
   checking changesets
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-trackedcmd.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,218 @@
+#testcases flat tree
+  $ . "$TESTDIR/narrow-library.sh"
+
+#if tree
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > treemanifest = 1
+  > EOF
+#endif
+
+  $ hg init master
+  $ cd master
+  $ cat >> .hg/hgrc <<EOF
+  > [narrow]
+  > serveellipses=True
+  > EOF
+
+  $ mkdir inside
+  $ echo 'inside' > inside/f
+  $ hg add inside/f
+  $ hg commit -m 'add inside'
+
+  $ mkdir widest
+  $ echo 'widest' > widest/f
+  $ hg add widest/f
+  $ hg commit -m 'add widest'
+
+  $ mkdir outside
+  $ echo 'outside' > outside/f
+  $ hg add outside/f
+  $ hg commit -m 'add outside'
+
+  $ cd ..
+
+narrow clone the inside file
+
+  $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  new changesets *:* (glob)
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd narrow
+  $ hg tracked
+  I path:inside
+  $ ls
+  inside
+  $ cat inside/f
+  inside
+  $ cd ..
+
+add more upstream files which we will include in a wider narrow spec
+
+  $ cd master
+
+  $ mkdir wider
+  $ echo 'wider' > wider/f
+  $ hg add wider/f
+  $ echo 'widest v2' > widest/f
+  $ hg commit -m 'add wider, update widest'
+
+  $ echo 'widest v3' > widest/f
+  $ hg commit -m 'update widest v3'
+
+  $ echo 'inside v2' > inside/f
+  $ hg commit -m 'update inside'
+
+  $ mkdir outside2
+  $ echo 'outside2' > outside2/f
+  $ hg add outside2/f
+  $ hg commit -m 'add outside2'
+
+  $ echo 'widest v4' > widest/f
+  $ hg commit -m 'update widest v4'
+
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  7: update widest v4
+  6: add outside2
+  5: update inside
+  4: update widest v3
+  3: add wider, update widest
+  2: add outside
+  1: add widest
+  0: add inside
+
+  $ cd ..
+
+Testing the --import-rules flag of `hg tracked` command
+
+  $ cd narrow
+  $ hg tracked --import-rules
+  hg tracked: option --import-rules requires argument
+  hg tracked [OPTIONS]... [REMOTE]
+  
+  show or change the current narrowspec
+  
+  options ([+] can be repeated):
+  
+      --addinclude VALUE [+]       new paths to include
+      --removeinclude VALUE [+]    old paths to no longer include
+      --addexclude VALUE [+]       new paths to exclude
+      --import-rules VALUE         import narrowspecs from a file
+      --removeexclude VALUE [+]    old paths to no longer exclude
+      --clear                      whether to replace the existing narrowspec
+      --force-delete-local-changes forces deletion of local changes when
+                                   narrowing
+   -e --ssh CMD                    specify ssh command to use
+      --remotecmd CMD              specify hg command to run on the remote side
+      --insecure                   do not verify server certificate (ignoring
+                                   web.cacerts config)
+  
+  (use 'hg tracked -h' to show more help)
+  [255]
+  $ hg tracked --import-rules doesnotexist
+  abort: cannot read narrowspecs from '$TESTTMP/narrow/doesnotexist': $ENOENT$
+  [255]
+
+  $ cat > specs <<EOF
+  > %include foo
+  > [include]
+  > path:widest/
+  > [exclude]
+  > path:inside/
+  > EOF
+
+  $ hg tracked --import-rules specs
+  abort: including other spec files using '%include' is not supported in narrowspec
+  [255]
+
+  $ cat > specs <<EOF
+  > [include]
+  > outisde
+  > [exclude]
+  > inside
+  > EOF
+
+  $ hg tracked --import-rules specs
+  comparing with ssh://user@dummy/master
+  searching for changes
+  looking for local changes to affected paths
+  deleting data/inside/f.i
+  deleting meta/inside/00manifest.i (tree !)
+  no changes found
+  saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 0 files
+  new changesets *:* (glob)
+  $ hg tracked
+  I path:outisde
+  X path:inside
+
+Testing the --import-rules flag with --addinclude and --addexclude
+
+  $ cat > specs <<EOF
+  > [include]
+  > widest
+  > EOF
+
+  $ hg tracked --import-rules specs --addinclude 'wider/'
+  comparing with ssh://user@dummy/master
+  searching for changes
+  no changes found
+  saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 1 changes to 1 files
+  new changesets *:* (glob)
+  $ hg tracked
+  I path:outisde
+  I path:wider
+  I path:widest
+  X path:inside
+
+  $ cat > specs <<EOF
+  > [exclude]
+  > outside2
+  > EOF
+
+  $ hg tracked --import-rules specs --addexclude 'widest'
+  comparing with ssh://user@dummy/master
+  searching for changes
+  looking for local changes to affected paths
+  deleting data/widest/f.i
+  deleting meta/widest/00manifest.i (tree !)
+  $ hg tracked
+  I path:outisde
+  I path:wider
+  X path:inside
+  X path:outside2
+  X path:widest
+
+  $ hg tracked --import-rules specs --clear
+  The --clear option is not yet supported.
+  [1]
+
+Testing with passing a out of wdir file
+
+  $ cat > ../nspecs <<EOF
+  > [include]
+  > widest
+  > EOF
+
+  $ hg tracked --import-rules ../nspecs
+  comparing with ssh://user@dummy/master
+  searching for changes
+  no changes found
+  saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-widen.hg (glob)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 0 changes to 0 files
+  new changesets *:* (glob)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-narrow-widen-no-ellipsis.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,412 @@
+#testcases tree flat
+  $ . "$TESTDIR/narrow-library.sh"
+
+#if tree
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > treemanifest = 1
+  > EOF
+#endif
+
+  $ hg init master
+  $ cd master
+
+  $ mkdir inside
+  $ echo 'inside' > inside/f
+  $ hg add inside/f
+  $ hg commit -m 'add inside'
+
+  $ mkdir widest
+  $ echo 'widest' > widest/f
+  $ hg add widest/f
+  $ hg commit -m 'add widest'
+
+  $ mkdir outside
+  $ echo 'outside' > outside/f
+  $ hg add outside/f
+  $ hg commit -m 'add outside'
+
+  $ cd ..
+
+narrow clone the inside file
+
+  $ hg clone --narrow ssh://user@dummy/master narrow --include inside
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 1 changes to 1 files
+  new changesets *:* (glob)
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd narrow
+  $ hg tracked
+  I path:inside
+  $ ls
+  inside
+  $ cat inside/f
+  inside
+  $ cd ..
+
+add more upstream files which we will include in a wider narrow spec
+
+  $ cd master
+
+  $ mkdir wider
+  $ echo 'wider' > wider/f
+  $ hg add wider/f
+  $ echo 'widest v2' > widest/f
+  $ hg commit -m 'add wider, update widest'
+
+  $ echo 'widest v3' > widest/f
+  $ hg commit -m 'update widest v3'
+
+  $ echo 'inside v2' > inside/f
+  $ hg commit -m 'update inside'
+
+  $ mkdir outside2
+  $ echo 'outside2' > outside2/f
+  $ hg add outside2/f
+  $ hg commit -m 'add outside2'
+
+  $ echo 'widest v4' > widest/f
+  $ hg commit -m 'update widest v4'
+
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  7: update widest v4
+  6: add outside2
+  5: update inside
+  4: update widest v3
+  3: add wider, update widest
+  2: add outside
+  1: add widest
+  0: add inside
+
+  $ cd ..
+
+Widen the narrow spec to see the widest file. This should not get the newly
+added upstream revisions.
+
+  $ cd narrow
+  $ hg tracked --addinclude widest/f --debug
+  comparing with ssh://user@dummy/master
+  running python "*dummyssh" *user@dummy* *hg -R master serve --stdio* (glob)
+  sending hello command
+  sending between command
+  remote: * (glob)
+  remote: capabilities: * (glob)
+  remote: 1
+  sending protocaps command
+  query 1; heads
+  sending batch command
+  searching for changes
+  all local heads known remotely
+  no changes found
+  sending getbundle command
+  bundle2-input-bundle: with-transaction
+  bundle2-input-part: "changegroup" (params: * mandatory) supported (glob)
+  adding changesets
+  adding manifests
+  adding widest/ revisions (tree !)
+  adding file changes
+  adding widest/f revisions (tree !)
+  added 0 changesets with 1 changes to 1 files
+  bundle2-input-part: total payload size * (glob)
+  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 2 parts total
+  checking for updated bookmarks
+  3 local changesets published
+   widest/f: add from widened narrow clone -> g
+  getting widest/f
+  $ hg tracked
+  I path:inside
+  I path:widest/f
+
+  $ cat widest/f
+  widest
+
+Pull down the newly added upstream revision.
+
+  $ hg pull
+  pulling from ssh://user@dummy/master
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 4 changes to 2 files
+  new changesets *:* (glob)
+  (run 'hg update' to get a working copy)
+  $ hg update -r 'desc("add wider")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cat widest/f
+  widest v2
+
+  $ hg update -r 'desc("update inside")'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat widest/f
+  widest v3
+  $ cat inside/f
+  inside v2
+
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  7: update widest v4
+  6: add outside2
+  5: update inside
+  4: update widest v3
+  3: add wider, update widest
+  2: add outside
+  1: add widest
+  0: add inside
+
+Check that widening with a newline fails
+
+  $ hg tracked --addinclude 'widest
+  > '
+  abort: newlines are not allowed in narrowspec paths
+  [255]
+
+widen the narrow spec to include the wider file
+
+  $ hg tracked --addinclude wider
+  comparing with ssh://user@dummy/master
+  searching for changes
+  no changes found
+  adding changesets
+  adding manifests
+  adding file changes
+  added 0 changesets with 1 changes to 1 files
+  5 local changesets published
+  $ hg tracked
+  I path:inside
+  I path:wider
+  I path:widest/f
+  $ hg update 'desc("add widest")'
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ cat widest/f
+  widest
+  $ hg update 'desc("add wider, update widest")'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat wider/f
+  wider
+  $ cat widest/f
+  widest v2
+  $ hg update 'desc("update widest v3")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat widest/f
+  widest v3
+  $ hg update 'desc("update widest v4")'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat widest/f
+  widest v4
+
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  7: update widest v4
+  6: add outside2
+  5: update inside
+  4: update widest v3
+  3: add wider, update widest
+  2: add outside
+  1: add widest
+  0: add inside
+
+separate suite of tests: files from 0-10 modified in changes 0-10. This allows
+more obvious precise tests tickling particular corner cases.
+
+  $ cd ..
+  $ hg init upstream
+  $ cd upstream
+  $ for x in `$TESTDIR/seq.py 0 10`
+  > do
+  >   mkdir d$x
+  >   echo $x > d$x/f
+  >   hg add d$x/f
+  >   hg commit -m "add d$x/f"
+  > done
+  $ hg log -T "{rev}: {desc}\n"
+  10: add d10/f
+  9: add d9/f
+  8: add d8/f
+  7: add d7/f
+  6: add d6/f
+  5: add d5/f
+  4: add d4/f
+  3: add d3/f
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
+
+make narrow clone with every third node.
+
+  $ cd ..
+  $ hg clone --narrow ssh://user@dummy/upstream narrow2 --include d0 --include d3 --include d6 --include d9
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 11 changesets with 4 changes to 4 files
+  new changesets *:* (glob)
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd narrow2
+  $ hg tracked
+  I path:d0
+  I path:d3
+  I path:d6
+  I path:d9
+  $ hg verify
+  checking changesets
+  checking manifests
+  checking directory manifests (tree !)
+  crosschecking files in changesets and manifests
+  checking files
+  checked 11 changesets with 4 changes to 4 files
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  10: add d10/f
+  9: add d9/f
+  8: add d8/f
+  7: add d7/f
+  6: add d6/f
+  5: add d5/f
+  4: add d4/f
+  3: add d3/f
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
+  $ hg tracked --addinclude d1
+  comparing with ssh://user@dummy/upstream
+  searching for changes
+  no changes found
+  adding changesets
+  adding manifests
+  adding file changes
+  added 0 changesets with 1 changes to 1 files
+  11 local changesets published
+  $ hg tracked
+  I path:d0
+  I path:d1
+  I path:d3
+  I path:d6
+  I path:d9
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  10: add d10/f
+  9: add d9/f
+  8: add d8/f
+  7: add d7/f
+  6: add d6/f
+  5: add d5/f
+  4: add d4/f
+  3: add d3/f
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
+
+Verify shouldn't claim the repo is corrupt after a widen.
+
+  $ hg verify
+  checking changesets
+  checking manifests
+  checking directory manifests (tree !)
+  crosschecking files in changesets and manifests
+  checking files
+  checked 11 changesets with 5 changes to 5 files
+
+Widening preserves parent of local commit
+
+  $ cd ..
+  $ hg clone -q --narrow ssh://user@dummy/upstream narrow3 --include d2 -r 2
+  $ cd narrow3
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
+  $ hg pull -q -r 3
+  $ hg co -q tip
+  $ hg pull -q -r 4
+  $ echo local > d2/f
+  $ hg ci -m local
+  created new head
+  $ hg tracked -q --addinclude d0 --addinclude d9
+
+Widening preserves bookmarks
+
+  $ cd ..
+  $ hg clone -q --narrow ssh://user@dummy/upstream narrow-bookmarks --include d4
+  $ cd narrow-bookmarks
+  $ echo local > d4/f
+  $ hg ci -m local
+  $ hg bookmarks bookmark
+  $ hg bookmarks
+   * bookmark                  11:* (glob)
+  $ hg -q tracked --addinclude d2
+  $ hg bookmarks
+   * bookmark                  11:* (glob)
+  $ hg log -r bookmark -T '{desc}\n'
+  local
+
+Widening that fails can be recovered from
+
+  $ cd ..
+  $ hg clone -q --narrow ssh://user@dummy/upstream interrupted --include d0
+  $ cd interrupted
+  $ echo local > d0/f
+  $ hg ci -m local
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  11: local
+  10: add d10/f
+  9: add d9/f
+  8: add d8/f
+  7: add d7/f
+  6: add d6/f
+  5: add d5/f
+  4: add d4/f
+  3: add d3/f
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
+  $ hg bookmarks bookmark
+  $ hg --config hooks.pretxnchangegroup.bad=false tracked --addinclude d1
+  comparing with ssh://user@dummy/upstream
+  searching for changes
+  no changes found
+  adding changesets
+  adding manifests
+  adding file changes
+  added 0 changesets with 1 changes to 1 files
+  11 local changesets published
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  11: local
+  10: add d10/f
+  9: add d9/f
+  8: add d8/f
+  7: add d7/f
+  6: add d6/f
+  5: add d5/f
+  4: add d4/f
+  3: add d3/f
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
+  $ hg bookmarks
+   * bookmark                  11:* (glob)
+  $ hg unbundle .hg/strip-backup/*-widen.hg
+  abort: .hg/strip-backup/*-widen.hg: $ENOTDIR$ (windows !)
+  abort: $ENOENT$: .hg/strip-backup/*-widen.hg (no-windows !)
+  [255]
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  11: local
+  10: add d10/f
+  9: add d9/f
+  8: add d8/f
+  7: add d7/f
+  6: add d6/f
+  5: add d5/f
+  4: add d4/f
+  3: add d3/f
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
+  $ hg bookmarks
+   * bookmark                  11:* (glob)
--- a/tests/test-narrow-widen.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow-widen.t	Wed Sep 26 20:33:09 2018 +0900
@@ -76,23 +76,23 @@
   $ echo 'widest v4' > widest/f
   $ hg commit -m 'update widest v4'
 
-  $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
-  *: update widest v4 (glob)
-  *: add outside2 (glob)
-  *: update inside (glob)
-  *: update widest v3 (glob)
-  *: add wider, update widest (glob)
-  *: add outside (glob)
-  *: add widest (glob)
-  *: add inside (glob)
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  7: update widest v4
+  6: add outside2
+  5: update inside
+  4: update widest v3
+  3: add wider, update widest
+  2: add outside
+  1: add widest
+  0: add inside
 
   $ cd ..
 
-Widen the narrow spec to see the wider file. This should not get the newly
+Widen the narrow spec to see the widest file. This should not get the newly
 added upstream revisions.
 
   $ cd narrow
-  $ hg tracked --addinclude wider/f
+  $ hg tracked --addinclude widest/f
   comparing with ssh://user@dummy/master
   searching for changes
   no changes found
@@ -100,11 +100,14 @@
   adding changesets
   adding manifests
   adding file changes
-  added 2 changesets with 1 changes to 1 files
+  added 3 changesets with 2 changes to 2 files
   new changesets *:* (glob)
   $ hg tracked
   I path:inside
-  I path:wider/f
+  I path:widest/f
+
+  $ cat widest/f
+  widest
 
 Pull down the newly added upstream revision.
 
@@ -114,28 +117,30 @@
   adding changesets
   adding manifests
   adding file changes
-  added 4 changesets with 2 changes to 2 files
+  added 5 changesets with 4 changes to 2 files
   new changesets *:* (glob)
   (run 'hg update' to get a working copy)
   $ hg update -r 'desc("add wider")'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cat wider/f
-  wider
+  $ cat widest/f
+  widest v2
 
   $ hg update -r 'desc("update inside")'
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cat wider/f
-  wider
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat widest/f
+  widest v3
   $ cat inside/f
   inside v2
 
-  $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
-  ...*: update widest v4 (glob)
-  *: update inside (glob)
-  ...*: update widest v3 (glob)
-  *: add wider, update widest (glob)
-  ...*: add outside (glob)
-  *: add inside (glob)
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  7: update widest v4
+  ...6: add outside2
+  5: update inside
+  4: update widest v3
+  3: add wider, update widest
+  ...2: add outside
+  1: add widest
+  0: add inside
 
 Check that widening with a newline fails
 
@@ -144,9 +149,9 @@
   abort: newlines are not allowed in narrowspec paths
   [255]
 
-widen the narrow spec to include the widest file
+widen the narrow spec to include the wider file
 
-  $ hg tracked --addinclude widest
+  $ hg tracked --addinclude wider
   comparing with ssh://user@dummy/master
   searching for changes
   no changes found
@@ -158,8 +163,8 @@
   new changesets *:* (glob)
   $ hg tracked
   I path:inside
-  I path:wider/f
-  I path:widest
+  I path:wider
+  I path:widest/f
   $ hg update 'desc("add widest")'
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ cat widest/f
@@ -179,15 +184,15 @@
   $ cat widest/f
   widest v4
 
-  $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
-  *: update widest v4 (glob)
-  ...*: add outside2 (glob)
-  *: update inside (glob)
-  *: update widest v3 (glob)
-  *: add wider, update widest (glob)
-  ...*: add outside (glob)
-  *: add widest (glob)
-  *: add inside (glob)
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  7: update widest v4
+  ...6: add outside2
+  5: update inside
+  4: update widest v3
+  3: add wider, update widest
+  ...2: add outside
+  1: add widest
+  0: add inside
 
 separate suite of tests: files from 0-10 modified in changes 0-10. This allows
 more obvious precise tests tickling particular corner cases.
@@ -206,18 +211,18 @@
   >   hg add d$x/f
   >   hg commit -m "add d$x/f"
   > done
-  $ hg log -T "{node|short}: {desc}\n"
-  *: add d10/f (glob)
-  *: add d9/f (glob)
-  *: add d8/f (glob)
-  *: add d7/f (glob)
-  *: add d6/f (glob)
-  *: add d5/f (glob)
-  *: add d4/f (glob)
-  *: add d3/f (glob)
-  *: add d2/f (glob)
-  *: add d1/f (glob)
-  *: add d0/f (glob)
+  $ hg log -T "{rev}: {desc}\n"
+  10: add d10/f
+  9: add d9/f
+  8: add d8/f
+  7: add d7/f
+  6: add d6/f
+  5: add d5/f
+  4: add d4/f
+  3: add d3/f
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
 
 make narrow clone with every third node.
 
@@ -243,16 +248,16 @@
   checking directory manifests (tree !)
   crosschecking files in changesets and manifests
   checking files
-  4 files, 8 changesets, 4 total revisions
-  $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
-  ...*: add d10/f (glob)
-  *: add d9/f (glob)
-  ...*: add d8/f (glob)
-  *: add d6/f (glob)
-  ...*: add d5/f (glob)
-  *: add d3/f (glob)
-  ...*: add d2/f (glob)
-  *: add d0/f (glob)
+  checked 8 changesets with 4 changes to 4 files
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  ...7: add d10/f
+  6: add d9/f
+  ...5: add d8/f
+  4: add d6/f
+  ...3: add d5/f
+  2: add d3/f
+  ...1: add d2/f
+  0: add d0/f
   $ hg tracked --addinclude d1
   comparing with ssh://user@dummy/upstream
   searching for changes
@@ -269,16 +274,16 @@
   I path:d3
   I path:d6
   I path:d9
-  $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
-  ...*: add d10/f (glob)
-  *: add d9/f (glob)
-  ...*: add d8/f (glob)
-  *: add d6/f (glob)
-  ...*: add d5/f (glob)
-  *: add d3/f (glob)
-  ...*: add d2/f (glob)
-  *: add d1/f (glob)
-  *: add d0/f (glob)
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  ...8: add d10/f
+  7: add d9/f
+  ...6: add d8/f
+  5: add d6/f
+  ...4: add d5/f
+  3: add d3/f
+  ...2: add d2/f
+  1: add d1/f
+  0: add d0/f
 
 Verify shouldn't claim the repo is corrupt after a widen.
 
@@ -288,16 +293,16 @@
   checking directory manifests (tree !)
   crosschecking files in changesets and manifests
   checking files
-  5 files, 9 changesets, 5 total revisions
+  checked 9 changesets with 5 changes to 5 files
 
 Widening preserves parent of local commit
 
   $ cd ..
   $ hg clone -q --narrow ssh://user@dummy/upstream narrow3 --include d2 -r 2
   $ cd narrow3
-  $ hg log -T "{if(ellipsis, '...')}{node|short}: {desc}\n"
-  *: add d2/f (glob)
-  ...*: add d1/f (glob)
+  $ hg log -T "{if(ellipsis, '...')}{rev}: {desc}\n"
+  1: add d2/f
+  ...0: add d1/f
   $ hg pull -q -r 3
   $ hg co -q tip
   $ hg pull -q -r 4
--- a/tests/test-narrow.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-narrow.t	Wed Sep 26 20:33:09 2018 +0900
@@ -22,31 +22,28 @@
   >   hg add d$x/f
   >   hg commit -m "add d$x/f"
   > done
-  $ hg log -T "{node|short}: {desc}\n"
-  *: add d10/f (glob)
-  *: add d9/f (glob)
-  *: add d8/f (glob)
-  *: add d7/f (glob)
-  *: add d6/f (glob)
-  *: add d5/f (glob)
-  *: add d4/f (glob)
-  *: add d3/f (glob)
-  *: add d2/f (glob)
-  *: add d1/f (glob)
-  *: add d0/f (glob)
+  $ hg log -T "{rev}: {desc}\n"
+  10: add d10/f
+  9: add d9/f
+  8: add d8/f
+  7: add d7/f
+  6: add d6/f
+  5: add d5/f
+  4: add d4/f
+  3: add d3/f
+  2: add d2/f
+  1: add d1/f
+  0: add d0/f
   $ cd ..
 
 Error if '.' or '..' are in the directory to track.
   $ hg clone --narrow ssh://user@dummy/master foo --include ./asdf
-  requesting all changes
   abort: "." and ".." are not allowed in narrowspec paths
   [255]
   $ hg clone --narrow ssh://user@dummy/master foo --include asdf/..
-  requesting all changes
   abort: "." and ".." are not allowed in narrowspec paths
   [255]
   $ hg clone --narrow ssh://user@dummy/master foo --include a/./c
-  requesting all changes
   abort: "." and ".." are not allowed in narrowspec paths
   [255]
 
@@ -111,15 +108,15 @@
   d6/f
   $ hg verify -q
 Force deletion of local changes
-  $ hg log -T "{node|short}: {desc} {outsidenarrow}\n"
-  *: local change to d3  (glob)
-  *: local change to d0  (glob)
-  *: add d10/f outsidenarrow (glob)
-  *: add d6/f  (glob)
-  *: add d5/f outsidenarrow (glob)
-  *: add d3/f  (glob)
-  *: add d2/f outsidenarrow (glob)
-  *: add d0/f  (glob)
+  $ hg log -T "{rev}: {desc} {outsidenarrow}\n"
+  8: local change to d3 
+  6: local change to d0 
+  5: add d10/f outsidenarrow
+  4: add d6/f 
+  3: add d5/f outsidenarrow
+  2: add d3/f 
+  1: add d2/f outsidenarrow
+  0: add d0/f 
   $ hg tracked --removeinclude d0 --force-delete-local-changes
   comparing with ssh://user@dummy/master
   searching for changes
@@ -133,14 +130,14 @@
   deleting data/d0/f/4374b5650fc5ae54ac857c0f0381971fdde376f7 (reposimplestore !)
   deleting data/d0/f/index (reposimplestore !)
 
-  $ hg log -T "{node|short}: {desc} {outsidenarrow}\n"
-  *: local change to d3  (glob)
-  *: add d10/f outsidenarrow (glob)
-  *: add d6/f  (glob)
-  *: add d5/f outsidenarrow (glob)
-  *: add d3/f  (glob)
-  *: add d2/f outsidenarrow (glob)
-  *: add d0/f outsidenarrow (glob)
+  $ hg log -T "{rev}: {desc} {outsidenarrow}\n"
+  7: local change to d3 
+  5: add d10/f outsidenarrow
+  4: add d6/f 
+  3: add d5/f outsidenarrow
+  2: add d3/f 
+  1: add d2/f outsidenarrow
+  0: add d0/f outsidenarrow
 Can restore stripped local changes after widening
   $ hg tracked --addinclude d0 -q
   $ hg unbundle .hg/strip-backup/*-narrow.hg -q
--- a/tests/test-newcgi.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-newcgi.t	Wed Sep 26 20:33:09 2018 +0900
@@ -52,15 +52,15 @@
   $ chmod 755 hgwebdir.cgi
 
   $ . "$TESTDIR/cgienv"
-  $ $PYTHON hgweb.cgi > page1
-  $ $PYTHON hgwebdir.cgi > page2
+  $ "$PYTHON" hgweb.cgi > page1
+  $ "$PYTHON" hgwebdir.cgi > page2
 
   $ PATH_INFO="/test/"
   $ PATH_TRANSLATED="/var/something/test.cgi"
   $ REQUEST_URI="/test/test/"
   $ SCRIPT_URI="http://hg.omnifarious.org/test/test/"
   $ SCRIPT_URL="/test/test/"
-  $ $PYTHON hgwebdir.cgi > page3
+  $ "$PYTHON" hgwebdir.cgi > page3
 
   $ grep -i error page1 page2 page3
   [1]
--- a/tests/test-newercgi.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-newercgi.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,7 +16,7 @@
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
   > 
-  > application = hgweb("test", "Empty test repository")
+  > application = hgweb(b"test", b"Empty test repository")
   > wsgicgi.launch(application)
   > HGWEB
 
@@ -39,22 +39,22 @@
   > from mercurial.hgweb import hgwebdir
   > from mercurial.hgweb import wsgicgi
   > 
-  > application = hgwebdir("hgweb.config")
+  > application = hgwebdir(b"hgweb.config")
   > wsgicgi.launch(application)
   > HGWEBDIR
 
   $ chmod 755 hgwebdir.cgi
 
   $ . "$TESTDIR/cgienv"
-  $ $PYTHON hgweb.cgi > page1
-  $ $PYTHON hgwebdir.cgi > page2
+  $ "$PYTHON" hgweb.cgi > page1
+  $ "$PYTHON" hgwebdir.cgi > page2
 
   $ PATH_INFO="/test/"
   $ PATH_TRANSLATED="/var/something/test.cgi"
   $ REQUEST_URI="/test/test/"
   $ SCRIPT_URI="http://hg.omnifarious.org/test/test/"
   $ SCRIPT_URL="/test/test/"
-  $ $PYTHON hgwebdir.cgi > page3
+  $ "$PYTHON" hgwebdir.cgi > page3
 
   $ grep -i error page1 page2 page3
   [1]
--- a/tests/test-no-symlinks.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-no-symlinks.t	Wed Sep 26 20:33:09 2018 +0900
@@ -48,7 +48,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 6 changes to 6 files
-  new changesets d326ae2d01ee:71d85cf3ba90
+  new changesets d326ae2d01ee:71d85cf3ba90 (2 drafts)
   (run 'hg update' to get a working copy)
   $ hg update
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-nointerrupt.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-nointerrupt.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1,3 +1,5 @@
+#require no-windows
+
 Dummy extension simulating unsafe long running command
   $ cat > sleepext.py <<EOF
   > import itertools
--- a/tests/test-notify-changegroup.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-notify-changegroup.t	Wed Sep 26 20:33:09 2018 +0900
@@ -39,7 +39,7 @@
 push
 
   $ hg --traceback --cwd b push ../a 2>&1 |
-  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
+  >     "$PYTHON" -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
@@ -85,7 +85,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
-  new changesets cb9a9f314b8b:ba677d0156c1
+  new changesets cb9a9f314b8b:ba677d0156c1 (2 drafts)
   (run 'hg update' to get a working copy)
   $ hg --cwd a rollback
   repository tip rolled back to revision -1 (undo unbundle)
@@ -93,12 +93,12 @@
 unbundle with correct source
 
   $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 |
-  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
+  >     "$PYTHON" -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   adding changesets
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
-  new changesets cb9a9f314b8b:ba677d0156c1
+  new changesets cb9a9f314b8b:ba677d0156c1 (2 drafts)
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -169,7 +169,7 @@
 push
 
   $ hg --traceback --cwd b --config notify.fromauthor=True push ../a 2>&1 |
-  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
+  >     "$PYTHON" -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
--- a/tests/test-notify.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-notify.t	Wed Sep 26 20:33:09 2018 +0900
@@ -190,7 +190,7 @@
 of the very long subject line
 pull (minimal config)
 
-  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
+  $ hg --traceback --cwd b pull ../a | "$PYTHON" $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -249,7 +249,7 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a  | $PYTHON $TESTTMP/filter.py
+  $ hg --traceback --cwd b pull ../a  | "$PYTHON" $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -297,7 +297,7 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --config notify.maxdiffstat=1 --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
+  $ hg --traceback --config notify.maxdiffstat=1 --cwd b pull ../a | "$PYTHON" $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -348,7 +348,7 @@
   (branch merge, don't forget to commit)
   $ hg ci -m merge -d '3 0'
   $ cd ..
-  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
+  $ hg --traceback --cwd b pull ../a | "$PYTHON" $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -412,9 +412,9 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
-  >   -m `$PYTHON -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
+  >   -m `"$PYTHON" -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
   $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
-  >   $PYTHON $TESTTMP/filter.py
+  >   "$PYTHON" $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -455,7 +455,7 @@
   > test = False
   > mbox = mbox
   > EOF
-  $ $PYTHON -c 'open("a/a", "ab").write("no" * 500 + "\xd1\x84" + "\n")'
+  $ "$PYTHON" -c 'open("a/a", "ab").write("no" * 500 + "\xd1\x84" + "\n")'
   $ hg --cwd a commit -A -m "long line"
   $ hg --traceback --cwd b pull ../a
   pulling from ../a
@@ -467,7 +467,7 @@
   new changesets a846b5f6ebb7
   notify: sending 2 subscribers 1 changes
   (run 'hg update' to get a working copy)
-  $ $PYTHON $TESTTMP/filter.py < b/mbox
+  $ "$PYTHON" $TESTTMP/filter.py < b/mbox
   From test@test.com ... ... .. ..:..:.. .... (re)
   MIME-Version: 1.0
   Content-Type: text/plain; charset="*" (glob)
@@ -527,7 +527,7 @@
   (branches are permanent and global, did you want a bookmark?)
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
+  $ hg --traceback --cwd b pull ../a | "$PYTHON" $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -557,7 +557,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
+  $ hg --traceback --cwd b pull ../a | "$PYTHON" $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -586,7 +586,7 @@
   $ mv "$HGRCPATH.new" $HGRCPATH
   $ echo a >> a/a
   $ hg --cwd a commit -m 'default template'
-  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  $ hg --cwd b pull ../a -q | "$PYTHON" $TESTTMP/filter.py
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -615,7 +615,7 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with style'
-  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  $ hg --cwd b pull ../a -q | "$PYTHON" $TESTTMP/filter.py
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
@@ -638,7 +638,7 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with template'
-  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  $ hg --cwd b pull ../a -q | "$PYTHON" $TESTTMP/filter.py
   MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
--- a/tests/test-obsmarker-template.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-obsmarker-template.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1450,7 +1450,7 @@
   added 1 changesets with 0 changes to 1 files (+1 heads)
   2 new obsolescence markers
   obsoleted 1 changesets
-  new changesets 7a230b46bf61
+  new changesets 7a230b46bf61 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg log --hidden -G
   o  changeset:   2:7a230b46bf61
@@ -2591,7 +2591,7 @@
   > [extensions]
   > amend =
   > EOF
-  $ $PYTHON <<'EOF'
+  $ "$PYTHON" <<'EOF'
   > with open('test1', 'wb') as f:
   >    f.write(b't\xe8st1') and None
   > with open('test2', 'wb') as f:
--- a/tests/test-obsolete-bundle-strip.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-obsolete-bundle-strip.t	Wed Sep 26 20:33:09 2018 +0900
@@ -207,7 +207,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 2 new obsolescence markers
   # unbundling: obsoleted 1 changesets
-  # unbundling: new changesets cf2c22470d67
+  # unbundling: new changesets cf2c22470d67 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
   $ testrevs 'desc("C-A")'
@@ -247,7 +247,7 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files (+1 heads)
   # unbundling: 3 new obsolescence markers
-  # unbundling: new changesets cf2c22470d67
+  # unbundling: new changesets cf2c22470d67 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 chain with prune children
@@ -374,7 +374,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 1 new obsolescence markers
   # unbundling: obsoleted 1 changesets
-  # unbundling: new changesets cf2c22470d67
+  # unbundling: new changesets cf2c22470d67 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 bundling multiple revisions
@@ -436,7 +436,7 @@
   # unbundling: adding file changes
   # unbundling: added 3 changesets with 3 changes to 3 files (+1 heads)
   # unbundling: 3 new obsolescence markers
-  # unbundling: new changesets cf2c22470d67
+  # unbundling: new changesets cf2c22470d67 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 chain with precursors also pruned
@@ -537,7 +537,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 1 new obsolescence markers
-  # unbundling: new changesets cf2c22470d67
+  # unbundling: new changesets cf2c22470d67 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
   $ testrevs 'desc("C-A")'
@@ -577,7 +577,7 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files (+1 heads)
   # unbundling: 3 new obsolescence markers
-  # unbundling: new changesets cf2c22470d67
+  # unbundling: new changesets cf2c22470d67 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 chain with missing prune
@@ -661,7 +661,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files
   # unbundling: 3 new obsolescence markers
-  # unbundling: new changesets cf2c22470d67
+  # unbundling: new changesets cf2c22470d67 (1 drafts)
   # unbundling: (run 'hg update' to get a working copy)
 
 chain with precursors also pruned
@@ -741,7 +741,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files
   # unbundling: 3 new obsolescence markers
-  # unbundling: new changesets cf2c22470d67
+  # unbundling: new changesets cf2c22470d67 (1 drafts)
   # unbundling: (run 'hg update' to get a working copy)
 
 Chain with fold and split
@@ -984,7 +984,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 6 new obsolescence markers
   # unbundling: obsoleted 3 changesets
-  # unbundling: new changesets 2f20ff6509f0
+  # unbundling: new changesets 2f20ff6509f0 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 Bundle multiple revisions
@@ -1086,7 +1086,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 7 new obsolescence markers
   # unbundling: obsoleted 2 changesets
-  # unbundling: new changesets 2f20ff6509f0
+  # unbundling: new changesets 2f20ff6509f0 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 * top one and initial precursors
@@ -1154,7 +1154,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 6 new obsolescence markers
   # unbundling: obsoleted 3 changesets
-  # unbundling: new changesets 2f20ff6509f0
+  # unbundling: new changesets 2f20ff6509f0 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 * top one and one of the split
@@ -1223,7 +1223,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 7 new obsolescence markers
   # unbundling: obsoleted 2 changesets
-  # unbundling: new changesets 2f20ff6509f0
+  # unbundling: new changesets 2f20ff6509f0 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 * all
@@ -1298,7 +1298,7 @@
   # unbundling: adding file changes
   # unbundling: added 5 changesets with 5 changes to 5 files (+4 heads)
   # unbundling: 9 new obsolescence markers
-  # unbundling: new changesets 2f20ff6509f0
+  # unbundling: new changesets 2f20ff6509f0 (1 drafts)
   # unbundling: (run 'hg heads' to see heads)
 
 changeset pruned on its own
@@ -1400,5 +1400,5 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files
   # unbundling: 1 new obsolescence markers
-  # unbundling: new changesets 9ac430e15fca
+  # unbundling: new changesets 9ac430e15fca (1 drafts)
   # unbundling: (run 'hg update' to get a working copy)
--- a/tests/test-obsolete-changeset-exchange.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-obsolete-changeset-exchange.t	Wed Sep 26 20:33:09 2018 +0900
@@ -51,7 +51,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
 
 Adding a changeset going extinct locally
 ------------------------------------------
--- a/tests/test-obsolete-distributed.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-obsolete-distributed.t	Wed Sep 26 20:33:09 2018 +0900
@@ -144,7 +144,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
-  new changesets 391a2bf12b1b
+  new changesets 391a2bf12b1b (1 drafts)
   (run 'hg heads' to see heads)
   $ hg log -G
   o  4:391a2bf12b1b c_B1
@@ -271,7 +271,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  new changesets d33b0a3a6464:ef908e42ce65
+  new changesets d33b0a3a6464:ef908e42ce65 (2 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 'desc("c_A")'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -319,7 +319,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   3 new obsolescence markers
-  new changesets 5b5708a437f2:956063ac4557
+  new changesets 5b5708a437f2:956063ac4557 (2 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 'desc("c_A")'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -390,7 +390,7 @@
   added 2 changesets with 0 changes to 2 files (+1 heads)
   6 new obsolescence markers
   obsoleted 2 changesets
-  new changesets 9866d64649a5:77ae25d99ff0
+  new changesets 9866d64649a5:77ae25d99ff0 (2 drafts)
   (run 'hg heads' to see heads)
   $ hg debugobsolete
   3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
--- a/tests/test-obsolete.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-obsolete.t	Wed Sep 26 20:33:09 2018 +0900
@@ -380,7 +380,7 @@
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
   5 new obsolescence markers
-  new changesets 1f0dee641bb7:6f9641995072
+  new changesets 1f0dee641bb7:6f9641995072 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
@@ -486,7 +486,7 @@
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
   5 new obsolescence markers
-  new changesets 1f0dee641bb7:6f9641995072
+  new changesets 1f0dee641bb7:6f9641995072 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
@@ -805,7 +805,7 @@
   adding manifests
   adding file changes
   added 62 changesets with 63 changes to 9 files (+60 heads)
-  new changesets 50c51b361e60:c15e9edfca13
+  new changesets 50c51b361e60:c15e9edfca13 (62 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ for node in `hg log -r 'desc(babar_)' --template '{node}\n'`;
   > do
@@ -1244,7 +1244,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
-  new changesets 4b34ecfb0d56:44526ebb0f98
+  new changesets 4b34ecfb0d56:44526ebb0f98 (2 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../other-bundleoverlay
@@ -1513,7 +1513,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
-  new changesets e016b03fd86f:b0551702f918
+  new changesets e016b03fd86f:b0551702f918 (2 drafts)
   (run 'hg update' to get a working copy)
   $ hg debugobsolete | sort
   e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
--- a/tests/test-oldcgi.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-oldcgi.t	Wed Sep 26 20:33:09 2018 +0900
@@ -4,7 +4,7 @@
 
   $ hg init test
   $ cat >hgweb.cgi <<HGWEB
-  > #!$PYTHON
+  > #!"$PYTHON"
   > #
   > # An example CGI script to use hgweb, edit as necessary
   > 
@@ -14,7 +14,7 @@
   > # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
   > from mercurial import hgweb
   > 
-  > h = hgweb.hgweb("test", "Empty test repository")
+  > h = hgweb.hgweb(b"test", b"Empty test repository")
   > h.run()
   > HGWEB
 
@@ -26,7 +26,7 @@
   > HGWEBDIRCONF
 
   $ cat >hgwebdir.cgi <<HGWEBDIR
-  > #!$PYTHON
+  > #!"$PYTHON"
   > #
   > # An example CGI script to export multiple hgweb repos, edit as necessary
   > 
@@ -62,15 +62,15 @@
   $ chmod 755 hgwebdir.cgi
 
   $ . "$TESTDIR/cgienv"
-  $ $PYTHON hgweb.cgi > page1
-  $ $PYTHON hgwebdir.cgi > page2
+  $ "$PYTHON" hgweb.cgi > page1
+  $ "$PYTHON" hgwebdir.cgi > page2
 
   $ PATH_INFO="/test/"
   $ PATH_TRANSLATED="/var/something/test.cgi"
   $ REQUEST_URI="/test/test/"
   $ SCRIPT_URI="http://hg.omnifarious.org/test/test/"
   $ SCRIPT_URL="/test/test/"
-  $ $PYTHON hgwebdir.cgi > page3
+  $ "$PYTHON" hgwebdir.cgi > page3
 
   $ grep -i error page1 page2 page3
   [1]
--- a/tests/test-pager-legacy.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-pager-legacy.t	Wed Sep 26 20:33:09 2018 +0900
@@ -14,7 +14,7 @@
   > [extensions]
   > pager=
   > [pager]
-  > pager = $PYTHON $TESTTMP/fakepager.py
+  > pager = "$PYTHON" $TESTTMP/fakepager.py
   > EOF
 
   $ hg init repo
@@ -22,7 +22,7 @@
   $ echo a >> a
   $ hg add a
   $ hg ci -m 'add a'
-  $ for x in `$PYTHON $TESTDIR/seq.py 1 10`; do
+  $ for x in `"$PYTHON" $TESTDIR/seq.py 1 10`; do
   >   echo a $x >> a
   >   hg ci -m "modify a $x"
   > done
--- a/tests/test-pager.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-pager.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,7 +16,7 @@
   > formatted = yes
   > color = no
   > [pager]
-  > pager = $PYTHON $TESTTMP/fakepager.py
+  > pager = "$PYTHON" $TESTTMP/fakepager.py
   > EOF
 
   $ hg init repo
@@ -24,7 +24,7 @@
   $ echo a >> a
   $ hg add a
   $ hg ci -m 'add a'
-  $ for x in `$PYTHON $TESTDIR/seq.py 1 10`; do
+  $ for x in `"$PYTHON" $TESTDIR/seq.py 1 10`; do
   >   echo a $x >> a
   >   hg ci -m "modify a $x"
   > done
@@ -404,7 +404,7 @@
   > [ui]
   > formatted=1
   > [pager]
-  > pager = $PYTHON $TESTTMP/printlesslv.py
+  > pager = "$PYTHON" $TESTTMP/printlesslv.py
   > EOF
   $ unset LESS
   $ unset LV
--- a/tests/test-parseindex.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-parseindex.t	Wed Sep 26 20:33:09 2018 +0900
@@ -61,7 +61,7 @@
   > for r in cl:
   >     print(short(cl.node(r)))
   > EOF
-  $ $PYTHON test.py
+  $ "$PYTHON" test.py
   2 revisions:
   7c31755bf9b5
   26333235a41c
@@ -74,7 +74,7 @@
 
   $ cd a
 
-  $ $PYTHON <<EOF
+  $ "$PYTHON" <<EOF
   > from __future__ import print_function
   > from mercurial import changelog, vfs
   > cl = changelog.changelog(vfs.vfs('.hg/store'))
@@ -137,7 +137,7 @@
   $ hg clone --pull -q --config phases.publish=False ../a segv
   $ rm -R limit/.hg/cache segv/.hg/cache
 
-  $ $PYTHON <<EOF
+  $ "$PYTHON" <<EOF
   > data = open("limit/.hg/store/00changelog.i", "rb").read()
   > for n, p in [(b'limit', b'\0\0\0\x02'), (b'segv', b'\0\x01\0\0')]:
   >     # corrupt p1 at rev0 and p2 at rev1
@@ -145,7 +145,7 @@
   >     open(n + b"/.hg/store/00changelog.i", "wb").write(d)
   > EOF
 
-  $ hg -R limit debugindex -f1 -c
+  $ hg -R limit debugrevlogindex -f1 -c
      rev flag     size   link     p1     p2       nodeid
        0 0000       62      0      2     -1 7c31755bf9b5
        1 0000       65      1      0      2 26333235a41c
@@ -155,7 +155,7 @@
         0       1        1       -1    base         63         62         63   1.01613        63         0    0.00000
         1       2        1       -1    base         66         65         66   1.01538        66         0    0.00000
 
-  $ hg -R segv debugindex -f1 -c
+  $ hg -R segv debugrevlogindex -f1 -c
      rev flag     size   link     p1     p2       nodeid
        0 0000       62      0  65536     -1 7c31755bf9b5
        1 0000       65      1      0  65536 26333235a41c
@@ -188,13 +188,13 @@
   >         print(inst)
   > EOF
 
-  $ $PYTHON test.py limit/.hg/store
+  $ "$PYTHON" test.py limit/.hg/store
   reachableroots: parent out of range
   compute_phases_map_sets: parent out of range
   index_headrevs: parent out of range
   find_gca_candidates: parent out of range
   find_deepest: parent out of range
-  $ $PYTHON test.py segv/.hg/store
+  $ "$PYTHON" test.py segv/.hg/store
   reachableroots: parent out of range
   compute_phases_map_sets: parent out of range
   index_headrevs: parent out of range
--- a/tests/test-parseindex2.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-parseindex2.py	Wed Sep 26 20:33:09 2018 +0900
@@ -8,12 +8,14 @@
 import struct
 import subprocess
 import sys
+import unittest
 
 from mercurial.node import (
     nullid,
     nullrev,
 )
 from mercurial import (
+    node as nodemod,
     policy,
     pycompat,
 )
@@ -61,9 +63,6 @@
     e[0] = offset_type(0, type)
     index[0] = tuple(e)
 
-    # add the magic null revision at -1
-    index.append((0, 0, 0, -1, -1, -1, -1, nullid))
-
     return index, cache
 
 data_inlined = (
@@ -132,88 +131,92 @@
                          stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
     return p.communicate()  # returns stdout, stderr
 
-def printhexfail(testnumber, hexversion, stdout, expected):
+def hexfailmsg(testnumber, hexversion, stdout, expected):
     try:
         hexstring = hex(hexversion)
     except TypeError:
         hexstring = None
-    print("FAILED: version test #%s with Python %s and patched "
-          "sys.hexversion %r (%r):\n Expected %s but got:\n-->'%s'\n" %
-          (testnumber, sys.version_info, hexversion, hexstring, expected,
-           stdout))
-
-def testversionokay(testnumber, hexversion):
-    stdout, stderr = importparsers(hexversion)
-    if stdout:
-        printhexfail(testnumber, hexversion, stdout, expected="no stdout")
-
-def testversionfail(testnumber, hexversion):
-    stdout, stderr = importparsers(hexversion)
-    # We include versionerrortext to distinguish from other ImportErrors.
-    errtext = b"ImportError: %s" % pycompat.sysbytes(parsers.versionerrortext)
-    if errtext not in stdout:
-        printhexfail(testnumber, hexversion, stdout,
-                     expected="stdout to contain %r" % errtext)
+    return ("FAILED: version test #%s with Python %s and patched "
+            "sys.hexversion %r (%r):\n Expected %s but got:\n-->'%s'\n" %
+            (testnumber, sys.version_info, hexversion, hexstring, expected,
+             stdout))
 
 def makehex(major, minor, micro):
     return int("%x%02x%02x00" % (major, minor, micro), 16)
 
-def runversiontests():
-    """Check the version-detection logic when importing parsers."""
-    info = sys.version_info
-    major, minor, micro = info[0], info[1], info[2]
-    # Test same major-minor versions.
-    testversionokay(1, makehex(major, minor, micro))
-    testversionokay(2, makehex(major, minor, micro + 1))
-    # Test different major-minor versions.
-    testversionfail(3, makehex(major + 1, minor, micro))
-    testversionfail(4, makehex(major, minor + 1, micro))
-    testversionfail(5, "'foo'")
+class parseindex2tests(unittest.TestCase):
+
+    def assertversionokay(self, testnumber, hexversion):
+        stdout, stderr = importparsers(hexversion)
+        self.assertFalse(
+            stdout, hexfailmsg(testnumber, hexversion, stdout, 'no stdout'))
+
+    def assertversionfail(self, testnumber, hexversion):
+        stdout, stderr = importparsers(hexversion)
+        # We include versionerrortext to distinguish from other ImportErrors.
+        errtext = b"ImportError: %s" % pycompat.sysbytes(
+            parsers.versionerrortext)
+        self.assertIn(errtext, stdout,
+                      hexfailmsg(testnumber, hexversion, stdout,
+                                 expected="stdout to contain %r" % errtext))
 
-def runtest() :
-    # Only test the version-detection logic if it is present.
-    try:
-        parsers.versionerrortext
-    except AttributeError:
-        pass
-    else:
-        runversiontests()
+    def testversiondetection(self):
+        """Check the version-detection logic when importing parsers."""
+        # Only test the version-detection logic if it is present.
+        try:
+            parsers.versionerrortext
+        except AttributeError:
+            return
+        info = sys.version_info
+        major, minor, micro = info[0], info[1], info[2]
+        # Test same major-minor versions.
+        self.assertversionokay(1, makehex(major, minor, micro))
+        self.assertversionokay(2, makehex(major, minor, micro + 1))
+        # Test different major-minor versions.
+        self.assertversionfail(3, makehex(major + 1, minor, micro))
+        self.assertversionfail(4, makehex(major, minor + 1, micro))
+        self.assertversionfail(5, "'foo'")
 
-    # Check that parse_index2() raises TypeError on bad arguments.
-    try:
-        parse_index2(0, True)
-    except TypeError:
-        pass
-    else:
-        print("Expected to get TypeError.")
+    def testbadargs(self):
+        # Check that parse_index2() raises TypeError on bad arguments.
+        with self.assertRaises(TypeError):
+            parse_index2(0, True)
 
-   # Check parsers.parse_index2() on an index file against the original
-   # Python implementation of parseindex, both with and without inlined data.
-
-    py_res_1 = py_parseindex(data_inlined, True)
-    c_res_1 = parse_index2(data_inlined, True)
+    def testparseindexfile(self):
+        # Check parsers.parse_index2() on an index file against the
+        # original Python implementation of parseindex, both with and
+        # without inlined data.
 
-    py_res_2 = py_parseindex(data_non_inlined, False)
-    c_res_2 = parse_index2(data_non_inlined, False)
+        want = py_parseindex(data_inlined, True)
+        got = parse_index2(data_inlined, True)
+        self.assertEqual(want, got) # inline data
 
-    if py_res_1 != c_res_1:
-        print("Parse index result (with inlined data) differs!")
-
-    if py_res_2 != c_res_2:
-        print("Parse index result (no inlined data) differs!")
+        want = py_parseindex(data_non_inlined, False)
+        got = parse_index2(data_non_inlined, False)
+        self.assertEqual(want, got) # no inline data
 
-    ix = parsers.parse_index2(data_inlined, True)[0]
-    for i, r in enumerate(ix):
-        if r[7] == nullid:
-            i = -1
-        try:
-            if ix[r[7]] != i:
-                print('Reverse lookup inconsistent for %r'
-                    % r[7].encode('hex'))
-        except TypeError:
-            # pure version doesn't support this
-            break
+        ix = parsers.parse_index2(data_inlined, True)[0]
+        for i, r in enumerate(ix):
+            if r[7] == nullid:
+                i = -1
+            try:
+                self.assertEqual(
+                    ix[r[7]], i,
+                    'Reverse lookup inconsistent for %r' % nodemod.hex(r[7]))
+            except TypeError:
+                # pure version doesn't support this
+                break
 
-    print("done")
+    def testminusone(self):
+        want = (0, 0, 0, -1, -1, -1, -1, nullid)
+        index, junk = parsers.parse_index2(data_inlined, True)
+        got = index[-1]
+        self.assertEqual(want, got) # inline data
 
-runtest()
+        index, junk = parsers.parse_index2(data_non_inlined, False)
+        got = index[-1]
+        self.assertEqual(want, got) # no inline data
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
--- a/tests/test-parseindex2.py.out	Tue Sep 25 16:32:38 2018 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-done
--- a/tests/test-patch-offset.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-patch-offset.t	Wed Sep 26 20:33:09 2018 +0900
@@ -23,7 +23,7 @@
 within this file.  If the offset isn't tracked then the hunks can be
 applied to the wrong lines of this file.
 
-  $ $PYTHON ../writepatterns.py a 34X 10A 1B 10A 1C 10A 1B 10A 1D 10A 1B 10A 1E 10A 1B 10A
+  $ "$PYTHON" ../writepatterns.py a 34X 10A 1B 10A 1C 10A 1B 10A 1D 10A 1B 10A 1E 10A 1B 10A
   $ hg commit -Am adda
   adding a
 
@@ -76,7 +76,7 @@
 
 compare imported changes against reference file
 
-  $ $PYTHON ../writepatterns.py aref 34X 10A 1B 1a 9A 1C 10A 1B 10A 1D 10A 1B 1a 9A 1E 10A 1B 1a 9A
+  $ "$PYTHON" ../writepatterns.py aref 34X 10A 1B 1a 9A 1C 10A 1B 10A 1D 10A 1B 1a 9A 1E 10A 1B 1a 9A
   $ diff aref a
 
   $ cd ..
--- a/tests/test-patch.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-patch.t	Wed Sep 26 20:33:09 2018 +0900
@@ -7,7 +7,7 @@
   > EOF
 
   $ echo "[ui]" >> $HGRCPATH
-  $ echo "patch=$PYTHON ../patchtool.py" >> $HGRCPATH
+  $ echo "patch=\"$PYTHON\" ../patchtool.py" >> $HGRCPATH
 
   $ hg init a
   $ cd a
--- a/tests/test-patchbomb-bookmark.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-patchbomb-bookmark.t	Wed Sep 26 20:33:09 2018 +0900
@@ -35,7 +35,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] bookmark
-  Message-Id: <patchbomb.347155260@*> (glob)
+  Message-Id: <patchbomb.347155260@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1981 00:01:00 +0000
   From: test
@@ -50,10 +50,10 @@
   X-Mercurial-Node: accde9b8b6dce861c185d0825c1affc09a79cb26
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <accde9b8b6dce861c185.347155261@*> (glob)
-  X-Mercurial-Series-Id: <accde9b8b6dce861c185.347155261@*> (glob)
-  In-Reply-To: <patchbomb.347155260@*> (glob)
-  References: <patchbomb.347155260@*> (glob)
+  Message-Id: <accde9b8b6dce861c185.347155261@test-hostname>
+  X-Mercurial-Series-Id: <accde9b8b6dce861c185.347155261@test-hostname>
+  In-Reply-To: <patchbomb.347155260@test-hostname>
+  References: <patchbomb.347155260@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1981 00:01:01 +0000
   From: test
@@ -81,10 +81,10 @@
   X-Mercurial-Node: 417defd1559c396ba06a44dce8dc1c2d2d653f3f
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <417defd1559c396ba06a.347155262@*> (glob)
-  X-Mercurial-Series-Id: <accde9b8b6dce861c185.347155261@*> (glob)
-  In-Reply-To: <patchbomb.347155260@*> (glob)
-  References: <patchbomb.347155260@*> (glob)
+  Message-Id: <417defd1559c396ba06a.347155262@test-hostname>
+  X-Mercurial-Series-Id: <accde9b8b6dce861c185.347155261@test-hostname>
+  In-Reply-To: <patchbomb.347155260@test-hostname>
+  References: <patchbomb.347155260@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1981 00:01:02 +0000
   From: test
@@ -145,8 +145,8 @@
   X-Mercurial-Node: 8dab2639fd35f1e337ad866c372a5c44f1064e3c
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <8dab2639fd35f1e337ad.378691260@*> (glob)
-  X-Mercurial-Series-Id: <8dab2639fd35f1e337ad.378691260@*> (glob)
+  Message-Id: <8dab2639fd35f1e337ad.378691260@test-hostname>
+  X-Mercurial-Series-Id: <8dab2639fd35f1e337ad.378691260@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Fri, 01 Jan 1982 00:01:00 +0000
   From: test
--- a/tests/test-patchbomb-tls.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-patchbomb-tls.t	Wed Sep 26 20:33:09 2018 +0900
@@ -5,7 +5,7 @@
   $ CERTSDIR="$TESTDIR/sslcerts"
   $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
 
-  $ $PYTHON "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid -d \
+  $ "$PYTHON" "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid -d \
   > --tls smtps --certificate `pwd`/server.pem
   listening at localhost:$HGPORT (?)
   $ cat a.pid >> $DAEMON_PIDS
--- a/tests/test-patchbomb.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-patchbomb.t	Wed Sep 26 20:33:09 2018 +0900
@@ -2,7 +2,6 @@
 wildcards in test expectations due to how many things like hostnames
 tend to make it into outputs. As a result, you may need to perform the
 following regular expression substitutions:
-@$HOSTNAME> -> @*> (glob)
 Mercurial-patchbomb/.* -> Mercurial-patchbomb/* (glob)
 /mixed; boundary="===+[0-9]+==" -> /mixed; boundary="===*== (glob)"
 --===+[0-9]+=+--$ -> --===*=-- (glob)
@@ -45,8 +44,8 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <8580ff50825a50c8f716.60@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.60@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -84,8 +83,8 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <*@*> (glob)
-  X-Mercurial-Series-Id: <*@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.60@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -159,8 +158,8 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <8580ff50825a50c8f716.60@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.60@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -197,8 +196,8 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <8580ff50825a50c8f716.60@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.60@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -236,7 +235,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
-  Message-Id: <patchbomb.120@*> (glob)
+  Message-Id: <patchbomb.120@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:02:00 +0000
   From: quux
@@ -252,10 +251,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.121@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.121@*> (glob)
-  In-Reply-To: <patchbomb.120@*> (glob)
-  References: <patchbomb.120@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.121@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.121@test-hostname>
+  In-Reply-To: <patchbomb.120@test-hostname>
+  References: <patchbomb.120@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:02:01 +0000
   From: quux
@@ -284,10 +283,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.122@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.121@*> (glob)
-  In-Reply-To: <patchbomb.120@*> (glob)
-  References: <patchbomb.120@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.122@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.121@test-hostname>
+  In-Reply-To: <patchbomb.120@test-hostname>
+  References: <patchbomb.120@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:02:02 +0000
   From: quux
@@ -366,7 +365,7 @@
   Content-Type: multipart/mixed; boundary="===*==" (glob)
   MIME-Version: 1.0
   Subject: test
-  Message-Id: <patchbomb.180@*> (glob)
+  Message-Id: <patchbomb.180@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:03:00 +0000
   From: quux
@@ -412,7 +411,7 @@
   Content-Type: multipart/mixed; boundary="===*==" (glob)
   MIME-Version: 1.0
   Subject: test
-  Message-Id: <patchbomb.180@*> (glob)
+  Message-Id: <patchbomb.180@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:03:00 +0000
   From: quux
@@ -439,10 +438,11 @@
   CgZcySARUyA2A2LGZKiZ3Y+Lu786z4z4MWXmsrAZCsqrl1az5y21PMcjpbThzWeXGT+/nutbmvvz
   zXYS3BoGxdrJDIYmlimJJiZpRokmqYYmaSYWFknmSSkmhqbmliamiZYWxuYmBhbJBgZcUBNZQe5K
   Epm7xF/LT+RLx/a9juFTomaYO/Rgsx4rwBN+IMCUDLOKAQBrsmti
+   (?)
   --===============*==-- (glob)
 
 utf-8 patch:
-  $ $PYTHON -c 'fp = open("utf", "wb"); fp.write("h\xC3\xB6mma!\n"); fp.close();'
+  $ "$PYTHON" -c 'fp = open("utf", "wb"); fp.write(b"h\xC3\xB6mma!\n"); fp.close();'
   $ hg commit -A -d '4 0' -m 'utf-8 content'
   adding description
   adding utf
@@ -454,14 +454,14 @@
   
   displaying [PATCH] utf-8 content ...
   MIME-Version: 1.0
-  Content-Type: text/plain; charset="us-ascii"
-  Content-Transfer-Encoding: 8bit
+  Content-Type: text/plain; charset="iso-8859-1"
+  Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <909a00e13e9d78b575ae.240@*> (glob)
-  X-Mercurial-Series-Id: <909a00e13e9d78b575ae.240@*> (glob)
+  Message-Id: <909a00e13e9d78b575ae.240@test-hostname>
+  X-Mercurial-Series-Id: <909a00e13e9d78b575ae.240@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:04:00 +0000
   From: quux
@@ -487,7 +487,7 @@
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/utf	Thu Jan 01 00:00:04 1970 +0000
   @@ -0,0 +1,1 @@
-  +h\xc3\xb6mma! (esc)
+  +h=C3=B6mma!
   
 
 mime encoded mbox (base64):
@@ -506,8 +506,8 @@
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <909a00e13e9d78b575ae.240@*> (glob)
-  X-Mercurial-Series-Id: <909a00e13e9d78b575ae.240@*> (glob)
+  Message-Id: <909a00e13e9d78b575ae.240@test-hostname>
+  X-Mercurial-Series-Id: <909a00e13e9d78b575ae.240@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:04:00 +0000
   From: Q <quux>
@@ -526,7 +526,14 @@
   QEAgLTAsMCArMSwxIEBACitow7ZtbWEhCg==
   
   
-  $ $PYTHON -c 'print open("mbox").read().split("\n\n")[1].decode("base64")'
+  >>> import base64
+  >>> patch = base64.b64decode(open("mbox").read().split("\n\n")[1])
+  >>> if not isinstance(patch, str):
+  ...     import sys
+  ...     sys.stdout.flush()
+  ...     junk = sys.stdout.buffer.write(patch + b"\n")
+  ... else:
+  ...     print(patch)
   # HG changeset patch
   # User test
   # Date 4 0
@@ -551,7 +558,7 @@
   $ rm mbox
 
 mime encoded mbox (quoted-printable):
-  $ $PYTHON -c 'fp = open("long", "wb"); fp.write("%s\nfoo\n\nbar\n" % ("x" * 1024)); fp.close();'
+  $ "$PYTHON" -c 'fp = open("long", "wb"); fp.write(b"%s\nfoo\n\nbar\n" % (b"x" * 1024)); fp.close();'
   $ hg commit -A -d '4 0' -m 'long line'
   adding long
 
@@ -568,8 +575,8 @@
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob)
-  X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob)
+  Message-Id: <a2ea8fc83dd8b93cfd86.240@test-hostname>
+  X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.240@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:04:00 +0000
   From: quux
@@ -622,8 +629,8 @@
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob)
-  X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.240@*> (glob)
+  Message-Id: <a2ea8fc83dd8b93cfd86.240@test-hostname>
+  X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.240@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:04:00 +0000
   From: quux
@@ -665,7 +672,7 @@
   $ rm mbox
 
 iso-8859-1 patch:
-  $ $PYTHON -c 'fp = open("isolatin", "wb"); fp.write("h\xF6mma!\n"); fp.close();'
+  $ "$PYTHON" -c 'fp = open("isolatin", "wb"); fp.write(b"h\xF6mma!\n"); fp.close();'
   $ hg commit -A -d '5 0' -m 'isolatin 8-bit encoding'
   adding isolatin
 
@@ -684,8 +691,8 @@
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <240fb913fc1b7ff15ddb.300@*> (glob)
-  X-Mercurial-Series-Id: <240fb913fc1b7ff15ddb.300@*> (glob)
+  Message-Id: <240fb913fc1b7ff15ddb.300@test-hostname>
+  X-Mercurial-Series-Id: <240fb913fc1b7ff15ddb.300@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:05:00 +0000
   From: quux
@@ -732,8 +739,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -791,7 +798,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -811,10 +818,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -847,10 +854,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -888,8 +895,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -931,8 +938,8 @@
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob)
-  X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob)
+  Message-Id: <a2ea8fc83dd8b93cfd86.60@test-hostname>
+  X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -991,7 +998,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 3] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1006,10 +1013,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 3
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -1044,10 +1051,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 3
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -1082,10 +1089,10 @@
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
   X-Mercurial-Series-Index: 3
   X-Mercurial-Series-Total: 3
-  Message-Id: <a2ea8fc83dd8b93cfd86.63@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <a2ea8fc83dd8b93cfd86.63@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:03 +0000
   From: quux
@@ -1142,8 +1149,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1193,8 +1200,8 @@
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob)
-  X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.60@*> (glob)
+  Message-Id: <a2ea8fc83dd8b93cfd86.60@test-hostname>
+  X-Mercurial-Series-Id: <a2ea8fc83dd8b93cfd86.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1260,8 +1267,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1323,7 +1330,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 3] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1338,10 +1345,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 3
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -1385,10 +1392,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 3
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -1432,10 +1439,10 @@
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
   X-Mercurial-Series-Index: 3
   X-Mercurial-Series-Total: 3
-  Message-Id: <a2ea8fc83dd8b93cfd86.63@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <a2ea8fc83dd8b93cfd86.63@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:03 +0000
   From: quux
@@ -1503,7 +1510,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 1] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1519,10 +1526,10 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.61@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.61@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -1556,7 +1563,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 1] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1573,10 +1580,10 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.61@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.61@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -1612,7 +1619,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1628,10 +1635,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -1660,10 +1667,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -1699,8 +1706,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1737,8 +1744,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1779,8 +1786,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1823,7 +1830,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -1838,10 +1845,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -1876,10 +1883,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -1923,8 +1930,8 @@
   X-Mercurial-Node: 7aead2484924c445ad8ce2613df91f52f9e502ed
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <7aead2484924c445ad8c.60@*> (glob)
-  X-Mercurial-Series-Id: <7aead2484924c445ad8c.60@*> (glob)
+  Message-Id: <7aead2484924c445ad8c.60@test-hostname>
+  X-Mercurial-Series-Id: <7aead2484924c445ad8c.60@test-hostname>
   In-Reply-To: <baz>
   References: <baz>
   User-Agent: Mercurial-patchbomb/* (glob)
@@ -1966,8 +1973,8 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.60@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.60@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@test-hostname>
   In-Reply-To: <baz>
   References: <baz>
   User-Agent: Mercurial-patchbomb/* (glob)
@@ -1998,8 +2005,8 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@test-hostname>
   In-Reply-To: <baz>
   References: <baz>
   User-Agent: Mercurial-patchbomb/* (glob)
@@ -2038,7 +2045,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   In-Reply-To: <baz>
   References: <baz>
   User-Agent: Mercurial-patchbomb/* (glob)
@@ -2056,10 +2063,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -2088,10 +2095,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -2129,8 +2136,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -2167,7 +2174,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 fooFlag] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -2183,10 +2190,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -2215,10 +2222,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -2256,8 +2263,8 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -2293,7 +2300,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 fooFlag barFlag] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -2309,10 +2316,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -2341,10 +2348,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -2383,8 +2390,8 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <8580ff50825a50c8f716.315532860@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.315532860@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.315532860@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:00 +0000
   From: quux
@@ -2422,7 +2429,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 R1] test
-  Message-Id: <patchbomb.60@*> (glob)
+  Message-Id: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -2438,10 +2445,10 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 2
-  Message-Id: <8580ff50825a50c8f716.61@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.61@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:01 +0000
   From: quux
@@ -2469,10 +2476,10 @@
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 2
-  Message-Id: <97d72e5f12c7e84f8506.62@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@*> (glob)
-  In-Reply-To: <patchbomb.60@*> (glob)
-  References: <patchbomb.60@*> (glob)
+  Message-Id: <97d72e5f12c7e84f8506.62@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.61@test-hostname>
+  In-Reply-To: <patchbomb.60@test-hostname>
+  References: <patchbomb.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:02 +0000
   From: quux
@@ -2508,8 +2515,8 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <8580ff50825a50c8f716.60@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.60@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.60@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:01:00 +0000
   From: quux
@@ -2531,10 +2538,11 @@
   
 
 test multi-byte domain parsing:
-  $ UUML=`$PYTHON -c 'import sys; sys.stdout.write("\374")'`
+  >>> with open('toaddress.txt', 'wb') as f:
+  ...  f.write(b'bar@\xfcnicode.com') and None
   $ HGENCODING=iso-8859-1
   $ export HGENCODING
-  $ hg email --date '1980-1-1 0:1' -m tmp.mbox -f quux -t "bar@${UUML}nicode.com" -s test -r 0
+  $ hg email --date '1980-1-1 0:1' -m tmp.mbox -f quux -t "`cat toaddress.txt`" -s test -r 0
   this patch series consists of 1 patches.
   
   Cc: 
@@ -2550,8 +2558,8 @@
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <8580ff50825a50c8f716.315532860@*> (glob)
-  X-Mercurial-Series-Id: <8580ff50825a50c8f716.315532860@*> (glob)
+  Message-Id: <8580ff50825a50c8f716.315532860@test-hostname>
+  X-Mercurial-Series-Id: <8580ff50825a50c8f716.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:00 +0000
   From: quux
@@ -2625,7 +2633,7 @@
   Content-Type: text/plain; charset="us-ascii"
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 6] test
-  Message-Id: <patchbomb.315532860@*> (glob)
+  Message-Id: <patchbomb.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:00 +0000
   From: test
@@ -2640,10 +2648,10 @@
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 6
-  Message-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob)
-  In-Reply-To: <patchbomb.315532860@*> (glob)
-  References: <patchbomb.315532860@*> (glob)
+  Message-Id: <ff2c9fa2018b15fa74b3.315532861@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@test-hostname>
+  In-Reply-To: <patchbomb.315532860@test-hostname>
+  References: <patchbomb.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:01 +0000
   From: test
@@ -2665,16 +2673,16 @@
   
   displaying [PATCH 2 of 6] utf-8 content ...
   MIME-Version: 1.0
-  Content-Type: text/plain; charset="us-ascii"
-  Content-Transfer-Encoding: 8bit
+  Content-Type: text/plain; charset="iso-8859-1"
+  Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH 2 of 6] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
   X-Mercurial-Series-Index: 2
   X-Mercurial-Series-Total: 6
-  Message-Id: <909a00e13e9d78b575ae.315532862@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob)
-  In-Reply-To: <patchbomb.315532860@*> (glob)
-  References: <patchbomb.315532860@*> (glob)
+  Message-Id: <909a00e13e9d78b575ae.315532862@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@test-hostname>
+  In-Reply-To: <patchbomb.315532860@test-hostname>
+  References: <patchbomb.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:02 +0000
   From: test
@@ -2699,7 +2707,7 @@
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/utf	Thu Jan 01 00:00:04 1970 +0000
   @@ -0,0 +1,1 @@
-  +h\xc3\xb6mma! (esc)
+  +h=C3=B6mma!
   
   displaying [PATCH 3 of 6] long line ...
   MIME-Version: 1.0
@@ -2709,10 +2717,10 @@
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
   X-Mercurial-Series-Index: 3
   X-Mercurial-Series-Total: 6
-  Message-Id: <a2ea8fc83dd8b93cfd86.315532863@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob)
-  In-Reply-To: <patchbomb.315532860@*> (glob)
-  References: <patchbomb.315532860@*> (glob)
+  Message-Id: <a2ea8fc83dd8b93cfd86.315532863@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@test-hostname>
+  In-Reply-To: <patchbomb.315532860@test-hostname>
+  References: <patchbomb.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:03 +0000
   From: test
@@ -2750,16 +2758,16 @@
   
   displaying [PATCH 4 of 6] isolatin 8-bit encoding ...
   MIME-Version: 1.0
-  Content-Type: text/plain; charset="us-ascii"
-  Content-Transfer-Encoding: 8bit
+  Content-Type: text/plain; charset="iso-8859-1"
+  Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH 4 of 6] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
   X-Mercurial-Series-Index: 4
   X-Mercurial-Series-Total: 6
-  Message-Id: <240fb913fc1b7ff15ddb.315532864@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob)
-  In-Reply-To: <patchbomb.315532860@*> (glob)
-  References: <patchbomb.315532860@*> (glob)
+  Message-Id: <240fb913fc1b7ff15ddb.315532864@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@test-hostname>
+  In-Reply-To: <patchbomb.315532860@test-hostname>
+  References: <patchbomb.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:04 +0000
   From: test
@@ -2777,7 +2785,7 @@
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/isolatin	Thu Jan 01 00:00:05 1970 +0000
   @@ -0,0 +1,1 @@
-  +h\xf6mma! (esc)
+  +h=F6mma!
   
   displaying [PATCH 5 of 6] Added tag zero, zero.foo for changeset 8580ff50825a ...
   MIME-Version: 1.0
@@ -2787,10 +2795,10 @@
   X-Mercurial-Node: 5d5ef15dfe5e7bd3a4ee154b5fff76c7945ec433
   X-Mercurial-Series-Index: 5
   X-Mercurial-Series-Total: 6
-  Message-Id: <5d5ef15dfe5e7bd3a4ee.315532865@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob)
-  In-Reply-To: <patchbomb.315532860@*> (glob)
-  References: <patchbomb.315532860@*> (glob)
+  Message-Id: <5d5ef15dfe5e7bd3a4ee.315532865@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@test-hostname>
+  In-Reply-To: <patchbomb.315532860@test-hostname>
+  References: <patchbomb.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:05 +0000
   From: test
@@ -2819,10 +2827,10 @@
   X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268
   X-Mercurial-Series-Index: 6
   X-Mercurial-Series-Total: 6
-  Message-Id: <2f9fa9b998c5fe3ac2bd.315532866@*> (glob)
-  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@*> (glob)
-  In-Reply-To: <patchbomb.315532860@*> (glob)
-  References: <patchbomb.315532860@*> (glob)
+  Message-Id: <2f9fa9b998c5fe3ac2bd.315532866@test-hostname>
+  X-Mercurial-Series-Id: <ff2c9fa2018b15fa74b3.315532861@test-hostname>
+  In-Reply-To: <patchbomb.315532860@test-hostname>
+  References: <patchbomb.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:06 +0000
   From: test
@@ -2864,8 +2872,8 @@
   X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268
   X-Mercurial-Series-Index: 1
   X-Mercurial-Series-Total: 1
-  Message-Id: <2f9fa9b998c5fe3ac2bd.315532860@*> (glob)
-  X-Mercurial-Series-Id: <2f9fa9b998c5fe3ac2bd.315532860@*> (glob)
+  Message-Id: <2f9fa9b998c5fe3ac2bd.315532860@test-hostname>
+  X-Mercurial-Series-Id: <2f9fa9b998c5fe3ac2bd.315532860@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:00 +0000
   From: test
--- a/tests/test-permissions.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-permissions.t	Wed Sep 26 20:33:09 2018 +0900
@@ -13,7 +13,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
   $ chmod -r .hg/store/data/a.i
 
@@ -32,7 +32,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
   $ chmod -w .hg/store/data/a.i
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-phabricator.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,77 @@
+#require vcr
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > phabricator = 
+  > EOF
+  $ hg init repo
+  $ cd repo
+  $ cat >> .hg/hgrc <<EOF
+  > [phabricator]
+  > url = https://phab.mercurial-scm.org/
+  > callsign = HG
+  > 
+  > [auth]
+  > hgphab.schemes = https
+  > hgphab.prefix = phab.mercurial-scm.org
+  > # When working on the extension and making phabricator interaction
+  > # changes, edit this to be a real phabricator token. When done, edit
+  > # it back, and make sure to also edit your VCR transcripts to match
+  > # whatever value you put here.
+  > hgphab.phabtoken = cli-hahayouwish
+  > EOF
+  $ VCR="$TESTDIR/phabricator"
+
+Error is handled reasonably. We override the phabtoken here so that
+when you're developing changes to phabricator.py you can edit the
+above config and have a real token in the test but not have to edit
+this test.
+  $ hg phabread --config auth.hgphab.phabtoken=cli-notavalidtoken \
+  >  --test-vcr "$VCR/phabread-conduit-error.json" D4480 | head
+  abort: Conduit Error (ERR-INVALID-AUTH): API token "cli-notavalidtoken" has the wrong length. API tokens should be 32 characters long.
+
+Basic phabread:
+  $ hg phabread --test-vcr "$VCR/phabread-4480.json" D4480 | head
+  # HG changeset patch
+  exchangev2: start to implement pull with wire protocol v2
+  
+  Wire protocol version 2 will take a substantially different
+  approach to exchange than version 1 (at least as far as pulling
+  is concerned).
+  
+  This commit establishes a new exchangev2 module for holding
+  code related to exchange using wire protocol v2. I could have
+  added things to the existing exchange module. But it is already
+
+phabupdate with an accept:
+  $ hg phabupdate --accept D4564 \
+  > -m 'I think I like where this is headed. Will read rest of series later.'\
+  >  --test-vcr "$VCR/accept-4564.json"
+
+Create a differential diff:
+  $ echo alpha > alpha
+  $ hg ci --addremove -m 'create alpha for phabricator test'
+  adding alpha
+  $ hg phabsend -r . --test-vcr "$VCR/phabsend-create-alpha.json"
+  D4596 - created - 5206a4fa1e6c: create alpha for phabricator test
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5206a4fa1e6c-dec9e777-phabsend.hg
+  $ echo more >> alpha
+  $ HGEDITOR=true hg ci --amend
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d8f232f7d799-c573510a-amend.hg
+  $ echo beta > beta
+  $ hg ci --addremove -m 'create beta for phabricator test'
+  adding beta
+  $ hg phabsend -r ".^::" --test-vcr "$VCR/phabsend-update-alpha-create-beta.json"
+  D4596 - updated - f70265671c65: create alpha for phabricator test
+  D4597 - created - 1a5640df7bbf: create beta for phabricator test
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/1a5640df7bbf-6daf3e6e-phabsend.hg
+
+Template keywords
+  $ hg log -T'{rev} {phabreview|json}\n'
+  1 {"id": "D4597", "url": "https://phab.mercurial-scm.org/D4597"}
+  0 {"id": "D4596", "url": "https://phab.mercurial-scm.org/D4596"}
+
+  $ hg log -T'{rev} {phabreview.url} {phabreview.id}\n'
+  1 https://phab.mercurial-scm.org/D4597 D4597
+  0 https://phab.mercurial-scm.org/D4596 D4596
+
+  $ cd ..
--- a/tests/test-phases-exchange.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-phases-exchange.t	Wed Sep 26 20:33:09 2018 +0900
@@ -204,7 +204,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
-  new changesets 054250a37db4:b555f63b6063
+  new changesets 054250a37db4:b555f63b6063 (5 drafts)
   test-debug-phase: new rev 0:  x -> 1
   test-debug-phase: new rev 1:  x -> 1
   test-debug-phase: new rev 2:  x -> 1
@@ -238,7 +238,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
-  new changesets 054250a37db4:54acac6f23ab
+  new changesets 054250a37db4:54acac6f23ab (3 drafts)
   test-debug-phase: new rev 0:  x -> 1
   test-debug-phase: new rev 1:  x -> 1
   test-debug-phase: new rev 2:  x -> 1
@@ -260,7 +260,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets f54f1bb90ff3
+  new changesets f54f1bb90ff3 (1 drafts)
   test-debug-phase: new rev 3:  x -> 1
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hgph
@@ -333,7 +333,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  new changesets d6bcb4f74035:145e75495359
+  new changesets d6bcb4f74035:145e75495359 (2 drafts)
   4 local changesets published
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
@@ -380,7 +380,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  new changesets d6bcb4f74035:145e75495359
+  new changesets d6bcb4f74035:145e75495359 (2 drafts)
   test-debug-phase: new rev 5:  x -> 1
   test-debug-phase: new rev 6:  x -> 1
   (run 'hg update' to get a working copy)
@@ -943,7 +943,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 435b5d83910c
+  new changesets 435b5d83910c (1 drafts)
   test-debug-phase: new rev 10:  x -> 1
   (run 'hg update' to get a working copy)
   $ hgph -R ../mu
@@ -1073,7 +1073,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 5237fb433fc8
+  new changesets 5237fb433fc8 (1 drafts)
   test-debug-phase: new rev 13:  x -> 1
   (run 'hg update' to get a working copy)
   $ hgph
@@ -1448,7 +1448,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+1 heads)
-  new changesets 426bada5c675:bb94757e651a
+  new changesets 426bada5c675:bb94757e651a (4 drafts)
   test-debug-phase: new rev 0:  x -> 0
   test-debug-phase: new rev 1:  x -> 0
   test-debug-phase: new rev 2:  x -> 0
@@ -1490,7 +1490,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+1 heads)
-  new changesets 426bada5c675:bb94757e651a
+  new changesets 426bada5c675:bb94757e651a (4 drafts)
   test-debug-phase: new rev 0:  x -> 0
   test-debug-phase: new rev 1:  x -> 0
   test-debug-phase: new rev 2:  x -> 0
@@ -1532,7 +1532,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+1 heads)
-  new changesets 426bada5c675:bb94757e651a
+  new changesets 426bada5c675:bb94757e651a (4 drafts)
   test-debug-phase: new rev 0:  x -> 0
   test-debug-phase: new rev 1:  x -> 0
   test-debug-phase: new rev 2:  x -> 0
--- a/tests/test-phases.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-phases.t	Wed Sep 26 20:33:09 2018 +0900
@@ -690,7 +690,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  7 files, 8 changesets, 7 total revisions
+  checked 8 changesets with 7 changes to 7 files
 
   $ cd ..
 
@@ -826,3 +826,81 @@
   rollback completed
   abort: pretxnclose-phase.nopublish_D hook exited with status 1
   [255]
+
+  $ cd ..
+
+Test for the "internal" phase
+=============================
+
+Check we deny its usage on older repository
+
+  $ hg init no-internal-phase --config format.internal-phase=no
+  $ cd no-internal-phase
+  $ cat .hg/requires
+  dotencode
+  fncache
+  generaldelta
+  revlogv1
+  store
+  $ echo X > X
+  $ hg add X
+  $ hg status
+  A X
+  $ hg --config "phases.new-commit=internal" commit -m "my test internal commit" 2>&1 | grep ProgrammingError
+  ** ProgrammingError: this repository does not support the internal phase
+      raise error.ProgrammingError(msg)
+  mercurial.error.ProgrammingError: this repository does not support the internal phase
+
+  $ cd ..
+
+Check it works fine with repository that supports it.
+
+  $ hg init internal-phase --config format.internal-phase=yes
+  $ cd internal-phase
+  $ cat .hg/requires
+  dotencode
+  fncache
+  generaldelta
+  internal-phase
+  revlogv1
+  store
+  $ mkcommit A
+  test-debug-phase: new rev 0:  x -> 1
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> draft
+
+Commit an internal changesets
+
+  $ echo B > B
+  $ hg add B
+  $ hg status
+  A B
+  $ hg --config "phases.new-commit=internal" commit -m "my test internal commit"
+  test-debug-phase: new rev 1:  x -> 96
+  test-hook-close-phase: c01c42dffc7f81223397e99652a0703f83e1c5ea:   -> internal
+
+Usual visibility rules apply when working directory parents
+
+  $ hg log -G -l 3
+  @  changeset:   1:c01c42dffc7f
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     my test internal commit
+  |
+  o  changeset:   0:4a2df7238c3b
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
+
+Commit is hidden as expected
+
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg log -G
+  @  changeset:   0:4a2df7238c3b
+     tag:         tip
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
--- a/tests/test-profile.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-profile.t	Wed Sep 26 20:33:09 2018 +0900
@@ -105,7 +105,7 @@
 
 statprof can be used as a standalone module
 
-  $ $PYTHON -m mercurial.statprof hotpath
+  $ "$PYTHON" -m mercurial.statprof hotpath
   must specify --file to load
   [1]
 
--- a/tests/test-pull-bundle.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-pull-bundle.t	Wed Sep 26 20:33:09 2018 +0900
@@ -59,7 +59,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets bbd179dfa0a7
+  new changesets bbd179dfa0a7 (1 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo.pullbundle
@@ -70,7 +70,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets ed1b79f46b9a
+  new changesets ed1b79f46b9a (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg pull -r 2
   pulling from http://localhost:$HGPORT2/
@@ -79,7 +79,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets effea6de0384
+  new changesets effea6de0384 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ cd ..
   $ killdaemons.py
@@ -110,7 +110,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets bbd179dfa0a7:ed1b79f46b9a
+  new changesets bbd179dfa0a7:ed1b79f46b9a (3 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ killdaemons.py
@@ -136,7 +136,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets bbd179dfa0a7
+  new changesets bbd179dfa0a7 (1 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo.pullbundle3
--- a/tests/test-pull-permission.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-pull-permission.t	Wed Sep 26 20:33:09 2018 +0900
@@ -28,6 +28,6 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
   $ cd ..
--- a/tests/test-pull-pull-corruption.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-pull-pull-corruption.t	Wed Sep 26 20:33:09 2018 +0900
@@ -70,6 +70,6 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 11 changesets, 11 total revisions
+  checked 11 changesets with 11 changes to 1 files
 
   $ cd ..
--- a/tests/test-pull.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-pull.t	Wed Sep 26 20:33:09 2018 +0900
@@ -23,7 +23,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
   $ hg serve -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
@@ -45,7 +45,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
   $ hg co
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -109,12 +109,12 @@
 It's tricky to make file:// URLs working on every platform with
 regular shell commands.
 
-  $ URL=`$PYTHON -c "from __future__ import print_function; import os; print('file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
+  $ URL=`"$PYTHON" -c "from __future__ import print_function; import os; print('file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
   $ hg pull -q "$URL"
   abort: file:// URLs can only refer to localhost
   [255]
 
-  $ URL=`$PYTHON -c "from __future__ import print_function; import os; print('file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
+  $ URL=`"$PYTHON" -c "from __future__ import print_function; import os; print('file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
   $ hg pull -q "$URL"
 
 SEC: check for unsafe ssh url
--- a/tests/test-purge.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-purge.t	Wed Sep 26 20:33:09 2018 +0900
@@ -49,7 +49,7 @@
 
   $ touch untracked_file
   $ touch untracked_file_readonly
-  $ $PYTHON <<EOF
+  $ "$PYTHON" <<EOF
   > import os, stat
   > f= 'untracked_file_readonly'
   > os.chmod(f, stat.S_IMODE(os.stat(f).st_mode) & ~stat.S_IWRITE)
--- a/tests/test-push-cgi.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-cgi.t	Wed Sep 26 20:33:09 2018 +0900
@@ -21,7 +21,7 @@
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
-  > application = hgweb('.', 'test repository')
+  > application = hgweb(b'.', b'test repository')
   > wsgicgi.launch(application)
   > HGWEB
   $ chmod 755 hgweb.cgi
@@ -38,7 +38,7 @@
 expect failure because heads doesn't match (formerly known as 'unsynced changes')
 
   $ QUERY_STRING="cmd=unbundle&heads=0000000000000000000000000000000000000000"; export QUERY_STRING
-  $ $PYTHON hgweb.cgi <bundle.hg >page1 2>&1
+  $ "$PYTHON" hgweb.cgi <bundle.hg >page1 2>&1
   $ cat page1
   Status: 200 Script output follows\r (esc)
   Content-Type: application/mercurial-0.1\r (esc)
@@ -50,7 +50,7 @@
 successful force push
 
   $ QUERY_STRING="cmd=unbundle&heads=666f726365"; export QUERY_STRING
-  $ $PYTHON hgweb.cgi <bundle.hg >page2 2>&1
+  $ "$PYTHON" hgweb.cgi <bundle.hg >page2 2>&1
   $ cat page2
   Status: 200 Script output follows\r (esc)
   Content-Type: application/mercurial-0.1\r (esc)
@@ -65,7 +65,7 @@
 successful push, list of heads
 
   $ QUERY_STRING="cmd=unbundle&heads=f7b1eb17ad24730a1651fccd46c43826d1bbc2ac"; export QUERY_STRING
-  $ $PYTHON hgweb.cgi <bundle.hg >page3 2>&1
+  $ "$PYTHON" hgweb.cgi <bundle.hg >page3 2>&1
   $ cat page3
   Status: 200 Script output follows\r (esc)
   Content-Type: application/mercurial-0.1\r (esc)
@@ -80,7 +80,7 @@
 successful push, SHA1 hash of heads (unbundlehash capability)
 
   $ QUERY_STRING="cmd=unbundle&heads=686173686564 5a785a5f9e0d433b88ed862b206b011b0c3a9d13"; export QUERY_STRING
-  $ $PYTHON hgweb.cgi <bundle.hg >page4 2>&1
+  $ "$PYTHON" hgweb.cgi <bundle.hg >page4 2>&1
   $ cat page4
   Status: 200 Script output follows\r (esc)
   Content-Type: application/mercurial-0.1\r (esc)
--- a/tests/test-push-checkheads-partial-C1.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-partial-C1.t	Wed Sep 26 20:33:09 2018 +0900
@@ -53,7 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-partial-C2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-partial-C2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -53,7 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-partial-C3.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-partial-C3.t	Wed Sep 26 20:33:09 2018 +0900
@@ -53,7 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-partial-C4.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-partial-C4.t	Wed Sep 26 20:33:09 2018 +0900
@@ -53,7 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-pruned-B2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -53,7 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B3.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-pruned-B3.t	Wed Sep 26 20:33:09 2018 +0900
@@ -53,7 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B4.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-pruned-B4.t	Wed Sep 26 20:33:09 2018 +0900
@@ -54,7 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B5.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-pruned-B5.t	Wed Sep 26 20:33:09 2018 +0900
@@ -57,7 +57,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  new changesets d73caddc5533:821fb21d0dd2
+  new changesets d73caddc5533:821fb21d0dd2 (2 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B8.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-pruned-B8.t	Wed Sep 26 20:33:09 2018 +0900
@@ -55,7 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-superceed-A2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-superceed-A2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -52,7 +52,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-superceed-A3.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-superceed-A3.t	Wed Sep 26 20:33:09 2018 +0900
@@ -55,7 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-superceed-A6.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-superceed-A6.t	Wed Sep 26 20:33:09 2018 +0900
@@ -59,7 +59,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
-  new changesets d73caddc5533:0f88766e02d6
+  new changesets d73caddc5533:0f88766e02d6 (2 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-superceed-A7.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-superceed-A7.t	Wed Sep 26 20:33:09 2018 +0900
@@ -59,7 +59,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
-  new changesets d73caddc5533:0f88766e02d6
+  new changesets d73caddc5533:0f88766e02d6 (2 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 'desc(C0)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-unpushed-D2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-unpushed-D2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -57,7 +57,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-unpushed-D3.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-unpushed-D3.t	Wed Sep 26 20:33:09 2018 +0900
@@ -56,7 +56,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d73caddc5533
+  new changesets d73caddc5533 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-unpushed-D4.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-unpushed-D4.t	Wed Sep 26 20:33:09 2018 +0900
@@ -73,7 +73,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
-  new changesets d73caddc5533:0f88766e02d6
+  new changesets d73caddc5533:0f88766e02d6 (2 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-unpushed-D5.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-checkheads-unpushed-D5.t	Wed Sep 26 20:33:09 2018 +0900
@@ -62,7 +62,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
-  new changesets d73caddc5533:0f88766e02d6
+  new changesets d73caddc5533:0f88766e02d6 (2 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 'desc(C0)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-race.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-race.t	Wed Sep 26 20:33:09 2018 +0900
@@ -102,7 +102,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > ssh = $PYTHON "$TESTDIR/dummyssh"
+  > ssh = "$PYTHON" "$TESTDIR/dummyssh"
   > # simplify output
   > logtemplate = {node|short} {desc} ({branch})
   > [phases]
@@ -148,7 +148,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 842e2fac6304
+  new changesets 842e2fac6304 (1 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone ssh://user@dummy/server client-other
@@ -157,7 +157,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 842e2fac6304
+  new changesets 842e2fac6304 (1 drafts)
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -242,7 +242,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets a9149a1428e2
+  new changesets a9149a1428e2 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -251,7 +251,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets a9149a1428e2
+  new changesets a9149a1428e2 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -260,7 +260,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets 98217d5a1659
+  new changesets 98217d5a1659 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -364,7 +364,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 59e76faf78bd
+  new changesets 59e76faf78bd (1 drafts)
   (run 'hg update' to get a working copy)
 
 #endif
@@ -384,7 +384,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 59e76faf78bd
+  new changesets 59e76faf78bd (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -393,7 +393,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 51c544a58128
+  new changesets 51c544a58128 (1 drafts)
   (run 'hg update' to get a working copy)
 
   $ hg -R server graph
@@ -522,7 +522,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d9e379a8c432
+  new changesets d9e379a8c432 (1 drafts)
   (run 'hg update' to get a working copy)
 
 #endif
@@ -542,7 +542,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets d9e379a8c432
+  new changesets d9e379a8c432 (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -551,7 +551,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets d603e2c0cdd7
+  new changesets d603e2c0cdd7 (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -693,7 +693,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets 833be552cfe6
+  new changesets 833be552cfe6 (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 #endif
@@ -713,7 +713,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets 833be552cfe6
+  new changesets 833be552cfe6 (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -722,7 +722,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets 75d69cba5402
+  new changesets 75d69cba5402 (1 drafts)
   (run 'hg heads' to see heads)
 
   $ hg -R server graph
@@ -878,7 +878,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets 89420bf00fae
+  new changesets 89420bf00fae (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 #endif
@@ -899,7 +899,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets 89420bf00fae
+  new changesets 89420bf00fae (1 drafts)
   (run 'hg heads' to see heads)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -908,7 +908,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets b35ed749f288
+  new changesets b35ed749f288 (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1026,7 +1026,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets cac2cead0ff0
+  new changesets cac2cead0ff0 (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1035,7 +1035,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets cac2cead0ff0
+  new changesets cac2cead0ff0 (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1044,7 +1044,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
-  new changesets be705100c623
+  new changesets be705100c623 (1 drafts)
   (run 'hg update' to get a working copy)
 
   $ hg -R server graph
@@ -1169,7 +1169,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
-  new changesets 866a66e18630
+  new changesets 866a66e18630 (1 drafts)
   (run 'hg update' to get a working copy)
 
 (creates named branch on head)
@@ -1191,7 +1191,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files
-  new changesets 866a66e18630:55a6f1c01b48
+  new changesets 866a66e18630:55a6f1c01b48 (2 drafts)
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1200,7 +1200,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
-  new changesets 6fd3090135df:55a6f1c01b48
+  new changesets 6fd3090135df:55a6f1c01b48 (2 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1349,7 +1349,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
-  new changesets b0ee3d6f51bc
+  new changesets b0ee3d6f51bc (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1358,7 +1358,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
-  new changesets b0ee3d6f51bc
+  new changesets b0ee3d6f51bc (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1367,7 +1367,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
-  new changesets d0a85b2252a9:1b58ee3f79e5
+  new changesets d0a85b2252a9:1b58ee3f79e5 (2 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1520,7 +1520,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
-  new changesets 2efd43f7b5ba:3d57ed3c1091
+  new changesets 2efd43f7b5ba:3d57ed3c1091 (2 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1529,7 +1529,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
-  new changesets 2efd43f7b5ba:3d57ed3c1091
+  new changesets 2efd43f7b5ba:3d57ed3c1091 (2 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1538,7 +1538,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
-  new changesets de7b9e2ba3f6
+  new changesets de7b9e2ba3f6 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1708,7 +1708,7 @@
   1 new obsolescence markers
   obsoleted 1 changesets
   1 new orphan changesets
-  new changesets 720c5163ecf6
+  new changesets 720c5163ecf6 (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1720,7 +1720,7 @@
   1 new obsolescence markers
   obsoleted 1 changesets
   1 new orphan changesets
-  new changesets 720c5163ecf6
+  new changesets 720c5163ecf6 (1 drafts)
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1730,7 +1730,7 @@
   adding file changes
   added 1 changesets with 0 changes to 0 files
   1 new orphan changesets
-  new changesets a98a47d8b85b
+  new changesets a98a47d8b85b (1 drafts)
   (run 'hg update' to get a working copy)
 
   $ hg -R server debugobsolete
--- a/tests/test-push-warn.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push-warn.t	Wed Sep 26 20:33:09 2018 +0900
@@ -419,7 +419,7 @@
   adding c
   created new head
 
-  $ for i in `$PYTHON $TESTDIR/seq.py 3`; do hg -R h up -q 0; echo $i > h/b; hg -R h ci -qAm$i; done
+  $ for i in `"$PYTHON" $TESTDIR/seq.py 3`; do hg -R h up -q 0; echo $i > h/b; hg -R h ci -qAm$i; done
 
   $ hg -R i push h
   pushing to h
--- a/tests/test-push.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-push.t	Wed Sep 26 20:33:09 2018 +0900
@@ -11,7 +11,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets bfaf4b5cbf01:916f1afdef90
+  new changesets bfaf4b5cbf01:916f1afdef90 (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ for i in 0 1 2 3 4 5 6 7 8; do
@@ -31,7 +31,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   
   pushing to test-revflag-1
   searching for changes
@@ -43,7 +43,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   
   pushing to test-revflag-2
   searching for changes
@@ -55,7 +55,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   
   pushing to test-revflag-3
   searching for changes
@@ -67,7 +67,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 4 changesets, 4 total revisions
+  checked 4 changesets with 4 changes to 1 files
   
   pushing to test-revflag-4
   searching for changes
@@ -79,7 +79,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   
   pushing to test-revflag-5
   searching for changes
@@ -91,7 +91,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   
   pushing to test-revflag-6
   searching for changes
@@ -103,7 +103,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 4 changesets, 5 total revisions
+  checked 4 changesets with 5 changes to 2 files
   
   pushing to test-revflag-7
   searching for changes
@@ -115,7 +115,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 5 changesets, 6 total revisions
+  checked 5 changesets with 6 changes to 3 files
   
   pushing to test-revflag-8
   searching for changes
@@ -127,7 +127,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
 
   $ cd test-revflag-8
 
@@ -146,7 +146,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
 
   $ cd ..
 
@@ -195,7 +195,7 @@
   crosschecking files in changesets and manifests
   checking files
    beta@1: dddc47b3ba30 not in manifests
-  2 files, 2 changesets, 4 total revisions
+  checked 2 changesets with 4 changes to 2 files
   1 integrity errors encountered!
   (first damaged changeset appears to be 1)
   [1]
@@ -230,7 +230,7 @@
   crosschecking files in changesets and manifests
   checking files
    beta@1: manifest refers to unknown revision dddc47b3ba30
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
   1 integrity errors encountered!
   (first damaged changeset appears to be 1)
   [1]
--- a/tests/test-py3-commands.t	Tue Sep 25 16:32:38 2018 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,239 +0,0 @@
-#require py3exe
-
-This test helps in keeping a track on which commands we can run on
-Python 3 and see what kind of errors are coming up.
-The full traceback is hidden to have a stable output.
-  $ HGBIN=`which hg`
-
-  $ for cmd in version debuginstall ; do
-  >   echo $cmd
-  >   $PYTHON3 $HGBIN $cmd 2>&1 2>&1 | tail -1
-  > done
-  version
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-  debuginstall
-  no problems detected
-
-#if test-repo
-Make a clone so that any features in the developer's .hg/hgrc that
-might confuse Python 3 don't break this test. When we can do commit in
-Python 3, we'll stop doing this. We use e76ed1e480ef for the clone
-because it has different files than 273ce12ad8f1, so we can test both
-`files` from dirstate and `files` loaded from a specific revision.
-
-  $ hg clone -r e76ed1e480ef "`dirname "$TESTDIR"`" testrepo 2>&1 | tail -1
-  15 files updated, 0 files merged, 0 files removed, 0 files unresolved
-
-Test using -R, which exercises some URL code:
-  $ $PYTHON3 $HGBIN -R testrepo files -r 273ce12ad8f1 | tail -1
-  testrepo/tkmerge
-
-Now prove `hg files` is reading the whole manifest. We have to grep
-out some potential warnings that come from hgrc as yet.
-  $ cd testrepo
-  $ $PYTHON3 $HGBIN files -r 273ce12ad8f1
-  .hgignore
-  PKG-INFO
-  README
-  hg
-  mercurial/__init__.py
-  mercurial/byterange.py
-  mercurial/fancyopts.py
-  mercurial/hg.py
-  mercurial/mdiff.py
-  mercurial/revlog.py
-  mercurial/transaction.py
-  notes.txt
-  setup.py
-  tkmerge
-
-  $ $PYTHON3 $HGBIN files -r 273ce12ad8f1 | wc -l
-  \s*14 (re)
-  $ $PYTHON3 $HGBIN files | wc -l
-  \s*15 (re)
-
-Test if log-like commands work:
-
-  $ $PYTHON3 $HGBIN tip
-  changeset:   10:e76ed1e480ef
-  tag:         tip
-  user:        oxymoron@cinder.waste.org
-  date:        Tue May 03 23:37:43 2005 -0800
-  summary:     Fix linking of changeset revs when merging
-  
-
-  $ $PYTHON3 $HGBIN log -r0
-  changeset:   0:9117c6561b0b
-  user:        mpm@selenic.com
-  date:        Tue May 03 13:16:10 2005 -0800
-  summary:     Add back links from file revisions to changeset revisions
-  
-
-  $ cd ..
-#endif
-
-Test if `hg config` works:
-
-  $ $PYTHON3 $HGBIN config
-  devel.all-warnings=true
-  devel.default-date=0 0
-  largefiles.usercache=$TESTTMP/.cache/largefiles
-  ui.slash=True
-  ui.interactive=False
-  ui.mergemarkers=detailed
-  ui.promptecho=True
-  web.address=localhost
-  web.ipv6=False
-
-  $ cat > included-hgrc <<EOF
-  > [extensions]
-  > babar = imaginary_elephant
-  > EOF
-  $ cat >> $HGRCPATH <<EOF
-  > %include $TESTTMP/included-hgrc
-  > EOF
-  $ $PYTHON3 $HGBIN version | tail -1
-  *** failed to import extension babar from imaginary_elephant: *: 'imaginary_elephant' (glob)
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-  $ rm included-hgrc
-  $ touch included-hgrc
-
-Test bytes-ness of policy.policy with HGMODULEPOLICY
-
-  $ HGMODULEPOLICY=py
-  $ export HGMODULEPOLICY
-  $ $PYTHON3 `which hg` debuginstall 2>&1 2>&1 | tail -1
-  no problems detected
-
-`hg init` can create empty repos
-`hg status works fine`
-`hg summary` also works!
-
-  $ $PYTHON3 `which hg` init py3repo
-  $ cd py3repo
-  $ echo "This is the file 'iota'." > iota
-  $ $PYTHON3 $HGBIN status
-  ? iota
-  $ $PYTHON3 $HGBIN add iota
-  $ $PYTHON3 $HGBIN status
-  A iota
-  $ hg diff --nodates --git
-  diff --git a/iota b/iota
-  new file mode 100644
-  --- /dev/null
-  +++ b/iota
-  @@ -0,0 +1,1 @@
-  +This is the file 'iota'.
-  $ $PYTHON3 $HGBIN commit --message 'commit performed in Python 3'
-  $ $PYTHON3 $HGBIN status
-
-  $ mkdir A
-  $ echo "This is the file 'mu'." > A/mu
-  $ $PYTHON3 $HGBIN addremove
-  adding A/mu
-  $ $PYTHON3 $HGBIN status
-  A A/mu
-  $ HGEDITOR='echo message > ' $PYTHON3 $HGBIN commit
-  $ $PYTHON3 $HGBIN status
-  $ $PYHON3 $HGBIN summary
-  parent: 1:e1e9167203d4 tip
-   message
-  branch: default
-  commit: (clean)
-  update: (current)
-  phases: 2 draft
-
-Test weird unicode-vs-bytes stuff
-
-  $ $PYTHON3 $HGBIN help | egrep -v '^ |^$'
-  Mercurial Distributed SCM
-  list of commands:
-  additional help topics:
-  (use 'hg help -v' to show built-in aliases and global options)
-
-  $ $PYTHON3 $HGBIN help help | egrep -v '^ |^$'
-  hg help [-ecks] [TOPIC]
-  show help for a given topic or a help overview
-  options ([+] can be repeated):
-  (some details hidden, use --verbose to show complete help)
-
-  $ $PYTHON3 $HGBIN help -k notopic
-  abort: no matches
-  (try 'hg help' for a list of topics)
-  [255]
-
-Prove the repo is valid using the Python 2 `hg`:
-  $ hg verify
-  checking changesets
-  checking manifests
-  crosschecking files in changesets and manifests
-  checking files
-  2 files, 2 changesets, 2 total revisions
-  $ hg log
-  changeset:   1:e1e9167203d4
-  tag:         tip
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     message
-  
-  changeset:   0:71c96e924262
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     commit performed in Python 3
-  
-
-  $ $PYTHON3 $HGBIN log -G
-  @  changeset:   1:e1e9167203d4
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     message
-  |
-  o  changeset:   0:71c96e924262
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     commit performed in Python 3
-  
-  $ $PYTHON3 $HGBIN log -Tjson
-  [
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [0, 0],
-    "desc": "message",
-    "node": "e1e9167203d450ca2f558af628955b5f5afd4489",
-    "parents": ["71c96e924262969ff0d8d3d695b0f75412ccc3d8"],
-    "phase": "draft",
-    "rev": 1,
-    "tags": ["tip"],
-    "user": "test"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [0, 0],
-    "desc": "commit performed in Python 3",
-    "node": "71c96e924262969ff0d8d3d695b0f75412ccc3d8",
-    "parents": ["0000000000000000000000000000000000000000"],
-    "phase": "draft",
-    "rev": 0,
-    "tags": [],
-    "user": "test"
-   }
-  ]
-
-Show that update works now!
-
-  $ $PYTHON3 $HGBIN up 0
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ $PYTHON3 $HGBIN identify
-  71c96e924262
-
-branches and bookmarks also works!
-
-  $ $PYTHON3 $HGBIN branches
-  default                        1:e1e9167203d4
-  $ $PYTHON3 $HGBIN bookmark book
-  $ $PYTHON3 $HGBIN bookmarks
-   * book                      0:71c96e924262
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-backup.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,150 @@
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > rebase=
+  > EOF
+
+==========================================
+Test history-editing-backup config option |
+==========================================
+Test with Pre-obsmarker rebase:
+1) When config option is not set:
+  $ hg init repo1
+  $ cd repo1
+  $ echo a>a
+  $ hg ci -qAma
+  $ echo b>b
+  $ hg ci -qAmb
+  $ echo c>c
+  $ hg ci -qAmc
+  $ hg up 0 -q
+  $ echo d>d
+  $ hg ci -qAmd
+  $ echo e>e
+  $ hg ci -qAme
+  $ hg log -GT "{rev}: {firstline(desc)}\n"
+  @  4: e
+  |
+  o  3: d
+  |
+  | o  2: c
+  | |
+  | o  1: b
+  |/
+  o  0: a
+  
+  $ hg rebase -s 1 -d .
+  rebasing 1:d2ae7f538514 "b"
+  rebasing 2:177f92b77385 "c"
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/d2ae7f538514-c7ed7a78-rebase.hg
+  $ hg log -GT "{rev}: {firstline(desc)}\n"
+  o  4: c
+  |
+  o  3: b
+  |
+  @  2: e
+  |
+  o  1: d
+  |
+  o  0: a
+  
+
+2) When config option is set:
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > history-editing-backup = False
+  > EOF
+
+  $ echo f>f
+  $ hg ci -Aqmf
+  $ echo g>g
+  $ hg ci -Aqmg
+  $ hg log -GT "{rev}: {firstline(desc)}\n"
+  @  6: g
+  |
+  o  5: f
+  |
+  | o  4: c
+  | |
+  | o  3: b
+  |/
+  o  2: e
+  |
+  o  1: d
+  |
+  o  0: a
+  
+  $ hg rebase -s 3 -d .
+  rebasing 3:05bff2a95b12 "b"
+  rebasing 4:1762bde4404d "c"
+
+  $ hg log -GT "{rev}: {firstline(desc)}\n"
+  o  6: c
+  |
+  o  5: b
+  |
+  @  4: g
+  |
+  o  3: f
+  |
+  o  2: e
+  |
+  o  1: d
+  |
+  o  0: a
+  
+Test when rebased revisions are stripped during abort:
+======================================================
+
+  $ echo conflict > c
+  $ hg ci -Am "conflict with c"
+  adding c
+  created new head
+  $ hg log -GT "{rev}: {firstline(desc)}\n"
+  @  7: conflict with c
+  |
+  | o  6: c
+  | |
+  | o  5: b
+  |/
+  o  4: g
+  |
+  o  3: f
+  |
+  o  2: e
+  |
+  o  1: d
+  |
+  o  0: a
+  
+When history-editing-backup = True:
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > history-editing-backup = True
+  > EOF
+  $ hg rebase -s 5 -d .
+  rebasing 5:1f8148a544ee "b"
+  rebasing 6:f8bc7d28e573 "c"
+  merging c
+  warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg rebase --abort
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/818c1a43c916-2b644d96-backup.hg
+  rebase aborted
+
+When history-editing-backup = False:
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > history-editing-backup = False
+  > EOF
+  $ hg rebase -s 5 -d .
+  rebasing 5:1f8148a544ee "b"
+  rebasing 6:f8bc7d28e573 "c"
+  merging c
+  warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg rebase --abort
+  rebase aborted
+  $ cd ..
+
--- a/tests/test-rebase-base-flag.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-base-flag.t	Wed Sep 26 20:33:09 2018 +0900
@@ -14,7 +14,7 @@
   > EOF
 
   $ rebasewithdag() {
-  >   N=`$PYTHON -c "print($N+1)"`
+  >   N=`"$PYTHON" -c "print($N+1)"`
   >   hg init repo$N && cd repo$N
   >   hg debugdrawdag
   >   hg rebase "$@" > _rebasetmp
--- a/tests/test-rebase-collapse.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-collapse.t	Wed Sep 26 20:33:09 2018 +0900
@@ -540,7 +540,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 11 changes to 7 files (+1 heads)
-  new changesets f447d5abf5ea:338e84e2e558
+  new changesets f447d5abf5ea:338e84e2e558 (4 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up -q tip
   $ hg tglog
--- a/tests/test-rebase-conflicts.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-conflicts.t	Wed Sep 26 20:33:09 2018 +0900
@@ -155,7 +155,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 8 changes to 3 files (+1 heads)
-  new changesets 24797d4f68de:2f2496ddf49d
+  new changesets 24797d4f68de:2f2496ddf49d (11 drafts)
   (run 'hg heads' to see heads)
   $ hg up default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-rebase-dest.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-dest.t	Wed Sep 26 20:33:09 2018 +0900
@@ -119,7 +119,7 @@
   > EOF
 
   $ rebasewithdag() {
-  >   N=`$PYTHON -c "print($N+1)"`
+  >   N=`"$PYTHON" -c "print($N+1)"`
   >   hg init repo$N && cd repo$N
   >   hg debugdrawdag
   >   hg rebase "$@" > _rebasetmp
--- a/tests/test-rebase-inmemory.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-inmemory.t	Wed Sep 26 20:33:09 2018 +0900
@@ -156,7 +156,92 @@
   |/
   o  0: b173517d0057 'a'
   
+
+Test reporting of path conflicts
+
+  $ hg rm a
+  $ mkdir a
+  $ touch a/a
+  $ hg ci -Am "a/a"
+  adding a/a
+  $ hg tglog
+  @  4: daf7dfc139cb 'a/a'
+  |
+  o  3: 844a7de3e617 'c'
+  |
+  | o  2: 09c044d2cb43 'd'
+  | |
+  | o  1: fc055c3b4d33 'b'
+  |/
+  o  0: b173517d0057 'a'
+  
+  $ hg rebase -r . -d 2
+  rebasing 4:daf7dfc139cb "a/a" (tip)
+  saved backup bundle to $TESTTMP/repo1/repo2/.hg/strip-backup/daf7dfc139cb-fdbfcf4f-rebase.hg
+
+  $ hg tglog
+  @  4: c6ad37a4f250 'a/a'
+  |
+  | o  3: 844a7de3e617 'c'
+  | |
+  o |  2: 09c044d2cb43 'd'
+  | |
+  o |  1: fc055c3b4d33 'b'
+  |/
+  o  0: b173517d0057 'a'
+  
+  $ echo foo > foo
+  $ hg ci -Aqm "added foo"
+  $ hg up '.^'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo bar > bar
+  $ hg ci -Aqm "added bar"
+  $ hg rm a/a
+  $ echo a > a
+  $ hg ci -Aqm "added a back!"
+  $ hg tglog
+  @  7: 855e9797387e 'added a back!'
+  |
+  o  6: d14530e5e3e6 'added bar'
+  |
+  | o  5: 9b94b9373deb 'added foo'
+  |/
+  o  4: c6ad37a4f250 'a/a'
+  |
+  | o  3: 844a7de3e617 'c'
+  | |
+  o |  2: 09c044d2cb43 'd'
+  | |
+  o |  1: fc055c3b4d33 'b'
+  |/
+  o  0: b173517d0057 'a'
+  
+  $ hg rebase -r . -d 5
+  rebasing 7:855e9797387e "added a back!" (tip)
+  saved backup bundle to $TESTTMP/repo1/repo2/.hg/strip-backup/855e9797387e-81ee4c5d-rebase.hg
+
+  $ hg tglog
+  @  7: bb3f02be2688 'added a back!'
+  |
+  | o  6: d14530e5e3e6 'added bar'
+  | |
+  o |  5: 9b94b9373deb 'added foo'
+  |/
+  o  4: c6ad37a4f250 'a/a'
+  |
+  | o  3: 844a7de3e617 'c'
+  | |
+  o |  2: 09c044d2cb43 'd'
+  | |
+  o |  1: fc055c3b4d33 'b'
+  |/
+  o  0: b173517d0057 'a'
+  
+
+  $ cd ..
+
 Test dry-run rebasing
+
   $ hg init repo3
   $ cd repo3
   $ echo a>a
@@ -325,6 +410,25 @@
   hit a merge conflict
   [1]
 
+In-memory rebase that fails due to merge conflicts
+
+  $ hg rebase -s 2 -d 7
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  rebasing 4:e860deea161a "e"
+  merging e
+  transaction abort!
+  rollback completed
+  hit merge conflicts; re-running rebase without in-memory merge
+  rebase aborted
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  rebasing 4:e860deea161a "e"
+  merging e
+  warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
 ==========================
 Test for --confirm option|
 ==========================
@@ -509,3 +613,31 @@
   o  0:cb9a9f314b8b test
      a
   
+#if execbit
+
+Test a metadata-only in-memory merge
+  $ cd $TESTTMP
+  $ hg init no_exception
+  $ cd no_exception
+# Produce the following graph:
+#   o  'add +x to foo.txt'
+#   | o  r1  (adds bar.txt, just for something to rebase to)
+#   |/
+#   o  r0   (adds foo.txt, no +x)
+  $ echo hi > foo.txt
+  $ hg ci -qAm r0
+  $ echo hi > bar.txt
+  $ hg ci -qAm r1
+  $ hg co -qr ".^"
+  $ chmod +x foo.txt
+  $ hg ci -qAm 'add +x to foo.txt'
+issue5960: this was raising an AttributeError exception
+  $ hg rebase -r . -d 1
+  rebasing 2:539b93e77479 "add +x to foo.txt" (tip)
+  saved backup bundle to $TESTTMP/no_exception/.hg/strip-backup/*.hg (glob)
+  $ hg diff -c tip
+  diff --git a/foo.txt b/foo.txt
+  old mode 100644
+  new mode 100755
+
+#endif
--- a/tests/test-rebase-named-branches.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-named-branches.t	Wed Sep 26 20:33:09 2018 +0900
@@ -16,7 +16,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-rebase-newancestor.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-newancestor.t	Wed Sep 26 20:33:09 2018 +0900
@@ -133,7 +133,8 @@
   note: rebase of 1:1d1a643d390e created no changes to commit
   rebasing 2:ec2c14fb2984 "dev: f-dev stuff"
   rebasing 4:4b019212aaf6 "dev: merge default"
-  other [source] changed f-default which local [dest] deleted
+  file 'f-default' was deleted in local [dest] but was modified in other [source].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
   rebasing 6:9455ee510502 "dev: merge default"
   saved backup bundle to $TESTTMP/ancestor-merge/.hg/strip-backup/1d1a643d390e-43e9e04b-rebase.hg
@@ -162,7 +163,8 @@
   > EOF
   rebasing 2:ec2c14fb2984 "dev: f-dev stuff"
   rebasing 4:4b019212aaf6 "dev: merge default"
-  other [source] changed f-default which local [dest] deleted
+  file 'f-default' was deleted in local [dest] but was modified in other [source].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
   rebasing 6:9455ee510502 "dev: merge default"
   saved backup bundle to $TESTTMP/ancestor-merge-2/.hg/strip-backup/ec2c14fb2984-62d0b222-rebase.hg
--- a/tests/test-rebase-obsolete.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-obsolete.t	Wed Sep 26 20:33:09 2018 +0900
@@ -15,6 +15,7 @@
   > [extensions]
   > rebase=
   > drawdag=$TESTDIR/drawdag.py
+  > strip=
   > EOF
 
 Setup rebase canonical repo
@@ -26,7 +27,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -574,7 +575,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 3
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1122,6 +1123,23 @@
   o  0:b173517d0057 a
   
   $ hg strip -r 8:
+  $ hg log -G -r 'a'::
+  *  7:1143e9adc121 f
+  |
+  | o  6:d60ebfa0f1cb e
+  | |
+  | o  5:027ad6c5830d d'
+  | |
+  x |  4:76be324c128b d (rewritten using replace as 5:027ad6c5830d)
+  |/
+  o  3:a82ac2b38757 c
+  |
+  | o  2:630d7c95eff7 x
+  | |
+  o |  1:488e1b7e7341 b
+  |/
+  o  0:b173517d0057 a
+  
 
 If the rebase set has an obsolete (d) with a successor (d') outside the rebase
 set and none in destination, we still get the divergence warning.
@@ -1493,6 +1511,26 @@
   
   $ cd ..
 
+Rebase merge where extinct node has successor that is not an ancestor of
+destination
+
+  $ hg init extinct-with-succ-not-in-dest
+  $ cd extinct-with-succ-not-in-dest
+
+  $ hg debugdrawdag <<EOF
+  > E C # replace: C -> E
+  > | |
+  > D B
+  > |/
+  > A
+  > EOF
+
+  $ hg rebase -d D -s B
+  rebasing 1:112478962961 "B" (B)
+  note: not rebasing 3:26805aba1e60 "C" (C) and its descendants as this would cause divergence
+
+  $ cd ..
+
   $ hg init p2-succ-in-dest-c
   $ cd p2-succ-in-dest-c
 
@@ -1788,3 +1826,312 @@
   |
   o  0:426bada5c675 A
   
+====================
+Test --stop option |
+====================
+  $ cd ..
+  $ hg init rbstop
+  $ cd rbstop
+  $ echo a>a
+  $ hg ci -Aqma
+  $ echo b>b
+  $ hg ci -Aqmb
+  $ echo c>c
+  $ hg ci -Aqmc
+  $ echo d>d
+  $ hg ci -Aqmd
+  $ hg up 0 -q
+  $ echo f>f
+  $ hg ci -Aqmf
+  $ echo D>d
+  $ hg ci -Aqm "conflict with d"
+  $ hg up 3 -q
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  o  5:00bfc9898aeb test
+  |  conflict with d
+  |
+  o  4:dafd40200f93 test
+  |  f
+  |
+  | @  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  | |  c
+  | |
+  | o  1:d2ae7f538514 test
+  |/   b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+  $ hg rebase -s 1 -d 5
+  rebasing 1:d2ae7f538514 "b"
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  merging d
+  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg rebase --stop
+  1 new orphan changesets
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  o  7:7fffad344617 test
+  |  c
+  |
+  o  6:b15528633407 test
+  |  b
+  |
+  o  5:00bfc9898aeb test
+  |  conflict with d
+  |
+  o  4:dafd40200f93 test
+  |  f
+  |
+  | @  3:055a42cdd887 test
+  | |  d
+  | |
+  | x  2:177f92b77385 test
+  | |  c
+  | |
+  | x  1:d2ae7f538514 test
+  |/   b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+Test it aborts if unstable csets is not allowed:
+===============================================
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution.allowunstable=False
+  > EOF
+
+  $ hg strip 6 --no-backup -q
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  o  5:00bfc9898aeb test
+  |  conflict with d
+  |
+  o  4:dafd40200f93 test
+  |  f
+  |
+  | @  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  | |  c
+  | |
+  | o  1:d2ae7f538514 test
+  |/   b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+  $ hg rebase -s 1 -d 5
+  rebasing 1:d2ae7f538514 "b"
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  merging d
+  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg rebase --stop
+  abort: cannot remove original changesets with unrebased descendants
+  (either enable obsmarkers to allow unstable revisions or use --keep to keep original changesets)
+  [255]
+  $ hg rebase --abort
+  saved backup bundle to $TESTTMP/rbstop/.hg/strip-backup/b15528633407-6eb72b6f-backup.hg
+  rebase aborted
+
+Test --stop when --keep is passed:
+==================================
+  $ hg rebase -s 1 -d 5 --keep
+  rebasing 1:d2ae7f538514 "b"
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  merging d
+  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg rebase --stop
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  o  7:7fffad344617 test
+  |  c
+  |
+  o  6:b15528633407 test
+  |  b
+  |
+  o  5:00bfc9898aeb test
+  |  conflict with d
+  |
+  o  4:dafd40200f93 test
+  |  f
+  |
+  | @  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  | |  c
+  | |
+  | o  1:d2ae7f538514 test
+  |/   b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+Test --stop aborts when --collapse was passed:
+=============================================
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution.allowunstable=True
+  > EOF
+
+  $ hg strip 6
+  saved backup bundle to $TESTTMP/rbstop/.hg/strip-backup/b15528633407-6eb72b6f-backup.hg
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  o  5:00bfc9898aeb test
+  |  conflict with d
+  |
+  o  4:dafd40200f93 test
+  |  f
+  |
+  | @  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  | |  c
+  | |
+  | o  1:d2ae7f538514 test
+  |/   b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+  $ hg rebase -s 1 -d 5 --collapse -m "collapsed b c d"
+  rebasing 1:d2ae7f538514 "b"
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  merging d
+  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg rebase --stop
+  abort: cannot stop in --collapse session
+  [255]
+  $ hg rebase --abort
+  rebase aborted
+  $ hg diff
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  o  5:00bfc9898aeb test
+  |  conflict with d
+  |
+  o  4:dafd40200f93 test
+  |  f
+  |
+  | @  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  | |  c
+  | |
+  | o  1:d2ae7f538514 test
+  |/   b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+Test --stop raise errors with conflicting options:
+=================================================
+  $ hg rebase -s 3 -d 5
+  rebasing 3:055a42cdd887 "d"
+  merging d
+  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg rebase --stop --dry-run
+  abort: cannot specify both --dry-run and --stop
+  [255]
+
+  $ hg rebase -s 3 -d 5
+  abort: rebase in progress
+  (use 'hg rebase --continue' or 'hg rebase --abort')
+  [255]
+  $ hg rebase --stop --continue
+  abort: cannot use --stop with --continue
+  [255]
+
+Test --stop moves bookmarks of original revisions to new rebased nodes:
+======================================================================
+  $ cd ..
+  $ hg init repo
+  $ cd repo
+
+  $ echo a > a
+  $ hg ci -Am A
+  adding a
+
+  $ echo b > b
+  $ hg ci -Am B
+  adding b
+  $ hg book X
+  $ hg book Y
+
+  $ echo c > c
+  $ hg ci -Am C
+  adding c
+  $ hg book Z
+
+  $ echo d > d
+  $ hg ci -Am D
+  adding d
+
+  $ hg up 0 -q
+  $ echo e > e
+  $ hg ci -Am E
+  adding e
+  created new head
+
+  $ echo doubt > d
+  $ hg ci -Am "conflict with d"
+  adding d
+
+  $ hg log -GT "{rev}: {node|short} '{desc}' bookmarks: {bookmarks}\n"
+  @  5: 39adf30bc1be 'conflict with d' bookmarks:
+  |
+  o  4: 9c1e55f411b6 'E' bookmarks:
+  |
+  | o  3: 67a385d4e6f2 'D' bookmarks: Z
+  | |
+  | o  2: 49cb3485fa0c 'C' bookmarks: Y
+  | |
+  | o  1: 6c81ed0049f8 'B' bookmarks: X
+  |/
+  o  0: 1994f17a630e 'A' bookmarks:
+  
+  $ hg rebase -s 1 -d 5
+  rebasing 1:6c81ed0049f8 "B" (X)
+  rebasing 2:49cb3485fa0c "C" (Y)
+  rebasing 3:67a385d4e6f2 "D" (Z)
+  merging d
+  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ hg rebase --stop
+  1 new orphan changesets
+  $ hg log -GT "{rev}: {node|short} '{desc}' bookmarks: {bookmarks}\n"
+  o  7: 9c86c650b686 'C' bookmarks: Y
+  |
+  o  6: 9b87b54e5fd8 'B' bookmarks: X
+  |
+  @  5: 39adf30bc1be 'conflict with d' bookmarks:
+  |
+  o  4: 9c1e55f411b6 'E' bookmarks:
+  |
+  | *  3: 67a385d4e6f2 'D' bookmarks: Z
+  | |
+  | x  2: 49cb3485fa0c 'C' bookmarks:
+  | |
+  | x  1: 6c81ed0049f8 'B' bookmarks:
+  |/
+  o  0: 1994f17a630e 'A' bookmarks:
+  
--- a/tests/test-rebase-parameters.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-parameters.t	Wed Sep 26 20:33:09 2018 +0900
@@ -17,7 +17,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -61,7 +61,7 @@
   [1]
 
   $ hg rebase --continue --abort
-  abort: cannot use both abort and continue
+  abort: cannot use --abort with --continue
   [255]
 
   $ hg rebase --continue --collapse
--- a/tests/test-rebase-partial.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-partial.t	Wed Sep 26 20:33:09 2018 +0900
@@ -15,7 +15,7 @@
   > EOF
 
   $ rebasewithdag() {
-  >   N=`$PYTHON -c "print($N+1)"`
+  >   N=`"$PYTHON" -c "print($N+1)"`
   >   hg init repo$N && cd repo$N
   >   hg debugdrawdag
   >   hg rebase "$@" > _rebasetmp
--- a/tests/test-rebase-scenario-global.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebase-scenario-global.t	Wed Sep 26 20:33:09 2018 +0900
@@ -18,7 +18,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
-  new changesets cd010b8cd998:02de42196ebe
+  new changesets cd010b8cd998:02de42196ebe (8 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -421,7 +421,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 9 files (+2 heads)
-  new changesets 9ae2ed22e576:479ddb54a924
+  new changesets 9ae2ed22e576:479ddb54a924 (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg tglog
   o  8: 479ddb54a924 'I'
--- a/tests/test-rebuildstate.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rebuildstate.t	Wed Sep 26 20:33:09 2018 +0900
@@ -47,14 +47,14 @@
 
 state dump after
 
-  $ hg debugstate --nodates | sort
+  $ hg debugstate --no-dates | sort
   n   0         -1 unset               bar
   n   0         -1 unset               foo
 
   $ hg debugadddrop --normal-lookup file1 file2
   $ hg debugadddrop --drop bar
   $ hg debugadddrop --drop
-  $ hg debugstate --nodates
+  $ hg debugstate --no-dates
   n   0         -1 unset               file1
   n   0         -1 unset               file2
   n   0         -1 unset               foo
@@ -78,13 +78,13 @@
   ? baz
   C foo
   $ hg debugadddrop --normal-lookup baz
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   r   0          0 * bar (glob)
   n   0         -1 * baz (glob)
   n 644          0 * foo (glob)
   a   0         -1 * qux (glob)
   $ hg debugrebuilddirstate --minimal
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   r   0          0 * bar (glob)
   n 644          0 * foo (glob)
   a   0         -1 * qux (glob)
@@ -104,16 +104,16 @@
   R bar
   ? baz
   C foo
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   r   0          0 * bar (glob)
   n 644          0 * foo (glob)
   a   0         -1 * qux (glob)
   $ hg debugadddrop --drop foo
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   r   0          0 * bar (glob)
   a   0         -1 * qux (glob)
   $ hg debugrebuilddirstate --minimal
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   r   0          0 * bar (glob)
   n   0         -1 * foo (glob)
   a   0         -1 * qux (glob)
--- a/tests/test-relink.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-relink.t	Wed Sep 26 20:33:09 2018 +0900
@@ -49,7 +49,7 @@
 
 Test files are read in binary mode
 
-  $ $PYTHON -c "open('.hg/store/data/dummy.i', 'wb').write(b'a\r\nb\n')"
+  $ "$PYTHON" -c "open('.hg/store/data/dummy.i', 'wb').write(b'a\r\nb\n')"
   $ cd ..
 
 
@@ -68,7 +68,7 @@
   $ echo b >> b
   $ hg ci -m changeb
   created new head
-  $ $PYTHON -c "open('.hg/store/data/dummy.i', 'wb').write(b'a\nb\r\n')"
+  $ "$PYTHON" -c "open('.hg/store/data/dummy.i', 'wb').write(b'a\nb\r\n')"
 
 
 relink
@@ -98,9 +98,9 @@
 
 check hardlinks
 
-  $ $PYTHON arelinked.py repo/.hg/store/data/a.i clone/.hg/store/data/a.i
+  $ "$PYTHON" arelinked.py repo/.hg/store/data/a.i clone/.hg/store/data/a.i
   repo/.hg/store/data/a.i == clone/.hg/store/data/a.i
-  $ $PYTHON arelinked.py repo/.hg/store/data/b.i clone/.hg/store/data/b.i
+  $ "$PYTHON" arelinked.py repo/.hg/store/data/b.i clone/.hg/store/data/b.i
   repo/.hg/store/data/b.i != clone/.hg/store/data/b.i
 
 #endif
--- a/tests/test-remove.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-remove.t	Wed Sep 26 20:33:09 2018 +0900
@@ -520,6 +520,14 @@
   deleting [===========================================>] 1/1\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   removing a
+  $ hg remove a -nv --color debug
+  \r (no-eol) (esc)
+  deleting [===========================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  deleting [===========================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  [addremove.removed ui.status|removing a]
   $ hg diff
 
   $ cat >> .hg/hgrc <<EOF
--- a/tests/test-removeemptydirs.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-removeemptydirs.t	Wed Sep 26 20:33:09 2018 +0900
@@ -87,8 +87,8 @@
   $ hg co -qr 'desc(first_rebase_source)'
   $ cd $TESTTMP/hgrebase/somedir
   $ hg --config extensions.rebase= rebase -qr . -d 'desc(first_rebase_dest)'
-  current directory was removed
-  (consider changing to repo root: $TESTTMP/hgrebase)
+  current directory was removed (rmcwd !)
+  (consider changing to repo root: $TESTTMP/hgrebase) (rmcwd !)
   $ cd $TESTTMP/hgrebase/somedir
 (The current node is the rebased first_rebase_source on top of
 first_rebase_dest)
@@ -174,7 +174,9 @@
 
 Histedit doing 'pick, pick, fold':
 
-  $ hg histedit --commands /dev/stdin <<EOF
+#if rmcwd
+
+  $ hg histedit --commands - <<EOF
   > pick 6274c77c93c3 1 add bar
   > pick ff70a87b588f 0 add foo
   > fold 9992bb0ac0db 2 add baz
@@ -196,6 +198,25 @@
   1:5c806432464a add foo
   0:d17db4b0303a add bar
 
+#else
+
+  $ cd $TESTTMP/issue5826_withrm
+
+  $ hg histedit --commands - <<EOF
+  > pick 6274c77c93c3 1 add bar
+  > pick ff70a87b588f 0 add foo
+  > fold 9992bb0ac0db 2 add baz
+  > EOF
+  saved backup bundle to $TESTTMP/issue5826_withrm/.hg/strip-backup/5c806432464a-cd4c8d86-histedit.hg
+
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  1:b9eddaa97cbc add foo
+  ***
+  add baz
+  0:d17db4b0303a add bar
+
+#endif
+
 Now test that again with experimental.removeemptydirs=false:
   $ hg init issue5826_norm
   $ cd issue5826_norm
@@ -227,7 +248,7 @@
 
 Histedit doing 'pick, pick, fold':
 
-  $ hg histedit --commands /dev/stdin <<EOF
+  $ hg histedit --commands - <<EOF
   > pick 6274c77c93c3 1 add bar
   > pick ff70a87b588f 0 add foo
   > fold 9992bb0ac0db 2 add baz
@@ -275,6 +296,9 @@
   > y
   > a
   > EOF
+
+The split succeeds on no-rmcwd platforms, which alters the rest of the tests
+#if rmcwd
   $ cat ../split_commands | hg split
   current directory was removed
   (consider changing to repo root: $TESTTMP/hgsplit)
@@ -292,6 +316,7 @@
   
   abort: $ENOENT$
   [255]
+#endif
 
 Let's try that again without the rmdir
   $ cd $TESTTMP/hgsplit/somedir
--- a/tests/test-rename-merge2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rename-merge2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -3,7 +3,7 @@
   $ cd t
   $ cat <<EOF > merge
   > import sys, os
-  > f = open(sys.argv[1], "wb")
+  > f = open(sys.argv[1], "w")
   > f.write("merge %s %s %s" % (sys.argv[1], sys.argv[2], sys.argv[3]))
   > f.close()
   > EOF
@@ -47,7 +47,7 @@
   >     echo "--------------"
   >     echo "test L:$1 R:$2 W:$3 - $4"
   >     echo "--------------"
-  >     hg merge -y --debug --traceback --tool="$PYTHON ../merge"
+  >     hg merge -y --debug --traceback --tool="\"$PYTHON\" ../merge"
   > 
   >     echo "--------------"
   >     hg status -camC -X rev
@@ -692,7 +692,8 @@
   starting 4 threads for background file closing (?)
    a: prompt deleted/changed -> m (premerge)
   picked tool ':prompt' for a (binary False symlink False changedelete True)
-  other [merge rev] changed a which local [working copy] deleted
+  file 'a' was deleted in local [working copy] but was modified in other [merge rev].
+  What do you want to do?
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
    b: both created -> m (premerge)
   picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob)
@@ -737,7 +738,8 @@
   starting 4 threads for background file closing (?)
    a: prompt changed/deleted -> m (premerge)
   picked tool ':prompt' for a (binary False symlink False changedelete True)
-  local [working copy] changed a which other [merge rev] deleted
+  file 'a' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
    b: both created -> m (premerge)
   picked tool '* ../merge' for b (binary False symlink False changedelete False) (glob)
--- a/tests/test-rename.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rename.t	Wed Sep 26 20:33:09 2018 +0900
@@ -71,6 +71,7 @@
 
   $ hg rename --after d1/a dummy
   d1/a: not recording move - dummy does not exist
+  [1]
 
 move a single file to an existing directory
 
@@ -266,8 +267,9 @@
 
   $ hg rename d1/* d2
   d2/b: not overwriting - file already committed
-  (hg rename --force to replace the file by recording a rename)
+  ('hg rename --force' to replace the file by recording a rename)
   moving d1/d11/a1 to d2/d11/a1
+  [1]
   $ hg status -C
   A d2/a
     d1/a
@@ -338,6 +340,7 @@
   d1/b: not recording move - d2/d21/b does not exist
   d1/ba: not recording move - d2/d21/ba does not exist
   moving d1/d11/a1 to d2/d21/a1
+  [1]
   $ hg status -C
   A d2/d21/a
     d1/a
@@ -371,7 +374,8 @@
   $ echo "ca" > d1/ca
   $ hg rename d1/ba d1/ca
   d1/ca: not overwriting - file exists
-  (hg rename --after to record the rename)
+  ('hg rename --after' to record the rename)
+  [1]
   $ hg status -C
   ? d1/ca
   $ hg update -C
@@ -395,7 +399,8 @@
   $ ln -s ba d1/ca
   $ hg rename --traceback d1/ba d1/ca
   d1/ca: not overwriting - file exists
-  (hg rename --after to record the rename)
+  ('hg rename --after' to record the rename)
+  [1]
   $ hg status -C
   ? d1/ca
   $ hg update -C
@@ -421,6 +426,7 @@
   $ hg rename d1/* d2/* d3
   moving d1/d11/a1 to d3/d11/a1
   d3/b: not overwriting - d2/b collides with d1/b
+  [1]
   $ hg status -C
   A d3/a
     d1/a
--- a/tests/test-repair-strip.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-repair-strip.t	Wed Sep 26 20:33:09 2018 +0900
@@ -21,7 +21,7 @@
   >   hg verify
   >   echo % journal contents
   >   if [ -f .hg/store/journal ]; then
-  >       cat .hg/store/journal | $PYTHON $TESTTMP/dumpjournal.py
+  >       cat .hg/store/journal | "$PYTHON" $TESTTMP/dumpjournal.py
   >   else
   >       echo "(no journal)"
   >   fi
@@ -63,7 +63,7 @@
    (expected 1)
    b@?: 736c29771fba not in manifests
   warning: orphan data file 'data/c.i'
-  2 files, 2 changesets, 3 total revisions
+  checked 2 changesets with 3 changes to 2 files
   2 warnings encountered!
   2 integrity errors encountered!
   % journal contents
@@ -76,7 +76,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
   $ teststrip 0 2 r .hg/store/data/b.i
   % before update 0, strip 2
   changeset:   0:cb9a9f314b8b
@@ -90,7 +90,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 4 changesets, 4 total revisions
+  checked 4 changesets with 4 changes to 3 files
   % journal contents
   (no journal)
   $ teststrip 0 2 w .hg/store/00manifest.i
@@ -120,7 +120,7 @@
    b@?: rev 1 points to nonexistent changeset 2
    (expected 1)
    c@?: rev 0 points to nonexistent changeset 3
-  3 files, 2 changesets, 4 total revisions
+  checked 2 changesets with 4 changes to 3 files
   1 warnings encountered!
   7 integrity errors encountered!
   (first damaged changeset appears to be 3)
@@ -134,6 +134,6 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
 
   $ cd ..
--- a/tests/test-resolve.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-resolve.t	Wed Sep 26 20:33:09 2018 +0900
@@ -67,6 +67,9 @@
   $ hg resolve -l
   R file1
   U file2
+  $ hg resolve --re-merge filez file2
+  arguments do not match paths that need resolving
+  (try: hg resolve --re-merge path:filez path:file2)
   $ hg resolve -m filez file2
   arguments do not match paths that need resolving
   (try: hg resolve -m path:filez path:file2)
@@ -373,4 +376,241 @@
 
   $ hg resolve -l
 
+resolve -m can be configured to look for remaining conflict markers
+  $ hg up -qC 2
+  $ hg merge -q --tool=internal:merge 1
+  warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
+  warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
+  [1]
+  $ hg resolve -l
+  U file1
+  U file2
+  $ echo 'remove markers' > file1
+  $ hg --config commands.resolve.mark-check=abort resolve -m
+  warning: the following files still have conflict markers:
+    file2
+  abort: conflict markers detected
+  (use --all to mark anyway)
+  [255]
+  $ hg resolve -l
+  U file1
+  U file2
+Try with --all from the hint
+  $ hg --config commands.resolve.mark-check=abort resolve -m --all
+  warning: the following files still have conflict markers:
+    file2
+  (no more unresolved files)
+  $ hg resolve -l
+  R file1
+  R file2
+Test option value 'warn'
+  $ hg resolve --unmark
+  $ hg resolve -l
+  U file1
+  U file2
+  $ hg --config commands.resolve.mark-check=warn resolve -m
+  warning: the following files still have conflict markers:
+    file2
+  (no more unresolved files)
+  $ hg resolve -l
+  R file1
+  R file2
+If the file is already marked as resolved, we don't warn about it
+  $ hg resolve --unmark file1
+  $ hg resolve -l
+  U file1
+  R file2
+  $ hg --config commands.resolve.mark-check=warn resolve -m
+  (no more unresolved files)
+  $ hg resolve -l
+  R file1
+  R file2
+If the user passes an invalid value, we treat it as 'none'.
+  $ hg resolve --unmark
+  $ hg resolve -l
+  U file1
+  U file2
+  $ hg --config commands.resolve.mark-check=nope resolve -m
+  (no more unresolved files)
+  $ hg resolve -l
+  R file1
+  R file2
+Test explicitly setting the otion to 'none'
+  $ hg resolve --unmark
+  $ hg resolve -l
+  U file1
+  U file2
+  $ hg --config commands.resolve.mark-check=none resolve -m
+  (no more unresolved files)
+  $ hg resolve -l
+  R file1
+  R file2
+Testing the --re-merge flag
+  $ hg resolve --unmark file1
+  $ hg resolve -l
+  U file1
+  R file2
+  $ hg resolve --mark --re-merge
+  abort: too many actions specified
+  [255]
+  $ hg resolve --re-merge --all
+  merging file1
+  warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
+  [1]
+Explicit re-merge
+  $ hg resolve --unmark file1
+  $ hg resolve --config commands.resolve.explicit-re-merge=1 --all
+  abort: no action specified
+  (use --mark, --unmark, --list or --re-merge)
+  [255]
+  $ hg resolve --config commands.resolve.explicit-re-merge=1 --re-merge --all
+  merging file1
+  warning: conflicts while merging file1! (edit, then use 'hg resolve --mark')
+  [1]
+
   $ cd ..
+
+======================================================
+Test 'hg resolve' confirm config option functionality |
+======================================================
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > rebase=
+  > EOF
+
+  $ hg init repo2
+  $ cd repo2
+
+  $ echo boss > boss
+  $ hg ci -Am "add boss"
+  adding boss
+
+  $ for emp in emp1 emp2 emp3; do echo work > $emp; done;
+  $ hg ci -Aqm "added emp1 emp2 emp3"
+
+  $ hg up 0
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+
+  $ for emp in emp1 emp2 emp3; do echo nowork > $emp; done;
+  $ hg ci -Aqm "added lazy emp1 emp2 emp3"
+
+  $ hg log -GT "{rev} {node|short} {firstline(desc)}\n"
+  @  2 0acfd4a49af0 added lazy emp1 emp2 emp3
+  |
+  | o  1 f30f98a8181f added emp1 emp2 emp3
+  |/
+  o  0 88660038d466 add boss
+  
+  $ hg rebase -s 1 -d 2
+  rebasing 1:f30f98a8181f "added emp1 emp2 emp3"
+  merging emp1
+  merging emp2
+  merging emp3
+  warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
+  warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
+  warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+Test when commands.resolve.confirm config option is not set:
+===========================================================
+  $ hg resolve --all
+  merging emp1
+  merging emp2
+  merging emp3
+  warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
+  warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
+  warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
+  [1]
+
+Test when config option is set:
+==============================
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > interactive = True
+  > [commands]
+  > resolve.confirm = True
+  > EOF
+
+  $ hg resolve
+  abort: no files or directories specified
+  (use --all to re-merge all unresolved files)
+  [255]
+  $ hg resolve --all << EOF
+  > n
+  > EOF
+  re-merge all unresolved files (yn)? n
+  abort: user quit
+  [255]
+
+  $ hg resolve --all << EOF
+  > y
+  > EOF
+  re-merge all unresolved files (yn)? y
+  merging emp1
+  merging emp2
+  merging emp3
+  warning: conflicts while merging emp1! (edit, then use 'hg resolve --mark')
+  warning: conflicts while merging emp2! (edit, then use 'hg resolve --mark')
+  warning: conflicts while merging emp3! (edit, then use 'hg resolve --mark')
+  [1]
+
+Test that commands.resolve.confirm respect --mark option (only when no patterns args are given):
+===============================================================================================
+
+  $ hg resolve -m emp1
+  $ hg resolve -l
+  R emp1
+  U emp2
+  U emp3
+
+  $ hg resolve -m << EOF
+  > n
+  > EOF
+  mark all unresolved files as resolved (yn)? n
+  abort: user quit
+  [255]
+
+  $ hg resolve -m << EOF
+  > y
+  > EOF
+  mark all unresolved files as resolved (yn)? y
+  (no more unresolved files)
+  continue: hg rebase --continue
+  $ hg resolve -l
+  R emp1
+  R emp2
+  R emp3
+
+Test that commands.resolve.confirm respect --unmark option (only when no patterns args are given):
+===============================================================================================
+
+  $ hg resolve -u emp1
+
+  $ hg resolve -l
+  U emp1
+  R emp2
+  R emp3
+
+  $ hg resolve -u << EOF
+  > n
+  > EOF
+  mark all resolved files as unresolved (yn)? n
+  abort: user quit
+  [255]
+
+  $ hg resolve -m << EOF
+  > y
+  > EOF
+  mark all unresolved files as resolved (yn)? y
+  (no more unresolved files)
+  continue: hg rebase --continue
+
+  $ hg resolve -l
+  R emp1
+  R emp2
+  R emp3
+
+  $ hg rebase --abort
+  rebase aborted
+  $ cd ..
--- a/tests/test-revert-interactive.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-revert-interactive.t	Wed Sep 26 20:33:09 2018 +0900
@@ -51,11 +51,8 @@
   > n
   > n
   > EOF
-  reverting f
-  reverting folder1/g
+  remove added file folder1/i (Yn)? y
   removing folder1/i
-  reverting folder2/h
-  remove added file folder1/i (Yn)? y
   diff --git a/f b/f
   2 hunks, 2 lines changed
   examine changes to 'f'? [Ynesfdaq?] y
@@ -115,6 +112,8 @@
   2 hunks, 2 lines changed
   examine changes to 'folder2/h'? [Ynesfdaq?] n
   
+  reverting f
+  reverting folder1/g
   $ cat f
   1
   2
@@ -140,8 +139,6 @@
 Test that --interactive lift the need for --all
 
   $ echo q | hg revert -i -r 2
-  reverting folder1/g
-  reverting folder2/h
   diff --git a/folder1/g b/folder1/g
   1 hunks, 1 lines changed
   examine changes to 'folder1/g'? [Ynesfdaq?] q
@@ -197,10 +194,6 @@
   > n
   > n
   > EOF
-  reverting f
-  reverting folder1/g
-  removing folder1/i
-  reverting folder2/h
   remove added file folder1/i (Yn)? n
   diff --git a/f b/f
   2 hunks, 2 lines changed
@@ -250,6 +243,8 @@
   2 hunks, 2 lines changed
   examine changes to 'folder2/h'? [Ynesfdaq?] n
   
+  reverting f
+  reverting folder1/g
   $ cat f
   1
   2
@@ -354,7 +349,6 @@
   > y
   > e
   > EOF
-  reverting k
   diff --git a/k b/k
   1 hunks, 2 lines changed
   examine changes to 'k'? [Ynesfdaq?] y
@@ -365,6 +359,7 @@
   +2
   discard this change to 'k'? [Ynesfdaq?] e
   
+  reverting k
   $ cat k
   42
 
@@ -378,15 +373,14 @@
   $ hg revert -i <<EOF
   > n
   > EOF
-  forgetting newfile
   forget added file newfile (Yn)? n
   $ hg status
   A newfile
   $ hg revert -i <<EOF
   > y
   > EOF
+  forget added file newfile (Yn)? y
   forgetting newfile
-  forget added file newfile (Yn)? y
   $ hg status
   ? newfile
 
@@ -406,7 +400,6 @@
   > y
   > y
   > EOF
-  reverting a
   diff --git a/a b/a
   1 hunks, 1 lines changed
   examine changes to 'a'? [Ynesfdaq?] y
@@ -417,6 +410,7 @@
   \ No newline at end of file
   apply this change to 'a'? [Ynesfdaq?] y
   
+  reverting a
   $ cat a
   0
 
--- a/tests/test-revert.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-revert.t	Wed Sep 26 20:33:09 2018 +0900
@@ -129,9 +129,9 @@
 ----------------------------------
 
   $ hg revert --all -r0
-  adding a
+  forgetting z
   removing d
-  forgetting z
+  adding a
 
 revert explicitly to parent (--rev)
 -----------------------------------
@@ -283,8 +283,8 @@
   $ echo foo > newdir/newfile
   $ hg add newdir/newfile
   $ hg revert b newdir
+  forgetting newdir/newfile
   reverting b/b
-  forgetting newdir/newfile
   $ echo foobar > b/b
   $ hg revert .
   reverting b/b
@@ -368,9 +368,9 @@
   $ hg update '.^'
   1 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ hg revert -rtip -a
+  removing ignored
   adding allyour
   adding base
-  removing ignored
   $ hg status -C
   A allyour
     ignored
@@ -495,7 +495,7 @@
 
 check list of planned files
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py filelist 2
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py filelist 2
   content1_content1_content1-tracked
   content1_content1_content1-untracked
   content1_content1_content3-tracked
@@ -532,6 +532,7 @@
 
   $ cat << EOF >> dircontent.py
   > # generate a simple text view of the directory for easy comparison
+  > from __future__ import print_function
   > import os
   > files = os.listdir('.')
   > files.sort()
@@ -539,7 +540,7 @@
   >     if os.path.isdir(filename):
   >         continue
   >     content = open(filename).read()
-  >     print '%-6s %s' % (content.strip(), filename)
+  >     print('%-6s %s' % (content.strip(), filename))
   > EOF
 
 Generate appropriate repo state
@@ -550,7 +551,7 @@
 
 Generate base changeset
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 1
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 1
   $ hg addremove --similarity 0
   adding content1_content1_content1-tracked
   adding content1_content1_content1-untracked
@@ -597,7 +598,7 @@
 
 (create a simple text version of the content)
 
-  $ $PYTHON ../dircontent.py > ../content-base.txt
+  $ "$PYTHON" ../dircontent.py > ../content-base.txt
   $ cat ../content-base.txt
   content1 content1_content1_content1-tracked
   content1 content1_content1_content1-untracked
@@ -622,7 +623,7 @@
 
 Create parent changeset
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 2
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 2
   $ hg addremove --similarity 0
   removing content1_missing_content1-tracked
   removing content1_missing_content1-untracked
@@ -661,7 +662,7 @@
 
 (create a simple text version of the content)
 
-  $ $PYTHON ../dircontent.py > ../content-parent.txt
+  $ "$PYTHON" ../dircontent.py > ../content-parent.txt
   $ cat ../content-parent.txt
   content1 content1_content1_content1-tracked
   content1 content1_content1_content1-untracked
@@ -686,7 +687,7 @@
 
 Setup working directory
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 wc
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 wc
   $ hg addremove --similarity 0
   adding content1_missing_content1-tracked
   adding content1_missing_content1-untracked
@@ -754,7 +755,7 @@
 
 (create a simple text version of the content)
 
-  $ $PYTHON ../dircontent.py > ../content-wc.txt
+  $ "$PYTHON" ../dircontent.py > ../content-wc.txt
   $ cat ../content-wc.txt
   content1 content1_content1_content1-tracked
   content1 content1_content1_content1-untracked
@@ -790,35 +791,35 @@
 check revert output
 
   $ hg revert --all
-  undeleting content1_content1_content1-untracked
-  reverting content1_content1_content3-tracked
-  undeleting content1_content1_content3-untracked
-  reverting content1_content1_missing-tracked
-  undeleting content1_content1_missing-untracked
-  reverting content1_content2_content1-tracked
-  undeleting content1_content2_content1-untracked
-  undeleting content1_content2_content2-untracked
-  reverting content1_content2_content3-tracked
-  undeleting content1_content2_content3-untracked
-  reverting content1_content2_missing-tracked
-  undeleting content1_content2_missing-untracked
   forgetting content1_missing_content1-tracked
   forgetting content1_missing_content3-tracked
   forgetting content1_missing_missing-tracked
-  undeleting missing_content2_content2-untracked
-  reverting missing_content2_content3-tracked
-  undeleting missing_content2_content3-untracked
-  reverting missing_content2_missing-tracked
-  undeleting missing_content2_missing-untracked
   forgetting missing_missing_content3-tracked
   forgetting missing_missing_missing-tracked
+  reverting content1_content1_content3-tracked
+  reverting content1_content1_missing-tracked
+  reverting content1_content2_content1-tracked
+  reverting content1_content2_content3-tracked
+  reverting content1_content2_missing-tracked
+  reverting missing_content2_content3-tracked
+  reverting missing_content2_missing-tracked
+  undeleting content1_content1_content1-untracked
+  undeleting content1_content1_content3-untracked
+  undeleting content1_content1_missing-untracked
+  undeleting content1_content2_content1-untracked
+  undeleting content1_content2_content2-untracked
+  undeleting content1_content2_content3-untracked
+  undeleting content1_content2_missing-untracked
+  undeleting missing_content2_content2-untracked
+  undeleting missing_content2_content3-untracked
+  undeleting missing_content2_missing-untracked
 
 Compare resulting directory with revert target.
 
 The diff is filtered to include change only. The only difference should be
 additional `.orig` backup file when applicable.
 
-  $ $PYTHON ../dircontent.py > ../content-parent-all.txt
+  $ "$PYTHON" ../dircontent.py > ../content-parent-all.txt
   $ cd ..
   $ diff -U 0 -- content-parent.txt content-parent-all.txt | grep _
   +content3 content1_content1_content3-tracked.orig
@@ -847,35 +848,35 @@
 check revert output
 
   $ hg revert --all --rev 'desc(base)'
-  undeleting content1_content1_content1-untracked
-  reverting content1_content1_content3-tracked
-  undeleting content1_content1_content3-untracked
-  reverting content1_content1_missing-tracked
-  undeleting content1_content1_missing-untracked
-  undeleting content1_content2_content1-untracked
-  reverting content1_content2_content2-tracked
-  undeleting content1_content2_content2-untracked
-  reverting content1_content2_content3-tracked
-  undeleting content1_content2_content3-untracked
-  reverting content1_content2_missing-tracked
-  undeleting content1_content2_missing-untracked
-  adding content1_missing_content1-untracked
-  reverting content1_missing_content3-tracked
-  adding content1_missing_content3-untracked
-  reverting content1_missing_missing-tracked
-  adding content1_missing_missing-untracked
+  forgetting missing_missing_content3-tracked
+  forgetting missing_missing_missing-tracked
   removing missing_content2_content2-tracked
   removing missing_content2_content3-tracked
   removing missing_content2_missing-tracked
-  forgetting missing_missing_content3-tracked
-  forgetting missing_missing_missing-tracked
+  reverting content1_content1_content3-tracked
+  reverting content1_content1_missing-tracked
+  reverting content1_content2_content2-tracked
+  reverting content1_content2_content3-tracked
+  reverting content1_content2_missing-tracked
+  reverting content1_missing_content3-tracked
+  reverting content1_missing_missing-tracked
+  adding content1_missing_content1-untracked
+  adding content1_missing_content3-untracked
+  adding content1_missing_missing-untracked
+  undeleting content1_content1_content1-untracked
+  undeleting content1_content1_content3-untracked
+  undeleting content1_content1_missing-untracked
+  undeleting content1_content2_content1-untracked
+  undeleting content1_content2_content2-untracked
+  undeleting content1_content2_content3-untracked
+  undeleting content1_content2_missing-untracked
 
 Compare resulting directory with revert target.
 
 The diff is filtered to include change only. The only difference should be
 additional `.orig` backup file when applicable.
 
-  $ $PYTHON ../dircontent.py > ../content-base-all.txt
+  $ "$PYTHON" ../dircontent.py > ../content-base-all.txt
   $ cd ..
   $ diff -U 0 -- content-base.txt content-base-all.txt | grep _
   +content3 content1_content1_content3-tracked.orig
@@ -902,7 +903,7 @@
 revert all files individually and check the output
 (output is expected to be different than in the --all case)
 
-  $ for file in `$PYTHON $TESTDIR/generate-working-copy-states.py filelist 2`; do
+  $ for file in `"$PYTHON" $TESTDIR/generate-working-copy-states.py filelist 2`; do
   >   echo '### revert for:' $file;
   >   hg revert $file;
   >   echo
@@ -979,7 +980,7 @@
 check resulting directory against the --all run
 (There should be no difference)
 
-  $ $PYTHON ../dircontent.py > ../content-parent-explicit.txt
+  $ "$PYTHON" ../dircontent.py > ../content-parent-explicit.txt
   $ cd ..
   $ diff -U 0 -- content-parent-all.txt content-parent-explicit.txt | grep _
   [1]
@@ -995,7 +996,7 @@
 revert all files individually and check the output
 (output is expected to be different than in the --all case)
 
-  $ for file in `$PYTHON $TESTDIR/generate-working-copy-states.py filelist 2`; do
+  $ for file in `"$PYTHON" $TESTDIR/generate-working-copy-states.py filelist 2`; do
   >   echo '### revert for:' $file;
   >   hg revert $file --rev 'desc(base)';
   >   echo
@@ -1072,7 +1073,7 @@
 check resulting directory against the --all run
 (There should be no difference)
 
-  $ $PYTHON ../dircontent.py > ../content-base-explicit.txt
+  $ "$PYTHON" ../dircontent.py > ../content-base-explicit.txt
   $ cd ..
   $ diff -U 0 -- content-base-all.txt content-base-explicit.txt | grep _
   [1]
@@ -1120,8 +1121,8 @@
   M A
   A B
   $ hg revert --rev 1 --all
+  removing B
   reverting A
-  removing B
   $ hg status --rev 1
 
 From the other parents
@@ -1140,8 +1141,8 @@
   M A
   A B
   $ hg revert --rev 1 --all
+  removing B
   reverting A
-  removing B
   $ hg status --rev 1
 
   $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-revisions.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,45 @@
+  $ hg init repo
+  $ cd repo
+
+  $ echo 0 > a
+  $ hg ci -qAm 0
+  $ for i in 5 8 14 43 167; do
+  >   hg up -q 0
+  >   echo $i > a
+  >   hg ci -qm $i
+  > done
+  $ cat <<EOF >> .hg/hgrc
+  > [alias]
+  > l = log -T '{rev}:{shortest(node,1)}\n'
+  > EOF
+
+  $ hg l
+  5:00f
+  4:7ba5d
+  3:7ba57
+  2:72
+  1:9
+  0:b
+  $ cat <<EOF >> .hg/hgrc
+  > [experimental]
+  > revisions.disambiguatewithin=not 4
+  > EOF
+  $ hg l
+  5:0
+  4:7ba5d
+  3:7b
+  2:72
+  1:9
+  0:b
+9 was unambiguous and still is
+  $ hg l -r 9
+  1:9
+7 was ambiguous and still is
+  $ hg l -r 7
+  abort: 00changelog.i@7: ambiguous identifier!
+  [255]
+7b is no longer ambiguous
+  $ hg l -r 7b
+  3:7b
+
+  $ cd ..
--- a/tests/test-revlog-ancestry.py.out	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-revlog-ancestry.py.out	Wed Sep 26 20:33:09 2018 +0900
@@ -1,15 +1,15 @@
 Ancestors of 5
 4 2 0 
 Ancestors of 6 and 5
-3 4 2 1 0 
+4 3 2 1 0 
 Ancestors of 5 and 4
 4 2 0 
 Ancestors of 7, stop at 6
 6 
 Ancestors of 7, including revs
-7 6 5 3 4 2 1 0 
+7 6 5 4 3 2 1 0 
 Ancestors of 7, 5 and 3, including revs
-7 5 3 6 4 2 1 0 
+7 6 5 4 3 2 1 0 
 
 Descendants of 5
 7 8 
--- a/tests/test-revlog-raw.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-revlog-raw.py	Wed Sep 26 20:33:09 2018 +0900
@@ -20,7 +20,7 @@
 
 # The test wants to control whether to use delta explicitly, based on
 # "storedeltachains".
-revlog.revlog._isgooddeltainfo = lambda self, d, textlen: self.storedeltachains
+revlog.revlog._isgooddeltainfo = lambda self, d, textlen: self._storedeltachains
 
 def abort(msg):
     print('abort: %s' % msg)
@@ -78,7 +78,7 @@
     else:
         flags = revlog.REVIDX_DEFAULT_FLAGS
     # Change storedeltachains temporarily, to override revlog's delta decision
-    rlog.storedeltachains = isdelta
+    rlog._storedeltachains = isdelta
     try:
         rlog.addrevision(text, tr, nextrev, p1, p2, flags=flags)
         return nextrev
@@ -86,7 +86,7 @@
         abort('rev %d: failed to append: %s' % (nextrev, ex))
     finally:
         # Restore storedeltachains. It is always True, see revlog.__init__
-        rlog.storedeltachains = True
+        rlog._storedeltachains = True
 
 def addgroupcopy(rlog, tr, destname=b'_destrevlog.i', optimaldelta=True):
     '''Copy revlog to destname using revlog.addgroup. Return the copied revlog.
--- a/tests/test-revlog.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-revlog.t	Wed Sep 26 20:33:09 2018 +0900
@@ -39,9 +39,14 @@
   ... Joa3dYtcYYYBAQ8Qr4OqZAYRICPTSr5WKd/42rV36d+8/VmrNpv7NP1jQAXrQE4BqQUARngwVA=="""
   ... .decode("base64").decode("zlib"))
 
-  $ hg debugindex a.i
+  $ hg debugrevlogindex a.i
      rev linkrev nodeid       p1           p2
        0       2 99e0332bd498 000000000000 000000000000
        1       3 6674f57a23d8 99e0332bd498 000000000000
-  $ hg debugdata a.i 1 2>&1 | egrep 'Error:.*decoded'
-  (mercurial\.\w+\.mpatch\.)?mpatchError: patch cannot be decoded (re)
+
+  >>> from mercurial import revlog, vfs
+  >>> tvfs = vfs.vfs(b'.')
+  >>> tvfs.options = {b'revlogv1': True}
+  >>> rl = revlog.revlog(tvfs, b'a.i')
+  >>> rl.revision(1)
+  mpatchError('patch cannot be decoded',)
--- a/tests/test-revset.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-revset.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1793,6 +1793,16 @@
 
 Test hexadecimal revision
   $ log 'id(2)'
+  $ log 'id(5)'
+  2
+  $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'id(x5)'
+  2
+  $ hg --config experimental.revisions.prefixhexnode=yes log --template '{rev}\n' -r 'x5'
+  2
+  $ 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'
+  abort: 00changelog.i@: ambiguous identifier!
+  [255]
   $ log 'id(23268)'
   4
   $ log 'id(2785f51eece)'
--- a/tests/test-revset2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-revset2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -346,7 +346,7 @@
 test ',' in `_list`
   $ log '0,1'
   hg: parse error: can't use a list in this context
-  (see hg help "revsets.x or y")
+  (see 'hg help "revsets.x or y"')
   [255]
   $ try '0,1,2'
   (list
@@ -354,7 +354,7 @@
     (symbol '1')
     (symbol '2'))
   hg: parse error: can't use a list in this context
-  (see hg help "revsets.x or y")
+  (see 'hg help "revsets.x or y"')
   [255]
 
 test that chained `or` operations make balanced addsets
@@ -413,14 +413,14 @@
 test that chained `or` operations never eat up stack (issue4624)
 (uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
 
-  $ hg log -T '{rev}\n' -r `$PYTHON -c "print '+'.join(['0:1'] * 500)"`
+  $ hg log -T '{rev}\n' -r `"$PYTHON" -c "print '+'.join(['0:1'] * 500)"`
   0
   1
 
 test that repeated `-r` options never eat up stack (issue4565)
 (uses `-r 0::1` to avoid possible optimization at old-style parser)
 
-  $ hg log -T '{rev}\n' `$PYTHON -c "for i in range(500): print '-r 0::1 ',"`
+  $ hg log -T '{rev}\n' `"$PYTHON" -c "for i in range(500): print '-r 0::1 ',"`
   0
   1
 
@@ -1527,7 +1527,7 @@
   $ hg init problematicencoding
   $ cd problematicencoding
 
-  $ $PYTHON > setup.sh <<EOF
+  $ "$PYTHON" > setup.sh <<EOF
   > print u'''
   > echo a > text
   > hg add text
@@ -1543,7 +1543,7 @@
   $ sh < setup.sh
 
 test in problematic encoding
-  $ $PYTHON > test.sh <<EOF
+  $ "$PYTHON" > test.sh <<EOF
   > print u'''
   > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
   > echo ====
--- a/tests/test-rollback.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-rollback.t	Wed Sep 26 20:33:09 2018 +0900
@@ -9,7 +9,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   $ hg parents
   changeset:   0:1f0dee641bb7
   tag:         tip
@@ -28,7 +28,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  0 files, 0 changesets, 0 total revisions
+  checked 0 changesets with 0 changes to 0 files
   $ hg parents
   $ hg status
   A a
@@ -197,7 +197,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
 
 rollback disabled by config
   $ cat >> $HGRCPATH <<EOF
@@ -436,7 +436,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
   $ cd ..
 
@@ -461,6 +461,6 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
 
   $ cd ..
--- a/tests/test-run-tests.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-run-tests.t	Wed Sep 26 20:33:09 2018 +0900
@@ -6,7 +6,7 @@
 
 Smoke test with install
 ============
-  $ $PYTHON $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
+  $ "$PYTHON" $TESTDIR/run-tests.py $HGTEST_RUN_TESTS_PURE -l
   
   # Ran 0 tests, 0 skipped, 0 failed.
 
@@ -14,14 +14,14 @@
 =============
   $ rt()
   > {
-  >     $PYTHON $TESTDIR/run-tests.py --with-hg=`which hg` "$@"
+  >     "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` "$@"
   > }
 
 error paths
 
 #if symlink
   $ ln -s `which true` hg
-  $ $PYTHON $TESTDIR/run-tests.py --with-hg=./hg
+  $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
   warning: --with-hg should specify an hg script
   
   # Ran 0 tests, 0 skipped, 0 failed.
@@ -30,7 +30,7 @@
 
 #if execbit
   $ touch hg
-  $ $PYTHON $TESTDIR/run-tests.py --with-hg=./hg
+  $ "$PYTHON" $TESTDIR/run-tests.py --with-hg=./hg
   usage: run-tests.py [options] [tests]
   run-tests.py: error: --with-hg must specify an executable hg script
   [2]
@@ -850,7 +850,7 @@
   > EOF
   
   --- $TESTTMP/test-cases.t
-  +++ $TESTTMP/test-cases.t.a.err
+  +++ $TESTTMP/test-cases.t#a.err
   @@ -1,6 +1,7 @@
    #testcases a b
    #if a
@@ -861,7 +861,7 @@
      $ echo 2
   Accept this change? [n] .
   --- $TESTTMP/test-cases.t
-  +++ $TESTTMP/test-cases.t.b.err
+  +++ $TESTTMP/test-cases.t#b.err
   @@ -5,4 +5,5 @@
    #endif
    #if b
@@ -896,6 +896,40 @@
   ..
   # Ran 2 tests, 0 skipped, 0 failed.
 
+When using multiple dimensions of "#testcases" in .t files
+
+  $ cat > test-cases.t <<'EOF'
+  > #testcases a b
+  > #testcases c d
+  > #if a d
+  >   $ echo $TESTCASE
+  >   a#d
+  > #endif
+  > #if b c
+  >   $ echo yes
+  >   no
+  > #endif
+  > EOF
+  $ rt test-cases.t
+  ..
+  --- $TESTTMP/test-cases.t
+  +++ $TESTTMP/test-cases.t#b#c.err
+  @@ -6,5 +6,5 @@
+   #endif
+   #if b c
+     $ echo yes
+  -  no
+  +  yes
+   #endif
+  
+  ERROR: test-cases.t#b#c output changed
+  !.
+  Failed test-cases.t#b#c: output changed
+  # Ran 4 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ rm test-cases.t#b#c.err
   $ rm test-cases.t
 
 (reinstall)
@@ -1249,7 +1283,7 @@
 Add support for external test formatter
 =======================================
 
-  $ CUSTOM_TEST_RESULT=basic_test_result $PYTHON $TESTDIR/run-tests.py --with-hg=`which hg` "$@" test-success.t test-failure.t
+  $ CUSTOM_TEST_RESULT=basic_test_result "$PYTHON" $TESTDIR/run-tests.py --with-hg=`which hg` "$@" test-success.t test-failure.t
   
   # Ran 2 tests, 0 skipped, 0 failed.
   ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
@@ -1540,7 +1574,7 @@
   $ rt
   .
   --- $TESTTMP/anothertests/cases/test-cases-abc.t
-  +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+  +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
   @@ -7,7 +7,7 @@
      $ V=C
    #endif
@@ -1563,7 +1597,7 @@
   $ rt --restart
   
   --- $TESTTMP/anothertests/cases/test-cases-abc.t
-  +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+  +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
   @@ -7,7 +7,7 @@
      $ V=C
    #endif
@@ -1584,11 +1618,11 @@
 --restart works with outputdir
 
   $ mkdir output
-  $ mv test-cases-abc.t.B.err output
+  $ mv test-cases-abc.t#B.err output
   $ rt --restart --outputdir output
   
   --- $TESTTMP/anothertests/cases/test-cases-abc.t
-  +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t.B.err
+  +++ $TESTTMP/anothertests/cases/output/test-cases-abc.t#B.err
   @@ -7,7 +7,7 @@
      $ V=C
    #endif
@@ -1631,7 +1665,7 @@
   $ rt "test-cases-abc.t#B"
   
   --- $TESTTMP/anothertests/cases/test-cases-abc.t
-  +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+  +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
   @@ -7,7 +7,7 @@
      $ V=C
    #endif
@@ -1654,7 +1688,7 @@
   $ rt test-cases-abc.t#B test-cases-abc.t#C
   
   --- $TESTTMP/anothertests/cases/test-cases-abc.t
-  +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+  +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
   @@ -7,7 +7,7 @@
      $ V=C
    #endif
@@ -1677,7 +1711,7 @@
   $ rt test-cases-abc.t#B test-cases-abc.t#D
   
   --- $TESTTMP/anothertests/cases/test-cases-abc.t
-  +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+  +++ $TESTTMP/anothertests/cases/test-cases-abc.t#B.err
   @@ -7,7 +7,7 @@
      $ V=C
    #endif
@@ -1711,7 +1745,7 @@
   $ rt test-cases-advanced-cases.t
   
   --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
-  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.case-with-dashes.err
+  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
   @@ -1,3 +1,3 @@
    #testcases simple case-with-dashes casewith_-.chars
      $ echo $TESTCASE
@@ -1721,7 +1755,7 @@
   ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
   !
   --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
-  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.casewith_-.chars.err
+  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
   @@ -1,3 +1,3 @@
    #testcases simple case-with-dashes casewith_-.chars
      $ echo $TESTCASE
@@ -1739,7 +1773,7 @@
   $ rt "test-cases-advanced-cases.t#case-with-dashes"
   
   --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
-  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.case-with-dashes.err
+  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#case-with-dashes.err
   @@ -1,3 +1,3 @@
    #testcases simple case-with-dashes casewith_-.chars
      $ echo $TESTCASE
@@ -1756,7 +1790,7 @@
   $ rt "test-cases-advanced-cases.t#casewith_-.chars"
   
   --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
-  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.casewith_-.chars.err
+  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t#casewith_-.chars.err
   @@ -1,3 +1,3 @@
    #testcases simple case-with-dashes casewith_-.chars
      $ echo $TESTCASE
--- a/tests/test-serve.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-serve.t	Wed Sep 26 20:33:09 2018 +0900
@@ -79,7 +79,7 @@
   listening at http://localhost/foo/ (bound to *$LOCALIP*:HGPORT1) (glob) (?)
   % errors
 
-  $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
+  $ "$PYTHON" $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
 
 With out of bounds accesses
 
--- a/tests/test-setdiscovery.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-setdiscovery.t	Wed Sep 26 20:33:09 2018 +0900
@@ -504,9 +504,9 @@
 #if false
 generate new bundles:
   $ hg init r1
-  $ for i in `$PYTHON $TESTDIR/seq.py 101`; do hg -R r1 up -qr null && hg -R r1 branch -q b$i && hg -R r1 ci -qmb$i; done
+  $ for i in `"$PYTHON" $TESTDIR/seq.py 101`; do hg -R r1 up -qr null && hg -R r1 branch -q b$i && hg -R r1 ci -qmb$i; done
   $ hg clone -q r1 r2
-  $ for i in `$PYTHON $TESTDIR/seq.py 10`; do hg -R r1 up -qr null && hg -R r1 branch -q c$i && hg -R r1 ci -qmc$i; done
+  $ for i in `"$PYTHON" $TESTDIR/seq.py 10`; do hg -R r1 up -qr null && hg -R r1 branch -q c$i && hg -R r1 ci -qmc$i; done
   $ hg -R r2 branch -q r2change && hg -R r2 ci -qmr2change
   $ hg -R r1 bundle -qa $TESTDIR/bundles/issue4438-r1.hg
   $ hg -R r2 bundle -qa $TESTDIR/bundles/issue4438-r2.hg
--- a/tests/test-share.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-share.t	Wed Sep 26 20:33:09 2018 +0900
@@ -32,6 +32,7 @@
   [1]
   $ ls -1 ../repo1/.hg/cache
   branch2-served
+  manifestfulltextcache (reporevlogstore !)
   rbc-names-v1
   rbc-revs-v1
   tags2-visible
@@ -297,15 +298,15 @@
 
 test behavior when sharing a shared repo
 
-  $ hg share -B repo3 repo5
+  $ hg share -B repo3 missingdir/repo5
   updating working directory
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cd repo5
+  $ cd missingdir/repo5
   $ hg book
      bm1                       3:b87954705719
      bm3                       4:62f4ded848e4
      bm4                       5:92793bfc8cad
-  $ cd ..
+  $ cd ../..
 
 test what happens when an active bookmark is deleted
 
@@ -438,6 +439,29 @@
 
   $ rm -r thatdir
 
+Demonstrate buggy behavior around requirements validation
+See comment in localrepo.py:makelocalrepository() for more.
+
+  $ hg init sharenewrequires
+  $ hg share sharenewrequires shareoldrequires
+  updating working directory
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cat >> sharenewrequires/.hg/requires << EOF
+  > missing-requirement
+  > EOF
+
+We cannot open the repo with the unknown requirement
+
+  $ hg -R sharenewrequires status
+  abort: repository requires features unknown to this Mercurial: missing-requirement!
+  (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
+  [255]
+
+BUG: we don't get the same error when opening the shared repo pointing to it
+
+  $ hg -R shareoldrequires status
+
 Explicitly kill daemons to let the test exit on Windows
 
   $ killdaemons.py
--- a/tests/test-shelve.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-shelve.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1,3 +1,5 @@
+#testcases stripbased phasebased
+
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
   > mq =
@@ -9,6 +11,15 @@
   > maxbackups = 2
   > EOF
 
+#if phasebased
+
+  $ cat <<EOF >> $HGRCPATH
+  > [format]
+  > internal-phase = yes
+  > EOF
+
+#endif
+
   $ hg init repo
   $ cd repo
   $ mkdir a b
@@ -102,6 +113,7 @@
   $ ls .hg/shelve-backup
   default.hg
   default.patch
+  default.shelve
 
 checks to make sure we dont create a directory or
 hidden file while choosing a new shelve name
@@ -206,8 +218,10 @@
   $ ls .hg/shelve-backup/
   default-1.hg
   default-1.patch
+  default-1.shelve
   default.hg
   default.patch
+  default.shelve
 
 local edits should not prevent a shelved change from applying
 
@@ -250,10 +264,13 @@
   $ ls .hg/shelve-backup/
   default-01.hg
   default-01.patch
+  default-01.shelve
   default-1.hg
   default-1.patch
+  default-1.shelve
   default.hg
   default.patch
+  default.shelve
 
   $ hg unshelve
   abort: no shelved changes to apply!
@@ -314,8 +331,10 @@
   $ ls .hg/shelve-backup/
   default-01.hg
   default-01.patch
+  default-01.shelve
   wibble.hg
   wibble.patch
+  wibble.shelve
 
 cause unshelving to result in a merge with 'a' conflicting
 
@@ -361,12 +380,24 @@
 
 ensure that we have a merge with unresolved conflicts
 
+#if phasebased
+  $ hg heads -q --template '{rev}\n'
+  8
+  5
+  $ hg parents -q --template '{rev}\n'
+  8
+  5
+#endif
+
+#if stripbased
   $ hg heads -q --template '{rev}\n'
   5
   4
   $ hg parents -q --template '{rev}\n'
   4
   5
+#endif
+
   $ hg status
   M a/a
   M b.rename/b
@@ -379,11 +410,11 @@
   +++ b/a/a
   @@ -1,2 +1,6 @@
    a
-  +<<<<<<< shelve:       562f7831e574 - shelve: pending changes temporary commit
+  +<<<<<<< shelve:       2377350b6337 - shelve: pending changes temporary commit
    c
   +=======
   +a
-  +>>>>>>> working-copy: 32c69314e062 - shelve: changes to: [mq]: second.patch
+  +>>>>>>> working-copy: a68ec3400638 - shelve: changes to: [mq]: second.patch
   diff --git a/b/b b/b.rename/b
   rename from b/b
   rename to b.rename/b
@@ -409,10 +440,11 @@
   $ hg unshelve -a
   unshelve of 'default' aborted
   $ hg heads -q
-  3:2e69b451d1ea
+  [37]:2e69b451d1ea (re)
   $ hg parents
-  changeset:   3:2e69b451d1ea
+  changeset:   [37]:2e69b451d1ea (re)
   tag:         tip
+  parent:      3:509104101065 (?)
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     second
@@ -465,14 +497,15 @@
 ensure the repo is as we hope
 
   $ hg parents
-  changeset:   3:2e69b451d1ea
+  changeset:   [37]:2e69b451d1ea (re)
   tag:         tip
+  parent:      3:509104101065 (?)
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     second
   
   $ hg heads -q
-  3:2e69b451d1ea
+  [37]:2e69b451d1ea (re)
 
   $ hg status -C
   A b.rename/b
@@ -499,6 +532,15 @@
   M a/a
   $ hg revert a/a
 
+#else
+
+Dummy shelve op, to keep rev numbers aligned
+
+  $ echo foo > a/a
+  $ hg shelve -q -n dummy a/a
+  $ hg unshelve -q dummy
+  $ hg revert a/a
+
 #endif
 
 #if symlink
@@ -512,6 +554,15 @@
   M a/a
   $ hg revert a/a
 
+#else
+
+Dummy shelve op, to keep rev numbers aligned
+
+  $ echo bar > a/a
+  $ hg shelve -q -n dummy a/a
+  $ hg unshelve -q dummy
+  $ hg revert a/a
+
 #endif
 
 set up another conflict between a commit and a shelved change
@@ -532,7 +583,7 @@
   rebasing shelved changes
   merging a/a
   $ hg parents -q
-  4:33f7f61e6c5e
+  (4|13):33f7f61e6c5e (re)
   $ hg shelve -l
   default         (*)* changes to: second (glob)
   $ hg status
@@ -555,7 +606,7 @@
   merging a/a
   note: unshelved changes already existed in the working copy
   $ hg parents -q
-  4:33f7f61e6c5e
+  (4|13):33f7f61e6c5e (re)
   $ hg shelve -l
   $ hg status
   A foo/foo
@@ -592,16 +643,16 @@
 
   $ hg bookmark test
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
   $ hg shelve
   shelved as test
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
   $ hg unshelve
   unshelving change 'test'
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
 
 shelve should still work even if mq is disabled
 
@@ -611,11 +662,11 @@
   $ hg --config extensions.mq=! shelve --list
   test            (*)* changes to: create conflict (glob)
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
   $ hg --config extensions.mq=! unshelve
   unshelving change 'test'
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
 
 shelve should leave dirstate clean (issue4055)
 
@@ -635,10 +686,11 @@
   $ hg shelve
   shelved as default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
   $ hg rebase -d 6c103be8f4e4 --config extensions.rebase=
-  rebasing 2:323bfa07f744 "xyz" (tip)
+  rebasing 2:323bfa07f744 "xyz"( \(tip\))? (re)
   merging x
-  saved backup bundle to $TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-78114325-rebase.hg
+  saved backup bundle to \$TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-(78114325|7ae538ef)-rebase.hg (re)
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
@@ -764,13 +816,13 @@
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
 #if repobundlerepo
-  $ hg log -G --template '{rev}  {desc|firstline}  {author}' -R bundle://.hg/shelved/default.hg -r 'bundle()'
-  o  4  changes to: commit stuff  shelve@localhost
+  $ hg log -G --template '{rev}  {desc|firstline}  {author}' -R bundle://.hg/shelved/default.hg -r 'bundle()' --hidden
+  o  [48]  changes to: commit stuff  shelve@localhost (re)
   |
   ~
 #endif
   $ hg log -G --template '{rev}  {desc|firstline}  {author}'
-  @  3  commit stuff  test
+  @  [37]  commit stuff  test (re)
   |
   | o  2  c  test
   |/
@@ -786,6 +838,22 @@
   warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
   [1]
+
+#if phasebased
+  $ hg log -G --template '{rev}  {desc|firstline}  {author}  {date|isodate}'
+  @  9  pending changes temporary commit  shelve@localhost  2004-01-10 13:37 +0000
+  |
+  | @  8  changes to: commit stuff  shelve@localhost  1970-01-01 00:00 +0000
+  |/
+  o  7  commit stuff  test  1970-01-01 00:00 +0000
+  |
+  | o  2  c  test  1970-01-01 00:00 +0000
+  |/
+  o  0  a  test  1970-01-01 00:00 +0000
+  
+#endif
+
+#if stripbased
   $ hg log -G --template '{rev}  {desc|firstline}  {author}  {date|isodate}'
   @  5  changes to: commit stuff  shelve@localhost  1970-01-01 00:00 +0000
   |
@@ -797,15 +865,17 @@
   |/
   o  0  a  test  1970-01-01 00:00 +0000
   
+#endif
+
   $ hg st
   M f
   ? f.orig
   $ cat f
-  <<<<<<< shelve:       5f6b880e719b - shelve: pending changes temporary commit
+  <<<<<<< shelve:       d44eae5c3d33 - shelve: pending changes temporary commit
   g
   =======
   f
-  >>>>>>> working-copy: 81152db69da7 - shelve: changes to: commit stuff
+  >>>>>>> working-copy: aef214a5229c - shelve: changes to: commit stuff
   $ cat f.orig
   g
   $ hg unshelve --abort -t false
@@ -847,7 +917,7 @@
   g
   =======
   f
-  >>>>>>> working-copy: 81152db69da7 - shelve: changes to: commit stuff
+  >>>>>>> working-copy: aef214a5229c - shelve: changes to: commit stuff
   $ cat f.orig
   g
   $ hg unshelve --abort
@@ -872,7 +942,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark test)
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
@@ -881,7 +951,7 @@
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
   [1]
   $ hg bookmark
-     test                      4:33f7f61e6c5e
+     test                      (4|13):33f7f61e6c5e (re)
 
 Test that resolving all conflicts in one direction (so that the rebase
 is a no-op), works (issue4398)
@@ -895,13 +965,13 @@
   note: unshelved changes already existed in the working copy
   unshelve of 'default' complete
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
   $ hg diff
   $ hg status
   ? a/a.orig
   ? foo/foo
   $ hg summary
-  parent: 4:33f7f61e6c5e tip
+  parent: (4|13):33f7f61e6c5e tip (re)
    create conflict
   branch: default
   bookmarks: *test
@@ -980,14 +1050,14 @@
   M a/a
   ? foo/foo
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
   $ hg unshelve
   unshelving change 'test'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
   merging a/a
   $ hg bookmark
-   * test                      4:33f7f61e6c5e
+   \* test                      (4|13):33f7f61e6c5e (re)
   $ cat a/a
   a
   a
@@ -1109,7 +1179,7 @@
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg debugbundle .hg/shelved/*.hg
-  45993d65fe9dc3c6d8764b9c3b07fa831ee7d92d
+  330882a04d2ce8487636b1fb292e5beea77fa1e3
   $ cd ..
 
 with general delta
@@ -1132,7 +1202,7 @@
   $ hg debugbundle .hg/shelved/*.hg
   Stream params: {Compression: BZ}
   changegroup -- {nbchanges: 1, version: 02} (mandatory: True)
-      45993d65fe9dc3c6d8764b9c3b07fa831ee7d92d
+      330882a04d2ce8487636b1fb292e5beea77fa1e3
   $ cd ..
 
 Test visibility of in-memory changes inside transaction to external hook
@@ -1179,24 +1249,24 @@
 
   $ sh $TESTTMP/checkvisibility.sh before-unshelving
   ==== before-unshelving:
-  VISIBLE 5:703117a2acfb
-  ACTUAL  5:703117a2acfb
+  VISIBLE (5|19):703117a2acfb (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
 
   $ hg unshelve --keep default
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
   ==== preupdate:
-  VISIBLE 6:66b86db80ee4
-  ACTUAL  5:703117a2acfb
+  VISIBLE (6|20):54c00d20fb3f (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
   ==== preupdate:
-  VISIBLE 8:92fdbb7b4de7
-  ACTUAL  5:703117a2acfb
+  VISIBLE (8|21):8efe6f7537dc (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
   ==== preupdate:
-  VISIBLE 6:66b86db80ee4
-  ACTUAL  5:703117a2acfb
+  VISIBLE (6|20):54c00d20fb3f (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
 
   $ cat >> .hg/hgrc <<EOF
@@ -1206,8 +1276,8 @@
 
   $ sh $TESTTMP/checkvisibility.sh after-unshelving
   ==== after-unshelving:
-  VISIBLE 5:703117a2acfb
-  ACTUAL  5:703117a2acfb
+  VISIBLE (5|19):703117a2acfb (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
 
 == test visibility to external update hook
@@ -1223,25 +1293,25 @@
 
   $ sh $TESTTMP/checkvisibility.sh before-unshelving
   ==== before-unshelving:
-  VISIBLE 5:703117a2acfb
-  ACTUAL  5:703117a2acfb
+  VISIBLE (5|19):703117a2acfb (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
 
   $ hg unshelve --keep default
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
   ==== update:
-  VISIBLE 6:66b86db80ee4
-  VISIBLE 7:206bf5d4f922
-  ACTUAL  5:703117a2acfb
+  VISIBLE (6|20):54c00d20fb3f (re)
+  VISIBLE 1?7:492ed9d705e5 (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
   ==== update:
-  VISIBLE 6:66b86db80ee4
-  ACTUAL  5:703117a2acfb
+  VISIBLE (6|20):54c00d20fb3f (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
   ==== update:
-  VISIBLE 5:703117a2acfb
-  ACTUAL  5:703117a2acfb
+  VISIBLE (5|19):703117a2acfb (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
 
   $ cat >> .hg/hgrc <<EOF
@@ -1251,8 +1321,8 @@
 
   $ sh $TESTTMP/checkvisibility.sh after-unshelving
   ==== after-unshelving:
-  VISIBLE 5:703117a2acfb
-  ACTUAL  5:703117a2acfb
+  VISIBLE (5|19):703117a2acfb (re)
+  ACTUAL  (5|19):703117a2acfb (re)
   ====
 
   $ cd ..
@@ -1303,31 +1373,31 @@
   > EOF
 
   $ hg bookmarks -R repo
-     test                      4:33f7f61e6c5e
+     test                      (4|13):33f7f61e6c5e (re)
   $ hg share -B repo share
   updating working directory
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd share
 
   $ hg bookmarks
-     test                      4:33f7f61e6c5e
+     test                      (4|13):33f7f61e6c5e (re)
   $ hg bookmarks foo
   $ hg bookmarks
-   * foo                       5:703117a2acfb
-     test                      4:33f7f61e6c5e
+   \* foo                       (5|19):703117a2acfb (re)
+     test                      (4|13):33f7f61e6c5e (re)
   $ echo x >> x
   $ hg shelve
   shelved as foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg bookmarks
-   * foo                       5:703117a2acfb
-     test                      4:33f7f61e6c5e
+   \* foo                       (5|19):703117a2acfb (re)
+     test                      (4|13):33f7f61e6c5e (re)
 
   $ hg unshelve
   unshelving change 'foo'
   $ hg bookmarks
-   * foo                       5:703117a2acfb
-     test                      4:33f7f61e6c5e
+   \* foo                       (5|19):703117a2acfb (re)
+     test                      (4|13):33f7f61e6c5e (re)
 
   $ cd ..
 
@@ -1772,8 +1842,8 @@
   > ashelve
   > 8b058dae057a5a78f393f4535d9e363dd5efac9d
   > 8b058dae057a5a78f393f4535d9e363dd5efac9d
-  > 8b058dae057a5a78f393f4535d9e363dd5efac9d 003d2d94241cc7aff0c3a148e966d6a4a377f3a7
-  > 003d2d94241cc7aff0c3a148e966d6a4a377f3a7
+  > 8b058dae057a5a78f393f4535d9e363dd5efac9d f543b27db2cdb41737e2e0008dc524c471da1446
+  > f543b27db2cdb41737e2e0008dc524c471da1446
   > 
   > nokeep
   > :no-active-bookmark
@@ -1785,5 +1855,30 @@
 mercurial does not crash
   $ hg unshelve --continue
   unshelve of 'ashelve' complete
+
+#if phasebased
+
+Unshelve without .shelve metadata:
+
+  $ hg shelve
+  shelved as default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat .hg/shelved/default.shelve
+  node=82e0cb9893247d12667017593ce1e5655860f1ac
+  $ hg strip --hidden --rev 82e0cb989324 --no-backup
+  $ rm .hg/shelved/default.shelve
+  $ echo 3 > a
+  $ hg unshelve
+  unshelving change 'default'
+  temporarily committing pending changes (restore with 'hg unshelve --abort')
+  rebasing shelved changes
+  merging a
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+  [1]
+  $ cat .hg/shelved/default.shelve
+  node=82e0cb9893247d12667017593ce1e5655860f1ac
+
+#endif
+
   $ cd ..
-
--- a/tests/test-show-work.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-show-work.t	Wed Sep 26 20:33:09 2018 +0900
@@ -57,7 +57,6 @@
   $ hg show work
   @  128c commit 2
   o  181c commit 1
-  |
   ~
 
 Multiple DAG heads will be shown
@@ -72,7 +71,6 @@
   | o  128c commit 2
   |/
   o  181c commit 1
-  |
   ~
 
 Even when wdir is something else
@@ -84,7 +82,6 @@
   | o  128c commit 2
   |/
   o  181c commit 1
-  |
   ~
 
 Draft child shows public head (multiple heads)
@@ -131,7 +128,6 @@
   | o  128c commit 2
   |/
   o  181c commit 1
-  |
   ~
 
   $ cd ..
@@ -162,7 +158,6 @@
   | o  128c (@) commit 2
   |/
   o  181c commit 1
-  |
   ~
 
   $ cd ..
@@ -185,7 +180,6 @@
   @  3758 Added tag 0.2 for changeset 6379c25b76f1
   o  6379 (0.2) commit 3
   o  a2ad Added tag 0.1 for changeset 6a75536ea0b1
-  |
   ~
 
   $ cd ..
@@ -246,7 +240,6 @@
   $ hg show work --color=debug
   @  [log.changeset changeset.draft changeset.unstable instability.orphan|32f3] [log.description|commit 3]
   x  [log.changeset changeset.draft changeset.obsolete|6a75] [log.description|commit 2]
-  |
   ~
 
   $ cd ..
--- a/tests/test-simple-update.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-simple-update.t	Wed Sep 26 20:33:09 2018 +0900
@@ -10,7 +10,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
   $ hg clone . ../branch
   updating to branch default
@@ -39,7 +39,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
 
   $ hg co
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -79,7 +79,7 @@
   > [worker]
   > numcpus = 4
   > EOF
-  $ for i in `$PYTHON $TESTDIR/seq.py 1 100`; do
+  $ for i in `"$PYTHON" $TESTDIR/seq.py 1 100`; do
   >   echo $i > $i
   > done
   $ hg ci -qAm 'add 100 files'
--- a/tests/test-sparse-clone.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-sparse-clone.t	Wed Sep 26 20:33:09 2018 +0900
@@ -2,7 +2,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > ssh = $PYTHON "$RUNTESTDIR/dummyssh"
+  > ssh = "$PYTHON" "$RUNTESTDIR/dummyssh"
   > username = nobody <no.reply@fb.com>
   > [extensions]
   > sparse=
--- a/tests/test-sparse-merges.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-sparse-merges.t	Wed Sep 26 20:33:09 2018 +0900
@@ -113,8 +113,76 @@
 
   $ hg merge
   temporarily included 1 file(s) in the sparse checkout for merging
-  local [working copy] changed d which other [merge rev] deleted
+  file 'd' was deleted in other [merge rev] but was modified in local [working copy].
+  What do you want to do?
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
   [1]
+
+  $ cd ..
+
+Testing merging of a file which is renamed+modified on one side and modified on
+another
+
+  $ hg init mvtest
+  $ cd mvtest
+  $ echo "syntax: glob" >> .hgignore
+  $ echo "*.orig" >> .hgignore
+  $ hg ci -Aqm "added .hgignore"
+  $ for ch in a d; do echo foo > $ch; hg ci -Aqm "added "$ch; done;
+  $ cat >> .hg/hgrc <<EOF
+  > [alias]
+  > glog = log -GT "{rev}:{node|short} {desc}"
+  > [extensions]
+  > sparse =
+  > EOF
+
+  $ hg glog
+  @  2:f29feff37cfc added d
+  |
+  o  1:617125d27d6b added a
+  |
+  o  0:53f3774ed939 added .hgignore
+  
+  $ echo babar >> a
+  $ hg ci -m "added babar to a"
+
+  $ hg up '.^'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg mv a amove
+  $ hg ci -m "moved a to amove"
+  created new head
+
+  $ hg up 3
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg glog
+  o  4:5d1e85955f6d moved a to amove
+  |
+  | @  3:a06e41a6c16c added babar to a
+  |/
+  o  2:f29feff37cfc added d
+  |
+  o  1:617125d27d6b added a
+  |
+  o  0:53f3774ed939 added .hgignore
+  
+  $ hg debugsparse --exclude "a"
+  $ ls
+  d
+
+  $ hg merge
+  temporarily included 1 file(s) in the sparse checkout for merging
+  merging a and amove to amove
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg up -C 4
+  cleaned up 1 temporarily added file(s) from the sparse checkout
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg merge
+  merging amove and a to amove
+  abort: cannot add 'a' - it is outside the sparse checkout
+  (include file with `hg debugsparse --include <pattern>` or use `hg add -s <file>` to include file directory while adding)
+  [255]
--- a/tests/test-sparse-profiles.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-sparse-profiles.t	Wed Sep 26 20:33:09 2018 +0900
@@ -119,7 +119,7 @@
 Verify conflicting merge pulls in the conflicting changes
 
   $ hg merge 1
-  temporarily included 1 file(s) in the sparse checkout for merging
+  temporarily included 2 file(s) in the sparse checkout for merging
   merging backend.sparse
   merging data.py
   warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark')
@@ -184,7 +184,7 @@
 
   $ hg rebase -d 2
   rebasing 1:a2b1de640a62 "edit profile"
-  temporarily included 1 file(s) in the sparse checkout for merging
+  temporarily included 2 file(s) in the sparse checkout for merging
   merging backend.sparse
   merging data.py
   warning: conflicts while merging backend.sparse! (edit, then use 'hg resolve --mark')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-sparse-revlog.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,134 @@
+====================================
+Test delta choice with sparse revlog
+====================================
+
+Sparse-revlog usually shows the most gain on Manifest. However, it is simpler
+to general an appropriate file, so we test with a single file instead. The
+goal is to observe intermediate snapshot being created.
+
+We need a large enough file. Part of the content needs to be replaced
+repeatedly while some of it changes rarely.
+
+  $ bundlepath="$TESTDIR/artifacts/cache/big-file-churn.hg"
+
+  $ expectedhash=`cat "$bundlepath".md5`
+  $ if [ ! -f "$bundlepath" ]; then
+  >     echo 'skipped: missing artifact, run "'"$TESTDIR"'/artifacts/scripts/generate-churning-bundle.py"'
+  >     exit 80
+  > fi
+  $ currenthash=`f -M "$bundlepath" | cut -d = -f 2`
+  $ if [ "$currenthash" != "$expectedhash" ]; then
+  >     echo 'skipped: outdated artifact, md5 "'"$currenthash"'" expected "'"$expectedhash"'" run "'"$TESTDIR"'/artifacts/scripts/generate-churning-bundle.py"'
+  >     exit 80
+  > fi
+
+  $ cat >> $HGRCPATH << EOF
+  > [format]
+  > sparse-revlog = yes
+  > maxchainlen = 15
+  > [storage]
+  > revlog.optimize-delta-parent-choice = yes
+  > EOF
+  $ hg init sparse-repo
+  $ cd sparse-repo
+  $ hg unbundle $bundlepath
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5001 changesets with 5001 changes to 1 files (+89 heads)
+  new changesets 9706f5af64f4:d9032adc8114 (5001 drafts)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to "d9032adc8114: commit #5000"
+  89 other heads for branch "default"
+
+  $ hg log --stat -r 0:3
+  changeset:   0:9706f5af64f4
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     initial commit
+  
+   SPARSE-REVLOG-TEST-FILE |  10500 ++++++++++++++++++++++++++++++++++++++++++++++
+   1 files changed, 10500 insertions(+), 0 deletions(-)
+  
+  changeset:   1:724907deaa5e
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     commit #1
+  
+   SPARSE-REVLOG-TEST-FILE |  1068 +++++++++++++++++++++++-----------------------
+   1 files changed, 534 insertions(+), 534 deletions(-)
+  
+  changeset:   2:62c41bce3e5d
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     commit #2
+  
+   SPARSE-REVLOG-TEST-FILE |  1068 +++++++++++++++++++++++-----------------------
+   1 files changed, 534 insertions(+), 534 deletions(-)
+  
+  changeset:   3:348a9cbd6959
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     commit #3
+  
+   SPARSE-REVLOG-TEST-FILE |  1068 +++++++++++++++++++++++-----------------------
+   1 files changed, 534 insertions(+), 534 deletions(-)
+  
+
+  $ f -s .hg/store/data/*.d
+  .hg/store/data/_s_p_a_r_s_e-_r_e_v_l_o_g-_t_e_s_t-_f_i_l_e.d: size=63002924
+  $ hg debugrevlog *
+  format : 1
+  flags  : generaldelta
+  
+  revisions     :     5001
+      merges    :      625 (12.50%)
+      normal    :     4376 (87.50%)
+  revisions     :     5001
+      empty     :        0 ( 0.00%)
+                     text  :        0 (100.00%)
+                     delta :        0 (100.00%)
+      snapshot  :      374 ( 7.48%)
+        lvl-0   :              4 ( 0.08%)
+        lvl-1   :             23 ( 0.46%)
+        lvl-2   :             63 ( 1.26%)
+        lvl-3   :            118 ( 2.36%)
+        lvl-4   :            166 ( 3.32%)
+      deltas    :     4627 (92.52%)
+  revision size : 63002924
+      snapshot  :  9888099 (15.69%)
+        lvl-0   :         804262 ( 1.28%)
+        lvl-1   :        1561380 ( 2.48%)
+        lvl-2   :        2096696 ( 3.33%)
+        lvl-3   :        2749539 ( 4.36%)
+        lvl-4   :        2676222 ( 4.25%)
+      deltas    : 53114825 (84.31%)
+  
+  chunks        :     5001
+      0x78 (x)  :     5001 (100.00%)
+  chunks size   : 63002924
+      0x78 (x)  : 63002924 (100.00%)
+  
+  avg chain length  :        9
+  max chain length  :       15
+  max chain reach   : 28907121
+  compression ratio :       27
+  
+  uncompressed data size (min/max/avg) : 346468 / 346472 / 346471
+  full revision size (min/max/avg)     : 201008 / 201141 / 201065
+  inter-snapshot size (min/max/avg)    : 11601 / 157413 / 24550
+      level-1   (min/max/avg)          : 13061 / 157413 / 67886
+      level-2   (min/max/avg)          : 11674 / 85631 / 33280
+      level-3   (min/max/avg)          : 11602 / 42957 / 23301
+      level-4   (min/max/avg)          : 11601 / 21475 / 16121
+  delta size (min/max/avg)             : 10649 / 105465 / 11479
+  
+  deltas against prev  : 3966 (85.71%)
+      where prev = p1  : 3922     (98.89%)
+      where prev = p2  :    0     ( 0.00%)
+      other            :   44     ( 1.11%)
+  deltas against p1    :  611 (13.21%)
+  deltas against p2    :   50 ( 1.08%)
+  deltas against other :    0 ( 0.00%)
--- a/tests/test-sparse.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-sparse.t	Wed Sep 26 20:33:09 2018 +0900
@@ -189,7 +189,7 @@
 
   $ hg rebase -d 1 -r 2 --config extensions.rebase=
   rebasing 2:b91df4f39e75 "edit hide" (tip)
-  temporarily included 1 file(s) in the sparse checkout for merging
+  temporarily included 2 file(s) in the sparse checkout for merging
   merging hide
   warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -224,7 +224,7 @@
 
   $ hg up -q 1
   $ hg merge -r 2
-  temporarily included 1 file(s) in the sparse checkout for merging
+  temporarily included 2 file(s) in the sparse checkout for merging
   merging hide
   warning: conflicts while merging hide! (edit, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
@@ -294,7 +294,7 @@
   $ touch dir1/notshown
   $ hg commit -A dir1/notshown -m "notshown"
   $ hg debugsparse --include 'dir1/dir2'
-  $ $PYTHON $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
+  $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
   ./
   ./dir1/
   ./dir1/dir2/
@@ -302,7 +302,7 @@
   ./hide.orig
   $ hg debugsparse --delete 'dir1/dir2'
   $ hg debugsparse --include 'glob:dir1/dir2'
-  $ $PYTHON $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
+  $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
   ./
   ./dir1/
   ./dir1/dir2/
@@ -385,10 +385,10 @@
   $ cp ../dirstateallexcluded .hg/dirstate
   $ touch includedadded
   $ hg add includedadded
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   a   0         -1 unset               includedadded
   $ hg debugrebuilddirstate --minimal
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   n   0         -1 unset               included
   a   0         -1 * includedadded (glob)
 
@@ -410,13 +410,13 @@
   included
 We have files in the dirstate that are included and excluded. Some are in the
 manifest and some are not.
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   n 644          0 * excluded (glob)
   a   0         -1 * excludednomanifest (glob)
   n 644          0 * included (glob)
   a   0         -1 * includedadded (glob)
   $ hg debugrebuilddirstate --minimal
-  $ hg debugdirstate --nodates
+  $ hg debugdirstate --no-dates
   n 644          0 * included (glob)
   a   0         -1 * includedadded (glob)
 
--- a/tests/test-split.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-split.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1,7 +1,7 @@
 #testcases obsstore-on obsstore-off
 
   $ cat > $TESTTMP/editor.py <<EOF
-  > #!$PYTHON
+  > #!"$PYTHON"
   > import os
   > import sys
   > path = os.path.join(os.environ['TESTTMP'], 'messages')
--- a/tests/test-ssh-bundle1.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-ssh-bundle1.t	Wed Sep 26 20:33:09 2018 +0900
@@ -59,10 +59,12 @@
 
 non-existent absolute path
 
+#if no-msys
   $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy//`pwd`/nonexistent local
   remote: abort: repository /$TESTTMP/nonexistent not found!
   abort: no suitable response from remote hg!
   [255]
+#endif
 
 clone remote via stream
 
@@ -82,7 +84,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 3 changesets, 2 total revisions
+  checked 3 changesets with 2 changes to 2 files
   $ hg branches
   default                        0:1160648e36ce
   $ cd ..
@@ -126,7 +128,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 3 changesets, 2 total revisions
+  checked 3 changesets with 2 changes to 2 files
   $ cat >> .hg/hgrc <<EOF
   > [hooks]
   > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
@@ -225,7 +227,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 4 changesets, 3 total revisions
+  checked 4 changesets with 3 changes to 2 files
   $ hg cat -r tip foo
   bleah
   $ echo z > z
@@ -393,7 +395,7 @@
   abort: no suitable response from remote hg!
   [255]
 
-  $ SSH_ORIGINAL_COMMAND="'hg' serve -R 'a'repo' --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
+  $ SSH_ORIGINAL_COMMAND="'hg' serve -R 'a'repo' --stdio" "$PYTHON" "$TESTDIR/../contrib/hg-ssh"
   Illegal command "'hg' serve -R 'a'repo' --stdio": No closing quotation
   [255]
 
@@ -477,12 +479,12 @@
   $ hg pull --debug ssh://user@dummy/remote
   pulling from ssh://user@dummy/remote
   running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob) (sshv2 !)
   sending hello command
   sending between command
-  remote: 413 (sshv1 !)
-  protocol upgraded to exp-ssh-v2-0001 (sshv2 !)
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 427 (sshv1 !)
+  protocol upgraded to exp-ssh-v2-0002 (sshv2 !)
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1 (sshv1 !)
   sending protocaps command
   preparing listkeys for "bookmarks"
@@ -502,7 +504,7 @@
 
   $ cat dummylog
   Got arguments 1:user@dummy 2:hg -R nonexistent serve --stdio
-  Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio
+  Got arguments 1:user@dummy 2:hg -R /$TESTTMP/nonexistent serve --stdio (no-msys !)
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio
   Got arguments 1:user@dummy 2:hg -R local-stream serve --stdio (no-reposimplestore !)
   Got arguments 1:user@dummy 2:hg -R remote serve --stdio (no-reposimplestore !)
--- a/tests/test-ssh-clone-r.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-ssh-clone-r.t	Wed Sep 26 20:33:09 2018 +0900
@@ -19,7 +19,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
-  new changesets bfaf4b5cbf01:916f1afdef90
+  new changesets bfaf4b5cbf01:916f1afdef90 (9 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -45,7 +45,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -57,7 +57,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -69,7 +69,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -81,7 +81,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 4 changesets, 4 total revisions
+  checked 4 changesets with 4 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -93,7 +93,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -105,7 +105,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 1 files
   adding changesets
   adding manifests
   adding file changes
@@ -117,7 +117,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 4 changesets, 5 total revisions
+  checked 4 changesets with 5 changes to 2 files
   adding changesets
   adding manifests
   adding file changes
@@ -129,7 +129,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 5 changesets, 6 total revisions
+  checked 5 changesets with 6 changes to 3 files
   adding changesets
   adding manifests
   adding file changes
@@ -141,7 +141,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
   $ cd test-8
   $ hg pull ../test-7
   pulling from ../test-7
@@ -157,7 +157,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
   $ cd ..
   $ cd test-1
   $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" -r 4 ssh://user@dummy/remote
@@ -174,7 +174,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 3 changesets, 2 total revisions
+  checked 3 changesets with 2 changes to 1 files
   $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote
   pulling from ssh://user@dummy/remote
   searching for changes
@@ -200,7 +200,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 5 changesets, 3 total revisions
+  checked 5 changesets with 3 changes to 1 files
   $ hg pull -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/remote
   pulling from ssh://user@dummy/remote
   searching for changes
@@ -215,6 +215,6 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  4 files, 9 changesets, 7 total revisions
+  checked 9 changesets with 7 changes to 4 files
 
   $ cd ..
--- a/tests/test-ssh-proto-unbundle.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-ssh-proto-unbundle.t	Wed Sep 26 20:33:09 2018 +0900
@@ -56,9 +56,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -100,17 +100,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -235,9 +235,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -285,17 +285,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -361,9 +361,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -412,17 +412,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -489,9 +489,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -539,17 +539,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -615,9 +615,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -666,17 +666,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -743,9 +743,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -796,17 +796,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -875,9 +875,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -925,17 +925,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -1001,9 +1001,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1054,17 +1054,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -1133,9 +1133,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1186,17 +1186,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -1271,9 +1271,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1322,17 +1322,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -1400,9 +1400,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1451,17 +1451,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -1531,9 +1531,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1584,17 +1584,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -1672,9 +1672,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1729,17 +1729,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -1812,9 +1812,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1858,17 +1858,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
@@ -1942,9 +1942,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1992,17 +1992,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending unbundle command
--- a/tests/test-ssh-proto.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-ssh-proto.t	Wed Sep 26 20:33:09 2018 +0900
@@ -22,7 +22,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > ssh = $PYTHON "$TESTDIR/dummyssh"
+  > ssh = "$PYTHON" "$TESTDIR/dummyssh"
   > [devel]
   > debug.peer-request = true
   > [extensions]
@@ -64,8 +64,8 @@
   devel-peer-request:   pairs: 81 bytes
   sending hello command
   sending between command
-  remote: 413
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 427
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
@@ -86,9 +86,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
 
 `hg debugserve --sshstdio` works
 
@@ -96,8 +96,8 @@
   $ hg debugserve --sshstdio << EOF
   > hello
   > EOF
-  413
-  capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  427
+  capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
 
 I/O logging works
 
@@ -105,24 +105,24 @@
   > hello
   > EOF
   o> write(4) -> 4:
-  o>     413\n
-  o> write(413) -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
-  413
-  capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     427\n
+  o> write(427) -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  427
+  capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> flush() -> None
 
   $ hg debugserve --sshstdio --logiofile $TESTTMP/io << EOF
   > hello
   > EOF
-  413
-  capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  427
+  capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
 
   $ cat $TESTTMP/io
   o> write(4) -> 4:
-  o>     413\n
-  o> write(413) -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> write(427) -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> flush() -> None
 
   $ cd ..
@@ -147,9 +147,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -185,8 +185,8 @@
   remote: banner: line 7
   remote: banner: line 8
   remote: banner: line 9
-  remote: 413
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 427
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
@@ -243,9 +243,9 @@
   o> readline() -> 15:
   o>     banner: line 9\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -295,13 +295,13 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
+  o>     427\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
 
@@ -314,8 +314,8 @@
   sending hello command
   sending between command
   remote: 0
-  remote: 413
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 427
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
@@ -363,9 +363,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -388,8 +388,8 @@
   remote: 0
   remote: 0
   remote: 0
-  remote: 413
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 427
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
@@ -445,9 +445,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -492,9 +492,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -537,9 +537,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -607,9 +607,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
 
 Incomplete dictionary send
 
@@ -689,9 +689,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -723,9 +723,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -766,9 +766,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -795,9 +795,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(105) -> 105:
   i>     between\n
   i>     pairs 81\n
@@ -836,9 +836,9 @@
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -885,9 +885,9 @@
   o> readline() -> 41:
   o>     68986213bd4485ea51533535e3fc9e78007a711f\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
 
@@ -912,7 +912,7 @@
   o> readline() -> 41:
   o>     68986213bd4485ea51533535e3fc9e78007a711f\n
   o> readline() -> 4:
-  o>     413\n
+  o>     427\n
 
 Send an upgrade request to a server that doesn't support that command
 
@@ -941,9 +941,9 @@
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -954,14 +954,14 @@
   $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server
   running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !)
   running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !)
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob)
   devel-peer-request: hello+between
   devel-peer-request:   pairs: 81 bytes
   sending hello command
   sending between command
   remote: 0
-  remote: 413
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 427
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
@@ -984,7 +984,7 @@
 
   $ hg debugwireproto --localssh --peer raw << EOF
   > raw
-  >     upgrade this-is-some-token proto=exp-ssh-v2-0001\n
+  >     upgrade this-is-some-token proto=exp-ssh-v2-0002\n
   >     hello\n
   >     between\n
   >     pairs 81\n
@@ -995,30 +995,30 @@
   > EOF
   using raw connection to peer
   i> write(153) -> 153:
-  i>     upgrade this-is-some-token proto=exp-ssh-v2-0001\n
+  i>     upgrade this-is-some-token proto=exp-ssh-v2-0002\n
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   o> readline() -> 44:
-  o>     upgraded this-is-some-token exp-ssh-v2-0001\n
+  o>     upgraded this-is-some-token exp-ssh-v2-0002\n
   o> readline() -> 4:
-  o>     412\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     426\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
 
   $ cd ..
 
   $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server
   running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !)
   running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !)
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob)
   devel-peer-request: hello+between
   devel-peer-request:   pairs: 81 bytes
   sending hello command
   sending between command
-  protocol upgraded to exp-ssh-v2-0001
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  protocol upgraded to exp-ssh-v2-0002
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
   sending protocaps command
@@ -1031,20 +1031,20 @@
   $ hg --config experimental.sshpeer.advertise-v2=true --debug debugcapabilities ssh://user@dummy/server
   running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !)
   running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !)
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob)
   devel-peer-request: hello+between
   devel-peer-request:   pairs: 81 bytes
   sending hello command
   sending between command
-  protocol upgraded to exp-ssh-v2-0001
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  protocol upgraded to exp-ssh-v2-0002
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
   sending protocaps command
   Main capabilities:
     batch
     branchmap
-    $USUAL_BUNDLE2_CAPS_SERVER$
+    $USUAL_BUNDLE2_CAPS$
     changegroupsubset
     getbundle
     known
@@ -1078,6 +1078,8 @@
       http
       https
     rev-branch-cache
+    stream
+      v2
 
 Command after upgrade to version 2 is processed
 
@@ -1085,7 +1087,7 @@
 
   $ hg debugwireproto --localssh --peer raw << EOF
   > raw
-  >      upgrade this-is-some-token proto=exp-ssh-v2-0001\n
+  >      upgrade this-is-some-token proto=exp-ssh-v2-0002\n
   >      hello\n
   >      between\n
   >      pairs 81\n
@@ -1100,29 +1102,29 @@
   > EOF
   using raw connection to peer
   i> write(153) -> 153:
-  i>     upgrade this-is-some-token proto=exp-ssh-v2-0001\n
+  i>     upgrade this-is-some-token proto=exp-ssh-v2-0002\n
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   o> readline() -> 44:
-  o>     upgraded this-is-some-token exp-ssh-v2-0001\n
+  o>     upgraded this-is-some-token exp-ssh-v2-0002\n
   o> readline() -> 4:
-  o>     412\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     426\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     397\n
-  o> readline() -> 397:
-  o>     capabilities: branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     411\n
+  o> readline() -> 411:
+  o>     capabilities: branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
 
 Multiple upgrades is not allowed
 
   $ hg debugwireproto --localssh --peer raw << EOF
   > raw
-  >     upgrade this-is-some-token proto=exp-ssh-v2-0001\n
+  >     upgrade this-is-some-token proto=exp-ssh-v2-0002\n
   >     hello\n
   >     between\n
   >     pairs 81\n
@@ -1138,17 +1140,17 @@
   > EOF
   using raw connection to peer
   i> write(153) -> 153:
-  i>     upgrade this-is-some-token proto=exp-ssh-v2-0001\n
+  i>     upgrade this-is-some-token proto=exp-ssh-v2-0002\n
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   o> readline() -> 44:
-  o>     upgraded this-is-some-token exp-ssh-v2-0001\n
+  o>     upgraded this-is-some-token exp-ssh-v2-0002\n
   o> readline() -> 4:
-  o>     412\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     426\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(45) -> 45:
   i>     upgrade another-token proto=irrelevant\n
   i>     hello\n
@@ -1218,9 +1220,9 @@
   i> write(6) -> 6:
   i>     hello\n
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   i> write(98) -> 98:
   i>     between\n
   i>     pairs 81\n
@@ -1234,14 +1236,14 @@
 
   $ hg debugwireproto --localssh --peer raw << EOF
   > raw
-  >     upgrade token proto=exp-ssh-v2-0001\n
+  >     upgrade token proto=exp-ssh-v2-0002\n
   >     invalid\n
   > readline
   > readavailable
   > EOF
   using raw connection to peer
   i> write(44) -> 44:
-  i>     upgrade token proto=exp-ssh-v2-0001\n
+  i>     upgrade token proto=exp-ssh-v2-0002\n
   i>     invalid\n
   o> readline() -> 1:
   o>     \n
@@ -1251,7 +1253,7 @@
 
   $ hg debugwireproto --localssh --peer raw << EOF
   > raw
-  >     upgrade token proto=exp-ssh-v2-0001\n
+  >     upgrade token proto=exp-ssh-v2-0002\n
   >     hello\n
   >     invalid\n
   > readline
@@ -1259,7 +1261,7 @@
   > EOF
   using raw connection to peer
   i> write(50) -> 50:
-  i>     upgrade token proto=exp-ssh-v2-0001\n
+  i>     upgrade token proto=exp-ssh-v2-0002\n
   i>     hello\n
   i>     invalid\n
   o> readline() -> 1:
@@ -1270,7 +1272,7 @@
 
   $ hg debugwireproto --localssh --peer raw << EOF
   > raw
-  >     upgrade token proto=exp-ssh-v2-0001\n
+  >     upgrade token proto=exp-ssh-v2-0002\n
   >     hello\n
   >     between\n
   >     invalid\n
@@ -1279,7 +1281,7 @@
   > EOF
   using raw connection to peer
   i> write(58) -> 58:
-  i>     upgrade token proto=exp-ssh-v2-0001\n
+  i>     upgrade token proto=exp-ssh-v2-0002\n
   i>     hello\n
   i>     between\n
   i>     invalid\n
@@ -1337,9 +1339,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1357,22 +1359,26 @@
   o>     bookmarks\t\n
   o>     namespaces\t\n
   o>     phases\t
-  response: {b'bookmarks': b'', b'namespaces': b'', b'phases': b''}
+  response: {
+    b'bookmarks': b'',
+    b'namespaces': b'',
+    b'phases': b''
+  }
   
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending listkeys command
@@ -1388,7 +1394,11 @@
   o>     bookmarks\t\n
   o>     namespaces\t\n
   o>     phases\t
-  response: {b'bookmarks': b'', b'namespaces': b'', b'phases': b''}
+  response: {
+    b'bookmarks': b'',
+    b'namespaces': b'',
+    b'phases': b''
+  }
 
   $ cd ..
 
@@ -1417,9 +1427,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1438,17 +1448,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending listkeys command
@@ -1478,9 +1488,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1495,22 +1505,24 @@
   o> bufferedreadline() -> 3:
   o>     46\n
   o> bufferedread(46) -> 46: bookA\t68986213bd4485ea51533535e3fc9e78007a711f
-  response: {b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f'}
+  response: {
+    b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f'
+  }
   
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending listkeys command
@@ -1523,7 +1535,9 @@
   o> bufferedreadline() -> 3:
   o>     46\n
   o> bufferedread(46) -> 46: bookA\t68986213bd4485ea51533535e3fc9e78007a711f
-  response: {b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f'}
+  response: {
+    b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f'
+  }
 
 With multiple bookmarks set
 
@@ -1541,9 +1555,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1560,22 +1574,25 @@
   o> bufferedread(93) -> 93:
   o>     bookA\t68986213bd4485ea51533535e3fc9e78007a711f\n
   o>     bookB\t1880f3755e2e52e3199e0ee5638128b08642f34d
-  response: {b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f', b'bookB': b'1880f3755e2e52e3199e0ee5638128b08642f34d'}
+  response: {
+    b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f',
+    b'bookB': b'1880f3755e2e52e3199e0ee5638128b08642f34d'
+  }
   
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending listkeys command
@@ -1590,7 +1607,10 @@
   o> bufferedread(93) -> 93:
   o>     bookA\t68986213bd4485ea51533535e3fc9e78007a711f\n
   o>     bookB\t1880f3755e2e52e3199e0ee5638128b08642f34d
-  response: {b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f', b'bookB': b'1880f3755e2e52e3199e0ee5638128b08642f34d'}
+  response: {
+    b'bookA': b'68986213bd4485ea51533535e3fc9e78007a711f',
+    b'bookB': b'1880f3755e2e52e3199e0ee5638128b08642f34d'
+  }
 
 Test pushkey for bookmarks
 
@@ -1610,9 +1630,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1641,17 +1661,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending pushkey command
@@ -1702,9 +1722,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1719,22 +1739,24 @@
   o> bufferedreadline() -> 3:
   o>     15\n
   o> bufferedread(15) -> 15: publishing\tTrue
-  response: {b'publishing': b'True'}
+  response: {
+    b'publishing': b'True'
+  }
   
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending listkeys command
@@ -1747,7 +1769,9 @@
   o> bufferedreadline() -> 3:
   o>     15\n
   o> bufferedread(15) -> 15: publishing\tTrue
-  response: {b'publishing': b'True'}
+  response: {
+    b'publishing': b'True'
+  }
 
 Create some commits
 
@@ -1781,9 +1805,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1801,22 +1825,26 @@
   o>     20b8a89289d80036e6c4e87c2083e3bea1586637\t1\n
   o>     c4750011d906c18ea2f0527419cbc1a544435150\t1\n
   o>     publishing\tTrue
-  response: {b'20b8a89289d80036e6c4e87c2083e3bea1586637': b'1', b'c4750011d906c18ea2f0527419cbc1a544435150': b'1', b'publishing': b'True'}
+  response: {
+    b'20b8a89289d80036e6c4e87c2083e3bea1586637': b'1',
+    b'c4750011d906c18ea2f0527419cbc1a544435150': b'1',
+    b'publishing': b'True'
+  }
   
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending listkeys command
@@ -1832,7 +1860,11 @@
   o>     20b8a89289d80036e6c4e87c2083e3bea1586637\t1\n
   o>     c4750011d906c18ea2f0527419cbc1a544435150\t1\n
   o>     publishing\tTrue
-  response: {b'20b8a89289d80036e6c4e87c2083e3bea1586637': b'1', b'c4750011d906c18ea2f0527419cbc1a544435150': b'1', b'publishing': b'True'}
+  response: {
+    b'20b8a89289d80036e6c4e87c2083e3bea1586637': b'1',
+    b'c4750011d906c18ea2f0527419cbc1a544435150': b'1',
+    b'publishing': b'True'
+  }
 
 Single draft head
 
@@ -1850,9 +1882,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1869,22 +1901,25 @@
   o> bufferedread(58) -> 58:
   o>     c4750011d906c18ea2f0527419cbc1a544435150\t1\n
   o>     publishing\tTrue
-  response: {b'c4750011d906c18ea2f0527419cbc1a544435150': b'1', b'publishing': b'True'}
+  response: {
+    b'c4750011d906c18ea2f0527419cbc1a544435150': b'1',
+    b'publishing': b'True'
+  }
   
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending listkeys command
@@ -1899,7 +1934,10 @@
   o> bufferedread(58) -> 58:
   o>     c4750011d906c18ea2f0527419cbc1a544435150\t1\n
   o>     publishing\tTrue
-  response: {b'c4750011d906c18ea2f0527419cbc1a544435150': b'1', b'publishing': b'True'}
+  response: {
+    b'c4750011d906c18ea2f0527419cbc1a544435150': b'1',
+    b'publishing': b'True'
+  }
 
 All public heads
 
@@ -1917,9 +1955,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -1934,22 +1972,24 @@
   o> bufferedreadline() -> 3:
   o>     15\n
   o> bufferedread(15) -> 15: publishing\tTrue
-  response: {b'publishing': b'True'}
+  response: {
+    b'publishing': b'True'
+  }
   
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending listkeys command
@@ -1962,7 +2002,9 @@
   o> bufferedreadline() -> 3:
   o>     15\n
   o> bufferedread(15) -> 15: publishing\tTrue
-  response: {b'publishing': b'True'}
+  response: {
+    b'publishing': b'True'
+  }
 
 Setting public phase via pushkey
 
@@ -1984,9 +2026,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -2016,17 +2058,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending pushkey command
@@ -2091,9 +2133,9 @@
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 4:
-  o>     413\n
-  o> readline() -> 413:
-  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
+  o>     427\n
+  o> readline() -> 427:
+  o>     capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash\n
   o> readline() -> 2:
   o>     1\n
   o> readline() -> 1:
@@ -2122,17 +2164,17 @@
   testing ssh2
   creating ssh peer from handshake results
   i> write(171) -> 171:
-  i>     upgrade * proto=exp-ssh-v2-0001\n (glob)
+  i>     upgrade * proto=exp-ssh-v2-0002\n (glob)
   i>     hello\n
   i>     between\n
   i>     pairs 81\n
   i>     0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
   i> flush() -> None
   o> readline() -> 62:
-  o>     upgraded * exp-ssh-v2-0001\n (glob)
+  o>     upgraded * exp-ssh-v2-0002\n (glob)
   o> readline() -> 4:
-  o>     412\n
-  o> read(412) -> 412: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  o>     426\n
+  o> read(426) -> 426: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   o> read(1) -> 1:
   o>     \n
   sending batch with 3 sub-commands
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-ssh-repoerror.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,68 @@
+#require unix-permissions no-root
+
+initial setup
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > ssh="$PYTHON" "$TESTDIR/dummyssh"
+  > EOF
+
+repository itself is non-readable
+---------------------------------
+
+  $ hg init no-read
+  $ hg id ssh://user@dummy/no-read
+  000000000000
+  $ chmod a-rx no-read
+
+  $ hg id ssh://user@dummy/no-read
+  remote: abort: Permission denied: '$TESTTMP/no-read/.hg'
+  abort: no suitable response from remote hg!
+  [255]
+
+special case files are visible, but unreadable
+----------------------------------------------
+
+This is "similar" to the test above, but the directory is "traversable". This
+seems an unexpected case in real life, but we test it anyway.
+
+  $ hg init other
+  $ hg id ssh://user@dummy/other
+  000000000000
+  $ for item in `find other | sort -r` ; do
+  >     chmod a-r $item
+  > done
+
+  $ hg id ssh://user@dummy/other
+  remote: abort: Permission denied: $TESTTMP/other/.hg/requires
+  abort: no suitable response from remote hg!
+  [255]
+
+directory toward the repository is read only
+--------------------------------------------
+
+  $ mkdir deep
+  $ hg init deep/nested
+
+  $ hg id ssh://user@dummy/deep/nested
+  000000000000
+
+  $ chmod a-rx deep
+
+  $ hg id ssh://user@dummy/deep/nested
+  remote: abort: Permission denied: '$TESTTMP/deep/nested/.hg'
+  abort: no suitable response from remote hg!
+  [255]
+
+repository has wrong requirement
+--------------------------------
+
+  $ hg init repo-future
+  $ hg id ssh://user@dummy/repo-future
+  000000000000
+  $ echo flying-car >> repo-future/.hg/requires
+  $ hg id ssh://user@dummy/repo-future
+  remote: abort: repository requires features unknown to this Mercurial: flying-car!
+  remote: (see https://mercurial-scm.org/wiki/MissingRequirement for more information)
+  abort: no suitable response from remote hg!
+  [255]
--- a/tests/test-ssh.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-ssh.t	Wed Sep 26 20:33:09 2018 +0900
@@ -60,10 +60,8 @@
 
   $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
   streaming all changes
-  4 files to transfer, 602 bytes of data
-  transferred 602 bytes in * seconds (*) (glob)
-  searching for changes
-  no changes found
+  8 files to transfer, 827 bytes of data
+  transferred 827 bytes in * seconds (*) (glob)
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local-stream
@@ -72,7 +70,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 3 changesets, 2 total revisions
+  checked 3 changesets with 2 changes to 2 files
   $ hg branches
   default                        0:1160648e36ce
   $ cd ..
@@ -82,10 +80,8 @@
   $ hg -R local-stream book mybook
   $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
   streaming all changes
-  4 files to transfer, 602 bytes of data
-  transferred 602 bytes in * seconds (*) (glob)
-  searching for changes
-  no changes found
+  9 files to transfer, 870 bytes of data
+  transferred 870 bytes in * seconds (*) (glob)
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd stream2
@@ -116,7 +112,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 3 changesets, 2 total revisions
+  checked 3 changesets with 2 changes to 2 files
   $ cat >> .hg/hgrc <<EOF
   > [hooks]
   > changegroup = sh -c "printenv.py changegroup-in-local 0 ../dummylog"
@@ -215,7 +211,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 4 changesets, 3 total revisions
+  checked 4 changesets with 3 changes to 2 files
   $ hg cat -r tip foo
   bleah
   $ echo z > z
@@ -284,7 +280,7 @@
 
   $ cat <<EOF >> ../remote/.hg/hgrc
   > [hooks]
-  > changegroup.stdout = $PYTHON $TESTTMP/badhook
+  > changegroup.stdout = "$PYTHON" $TESTTMP/badhook
   > changegroup.pystdout = python:$TESTTMP/badpyhook.py:hook
   > EOF
   $ echo r > r
@@ -408,7 +404,7 @@
   abort: no suitable response from remote hg!
   [255]
 
-  $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" $PYTHON "$TESTDIR/../contrib/hg-ssh"
+  $ SSH_ORIGINAL_COMMAND="'hg' -R 'a'repo' serve --stdio" "$PYTHON" "$TESTDIR/../contrib/hg-ssh"
   Illegal command "'hg' -R 'a'repo' serve --stdio": No closing quotation
   [255]
 
@@ -492,14 +488,14 @@
   $ hg pull --debug ssh://user@dummy/remote --config devel.debug.peer-request=yes
   pulling from ssh://user@dummy/remote
   running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
-  sending upgrade request: * proto=exp-ssh-v2-0001 (glob) (sshv2 !)
+  sending upgrade request: * proto=exp-ssh-v2-0002 (glob) (sshv2 !)
   devel-peer-request: hello+between
   devel-peer-request:   pairs: 81 bytes
   sending hello command
   sending between command
-  remote: 413 (sshv1 !)
-  protocol upgraded to exp-ssh-v2-0001 (sshv2 !)
-  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  remote: 427 (sshv1 !)
+  protocol upgraded to exp-ssh-v2-0002 (sshv2 !)
+  remote: capabilities: batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset getbundle known lookup protocaps pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   remote: 1 (sshv1 !)
   devel-peer-request: protocaps
   devel-peer-request:   caps: * bytes (glob)
--- a/tests/test-static-http.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-static-http.t	Wed Sep 26 20:33:09 2018 +0900
@@ -9,7 +9,7 @@
 This server doesn't do range requests so it's basically only good for
 one pull
 
-  $ $PYTHON "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid \
+  $ "$PYTHON" "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid \
   > --logfile server.log
   $ cat dumb.pid >> $DAEMON_PIDS
   $ hg init remote
@@ -43,7 +43,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 1 changesets, 2 total revisions
+  checked 1 changesets with 2 changes to 2 files
   $ cat bar
   foo
   $ cd ../remote
@@ -130,7 +130,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 1 changesets, 3 total revisions
+  checked 1 changesets with 3 changes to 3 files
   checking subrepo links
   $ cat a
   a
@@ -151,7 +151,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  0 files, 0 changesets, 0 total revisions
+  checked 0 changesets with 0 changes to 0 files
   $ hg paths
   default = static-http://localhost:$HGPORT/remotempty
 
--- a/tests/test-status-color.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-status-color.t	Wed Sep 26 20:33:09 2018 +0900
@@ -168,10 +168,10 @@
   $ touch modified removed deleted ignored
   $ echo "^ignored$" > .hgignore
   $ hg ci -A -m 'initial checkin'
-  adding .hgignore
-  adding deleted
-  adding modified
-  adding removed
+  \x1b[0;32madding .hgignore\x1b[0m (esc)
+  \x1b[0;32madding deleted\x1b[0m (esc)
+  \x1b[0;32madding modified\x1b[0m (esc)
+  \x1b[0;32madding removed\x1b[0m (esc)
   $ hg log --color=debug
   [log.changeset changeset.draft|changeset:   0:389aef86a55e]
   [log.tag|tag:         tip]
@@ -296,10 +296,10 @@
   $ touch modified removed deleted ignored
   $ echo "^ignored$" > .hgignore
   $ hg commit -A -m 'initial checkin'
-  adding .hgignore
-  adding deleted
-  adding modified
-  adding removed
+  \x1b[0;32madding .hgignore\x1b[0m (esc)
+  \x1b[0;32madding deleted\x1b[0m (esc)
+  \x1b[0;32madding modified\x1b[0m (esc)
+  \x1b[0;32madding removed\x1b[0m (esc)
   $ touch added unknown ignored
   $ hg add added
   $ echo "test" >> modified
@@ -393,6 +393,7 @@
 
   $ hg unknowncommand > /dev/null
   hg: unknown command 'unknowncommand'
+  (use 'hg help' for a list of commands)
   [255]
 
 color coding of error message without curses
@@ -400,6 +401,7 @@
   $ echo 'raise ImportError' > curses.py
   $ PYTHONPATH=`pwd`:$PYTHONPATH hg unknowncommand > /dev/null
   hg: unknown command 'unknowncommand'
+  (use 'hg help' for a list of commands)
   [255]
 
   $ cd ..
--- a/tests/test-status-inprocess.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-status-inprocess.py	Wed Sep 26 20:33:09 2018 +0900
@@ -22,7 +22,7 @@
 u = uimod.ui.load()
 
 print('% creating repo')
-repo = localrepo.localrepository(u, b'.', create=True)
+repo = localrepo.instance(u, b'.', create=True)
 
 f = open('test.py', 'w')
 try:
--- a/tests/test-status-rev.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-status-rev.t	Wed Sep 26 20:33:09 2018 +0900
@@ -5,7 +5,7 @@
 
 First commit
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 1
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 1
   $ hg addremove --similarity 0
   adding content1_content1_content1-tracked
   adding content1_content1_content1-untracked
@@ -31,7 +31,7 @@
 
 Second commit
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 2
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 2
   $ hg addremove --similarity 0
   removing content1_missing_content1-tracked
   removing content1_missing_content1-untracked
@@ -49,7 +49,7 @@
 
 Working copy
 
-  $ $PYTHON $TESTDIR/generate-working-copy-states.py state 2 wc
+  $ "$PYTHON" $TESTDIR/generate-working-copy-states.py state 2 wc
   $ hg addremove --similarity 0
   adding content1_missing_content1-tracked
   adding content1_missing_content1-untracked
--- a/tests/test-status.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-status.t	Wed Sep 26 20:33:09 2018 +0900
@@ -239,8 +239,8 @@
     "status": "A"
    },
    {
-    "copy": "modified",
     "path": "copied",
+    "source": "modified",
     "status": "A"
    },
    {
@@ -282,7 +282,7 @@
 
 Test templater support:
 
-  $ hg status -AT "[{status}]\t{if(copy, '{copy} -> ')}{path}\n"
+  $ hg status -AT "[{status}]\t{if(source, '{source} -> ')}{path}\n"
   [M]	.hgignore
   [A]	added
   [A]	modified -> copied
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-storage.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,46 @@
+# This test verifies the conformance of various classes to various
+# storage interfaces.
+from __future__ import absolute_import
+
+import silenttestrunner
+
+from mercurial import (
+    filelog,
+    transaction,
+    ui as uimod,
+    vfs as vfsmod,
+)
+
+from mercurial.testing import (
+    storage as storagetesting,
+)
+
+STATE = {
+    'lastindex': 0,
+    'ui': uimod.ui(),
+    'vfs': vfsmod.vfs(b'.', realpath=True),
+}
+
+def makefilefn(self):
+    """Factory for filelog instances."""
+    fl = filelog.filelog(STATE['vfs'], 'filelog-%d' % STATE['lastindex'])
+    STATE['lastindex'] += 1
+    return fl
+
+def maketransaction(self):
+    vfsmap = {'plain': STATE['vfs']}
+
+    return transaction.transaction(STATE['ui'].warn, STATE['vfs'], vfsmap,
+                                  'journal', 'undo')
+
+# Assigning module-level attributes that inherit from unittest.TestCase
+# is all that is needed to register tests.
+filelogindextests = storagetesting.makeifileindextests(makefilefn,
+                                                       maketransaction)
+filelogdatatests = storagetesting.makeifiledatatests(makefilefn,
+                                                     maketransaction)
+filelogmutationtests = storagetesting.makeifilemutationtests(makefilefn,
+                                                             maketransaction)
+
+if __name__ == '__main__':
+    silenttestrunner.main(__name__)
--- a/tests/test-stream-bundle-v2.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-stream-bundle-v2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -88,6 +88,7 @@
   transferred 1.65 KB in \d\.\d seconds \(.*/sec\) (re)
   bundle2-input-part: total payload size 1840
   bundle2-input-bundle: 0 parts total
+  updating the branch cache
   finished applying clone bundle
   query 1; heads
   sending batch command
@@ -142,6 +143,7 @@
   transferred 1.65 KB in *.* seconds (*/sec) (glob)
   bundle2-input-part: total payload size 1840
   bundle2-input-bundle: 0 parts total
+  updating the branch cache
   finished applying clone bundle
   query 1; heads
   sending batch command
--- a/tests/test-strict.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-strict.t	Wed Sep 26 20:33:09 2018 +0900
@@ -15,29 +15,7 @@
 
   $ hg an a
   hg: unknown command 'an'
-  Mercurial Distributed SCM
-  
-  basic commands:
-  
-   add           add the specified files on the next commit
-   annotate      show changeset information by line for each file
-   clone         make a copy of an existing repository
-   commit        commit the specified files or all outstanding changes
-   diff          diff repository (or selected files)
-   export        dump the header and diffs for one or more changesets
-   forget        forget the specified files on the next commit
-   init          create a new repository in the given directory
-   log           show revision history of entire repository or files
-   merge         merge another revision into working directory
-   pull          pull changes from the specified source
-   push          push changes to the specified destination
-   remove        remove the specified files on the next commit
-   serve         start stand-alone webserver
-   status        show changed files in the working directory
-   summary       summarize working directory state
-   update        update working directory (or switch revisions)
-  
-  (use 'hg help' for the full list of commands or 'hg -v' for details)
+  (use 'hg help' for a list of commands)
   [255]
   $ hg annotate a
   0: a
--- a/tests/test-strip-cross.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-strip-cross.t	Wed Sep 26 20:33:09 2018 +0900
@@ -103,7 +103,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  7 files, 4 changesets, 15 total revisions
+  checked 4 changesets with 15 changes to 7 files
   
   % Trying to strip revision 1
   saved backup bundle to $TESTTMP/1/.hg/strip-backup/*-backup.hg (glob)
@@ -112,7 +112,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  7 files, 4 changesets, 14 total revisions
+  checked 4 changesets with 14 changes to 7 files
   
   % Trying to strip revision 2
   saved backup bundle to $TESTTMP/2/.hg/strip-backup/*-backup.hg (glob)
@@ -121,7 +121,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  7 files, 4 changesets, 14 total revisions
+  checked 4 changesets with 14 changes to 7 files
   
   % Trying to strip revision 3
   saved backup bundle to $TESTTMP/3/.hg/strip-backup/*-backup.hg (glob)
@@ -130,7 +130,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  7 files, 4 changesets, 19 total revisions
+  checked 4 changesets with 19 changes to 7 files
   
   % Trying to strip revision 4
   saved backup bundle to $TESTTMP/4/.hg/strip-backup/*-backup.hg (glob)
@@ -139,5 +139,5 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  7 files, 4 changesets, 19 total revisions
+  checked 4 changesets with 19 changes to 7 files
   
--- a/tests/test-strip.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-strip.t	Wed Sep 26 20:33:09 2018 +0900
@@ -220,7 +220,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 1 files (+1 heads)
-  new changesets 264128213d29
+  new changesets 264128213d29 (1 drafts)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ rm .hg/strip-backup/*
   $ hg log --graph
@@ -1123,7 +1123,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
-  new changesets 35358f982181:4cf5e92caec2
+  new changesets 35358f982181:4cf5e92caec2 (2 drafts)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg strip -k -r 35358f982181
--- a/tests/test-subrepo-missing.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-subrepo-missing.t	Wed Sep 26 20:33:09 2018 +0900
@@ -114,7 +114,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
   checking subrepo links
   subrepo 'subrepo' is hidden in revision a66de08943b6
   subrepo 'subrepo' is hidden in revision 674d05939c1e
@@ -128,7 +128,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 5 changesets, 5 total revisions
+  checked 5 changesets with 5 changes to 2 files
   checking subrepo links
   0: repository $TESTTMP/repo/subrepo not found
   1: repository $TESTTMP/repo/subrepo not found
--- a/tests/test-subrepo-svn.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-subrepo-svn.t	Wed Sep 26 20:33:09 2018 +0900
@@ -2,9 +2,9 @@
 
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
-  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`"$PYTHON" -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
 
   $ filter_svn_output () {
@@ -247,7 +247,7 @@
 
 verify subrepo is contained within the repo directory
 
-  $ $PYTHON -c "import os.path; print os.path.exists('s')"
+  $ "$PYTHON" -c "from __future__ import print_function; import os.path; print(os.path.exists('s'))"
   True
 
 update to nullrev (must delete the subrepo)
--- a/tests/test-subrepo.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-subrepo.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1066,19 +1066,18 @@
   $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
   [
    {
-    "abspath": "foo",
     "data": "test\ntest\n",
-    "path": "sub/repo/foo"
+    "path": "foo"
    }
   ]
 
  non-exact match:
 
-  $ hg cat -T '{path}\n' 'glob:**'
+  $ hg cat -T '{path|relpath}\n' 'glob:**'
   .hgsub
   .hgsubstate
   sub/repo/foo
-  $ hg cat -T '{path}\n' 're:^sub'
+  $ hg cat -T '{path|relpath}\n' 're:^sub'
   sub/repo/foo
 
  missing subrepos in working directory:
--- a/tests/test-symlink-os-yes-fs-no.py.out	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-symlink-os-yes-fs-no.py.out	Wed Sep 26 20:33:09 2018 +0900
@@ -2,11 +2,11 @@
 adding manifests
 adding file changes
 added 1 changesets with 4 changes to 4 files
-new changesets d326ae2d01ee
+new changesets d326ae2d01ee (1 drafts)
 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 adding changesets
 adding manifests
 adding file changes
 added 1 changesets with 4 changes to 4 files
-new changesets d326ae2d01ee
+new changesets d326ae2d01ee (1 drafts)
 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-tag.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-tag.t	Wed Sep 26 20:33:09 2018 +0900
@@ -230,7 +230,7 @@
 Issue601: hg tag doesn't do the right thing if .hgtags or localtags
 doesn't end with EOL
 
-  $ $PYTHON << EOF
+  $ "$PYTHON" << EOF
   > f = open('.hg/localtags'); last = f.readlines()[-1][:-1]; f.close()
   > f = open('.hg/localtags', 'w'); f.write(last); f.close()
   > EOF
@@ -242,7 +242,7 @@
   c2899151f4e76890c602a2597a650a72666681bf localnewline
   
 
-  $ $PYTHON << EOF
+  $ "$PYTHON" << EOF
   > f = open('.hgtags'); last = f.readlines()[-1][:-1]; f.close()
   > f = open('.hgtags', 'w'); f.write(last); f.close()
   > EOF
--- a/tests/test-template-functions.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-template-functions.t	Wed Sep 26 20:33:09 2018 +0900
@@ -892,6 +892,11 @@
   $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
   4:107
 
+  $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n'
+  4:x10
+  $ hg --config experimental.revisions.prefixhexnode=yes log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
+  4:x10
+
  node 'c562' should be unique if the other 'c562' nodes are hidden
  (but we don't try the slow path to filter out hidden nodes for now)
 
@@ -1193,6 +1198,12 @@
   
   0
   
+
+  $ hg log -l1 -T "{files('aa') % '{file}\n'}"
+  aa
+  $ hg log -l1 -T "{files('aa') % '{path}\n'}"
+  aa
+
   $ hg rm a
   $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
   2147483647
@@ -1382,7 +1393,7 @@
 
   $ hg init nonascii
   $ cd nonascii
-  $ $PYTHON <<EOF
+  $ "$PYTHON" <<EOF
   > open('latin1', 'wb').write(b'\xe9')
   > open('utf-8', 'wb').write(b'\xc3\xa9')
   > EOF
--- a/tests/test-template-keywords.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-template-keywords.t	Wed Sep 26 20:33:09 2018 +0900
@@ -52,11 +52,34 @@
   $ hg log -r 'wdir()' -T '{rev}:{node}\n'
   2147483647:ffffffffffffffffffffffffffffffffffffffff
 
-Some keywords are invalid for working-directory revision, but they should
-never cause crash:
+  $ hg log -r 'wdir()' -Tjson --debug
+  [
+   {
+    "added": [],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [0, 0],
+    "desc": "",
+    "extra": {"branch": "default"},
+    "manifest": "ffffffffffffffffffffffffffffffffffffffff",
+    "modified": [],
+    "node": "ffffffffffffffffffffffffffffffffffffffff",
+    "parents": ["95c24699272ef57d062b8bccc32c878bf841784a"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 2147483647,
+    "tags": [],
+    "user": "test"
+   }
+  ]
 
   $ hg log -r 'wdir()' -T '{manifest}\n'
-  
+  2147483647:ffffffffffff
+
+Changectx-derived keywords are disabled within {manifest} as {node} changes:
+
+  $ hg log -r0 -T 'outer:{p1node} {manifest % "inner:{p1node}"}\n'
+  outer:0000000000000000000000000000000000000000 inner:
 
 Check that {phase} works correctly on parents:
 
@@ -91,7 +114,7 @@
   $ for key in author branch branches date desc file_adds file_dels file_mods \
   >         file_copies file_copies_switch files \
   >         manifest node parents rev tags diffstat extras \
-  >         p1rev p2rev p1node p2node; do
+  >         p1rev p2rev p1node p2node user; do
   >     for mode in '' --verbose --debug; do
   >         hg log $mode --template "$key$mode: {$key}\n"
   >     done
@@ -702,6 +725,33 @@
   p2node--debug: 0000000000000000000000000000000000000000
   p2node--debug: 0000000000000000000000000000000000000000
   p2node--debug: 0000000000000000000000000000000000000000
+  user: test
+  user: User Name <user@hostname>
+  user: person
+  user: person
+  user: person
+  user: person
+  user: other@place
+  user: A. N. Other <other@place>
+  user: User Name <user@hostname>
+  user--verbose: test
+  user--verbose: User Name <user@hostname>
+  user--verbose: person
+  user--verbose: person
+  user--verbose: person
+  user--verbose: person
+  user--verbose: other@place
+  user--verbose: A. N. Other <other@place>
+  user--verbose: User Name <user@hostname>
+  user--debug: test
+  user--debug: User Name <user@hostname>
+  user--debug: person
+  user--debug: person
+  user--debug: person
+  user--debug: person
+  user--debug: other@place
+  user--debug: A. N. Other <other@place>
+  user--debug: User Name <user@hostname>
 
 Add a dummy commit to make up for the instability of the above:
 
@@ -718,6 +768,64 @@
   $ hg rm a
   $ hg ci -m "Modify, add, remove, rename"
 
+Test files list:
+
+  $ hg log -l1 -T '{join(file_mods, " ")}\n'
+  third
+  $ hg log -l1 -T '{file_mods % "{file}\n"}'
+  third
+  $ hg log -l1 -T '{file_mods % "{path}\n"}'
+  third
+
+  $ hg log -l1 -T '{join(files, " ")}\n'
+  a b fifth fourth third
+  $ hg log -l1 -T '{files % "{file}\n"}'
+  a
+  b
+  fifth
+  fourth
+  third
+  $ hg log -l1 -T '{files % "{path}\n"}'
+  a
+  b
+  fifth
+  fourth
+  third
+
+Test file copies dict:
+
+  $ hg log -r8 -T '{join(file_copies, " ")}\n'
+  fourth (second)
+  $ hg log -r8 -T '{file_copies % "{name} <- {source}\n"}'
+  fourth <- second
+  $ hg log -r8 -T '{file_copies % "{path} <- {source}\n"}'
+  fourth <- second
+
+  $ hg log -r8 -T '{join(file_copies_switch, " ")}\n'
+  
+  $ hg log -r8 -C -T '{join(file_copies_switch, " ")}\n'
+  fourth (second)
+  $ hg log -r8 -C -T '{file_copies_switch % "{name} <- {source}\n"}'
+  fourth <- second
+  $ hg log -r8 -C -T '{file_copies_switch % "{path} <- {source}\n"}'
+  fourth <- second
+
+Test file attributes:
+
+  $ hg log -l1 -T '{files % "{status} {pad(size, 3, left=True)} {path}\n"}'
+  R     a
+  A   0 b
+  A   7 fifth
+  R     fourth
+  M  13 third
+
+Test file status including clean ones:
+
+  $ hg log -r9 -T '{files("**") % "{status} {path}\n"}'
+  A a
+  C fourth
+  C third
+
 Test index keyword:
 
   $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
--- a/tests/test-template-map.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-template-map.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1756,5 +1756,5 @@
 
   $ hg -R latesttag log -r tip --style=style1989
   M|test
-  11,test
+  11,
   branch: test
--- a/tests/test-tools.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-tools.t	Wed Sep 26 20:33:09 2018 +0900
@@ -51,10 +51,10 @@
 #endif
 
 #if no-windows
-  $ $PYTHON $TESTDIR/seq.py 10 > bar
+  $ "$PYTHON" $TESTDIR/seq.py 10 > bar
 #else
 Convert CRLF -> LF for consistency
-  $ $PYTHON $TESTDIR/seq.py 10 | sed "s/$//" > bar
+  $ "$PYTHON" $TESTDIR/seq.py 10 | sed "s/$//" > bar
 #endif
 
 #if unix-permissions symlink
--- a/tests/test-transplant.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-transplant.t	Wed Sep 26 20:33:09 2018 +0900
@@ -758,7 +758,7 @@
   $ cd twin2
   $ echo '[patch]' >> .hg/hgrc
   $ echo 'eol = crlf' >> .hg/hgrc
-  $ $PYTHON -c "open('b', 'wb').write(b'b\r\nb\r\n')"
+  $ "$PYTHON" -c "open('b', 'wb').write(b'b\r\nb\r\n')"
   $ hg ci -Am addb
   adding b
   $ hg transplant -s ../twin1 tip
--- a/tests/test-treemanifest.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-treemanifest.t	Wed Sep 26 20:33:09 2018 +0900
@@ -1,6 +1,6 @@
   $ cat << EOF >> $HGRCPATH
   > [ui]
-  > ssh=$PYTHON "$TESTDIR/dummyssh"
+  > ssh="$PYTHON" "$TESTDIR/dummyssh"
   > EOF
 
 Set up repo
@@ -341,7 +341,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  new changesets 51cfd7b1e13b
+  new changesets 51cfd7b1e13b (1 drafts)
   (run 'hg update' to get a working copy)
   $ hg --config extensions.strip= strip tip
   saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/*-backup.hg (glob)
@@ -410,7 +410,7 @@
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  10 files, 11 changesets, 15 total revisions
+  checked 11 changesets with 15 changes to 10 files
 
 Create deeper repo with tree manifests.
 
@@ -578,7 +578,7 @@
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
 
 #if repofncache
 Dirlogs are included in fncache
@@ -636,7 +636,7 @@
    b/bar/orange/fly/housefly.txt@0: in changeset but not in manifest
    b/foo/apple/bees/flower.py@0: in changeset but not in manifest
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
   6 warnings encountered! (reporevlogstore !)
   9 integrity errors encountered!
   (first damaged changeset appears to be 0)
@@ -661,7 +661,7 @@
    (expected None)
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
   2 warnings encountered!
   8 integrity errors encountered!
   (first damaged changeset appears to be 2)
@@ -718,7 +718,7 @@
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
   $ cd ..
 
 #if reporevlogstore
@@ -766,7 +766,7 @@
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
 
 Local clone with encodedstore
   $ hg clone -U deeprepo-encodedstore local-clone-encodedstore
@@ -776,7 +776,7 @@
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
 
 Local clone with fncachestore
   $ hg clone -U deeprepo local-clone-fncachestore
@@ -786,55 +786,49 @@
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
 
 Stream clone with basicstore
   $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT1 stream-clone-basicstore
   streaming all changes
-  18 files to transfer, * of data (glob)
+  21 files to transfer, * of data (glob)
   transferred * in * seconds (*) (glob)
-  searching for changes
-  no changes found
   $ hg -R stream-clone-basicstore verify
   checking changesets
   checking manifests
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
 
 Stream clone with encodedstore
   $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT2 stream-clone-encodedstore
   streaming all changes
-  18 files to transfer, * of data (glob)
+  21 files to transfer, * of data (glob)
   transferred * in * seconds (*) (glob)
-  searching for changes
-  no changes found
   $ hg -R stream-clone-encodedstore verify
   checking changesets
   checking manifests
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
 
 Stream clone with fncachestore
   $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT stream-clone-fncachestore
   streaming all changes
-  18 files to transfer, * of data (glob)
+  22 files to transfer, * of data (glob)
   transferred * in * seconds (*) (glob)
-  searching for changes
-  no changes found
   $ hg -R stream-clone-fncachestore verify
   checking changesets
   checking manifests
   checking directory manifests
   crosschecking files in changesets and manifests
   checking files
-  8 files, 4 changesets, 18 total revisions
+  checked 4 changesets with 18 changes to 8 files
 
 Packed bundle
   $ hg -R deeprepo debugcreatestreamclonebundle repo-packed.hg
--- a/tests/test-unionrepo.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-unionrepo.t	Wed Sep 26 20:33:09 2018 +0900
@@ -128,7 +128,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 11 changes to 6 files (+1 heads)
-  new changesets f093fec0529b:2f0d178c469c
+  new changesets f093fec0529b:2f0d178c469c (6 drafts)
 
   $ hg -R repo3 paths
   default = union:repo1+repo2
@@ -138,7 +138,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  6 files, 6 changesets, 11 total revisions
+  checked 6 changesets with 11 changes to 6 files
 
   $ hg -R repo3 heads --template '{rev}:{node|short}  {desc|firstline}\n'
   5:2f0d178c469c  repo2-3
--- a/tests/test-update-names.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-update-names.t	Wed Sep 26 20:33:09 2018 +0900
@@ -50,7 +50,8 @@
   $ hg st
   ? name/file
   $ hg up 1
-  abort: Directory not empty: '$TESTTMP/r1/r2/name'
+  abort: Unlinking directory not permitted: *$TESTTMP/r1/r2/name* (glob) (windows !)
+  abort: Directory not empty: '?\$TESTTMP/r1/r2/name'? (re) (no-windows !)
   [255]
   $ cat name/file
   text
--- a/tests/test-upgrade-repo.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-upgrade-repo.t	Wed Sep 26 20:33:09 2018 +0900
@@ -406,7 +406,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 3 changesets, 3 total revisions
+  checked 3 changesets with 3 changes to 3 files
 
 old store should be backed up
 
@@ -613,7 +613,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  2 files, 2 changesets, 2 total revisions
+  checked 2 changesets with 2 changes to 2 files
   $ hg debugdata lfs.bin 0
   version https://git-lfs.github.com/spec/v1
   oid sha256:d0beab232adff5ba365880366ad30b1edb85c4c5372442b5d2fe27adc96d653f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-util.py	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,137 @@
+# unit tests for mercuril.util utilities
+from __future__ import absolute_import
+
+import contextlib
+import itertools
+import unittest
+
+from mercurial import pycompat, util, utils
+
+@contextlib.contextmanager
+def mocktimer(incr=0.1, *additional_targets):
+    """Replaces util.timer and additional_targets with a mock
+
+    The timer starts at 0. On each call the time incremented by the value
+    of incr. If incr is an iterable, then the time is incremented by the
+    next value from that iterable, looping in a cycle when reaching the end.
+
+    additional_targets must be a sequence of (object, attribute_name) tuples;
+    the mock is set with setattr(object, attribute_name, mock).
+
+    """
+    time = [0]
+    try:
+        incr = itertools.cycle(incr)
+    except TypeError:
+        incr = itertools.repeat(incr)
+
+    def timer():
+        time[0] += next(incr)
+        return time[0]
+
+    # record original values
+    orig = util.timer
+    additional_origs = [(o, a, getattr(o, a)) for o, a in additional_targets]
+
+    # mock out targets
+    util.timer = timer
+    for obj, attr in additional_targets:
+        setattr(obj, attr, timer)
+
+    try:
+        yield
+    finally:
+        # restore originals
+        util.timer = orig
+        for args in additional_origs:
+            setattr(*args)
+
+# attr.s default factory for util.timedstats.start binds the timer we
+# need to mock out.
+_start_default = (util.timedcmstats.start.default, 'factory')
+
+@contextlib.contextmanager
+def capturestderr():
+    """Replace utils.procutil.stderr with a pycompat.bytesio instance
+
+    The instance is made available as the return value of __enter__.
+
+    This contextmanager is reentrant.
+
+    """
+    orig = utils.procutil.stderr
+    utils.procutil.stderr = pycompat.bytesio()
+    try:
+        yield utils.procutil.stderr
+    finally:
+        utils.procutil.stderr = orig
+
+class timedtests(unittest.TestCase):
+    def testtimedcmstatsstr(self):
+        stats = util.timedcmstats()
+        self.assertEqual(str(stats), '<unknown>')
+        self.assertEqual(bytes(stats), b'<unknown>')
+        stats.elapsed = 12.34
+        self.assertEqual(str(stats), pycompat.sysstr(util.timecount(12.34)))
+        self.assertEqual(bytes(stats), util.timecount(12.34))
+
+    def testtimedcmcleanexit(self):
+        # timestamps 1, 4, elapsed time of 4 - 1 = 3
+        with mocktimer([1, 3], _start_default):
+            with util.timedcm('pass') as stats:
+                # actual context doesn't matter
+                pass
+
+        self.assertEqual(stats.start, 1)
+        self.assertEqual(stats.elapsed, 3)
+        self.assertEqual(stats.level, 1)
+
+    def testtimedcmnested(self):
+        # timestamps 1, 3, 6, 10, elapsed times of 6 - 3 = 3 and 10 - 1 = 9
+        with mocktimer([1, 2, 3, 4], _start_default):
+            with util.timedcm('outer') as outer_stats:
+                with util.timedcm('inner') as inner_stats:
+                    # actual context doesn't matter
+                    pass
+
+        self.assertEqual(outer_stats.start, 1)
+        self.assertEqual(outer_stats.elapsed, 9)
+        self.assertEqual(outer_stats.level, 1)
+
+        self.assertEqual(inner_stats.start, 3)
+        self.assertEqual(inner_stats.elapsed, 3)
+        self.assertEqual(inner_stats.level, 2)
+
+    def testtimedcmexception(self):
+        # timestamps 1, 4, elapsed time of 4 - 1 = 3
+        with mocktimer([1, 3], _start_default):
+            try:
+                with util.timedcm('exceptional') as stats:
+                    raise ValueError()
+            except ValueError:
+                pass
+
+        self.assertEqual(stats.start, 1)
+        self.assertEqual(stats.elapsed, 3)
+        self.assertEqual(stats.level, 1)
+
+    def testtimeddecorator(self):
+        @util.timed
+        def testfunc(callcount=1):
+            callcount -= 1
+            if callcount:
+                testfunc(callcount)
+
+        # timestamps 1, 2, 3, 4, elapsed time of 3 - 2 = 1 and 4 - 1 = 3
+        with mocktimer(1, _start_default):
+            with capturestderr() as out:
+                testfunc(2)
+
+        self.assertEqual(out.getvalue(), (
+            b'    testfunc: 1.000 s\n'
+            b'  testfunc: 3.000 s\n'
+        ))
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
--- a/tests/test-verify.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-verify.t	Wed Sep 26 20:33:09 2018 +0900
@@ -20,7 +20,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 1 changesets, 3 total revisions
+  checked 1 changesets with 3 changes to 3 files
 
 verify with journal
 
@@ -31,7 +31,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  3 files, 1 changesets, 3 total revisions
+  checked 1 changesets with 3 changes to 3 files
   $ rm .hg/store/journal
 
 introduce some bugs in repo
@@ -55,7 +55,7 @@
    warning: revlog 'data/bar.txt.i' not in fncache!
    0: empty or missing bar.txt
    bar.txt@0: manifest refers to unknown revision 256559129457
-  3 files, 1 changesets, 0 total revisions
+  checked 1 changesets with 0 changes to 3 files
   3 warnings encountered!
   hint: run "hg debugrebuildfncache" to recover from corrupt fncache
   6 integrity errors encountered!
@@ -280,7 +280,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  0 files, 1 changesets, 0 total revisions
+  checked 1 changesets with 0 changes to 0 files
 
 test revlog corruption
 
@@ -299,7 +299,7 @@
   checking files
    a@1: broken revlog! (index data/a.i is corrupted)
   warning: orphan data file 'data/a.i'
-  1 files, 2 changesets, 0 total revisions
+  checked 2 changesets with 0 changes to 1 files
   1 warnings encountered!
   1 integrity errors encountered!
   (first damaged changeset appears to be 1)
@@ -317,7 +317,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   $ cd ..
 
 test flag processor and skipflags
@@ -335,7 +335,7 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
   $ cat >> $TESTTMP/break-base64.py <<EOF
   > from __future__ import absolute_import
@@ -352,7 +352,7 @@
   crosschecking files in changesets and manifests
   checking files
    base64@0: unpacking 794cee7777cb: integrity check failed on data/base64.i:0
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
   1 integrity errors encountered!
   (first damaged changeset appears to be 0)
   [1]
@@ -361,5 +361,5 @@
   checking manifests
   crosschecking files in changesets and manifests
   checking files
-  1 files, 1 changesets, 1 total revisions
+  checked 1 changesets with 1 changes to 1 files
 
--- a/tests/test-walk.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-walk.t	Wed Sep 26 20:33:09 2018 +0900
@@ -603,13 +603,13 @@
 
 Test listfile and listfile0
 
-  $ $PYTHON -c "open('listfile0', 'wb').write(b'fenugreek\0new\0')"
+  $ "$PYTHON" -c "open('listfile0', 'wb').write(b'fenugreek\0new\0')"
   $ hg debugwalk -v -I 'listfile0:listfile0'
   * matcher:
   <includematcher includes='(?:fenugreek(?:/|$)|new(?:/|$))'>
   f  fenugreek  fenugreek
   f  new        new
-  $ $PYTHON -c "open('listfile', 'wb').write(b'fenugreek\nnew\r\nmammals/skunk\n')"
+  $ "$PYTHON" -c "open('listfile', 'wb').write(b'fenugreek\nnew\r\nmammals/skunk\n')"
   $ hg debugwalk -v -I 'listfile:listfile'
   * matcher:
   <includematcher includes='(?:fenugreek(?:/|$)|new(?:/|$)|mammals/skunk(?:/|$))'>
@@ -644,7 +644,7 @@
   > for i in range(20000 // 100):
   >   print('x' * 100)
   > EOF
-  $ $PYTHON printnum.py >> overflow.list
+  $ "$PYTHON" printnum.py >> overflow.list
   $ echo fenugreek >> overflow.list
   $ hg debugwalk 'listfile:overflow.list' 2>&1 | egrep -v '^xxx'
   f  fennel     fennel     exact
--- a/tests/test-win32text.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-win32text.t	Wed Sep 26 20:33:09 2018 +0900
@@ -28,7 +28,7 @@
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cp .hg/hgrc ../zoz/.hg
-  $ $PYTHON unix2dos.py f
+  $ "$PYTHON" unix2dos.py f
 
 commit should fail
 
@@ -102,7 +102,7 @@
 
   $ mkdir d
   $ echo hello > d/f2
-  $ $PYTHON unix2dos.py d/f2
+  $ "$PYTHON" unix2dos.py d/f2
   $ hg add d/f2
   $ hg ci -m 3
   attempt to commit or push text file(s) using CRLF line endings
@@ -118,7 +118,7 @@
   $ hg rem f
   $ hg ci -m 4
 
-  $ $PYTHON -c 'open("bin", "wb").write(b"hello\x00\x0D\x0A")'
+  $ "$PYTHON" -c 'open("bin", "wb").write(b"hello\x00\x0D\x0A")'
   $ hg add bin
   $ hg ci -m 5
   $ hg log -v
@@ -181,7 +181,7 @@
   adding dupe/b
   adding dupe/c
   adding dupe/d
-  $ $PYTHON unix2dos.py dupe/b dupe/c dupe/d
+  $ "$PYTHON" unix2dos.py dupe/b dupe/c dupe/d
   $ hg -R dupe ci -m a dupe/a
   $ hg -R dupe ci -m b/c dupe/[bc]
   $ hg -R dupe ci -m d dupe/d
@@ -342,7 +342,7 @@
   
   $ rm .hg/hgrc
   $ (echo some; echo text) > f3
-  $ $PYTHON -c 'open("f4.bat", "wb").write(b"rem empty\x0D\x0A")'
+  $ "$PYTHON" -c 'open("f4.bat", "wb").write(b"rem empty\x0D\x0A")'
   $ hg add f3 f4.bat
   $ hg ci -m 6
   $ cat bin
@@ -395,7 +395,7 @@
   $ cat f4.bat
   rem empty\r (esc)
 
-  $ $PYTHON -c 'open("f5.sh", "wb").write(b"# empty\x0D\x0A")'
+  $ "$PYTHON" -c 'open("f5.sh", "wb").write(b"# empty\x0D\x0A")'
   $ hg add f5.sh
   $ hg ci -m 7
   $ cat f5.sh
--- a/tests/test-wireproto-command-branchmap.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-wireproto-command-branchmap.t	Wed Sep 26 20:33:09 2018 +0900
@@ -43,7 +43,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending branchmap command
-  s>     POST /api/exp-http-v2-0001/ro/branchmap HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/branchmap HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -59,14 +59,34 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     83\r\n
-  s>     {\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\xa3Gbranch1\x81T\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88Gbranch2\x81T"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfcGdefault\x82T&\x80Z\xba\x1e`\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     78\r\n
+  s>     p\x00\x00\x01\x00\x02\x001
+  s>     \xa3Gbranch1\x81T\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88Gbranch2\x81T"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfcGdefault\x82T&\x80Z\xba\x1e`\n
   s>     \x82\xe96a\x14\x9f#\x13\x86j"\x1a{T\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82
   s>     \r\n
-  received frame(size=123; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=112; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: {b'branch1': [b'\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88'], b'branch2': [b'"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfc'], b'default': [b'&\x80Z\xba\x1e`\n\x82\xe96a\x14\x9f#\x13\x86j"\x1a{', b'\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82']}
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: {
+    b'branch1': [
+      b'\xb5\xfa\xac\xdf\xd2c7h\xcb1R3l\xc0\x953\x81&f\x88'
+    ],
+    b'branch2': [
+      b'"Aa\xc7X\x9a\xa4\x8f\xa8:H\xfe\xff^\x95\xb5j\xe3\'\xfc'
+    ],
+    b'default': [
+      b'&\x80Z\xba\x1e`\n\x82\xe96a\x14\x9f#\x13\x86j"\x1a{',
+      b'\xbe\x0e\xf7<\x17\xad\xe3\xfc\x89\xdcAp\x1e\xb9\xfc:\x91\xb5\x82\x82'
+    ]
+  }
 
   $ cat error.log
--- a/tests/test-wireproto-command-capabilities.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-wireproto-command-capabilities.t	Wed Sep 26 20:33:09 2018 +0900
@@ -34,7 +34,7 @@
   s>     Content-Type: application/mercurial-0.1\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
 
 A proper request without the API server enabled returns the legacy response
 
@@ -59,7 +59,7 @@
   s>     Content-Type: application/mercurial-0.1\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
 
 Restart with just API server enabled. This enables serving the new format.
 
@@ -95,7 +95,7 @@
   s>     Content-Type: application/mercurial-0.1\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
 
 X-HgUpgrade-<N> without known serialization in X-HgProto-<N> uses legacy response
 
@@ -120,7 +120,7 @@
   s>     Content-Type: application/mercurial-0.1\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
 
 X-HgUpgrade-<N> + X-HgProto-<N> headers trigger new response format
 
@@ -145,8 +145,12 @@
   s>     Content-Type: application/mercurial-cbor\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     \xa3Dapis\xa0GapibaseDapi/Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
-  cbor> {b'apibase': b'api/', b'apis': {}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
+  s>     \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  cbor> {
+    b'apibase': b'api/',
+    b'apis': {},
+    b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
+  }
 
 Restart server to enable HTTPv2
 
@@ -178,15 +182,19 @@
   s>     Content-Type: application/mercurial-cbor\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     \xa3Dapis\xa0GapibaseDapi/Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
-  cbor> {b'apibase': b'api/', b'apis': {}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
+  s>     \xa3GapibaseDapi/Dapis\xa0Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  cbor> {
+    b'apibase': b'api/',
+    b'apis': {},
+    b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
+  }
 
 Request for HTTPv2 service returns information about it
 
   $ sendhttpraw << EOF
   > httprequest GET ?cmd=capabilities
   >    user-agent: test
-  >    x-hgupgrade-1: exp-http-v2-0001 foo bar
+  >    x-hgupgrade-1: exp-http-v2-0002 foo bar
   >    x-hgproto-1: cbor
   > EOF
   using raw connection to peer
@@ -194,7 +202,7 @@
   s>     Accept-Encoding: identity\r\n
   s>     user-agent: test\r\n
   s>     x-hgproto-1: cbor\r\n
-  s>     x-hgupgrade-1: exp-http-v2-0001 foo bar\r\n
+  s>     x-hgupgrade-1: exp-http-v2-0002 foo bar\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
   s>     \r\n
   s> makefile('rb', None)
@@ -204,8 +212,205 @@
   s>     Content-Type: application/mercurial-cbor\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x81\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0005GapibaseDapi/Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
-  cbor> {b'apibase': b'api/', b'apis': {b'exp-http-v2-0001': {b'commands': {b'branchmap': {b'args': {}, b'permissions': [b'pull']}, b'capabilities': {b'args': {}, b'permissions': [b'pull']}, b'heads': {b'args': {b'publiconly': False}, b'permissions': [b'pull']}, b'known': {b'args': {b'nodes': [b'deadbeef']}, b'permissions': [b'pull']}, b'listkeys': {b'args': {b'namespace': b'ns'}, b'permissions': [b'pull']}, b'lookup': {b'args': {b'key': b'foo'}, b'permissions': [b'pull']}, b'pushkey': {b'args': {b'key': b'key', b'namespace': b'ns', b'new': b'new', b'old': b'old'}, b'permissions': [b'push']}}, b'compression': [{b'name': b'zlib'}], b'framingmediatypes': [b'application/mercurial-exp-framing-0005'], b'rawrepoformats': [b'generaldelta', b'revlogv1']}}, b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'}
+  s>     \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  cbor> {
+    b'apibase': b'api/',
+    b'apis': {
+      b'exp-http-v2-0002': {
+        b'commands': {
+          b'branchmap': {
+            b'args': {},
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'capabilities': {
+            b'args': {},
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'changesetdata': {
+            b'args': {
+              b'fields': {
+                b'default': set([]),
+                b'required': False,
+                b'type': b'set',
+                b'validvalues': set([
+                  b'bookmarks',
+                  b'parents',
+                  b'phase',
+                  b'revision'
+                ])
+              },
+              b'noderange': {
+                b'default': None,
+                b'required': False,
+                b'type': b'list'
+              },
+              b'nodes': {
+                b'default': None,
+                b'required': False,
+                b'type': b'list'
+              },
+              b'nodesdepth': {
+                b'default': None,
+                b'required': False,
+                b'type': b'int'
+              }
+            },
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'filedata': {
+            b'args': {
+              b'fields': {
+                b'default': set([]),
+                b'required': False,
+                b'type': b'set',
+                b'validvalues': set([
+                  b'parents',
+                  b'revision'
+                ])
+              },
+              b'haveparents': {
+                b'default': False,
+                b'required': False,
+                b'type': b'bool'
+              },
+              b'nodes': {
+                b'required': True,
+                b'type': b'list'
+              },
+              b'path': {
+                b'required': True,
+                b'type': b'bytes'
+              }
+            },
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'heads': {
+            b'args': {
+              b'publiconly': {
+                b'default': False,
+                b'required': False,
+                b'type': b'bool'
+              }
+            },
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'known': {
+            b'args': {
+              b'nodes': {
+                b'default': [],
+                b'required': False,
+                b'type': b'list'
+              }
+            },
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'listkeys': {
+            b'args': {
+              b'namespace': {
+                b'required': True,
+                b'type': b'bytes'
+              }
+            },
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'lookup': {
+            b'args': {
+              b'key': {
+                b'required': True,
+                b'type': b'bytes'
+              }
+            },
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'manifestdata': {
+            b'args': {
+              b'fields': {
+                b'default': set([]),
+                b'required': False,
+                b'type': b'set',
+                b'validvalues': set([
+                  b'parents',
+                  b'revision'
+                ])
+              },
+              b'haveparents': {
+                b'default': False,
+                b'required': False,
+                b'type': b'bool'
+              },
+              b'nodes': {
+                b'required': True,
+                b'type': b'list'
+              },
+              b'tree': {
+                b'required': True,
+                b'type': b'bytes'
+              }
+            },
+            b'permissions': [
+              b'pull'
+            ]
+          },
+          b'pushkey': {
+            b'args': {
+              b'key': {
+                b'required': True,
+                b'type': b'bytes'
+              },
+              b'namespace': {
+                b'required': True,
+                b'type': b'bytes'
+              },
+              b'new': {
+                b'required': True,
+                b'type': b'bytes'
+              },
+              b'old': {
+                b'required': True,
+                b'type': b'bytes'
+              }
+            },
+            b'permissions': [
+              b'push'
+            ]
+          }
+        },
+        b'compression': [
+          {
+            b'name': b'zlib'
+          }
+        ],
+        b'framingmediatypes': [
+          b'application/mercurial-exp-framing-0005'
+        ],
+        b'pathfilterprefixes': set([
+          b'path:',
+          b'rootfilesin:'
+        ]),
+        b'rawrepoformats': [
+          b'generaldelta',
+          b'revlogv1'
+        ]
+      }
+    },
+    b'v1capabilities': b'batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash'
+  }
 
 capabilities command returns expected info
 
@@ -217,7 +422,7 @@
   s>     Accept-Encoding: identity\r\n
   s>     vary: X-HgProto-1,X-HgUpgrade-1\r\n
   s>     x-hgproto-1: cbor\r\n
-  s>     x-hgupgrade-1: exp-http-v2-0001\r\n
+  s>     x-hgupgrade-1: exp-http-v2-0002\r\n
   s>     accept: application/mercurial-0.1\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
   s>     user-agent: Mercurial debugwireproto\r\n
@@ -229,11 +434,11 @@
   s>     Content-Type: application/mercurial-cbor\r\n
   s>     Content-Length: *\r\n (glob)
   s>     \r\n
-  s>     \xa3Dapis\xa1Pexp-http-v2-0001\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x81\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0005GapibaseDapi/Nv1capabilitiesY\x01\xc5batch branchmap $USUAL_BUNDLE2_CAPS_SERVER$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  s>     \xa3GapibaseDapi/Dapis\xa1Pexp-http-v2-0002\xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1Nv1capabilitiesY\x01\xd3batch branchmap $USUAL_BUNDLE2_CAPS$ changegroupsubset compression=$BUNDLE2_COMPRESSIONS$ getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   sending capabilities command
-  s>     POST /api/exp-http-v2-0001/ro/capabilities HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/capabilities HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
-  s>     *\r\n (glob)
+  s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
   s>     content-length: 27\r\n
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
@@ -247,13 +452,215 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     1d7\r\n
-  s>     \xcf\x01\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\xa4Hcommands\xa7Eheads\xa2Dargs\xa1Jpubliconly\xf4Kpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\x81HdeadbeefKpermissions\x81DpullFlookup\xa2Dargs\xa1CkeyCfooKpermissions\x81DpullGpushkey\xa2Dargs\xa4CkeyCkeyCnewCnewColdColdInamespaceBnsKpermissions\x81DpushHlistkeys\xa2Dargs\xa1InamespaceBnsKpermissions\x81DpullIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullKcompression\x81\xa1DnameDzlibNrawrepoformats\x82LgeneraldeltaHrevlogv1Qframingmediatypes\x81X&application/mercurial-exp-framing-0005
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=463; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     520\r\n
+  s>     \x18\x05\x00\x01\x00\x02\x001
+  s>     \xa5Hcommands\xaaIbranchmap\xa2Dargs\xa0Kpermissions\x81DpullLcapabilities\xa2Dargs\xa0Kpermissions\x81DpullMchangesetdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x84IbookmarksGparentsEphaseHrevisionInoderange\xa3Gdefault\xf6Hrequired\xf4DtypeDlistEnodes\xa3Gdefault\xf6Hrequired\xf4DtypeDlistJnodesdepth\xa3Gdefault\xf6Hrequired\xf4DtypeCintKpermissions\x81DpullHfiledata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDpath\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullEheads\xa2Dargs\xa1Jpubliconly\xa3Gdefault\xf4Hrequired\xf4DtypeDboolKpermissions\x81DpullEknown\xa2Dargs\xa1Enodes\xa3Gdefault\x80Hrequired\xf4DtypeDlistKpermissions\x81DpullHlistkeys\xa2Dargs\xa1Inamespace\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullFlookup\xa2Dargs\xa1Ckey\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullLmanifestdata\xa2Dargs\xa4Ffields\xa4Gdefault\xd9\x01\x02\x80Hrequired\xf4DtypeCsetKvalidvalues\xd9\x01\x02\x82GparentsHrevisionKhaveparents\xa3Gdefault\xf4Hrequired\xf4DtypeDboolEnodes\xa2Hrequired\xf5DtypeDlistDtree\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpullGpushkey\xa2Dargs\xa4Ckey\xa2Hrequired\xf5DtypeEbytesInamespace\xa2Hrequired\xf5DtypeEbytesCnew\xa2Hrequired\xf5DtypeEbytesCold\xa2Hrequired\xf5DtypeEbytesKpermissions\x81DpushKcompression\x81\xa1DnameDzlibQframingmediatypes\x81X&application/mercurial-exp-framing-0005Rpathfilterprefixes\xd9\x01\x02\x82Epath:Lrootfilesin:Nrawrepoformats\x82LgeneraldeltaHrevlogv1
+  s>     \r\n
+  received frame(size=1304; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: [{b'status': b'ok'}, {b'commands': {b'branchmap': {b'args': {}, b'permissions': [b'pull']}, b'capabilities': {b'args': {}, b'permissions': [b'pull']}, b'heads': {b'args': {b'publiconly': False}, b'permissions': [b'pull']}, b'known': {b'args': {b'nodes': [b'deadbeef']}, b'permissions': [b'pull']}, b'listkeys': {b'args': {b'namespace': b'ns'}, b'permissions': [b'pull']}, b'lookup': {b'args': {b'key': b'foo'}, b'permissions': [b'pull']}, b'pushkey': {b'args': {b'key': b'key', b'namespace': b'ns', b'new': b'new', b'old': b'old'}, b'permissions': [b'push']}}, b'compression': [{b'name': b'zlib'}], b'framingmediatypes': [b'application/mercurial-exp-framing-0005'], b'rawrepoformats': [b'generaldelta', b'revlogv1']}]
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'commands': {
+        b'branchmap': {
+          b'args': {},
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'capabilities': {
+          b'args': {},
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'changesetdata': {
+          b'args': {
+            b'fields': {
+              b'default': set([]),
+              b'required': False,
+              b'type': b'set',
+              b'validvalues': set([
+                b'bookmarks',
+                b'parents',
+                b'phase',
+                b'revision'
+              ])
+            },
+            b'noderange': {
+              b'default': None,
+              b'required': False,
+              b'type': b'list'
+            },
+            b'nodes': {
+              b'default': None,
+              b'required': False,
+              b'type': b'list'
+            },
+            b'nodesdepth': {
+              b'default': None,
+              b'required': False,
+              b'type': b'int'
+            }
+          },
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'filedata': {
+          b'args': {
+            b'fields': {
+              b'default': set([]),
+              b'required': False,
+              b'type': b'set',
+              b'validvalues': set([
+                b'parents',
+                b'revision'
+              ])
+            },
+            b'haveparents': {
+              b'default': False,
+              b'required': False,
+              b'type': b'bool'
+            },
+            b'nodes': {
+              b'required': True,
+              b'type': b'list'
+            },
+            b'path': {
+              b'required': True,
+              b'type': b'bytes'
+            }
+          },
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'heads': {
+          b'args': {
+            b'publiconly': {
+              b'default': False,
+              b'required': False,
+              b'type': b'bool'
+            }
+          },
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'known': {
+          b'args': {
+            b'nodes': {
+              b'default': [],
+              b'required': False,
+              b'type': b'list'
+            }
+          },
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'listkeys': {
+          b'args': {
+            b'namespace': {
+              b'required': True,
+              b'type': b'bytes'
+            }
+          },
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'lookup': {
+          b'args': {
+            b'key': {
+              b'required': True,
+              b'type': b'bytes'
+            }
+          },
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'manifestdata': {
+          b'args': {
+            b'fields': {
+              b'default': set([]),
+              b'required': False,
+              b'type': b'set',
+              b'validvalues': set([
+                b'parents',
+                b'revision'
+              ])
+            },
+            b'haveparents': {
+              b'default': False,
+              b'required': False,
+              b'type': b'bool'
+            },
+            b'nodes': {
+              b'required': True,
+              b'type': b'list'
+            },
+            b'tree': {
+              b'required': True,
+              b'type': b'bytes'
+            }
+          },
+          b'permissions': [
+            b'pull'
+          ]
+        },
+        b'pushkey': {
+          b'args': {
+            b'key': {
+              b'required': True,
+              b'type': b'bytes'
+            },
+            b'namespace': {
+              b'required': True,
+              b'type': b'bytes'
+            },
+            b'new': {
+              b'required': True,
+              b'type': b'bytes'
+            },
+            b'old': {
+              b'required': True,
+              b'type': b'bytes'
+            }
+          },
+          b'permissions': [
+            b'push'
+          ]
+        }
+      },
+      b'compression': [
+        {
+          b'name': b'zlib'
+        }
+      ],
+      b'framingmediatypes': [
+        b'application/mercurial-exp-framing-0005'
+      ],
+      b'pathfilterprefixes': set([
+        b'path:',
+        b'rootfilesin:'
+      ]),
+      b'rawrepoformats': [
+        b'generaldelta',
+        b'revlogv1'
+      ]
+    }
+  ]
 
   $ cat error.log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-wireproto-command-changesetdata.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,1078 @@
+  $ . $TESTDIR/wireprotohelpers.sh
+
+  $ hg init server
+  $ enablehttpv2 server
+  $ cd server
+  $ cat >> .hg/hgrc << EOF
+  > [phases]
+  > publish = false
+  > EOF
+  $ echo a0 > a
+  $ echo b0 > b
+
+  $ hg -q commit -A -m 'commit 0'
+
+  $ echo a1 > a
+  $ echo b1 > b
+  $ hg commit -m 'commit 1'
+  $ echo b2 > b
+  $ hg commit -m 'commit 2'
+  $ hg phase --public -r .
+
+  $ hg -q up -r 0
+  $ echo a2 > a
+  $ hg commit -m 'commit 3'
+  created new head
+
+  $ hg log -G -T '{rev}:{node} {desc}\n'
+  @  3:eae5f82c2e622368d27daecb76b7e393d0f24211 commit 3
+  |
+  | o  2:0bb8ad894a15b15380b2a2a5b183e20f2a4b28dd commit 2
+  | |
+  | o  1:7592917e1c3e82677cb0a4bc715ca25dd12d28c1 commit 1
+  |/
+  o  0:3390ef850073fbc2f0dfff2244342c8e9229013a commit 0
+  
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+No arguments is an invalid request
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 28\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     \x14\x00\x00\x01\x00\x01\x01\x11\xa1DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     49\r\n
+  s>     A\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa1GmessageX"noderange or nodes must be definedFstatusEerror
+  s>     \r\n
+  received frame(size=65; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: noderange or nodes must be defined!
+  [255]
+
+Empty noderange heads results in an error
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     noderange eval:[[],[]]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 47\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     \'\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Inoderange\x82\x80\x80DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     51\r\n
+  s>     I\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa1GmessageX*heads in noderange request cannot be emptyFstatusEerror
+  s>     \r\n
+  received frame(size=73; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: heads in noderange request cannot be empty!
+  [255]
+
+nodesdepth requires nodes argument
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     nodesdepth 42
+  >     noderange eval:[[], [b'ignored']]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 69\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     =\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Inoderange\x82\x80\x81GignoredJnodesdepthB42DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     4d\r\n
+  s>     E\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa1GmessageX&nodesdepth requires the nodes argumentFstatusEerror
+  s>     \r\n
+  received frame(size=69; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: nodesdepth requires the nodes argument!
+  [255]
+
+Sending just noderange heads sends all revisions
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     noderange eval:[[], [b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 89\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     Q\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Inoderange\x82\x80\x82T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     81\r\n
+  s>     y\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x04\xa1DnodeT3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\xa1DnodeTu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1\xa1DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd\xa1DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11
+  s>     \r\n
+  received frame(size=121; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 4
+    },
+    {
+      b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:'
+    },
+    {
+      b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1'
+    },
+    {
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd'
+    },
+    {
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    }
+  ]
+
+Sending root nodes limits what data is sent
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     noderange eval:[[b'\x33\x90\xef\x85\x00\x73\xfb\xc2\xf0\xdf\xff\x22\x44\x34\x2c\x8e\x92\x29\x01\x3a'], [b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd']]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 89\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     Q\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Inoderange\x82\x81T3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\x81T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddDnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     4b\r\n
+  s>     C\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa1DnodeTu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1\xa1DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd
+  s>     \r\n
+  received frame(size=67; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1'
+    },
+    {
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd'
+    }
+  ]
+
+Requesting data on a single node by node works
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     nodes eval:[b'\x33\x90\xef\x85\x00\x73\xfb\xc2\xf0\xdf\xff\x22\x44\x34\x2c\x8e\x92\x29\x01\x3a']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 62\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     6\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x81T3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     30\r\n
+  s>     (\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa1DnodeT3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:
+  s>     \r\n
+  received frame(size=40; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:'
+    }
+  ]
+
+Specifying a noderange and nodes takes union
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     noderange eval:[[b'\x75\x92\x91\x7e\x1c\x3e\x82\x67\x7c\xb0\xa4\xbc\x71\x5c\xa2\x5d\xd1\x2d\x28\xc1'], [b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd']]
+  >     nodes eval:[b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 117\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     m\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Inoderange\x82\x81Tu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1\x81T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddEnodes\x81T\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     4b\r\n
+  s>     C\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa1DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11\xa1DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd
+  s>     \r\n
+  received frame(size=67; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    },
+    {
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd'
+    }
+  ]
+
+nodesdepth of 1 limits to exactly requested nodes
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     nodes eval:[b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']
+  >     nodesdepth eval:1
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 74\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     B\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Enodes\x81T\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11Jnodesdepth\x01DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     30\r\n
+  s>     (\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa1DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11
+  s>     \r\n
+  received frame(size=40; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    }
+  ]
+
+nodesdepth of 2 limits to first ancestor
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     nodes eval:[b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']
+  >     nodesdepth eval:2
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 74\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     B\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Enodes\x81T\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11Jnodesdepth\x02DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     4b\r\n
+  s>     C\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa1DnodeT3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\xa1DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11
+  s>     \r\n
+  received frame(size=67; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:'
+    },
+    {
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    }
+  ]
+
+nodesdepth with multiple nodes
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     nodes eval:[b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11', b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd']
+  >     nodesdepth eval:2
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 95\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     W\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Enodes\x82T\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddJnodesdepth\x02DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     81\r\n
+  s>     y\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x04\xa1DnodeT3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\xa1DnodeTu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1\xa1DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd\xa1DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11
+  s>     \r\n
+  received frame(size=121; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 4
+    },
+    {
+      b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:'
+    },
+    {
+      b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1'
+    },
+    {
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd'
+    },
+    {
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    }
+  ]
+
+Parents data is transferred upon request
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     fields eval:[b'parents']
+  >     nodes eval:[b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 78\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     F\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Ffields\x81GparentsEnodes\x81T\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     63\r\n
+  s>     [\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11Gparents\x82T3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  s>     \r\n
+  received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11',
+      b'parents': [
+        b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ]
+    }
+  ]
+
+Phase data is transferred upon request
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     fields eval:[b'phase']
+  >     nodes eval:[b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 76\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     D\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Ffields\x81EphaseEnodes\x81T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddDnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     3d\r\n
+  s>     5\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddEphaseFpublic
+  s>     \r\n
+  received frame(size=53; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd',
+      b'phase': b'public'
+    }
+  ]
+
+Revision data is transferred upon request
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     fields eval:[b'revision']
+  >     nodes eval:[b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 79\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     G\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Ffields\x81HrevisionEnodes\x81T\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     8c\r\n
+  s>     \x84\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2Ofieldsfollowing\x81\x82Hrevision\x18=DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11X=1b74476799ec8318045db759b1b4bcc9b839d0aa\n
+  s>     test\n
+  s>     0 0\n
+  s>     a\n
+  s>     \n
+  s>     commit 3
+  s>     \r\n
+  received frame(size=132; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          61
+        ]
+      ],
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    },
+    b'1b74476799ec8318045db759b1b4bcc9b839d0aa\ntest\n0 0\na\n\ncommit 3'
+  ]
+
+Bookmarks key isn't present if no bookmarks data
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     fields eval:[b'bookmarks']
+  >     noderange eval:[[], [b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 107\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     c\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Ffields\x81IbookmarksInoderange\x82\x80\x82T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     81\r\n
+  s>     y\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x04\xa1DnodeT3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\xa1DnodeTu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1\xa1DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd\xa1DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11
+  s>     \r\n
+  received frame(size=121; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 4
+    },
+    {
+      b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:'
+    },
+    {
+      b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1'
+    },
+    {
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd'
+    },
+    {
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    }
+  ]
+
+Bookmarks are sent when requested
+
+  $ hg -R ../server bookmark -r 0bb8ad894a15b15380b2a2a5b183e20f2a4b28dd book-1
+  $ hg -R ../server bookmark -r eae5f82c2e622368d27daecb76b7e393d0f24211 book-2
+  $ hg -R ../server bookmark -r eae5f82c2e622368d27daecb76b7e393d0f24211 book-3
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     fields eval:[b'bookmarks']
+  >     noderange eval:[[], [b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 107\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     c\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Ffields\x81IbookmarksInoderange\x82\x80\x82T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     ac\r\n
+  s>     \xa4\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x04\xa1DnodeT3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\xa1DnodeTu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1\xa2Ibookmarks\x81Fbook-1DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd\xa2Ibookmarks\x82Fbook-2Fbook-3DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11
+  s>     \r\n
+  received frame(size=164; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 4
+    },
+    {
+      b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:'
+    },
+    {
+      b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1'
+    },
+    {
+      b'bookmarks': [
+        b'book-1'
+      ],
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd'
+    },
+    {
+      b'bookmarks': [
+        b'book-2',
+        b'book-3'
+      ],
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    }
+  ]
+
+Bookmarks are sent when we make a no-new-revisions request
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     fields eval:[b'bookmarks', b'revision']
+  >     noderange eval:[[b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11'], [b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 137\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     \x81\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Ffields\x82IbookmarksHrevisionInoderange\x82\x81T\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11\x82T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     14b\r\n
+  s>     C\x01\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa2Ofieldsfollowing\x81\x82Hrevision\x18?DnodeTu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1X?7f144aea0ba742713887b564d57e9d12f12ff382\n
+  s>     test\n
+  s>     0 0\n
+  s>     a\n
+  s>     b\n
+  s>     \n
+  s>     commit 1\xa3Ibookmarks\x81Fbook-1Ofieldsfollowing\x81\x82Hrevision\x18=DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddX=37f0a2d1c28ffe4b879109a7d1bbf8f07b3c763b\n
+  s>     test\n
+  s>     0 0\n
+  s>     b\n
+  s>     \n
+  s>     commit 2\xa2Ibookmarks\x82Fbook-2Fbook-3DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11
+  s>     \r\n
+  received frame(size=323; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          63
+        ]
+      ],
+      b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1'
+    },
+    b'7f144aea0ba742713887b564d57e9d12f12ff382\ntest\n0 0\na\nb\n\ncommit 1',
+    {
+      b'bookmarks': [
+        b'book-1'
+      ],
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          61
+        ]
+      ],
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd'
+    },
+    b'37f0a2d1c28ffe4b879109a7d1bbf8f07b3c763b\ntest\n0 0\nb\n\ncommit 2',
+    {
+      b'bookmarks': [
+        b'book-2',
+        b'book-3'
+      ],
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11'
+    }
+  ]
+
+Multiple fields can be transferred
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     fields eval:[b'parents', b'revision']
+  >     nodes eval:[b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 87\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     O\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Ffields\x82GparentsHrevisionEnodes\x81T\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     bf\r\n
+  s>     \xb7\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa3Ofieldsfollowing\x81\x82Hrevision\x18=DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11Gparents\x82T3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X=1b74476799ec8318045db759b1b4bcc9b839d0aa\n
+  s>     test\n
+  s>     0 0\n
+  s>     a\n
+  s>     \n
+  s>     commit 3
+  s>     \r\n
+  received frame(size=183; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          61
+        ]
+      ],
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11',
+      b'parents': [
+        b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ]
+    },
+    b'1b74476799ec8318045db759b1b4bcc9b839d0aa\ntest\n0 0\na\n\ncommit 3'
+  ]
+
+Base nodes have just their metadata (e.g. phase) transferred
+
+  $ sendhttpv2peer << EOF
+  > command changesetdata
+  >     fields eval:[b'phase', b'parents', b'revision']
+  >     noderange eval:[[b'\x33\x90\xef\x85\x00\x73\xfb\xc2\xf0\xdf\xff\x22\x44\x34\x2c\x8e\x92\x29\x01\x3a'], [b'\x0b\xb8\xad\x89\x4a\x15\xb1\x53\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f\x2a\x4b\x28\xdd', b'\xea\xe5\xf8\x2c\x2e\x62\x23\x68\xd2\x7d\xae\xcb\x76\xb7\xe3\x93\xd0\xf2\x42\x11']]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending changesetdata command
+  s>     POST /api/exp-http-v2-0002/ro/changesetdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 141\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     \x85\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Ffields\x83EphaseGparentsHrevisionInoderange\x82\x81T3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:\x82T\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11DnameMchangesetdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     263\r\n
+  s>     [\x02\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x03\xa2DnodeT3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:EphaseFpublic\xa4Ofieldsfollowing\x81\x82Hrevision\x18?DnodeTu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1Gparents\x82T3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00EphaseFpublicX?7f144aea0ba742713887b564d57e9d12f12ff382\n
+  s>     test\n
+  s>     0 0\n
+  s>     a\n
+  s>     b\n
+  s>     \n
+  s>     commit 1\xa4Ofieldsfollowing\x81\x82Hrevision\x18=DnodeT\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xddGparents\x82Tu\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00EphaseFpublicX=37f0a2d1c28ffe4b879109a7d1bbf8f07b3c763b\n
+  s>     test\n
+  s>     0 0\n
+  s>     b\n
+  s>     \n
+  s>     commit 2\xa4Ofieldsfollowing\x81\x82Hrevision\x18=DnodeT\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11Gparents\x82T3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00EphaseEdraftX=1b74476799ec8318045db759b1b4bcc9b839d0aa\n
+  s>     test\n
+  s>     0 0\n
+  s>     a\n
+  s>     \n
+  s>     commit 3
+  s>     \r\n
+  received frame(size=603; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 3
+    },
+    {
+      b'node': b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:',
+      b'phase': b'public'
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          63
+        ]
+      ],
+      b'node': b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1',
+      b'parents': [
+        b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ],
+      b'phase': b'public'
+    },
+    b'7f144aea0ba742713887b564d57e9d12f12ff382\ntest\n0 0\na\nb\n\ncommit 1',
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          61
+        ]
+      ],
+      b'node': b'\x0b\xb8\xad\x89J\x15\xb1S\x80\xb2\xa2\xa5\xb1\x83\xe2\x0f*K(\xdd',
+      b'parents': [
+        b'u\x92\x91~\x1c>\x82g|\xb0\xa4\xbcq\\\xa2]\xd1-(\xc1',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ],
+      b'phase': b'public'
+    },
+    b'37f0a2d1c28ffe4b879109a7d1bbf8f07b3c763b\ntest\n0 0\nb\n\ncommit 2',
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          61
+        ]
+      ],
+      b'node': b'\xea\xe5\xf8,.b#h\xd2}\xae\xcbv\xb7\xe3\x93\xd0\xf2B\x11',
+      b'parents': [
+        b'3\x90\xef\x85\x00s\xfb\xc2\xf0\xdf\xff"D4,\x8e\x92)\x01:',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ],
+      b'phase': b'draft'
+    },
+    b'1b74476799ec8318045db759b1b4bcc9b839d0aa\ntest\n0 0\na\n\ncommit 3'
+  ]
+
+  $ cat error.log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-wireproto-command-filedata.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,644 @@
+  $ . $TESTDIR/wireprotohelpers.sh
+
+  $ hg init server
+  $ enablehttpv2 server
+  $ cd server
+  $ echo a0 > a
+  $ echo b0 > b
+  $ mkdir -p dir0/child0 dir0/child1 dir1
+  $ echo c0 > dir0/c
+  $ echo d0 > dir0/d
+  $ echo e0 > dir0/child0/e
+  $ echo f0 > dir0/child1/f
+  $ hg -q commit -A -m 'commit 0'
+
+  $ echo a1 > a
+  $ echo d1 > dir0/d
+  $ hg commit -m 'commit 1'
+  $ echo f0 > dir0/child1/f
+  $ hg commit -m 'commit 2'
+  nothing changed
+  [1]
+
+  $ hg -q up -r 0
+  $ echo a2 > a
+  $ hg commit -m 'commit 3'
+  created new head
+
+  $ hg log -G -T '{rev}:{node} {desc}\n'
+  @  2:c8757a2ffe552850d1e0dfe60d295ebf64c196d9 commit 3
+  |
+  | o  1:650165e803375748a94df471e5b58d85763e0b29 commit 1
+  |/
+  o  0:6d85ca1270b377d320098556ba5bfad34a9ee12d commit 0
+  
+
+  $ hg --debug debugindex a
+     rev linkrev nodeid                                   p1                                       p2
+       0       0 2b4eb07319bfa077a40a2f04913659aef0da42da 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+       1       1 9a38122997b3ac97be2a9aa2e556838341fdf2cc 2b4eb07319bfa077a40a2f04913659aef0da42da 0000000000000000000000000000000000000000
+       2       2 0879345e39377229634b420c639454156726c6b6 2b4eb07319bfa077a40a2f04913659aef0da42da 0000000000000000000000000000000000000000
+
+  $ hg --debug debugindex dir0/child0/e
+     rev linkrev nodeid                                   p1                                       p2
+       0       0 bbba6c06b30f443d34ff841bc985c4d0827c6be4 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+Missing arguments is an error
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 23\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     \x0f\x00\x00\x01\x00\x01\x01\x11\xa1DnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     4e\r\n
+  s>     F\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa1GmessageX\'missing required arguments: nodes, pathFstatusEerror
+  s>     \r\n
+  received frame(size=70; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: missing required arguments: nodes, path!
+  [255]
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 36\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     \x1c\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x80DnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     47\r\n
+  s>     ?\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa1GmessageX missing required arguments: pathFstatusEerror
+  s>     \r\n
+  received frame(size=63; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: missing required arguments: path!
+  [255]
+
+Unknown node is an error
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa']
+  >     path eval:b'a'
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 64\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     8\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Enodes\x81T\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaaDpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     6b\r\n
+  s>     c\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa2Dargs\x81X(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaGmessageUunknown file node: %sFstatusEerror
+  s>     \r\n
+  received frame(size=99; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: unknown file node: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!
+  [255]
+
+Fetching a single revision returns just metadata by default
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\x9a\x38\x12\x29\x97\xb3\xac\x97\xbe\x2a\x9a\xa2\xe5\x56\x83\x83\x41\xfd\xf2\xcc']
+  >     path eval:b'a'
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 64\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     8\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Enodes\x81T\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccDpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     30\r\n
+  s>     (\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa1DnodeT\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc
+  s>     \r\n
+  received frame(size=40; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'node': b'\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
+    }
+  ]
+
+Requesting parents works
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\x9a\x38\x12\x29\x97\xb3\xac\x97\xbe\x2a\x9a\xa2\xe5\x56\x83\x83\x41\xfd\xf2\xcc']
+  >     path eval:b'a'
+  >     fields eval:[b'parents']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 80\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     H\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81GparentsEnodes\x81T\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccDpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     63\r\n
+  s>     [\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2DnodeT\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccGparents\x82T+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaT\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  s>     \r\n
+  received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'node': b'\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
+      b'parents': [
+        b'+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ]
+    }
+  ]
+
+Requesting revision data works
+(haveparents defaults to False, so fulltext is emitted)
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\x9a\x38\x12\x29\x97\xb3\xac\x97\xbe\x2a\x9a\xa2\xe5\x56\x83\x83\x41\xfd\xf2\xcc']
+  >     path eval:b'a'
+  >     fields eval:[b'revision']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 81\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     I\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81HrevisionEnodes\x81T\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccDpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     50\r\n
+  s>     H\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2Ofieldsfollowing\x81\x82Hrevision\x03DnodeT\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccCa1\n
+  s>     \r\n
+  received frame(size=72; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          3
+        ]
+      ],
+      b'node': b'\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
+    },
+    b'a1\n'
+  ]
+
+haveparents=False should be same as above
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\x9a\x38\x12\x29\x97\xb3\xac\x97\xbe\x2a\x9a\xa2\xe5\x56\x83\x83\x41\xfd\xf2\xcc']
+  >     path eval:b'a'
+  >     fields eval:[b'revision']
+  >     haveparents eval:False
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 94\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     V\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4Ffields\x81HrevisionKhaveparents\xf4Enodes\x81T\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccDpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     50\r\n
+  s>     H\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2Ofieldsfollowing\x81\x82Hrevision\x03DnodeT\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccCa1\n
+  s>     \r\n
+  received frame(size=72; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          3
+        ]
+      ],
+      b'node': b'\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
+    },
+    b'a1\n'
+  ]
+
+haveparents=True should emit a delta
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\x9a\x38\x12\x29\x97\xb3\xac\x97\xbe\x2a\x9a\xa2\xe5\x56\x83\x83\x41\xfd\xf2\xcc']
+  >     path eval:b'a'
+  >     fields eval:[b'revision']
+  >     haveparents eval:True
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 94\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     V\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4Ffields\x81HrevisionKhaveparents\xf5Enodes\x81T\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccDpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     7c\r\n
+  s>     t\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa3MdeltabasenodeT+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaOfieldsfollowing\x81\x82Edelta\x0fDnodeT\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccO\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03a1\n
+  s>     \r\n
+  received frame(size=116; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'deltabasenode': b'+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+      b'fieldsfollowing': [
+        [
+          b'delta',
+          15
+        ]
+      ],
+      b'node': b'\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
+    },
+    b'\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03a1\n'
+  ]
+
+Requesting multiple revisions works
+(first revision is a fulltext since haveparents=False by default)
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\x2b\x4e\xb0\x73\x19\xbf\xa0\x77\xa4\x0a\x2f\x04\x91\x36\x59\xae\xf0\xda\x42\xda', b'\x9a\x38\x12\x29\x97\xb3\xac\x97\xbe\x2a\x9a\xa2\xe5\x56\x83\x83\x41\xfd\xf2\xcc']
+  >     path eval:b'a'
+  >     fields eval:[b'revision']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 102\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     ^\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81HrevisionEnodes\x82T+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaT\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccDpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     b7\r\n
+  s>     \xaf\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa2Ofieldsfollowing\x81\x82Hrevision\x03DnodeT+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaCa0\n
+  s>     \xa3MdeltabasenodeT+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaOfieldsfollowing\x81\x82Edelta\x0fDnodeT\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccO\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03a1\n
+  s>     \r\n
+  received frame(size=175; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          3
+        ]
+      ],
+      b'node': b'+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda'
+    },
+    b'a0\n',
+    {
+      b'deltabasenode': b'+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+      b'fieldsfollowing': [
+        [
+          b'delta',
+          15
+        ]
+      ],
+      b'node': b'\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
+    },
+    b'\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03a1\n'
+  ]
+
+Revisions are sorted by DAG order, parents first
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\x9a\x38\x12\x29\x97\xb3\xac\x97\xbe\x2a\x9a\xa2\xe5\x56\x83\x83\x41\xfd\xf2\xcc', b'\x2b\x4e\xb0\x73\x19\xbf\xa0\x77\xa4\x0a\x2f\x04\x91\x36\x59\xae\xf0\xda\x42\xda']
+  >     path eval:b'a'
+  >     fields eval:[b'revision']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 102\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     ^\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81HrevisionEnodes\x82T\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccT+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaDpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     b7\r\n
+  s>     \xaf\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa2Ofieldsfollowing\x81\x82Hrevision\x03DnodeT+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaCa0\n
+  s>     \xa3MdeltabasenodeT+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaOfieldsfollowing\x81\x82Edelta\x0fDnodeT\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xccO\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03a1\n
+  s>     \r\n
+  received frame(size=175; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          3
+        ]
+      ],
+      b'node': b'+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda'
+    },
+    b'a0\n',
+    {
+      b'deltabasenode': b'+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+      b'fieldsfollowing': [
+        [
+          b'delta',
+          15
+        ]
+      ],
+      b'node': b'\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
+    },
+    b'\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x03a1\n'
+  ]
+
+Requesting parents and revision data works
+
+  $ sendhttpv2peer << EOF
+  > command filedata
+  >     nodes eval:[b'\x08\x79\x34\x5e\x39\x37\x72\x29\x63\x4b\x42\x0c\x63\x94\x54\x15\x67\x26\xc6\xb6']
+  >     path eval:b'a'
+  >     fields eval:[b'parents', b'revision']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending filedata command
+  s>     POST /api/exp-http-v2-0002/ro/filedata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 89\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     Q\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x82GparentsHrevisionEnodes\x81T\x08y4^97r)cKB\x0cc\x94T\x15g&\xc6\xb6DpathAaDnameHfiledata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     83\r\n
+  s>     {\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa3Ofieldsfollowing\x81\x82Hrevision\x03DnodeT\x08y4^97r)cKB\x0cc\x94T\x15g&\xc6\xb6Gparents\x82T+N\xb0s\x19\xbf\xa0w\xa4\n
+  s>     /\x04\x916Y\xae\xf0\xdaB\xdaT\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Ca2\n
+  s>     \r\n
+  received frame(size=123; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          3
+        ]
+      ],
+      b'node': b'\x08y4^97r)cKB\x0cc\x94T\x15g&\xc6\xb6',
+      b'parents': [
+        b'+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ]
+    },
+    b'a2\n'
+  ]
+
+  $ cat error.log
--- a/tests/test-wireproto-command-heads.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-wireproto-command-heads.t	Wed Sep 26 20:33:09 2018 +0900
@@ -35,7 +35,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending heads command
-  s>     POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -51,14 +51,27 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     53\r\n
-  s>     K\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\x83T\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0bT\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^T)Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=75; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     48\r\n
+  s>     @\x00\x00\x01\x00\x02\x001
+  s>     \x83T\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0bT\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^T)Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A
+  s>     \r\n
+  received frame(size=64; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: [b'\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0b', b'\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^', b')Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A']
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: [
+    b'\x1dok\x91\xd4J\xab\xa6\xd5\xe5\x80\xbc0\xa9\x94\x850\xdb\xe0\x0b',
+    b'\xaeI.6\xb0\xc83\x9f\xfa\xf3(\xd0\x0b\x85\xb4R]\xe1\x16^',
+    b')Dm-\xc5A\x9c_\x97Dz\x8b\xc0b\xe4\xcc2\x8b\xf2A'
+  ]
 
 Requesting just the public heads works
 
@@ -68,7 +81,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending heads command
-  s>     POST /api/exp-http-v2-0001/ro/heads HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/heads HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -84,13 +97,24 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     29\r\n
-  s>     !\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\x81Tx\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=33; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     1e\r\n
+  s>     \x16\x00\x00\x01\x00\x02\x001
+  s>     \x81Tx\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc
+  s>     \r\n
+  received frame(size=22; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: [b'x\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc']
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: [
+    b'x\xd2\xdc\xa46\xb2\xf5\xb1\x88\xac&~)\xb8\x1e\x07&m8\xfc'
+  ]
 
   $ cat error.log
--- a/tests/test-wireproto-command-known.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-wireproto-command-known.t	Wed Sep 26 20:33:09 2018 +0900
@@ -27,7 +27,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending known command
-  s>     POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/known HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -43,13 +43,22 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     14\r\n
-  s>     \x0c\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok@
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=12; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     9\r\n
+  s>     \x01\x00\x00\x01\x00\x02\x001
+  s>     @
+  s>     \r\n
+  received frame(size=1; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
   response: []
 
 Single known node works
@@ -60,7 +69,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending known command
-  s>     POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/known HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -76,14 +85,25 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     15\r\n
-  s>     \r\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBokA1
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=13; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     a\r\n
+  s>     \x02\x00\x00\x01\x00\x02\x001
+  s>     A1
+  s>     \r\n
+  received frame(size=2; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: [True]
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: [
+    True
+  ]
 
 Multiple nodes works
 
@@ -93,7 +113,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending known command
-  s>     POST /api/exp-http-v2-0001/ro/known HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/known HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -109,13 +129,26 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     17\r\n
-  s>     \x0f\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBokC101
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=15; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     c\r\n
+  s>     \x04\x00\x00\x01\x00\x02\x001
+  s>     C101
+  s>     \r\n
+  received frame(size=4; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: [True, False, True]
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: [
+    True,
+    False,
+    True
+  ]
 
   $ cat error.log
--- a/tests/test-wireproto-command-listkeys.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-wireproto-command-listkeys.t	Wed Sep 26 20:33:09 2018 +0900
@@ -31,7 +31,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending listkeys command
-  s>     POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/listkeys HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -47,14 +47,27 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     33\r\n
-  s>     +\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\xa3Fphases@Ibookmarks@Jnamespaces@
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=43; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     28\r\n
+  s>      \x00\x00\x01\x00\x02\x001
+  s>     \xa3Ibookmarks@Jnamespaces@Fphases@
+  s>     \r\n
+  received frame(size=32; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: {b'bookmarks': b'', b'namespaces': b'', b'phases': b''}
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: {
+    b'bookmarks': b'',
+    b'namespaces': b'',
+    b'phases': b''
+  }
 
 Request for phases works
 
@@ -64,7 +77,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending listkeys command
-  s>     POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/listkeys HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -80,14 +93,26 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     50\r\n
-  s>     H\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\xa2JpublishingDTrueX(be0ef73c17ade3fc89dc41701eb9fc3a91b58282A1
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=72; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     45\r\n
+  s>     =\x00\x00\x01\x00\x02\x001
+  s>     \xa2X(be0ef73c17ade3fc89dc41701eb9fc3a91b58282A1JpublishingDTrue
+  s>     \r\n
+  received frame(size=61; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: {b'be0ef73c17ade3fc89dc41701eb9fc3a91b58282': b'1', b'publishing': b'True'}
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: {
+    b'be0ef73c17ade3fc89dc41701eb9fc3a91b58282': b'1',
+    b'publishing': b'True'
+  }
 
 Request for bookmarks works
 
@@ -97,7 +122,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending listkeys command
-  s>     POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/listkeys HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -113,13 +138,24 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     40\r\n
-  s>     8\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\xa1A@X(26805aba1e600a82e93661149f2313866a221a7b
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=56; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     35\r\n
+  s>     -\x00\x00\x01\x00\x02\x001
+  s>     \xa1A@X(26805aba1e600a82e93661149f2313866a221a7b
+  s>     \r\n
+  received frame(size=45; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: {b'@': b'26805aba1e600a82e93661149f2313866a221a7b'}
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: {
+    b'@': b'26805aba1e600a82e93661149f2313866a221a7b'
+  }
 
   $ cat error.log
--- a/tests/test-wireproto-command-lookup.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-wireproto-command-lookup.t	Wed Sep 26 20:33:09 2018 +0900
@@ -43,13 +43,22 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     28\r\n
-  s>      \x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBokTBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=32; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     1d\r\n
+  s>     \x15\x00\x00\x01\x00\x02\x001
+  s>     TBk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0
+  s>     \r\n
+  received frame(size=21; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
   response: b'Bk\xad\xa5\xc6u\x98\xcae\x03mW\xd9\xe4\xb6K\x0c\x1c\xe7\xa0'
 
   $ cat error.log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-wireproto-command-manifestdata.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,749 @@
+  $ . $TESTDIR/wireprotohelpers.sh
+
+  $ hg init server
+  $ enablehttpv2 server
+  $ cd server
+  $ echo a0 > a
+  $ echo b0 > b
+  $ mkdir -p dir0/child0 dir0/child1 dir1
+  $ echo c0 > dir0/c
+  $ echo d0 > dir0/d
+  $ echo e0 > dir0/child0/e
+  $ echo f0 > dir0/child1/f
+  $ hg -q commit -A -m 'commit 0'
+
+  $ echo a1 > a
+  $ echo d1 > dir0/d
+  $ hg commit -m 'commit 1'
+  $ echo f0 > dir0/child1/f
+  $ hg commit -m 'commit 2'
+  nothing changed
+  [1]
+
+  $ hg -q up -r 0
+  $ echo a2 > a
+  $ hg commit -m 'commit 3'
+  created new head
+
+  $ hg log -G -T '{rev}:{node} {desc}\n'
+  @  2:c8757a2ffe552850d1e0dfe60d295ebf64c196d9 commit 3
+  |
+  | o  1:650165e803375748a94df471e5b58d85763e0b29 commit 1
+  |/
+  o  0:6d85ca1270b377d320098556ba5bfad34a9ee12d commit 0
+  
+
+  $ hg --debug debugindex -m
+     rev linkrev nodeid                                   p1                                       p2
+       0       0 1b175b595f022cfab5b809cc0ed551bd0b3ff5e4 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000
+       1       1 91e0bdbfb0dde0023fa063edc1445f207a22eac7 1b175b595f022cfab5b809cc0ed551bd0b3ff5e4 0000000000000000000000000000000000000000
+       2       2 46a6721b5edaf0ea04b79a5cb3218854a4d2aba0 1b175b595f022cfab5b809cc0ed551bd0b3ff5e4 0000000000000000000000000000000000000000
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+Missing arguments is an error
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 27\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     \x13\x00\x00\x01\x00\x01\x01\x11\xa1DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     4e\r\n
+  s>     F\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa1GmessageX\'missing required arguments: nodes, treeFstatusEerror
+  s>     \r\n
+  received frame(size=70; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: missing required arguments: nodes, tree!
+  [255]
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[]
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 40\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>      \x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa1Enodes\x80DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     47\r\n
+  s>     ?\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa1GmessageX missing required arguments: treeFstatusEerror
+  s>     \r\n
+  received frame(size=63; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: missing required arguments: tree!
+  [255]
+
+Unknown node is an error
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa']
+  >     tree eval:b''
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 67\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     ;\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Enodes\x81T\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaaDtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     51\r\n
+  s>     I\x00\x00\x01\x00\x02\x012
+  s>     \xa2Eerror\xa2Dargs\x81T\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaaGmessagePunknown node: %sFstatusEerror
+  s>     \r\n
+  received frame(size=73; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  s>     0\r\n
+  s>     \r\n
+  abort: unknown node: \xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa\xaa! (esc)
+  [255]
+
+Fetching a single revision returns just metadata by default
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0']
+  >     tree eval:b''
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 67\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     ;\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa2Enodes\x81TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     30\r\n
+  s>     (\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa1DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0
+  s>     \r\n
+  received frame(size=40; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0'
+    }
+  ]
+
+Requesting parents works
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0']
+  >     tree eval:b''
+  >     fields eval:[b'parents']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 83\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     K\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81GparentsEnodes\x81TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     63\r\n
+  s>     [\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Gparents\x82T\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
+  s>     \r\n
+  received frame(size=91; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0',
+      b'parents': [
+        b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ]
+    }
+  ]
+
+Requesting revision data works
+(haveparents defaults to false, so fulltext is emitted)
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0']
+  >     tree eval:b''
+  >     fields eval:[b'revision']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 84\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     L\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81HrevisionEnodes\x81TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     175\r\n
+  s>     m\x01\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2Ofieldsfollowing\x81\x82Hrevision\x19\x01$DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Y\x01$a\x000879345e39377229634b420c639454156726c6b6\n
+  s>     b\x00819e258d31a5e1606629f365bb902a1b21ee4216\n
+  s>     dir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\n
+  s>     dir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\n
+  s>     dir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\n
+  s>     dir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n
+  s>     \r\n
+  received frame(size=365; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          292
+        ]
+      ],
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0'
+    },
+    b'a\x000879345e39377229634b420c639454156726c6b6\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n'
+  ]
+
+haveparents=False yields same output
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0']
+  >     tree eval:b''
+  >     fields eval:[b'revision']
+  >     haveparents eval:False
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 97\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     Y\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4Ffields\x81HrevisionKhaveparents\xf4Enodes\x81TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     175\r\n
+  s>     m\x01\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa2Ofieldsfollowing\x81\x82Hrevision\x19\x01$DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Y\x01$a\x000879345e39377229634b420c639454156726c6b6\n
+  s>     b\x00819e258d31a5e1606629f365bb902a1b21ee4216\n
+  s>     dir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\n
+  s>     dir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\n
+  s>     dir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\n
+  s>     dir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n
+  s>     \r\n
+  received frame(size=365; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          292
+        ]
+      ],
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0'
+    },
+    b'a\x000879345e39377229634b420c639454156726c6b6\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n'
+  ]
+
+haveparents=True will emit delta
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0']
+  >     tree eval:b''
+  >     fields eval:[b'revision']
+  >     haveparents eval:True
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 97\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     Y\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4Ffields\x81HrevisionKhaveparents\xf5Enodes\x81TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     a6\r\n
+  s>     \x9e\x00\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x01\xa3MdeltabasenodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Ofieldsfollowing\x81\x82Edelta\x187DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0X7\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n
+  s>     \r\n
+  received frame(size=158; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 1
+    },
+    {
+      b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4',
+      b'fieldsfollowing': [
+        [
+          b'delta',
+          55
+        ]
+      ],
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0'
+    },
+    b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n'
+  ]
+
+Requesting multiple revisions works
+(haveparents defaults to false, so fulltext is emitted unless a parent
+has been emitted)
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x1b\x17\x5b\x59\x5f\x02\x2c\xfa\xb5\xb8\x09\xcc\x0e\xd5\x51\xbd\x0b\x3f\xf5\xe4', b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0']
+  >     tree eval:b''
+  >     fields eval:[b'revision']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 105\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     a\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81HrevisionEnodes\x82T\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     206\r\n
+  s>     \xfe\x01\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa2Ofieldsfollowing\x81\x82Hrevision\x19\x01$DnodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Y\x01$a\x002b4eb07319bfa077a40a2f04913659aef0da42da\n
+  s>     b\x00819e258d31a5e1606629f365bb902a1b21ee4216\n
+  s>     dir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\n
+  s>     dir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\n
+  s>     dir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\n
+  s>     dir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n
+  s>     \xa3MdeltabasenodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Ofieldsfollowing\x81\x82Edelta\x187DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0X7\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n
+  s>     \r\n
+  received frame(size=510; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          292
+        ]
+      ],
+      b'node': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4'
+    },
+    b'a\x002b4eb07319bfa077a40a2f04913659aef0da42da\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n',
+    {
+      b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4',
+      b'fieldsfollowing': [
+        [
+          b'delta',
+          55
+        ]
+      ],
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0'
+    },
+    b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n'
+  ]
+
+With haveparents=True, first revision is a delta instead of fulltext
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x1b\x17\x5b\x59\x5f\x02\x2c\xfa\xb5\xb8\x09\xcc\x0e\xd5\x51\xbd\x0b\x3f\xf5\xe4', b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0']
+  >     tree eval:b''
+  >     fields eval:[b'revision']
+  >     haveparents eval:True
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 118\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     n\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4Ffields\x81HrevisionKhaveparents\xf5Enodes\x82T\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     206\r\n
+  s>     \xfe\x01\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa2Ofieldsfollowing\x81\x82Hrevision\x19\x01$DnodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Y\x01$a\x002b4eb07319bfa077a40a2f04913659aef0da42da\n
+  s>     b\x00819e258d31a5e1606629f365bb902a1b21ee4216\n
+  s>     dir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\n
+  s>     dir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\n
+  s>     dir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\n
+  s>     dir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n
+  s>     \xa3MdeltabasenodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Ofieldsfollowing\x81\x82Edelta\x187DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0X7\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n
+  s>     \r\n
+  received frame(size=510; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          292
+        ]
+      ],
+      b'node': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4'
+    },
+    b'a\x002b4eb07319bfa077a40a2f04913659aef0da42da\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n',
+    {
+      b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4',
+      b'fieldsfollowing': [
+        [
+          b'delta',
+          55
+        ]
+      ],
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0'
+    },
+    b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n'
+  ]
+
+Revisions are sorted by DAG order, parents first
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0', b'\x1b\x17\x5b\x59\x5f\x02\x2c\xfa\xb5\xb8\x09\xcc\x0e\xd5\x51\xbd\x0b\x3f\xf5\xe4']
+  >     tree eval:b''
+  >     fields eval:[b'revision']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 105\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     a\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x81HrevisionEnodes\x82TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0T\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     206\r\n
+  s>     \xfe\x01\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa2Ofieldsfollowing\x81\x82Hrevision\x19\x01$DnodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Y\x01$a\x002b4eb07319bfa077a40a2f04913659aef0da42da\n
+  s>     b\x00819e258d31a5e1606629f365bb902a1b21ee4216\n
+  s>     dir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\n
+  s>     dir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\n
+  s>     dir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\n
+  s>     dir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n
+  s>     \xa3MdeltabasenodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Ofieldsfollowing\x81\x82Edelta\x187DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0X7\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n
+  s>     \r\n
+  received frame(size=510; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          292
+        ]
+      ],
+      b'node': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4'
+    },
+    b'a\x002b4eb07319bfa077a40a2f04913659aef0da42da\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n',
+    {
+      b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4',
+      b'fieldsfollowing': [
+        [
+          b'delta',
+          55
+        ]
+      ],
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0'
+    },
+    b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n'
+  ]
+
+Requesting parents and revision data works
+
+  $ sendhttpv2peer << EOF
+  > command manifestdata
+  >     nodes eval:[b'\x1b\x17\x5b\x59\x5f\x02\x2c\xfa\xb5\xb8\x09\xcc\x0e\xd5\x51\xbd\x0b\x3f\xf5\xe4', b'\x46\xa6\x72\x1b\x5e\xda\xf0\xea\x04\xb7\x9a\x5c\xb3\x21\x88\x54\xa4\xd2\xab\xa0']
+  >     tree eval:b''
+  >     fields eval:[b'parents', b'revision']
+  > EOF
+  creating http peer for wire protocol version 2
+  sending manifestdata command
+  s>     POST /api/exp-http-v2-0002/ro/manifestdata HTTP/1.1\r\n
+  s>     Accept-Encoding: identity\r\n
+  s>     accept: application/mercurial-exp-framing-0005\r\n
+  s>     content-type: application/mercurial-exp-framing-0005\r\n
+  s>     content-length: 113\r\n
+  s>     host: $LOCALIP:$HGPORT\r\n (glob)
+  s>     user-agent: Mercurial debugwireproto\r\n
+  s>     \r\n
+  s>     i\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa3Ffields\x82GparentsHrevisionEnodes\x82T\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4TF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Dtree@DnameLmanifestdata
+  s> makefile('rb', None)
+  s>     HTTP/1.1 200 OK\r\n
+  s>     Server: testing stub value\r\n
+  s>     Date: $HTTP_DATE$\r\n
+  s>     Content-Type: application/mercurial-exp-framing-0005\r\n
+  s>     Transfer-Encoding: chunked\r\n
+  s>     \r\n
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
+  s>     \r\n
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     26c\r\n
+  s>     d\x02\x00\x01\x00\x02\x001
+  s>     \xa1Jtotalitems\x02\xa3Ofieldsfollowing\x81\x82Hrevision\x19\x01$DnodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Gparents\x82T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Y\x01$a\x002b4eb07319bfa077a40a2f04913659aef0da42da\n
+  s>     b\x00819e258d31a5e1606629f365bb902a1b21ee4216\n
+  s>     dir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\n
+  s>     dir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\n
+  s>     dir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\n
+  s>     dir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n
+  s>     \xa4MdeltabasenodeT\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4Ofieldsfollowing\x81\x82Edelta\x187DnodeTF\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0Gparents\x82T\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00X7\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n
+  s>     \r\n
+  received frame(size=612; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
+  s>     0\r\n
+  s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: gen[
+    {
+      b'totalitems': 2
+    },
+    {
+      b'fieldsfollowing': [
+        [
+          b'revision',
+          292
+        ]
+      ],
+      b'node': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4',
+      b'parents': [
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ]
+    },
+    b'a\x002b4eb07319bfa077a40a2f04913659aef0da42da\nb\x00819e258d31a5e1606629f365bb902a1b21ee4216\ndir0/c\x00914445346a0ca0629bd47ceb5dfe07e4d4cf2501\ndir0/child0/e\x00bbba6c06b30f443d34ff841bc985c4d0827c6be4\ndir0/child1/f\x0012fc7dcd773b5a0a929ce195228083c6ddc9cec4\ndir0/d\x00538206dc971e521540d6843abfe6d16032f6d426\n',
+    {
+      b'deltabasenode': b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4',
+      b'fieldsfollowing': [
+        [
+          b'delta',
+          55
+        ]
+      ],
+      b'node': b'F\xa6r\x1b^\xda\xf0\xea\x04\xb7\x9a\\\xb3!\x88T\xa4\xd2\xab\xa0',
+      b'parents': [
+        b'\x1b\x17[Y_\x02,\xfa\xb5\xb8\t\xcc\x0e\xd5Q\xbd\x0b?\xf5\xe4',
+        b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+      ]
+    },
+    b'\x00\x00\x00\x00\x00\x00\x00+\x00\x00\x00+a\x000879345e39377229634b420c639454156726c6b6\n'
+  ]
+
+  $ cat error.log
--- a/tests/test-wireproto-command-pushkey.t	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-wireproto-command-pushkey.t	Wed Sep 26 20:33:09 2018 +0900
@@ -38,7 +38,7 @@
   s>     host: $LOCALIP:$HGPORT\r\n (glob)
   s>     user-agent: Mercurial debugwireproto\r\n
   s>     \r\n
-  s>     a\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4CkeyA@CnewX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0Cold@InamespaceIbookmarksDnameGpushkey
+  s>     a\x00\x00\x01\x00\x01\x01\x11\xa2Dargs\xa4CkeyA@InamespaceIbookmarksCnewX(426bada5c67598ca65036d57d9e4b64b0c1ce7a0Cold@DnameGpushkey
   s> makefile('rb', None)
   s>     HTTP/1.1 200 OK\r\n
   s>     Server: testing stub value\r\n
@@ -46,13 +46,22 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     14\r\n
-  s>     \x0c\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\xf5
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=12; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     9\r\n
+  s>     \x01\x00\x00\x01\x00\x02\x001
+  s>     \xf5
+  s>     \r\n
+  received frame(size=1; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
   response: True
 
   $ sendhttpv2peer << EOF
@@ -61,7 +70,7 @@
   > EOF
   creating http peer for wire protocol version 2
   sending listkeys command
-  s>     POST /api/exp-http-v2-0001/ro/listkeys HTTP/1.1\r\n
+  s>     POST /api/exp-http-v2-0002/ro/listkeys HTTP/1.1\r\n
   s>     Accept-Encoding: identity\r\n
   s>     accept: application/mercurial-exp-framing-0005\r\n
   s>     content-type: application/mercurial-exp-framing-0005\r\n
@@ -77,13 +86,24 @@
   s>     Content-Type: application/mercurial-exp-framing-0005\r\n
   s>     Transfer-Encoding: chunked\r\n
   s>     \r\n
-  s>     40\r\n
-  s>     8\x00\x00\x01\x00\x02\x012
-  s>     \xa1FstatusBok\xa1A@X(426bada5c67598ca65036d57d9e4b64b0c1ce7a0
+  s>     13\r\n
+  s>     \x0b\x00\x00\x01\x00\x02\x011
+  s>     \xa1FstatusBok
   s>     \r\n
-  received frame(size=56; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=eos)
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  s>     35\r\n
+  s>     -\x00\x00\x01\x00\x02\x001
+  s>     \xa1A@X(426bada5c67598ca65036d57d9e4b64b0c1ce7a0
+  s>     \r\n
+  received frame(size=45; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  s>     8\r\n
+  s>     \x00\x00\x00\x01\x00\x02\x002
+  s>     \r\n
   s>     0\r\n
   s>     \r\n
-  response: {b'@': b'426bada5c67598ca65036d57d9e4b64b0c1ce7a0'}
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  response: {
+    b'@': b'426bada5c67598ca65036d57d9e4b64b0c1ce7a0'
+  }
 
   $ cat error.log
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-wireproto-exchangev2.t	Wed Sep 26 20:33:09 2018 +0900
@@ -0,0 +1,617 @@
+Tests for wire protocol version 2 exchange.
+Tests in this file should be folded into existing tests once protocol
+v2 has enough features that it can be enabled via #testcase in existing
+tests.
+
+  $ . $TESTDIR/wireprotohelpers.sh
+  $ enablehttpv2client
+
+  $ hg init server-simple
+  $ enablehttpv2 server-simple
+  $ cd server-simple
+  $ cat >> .hg/hgrc << EOF
+  > [phases]
+  > publish = false
+  > EOF
+  $ echo a0 > a
+  $ echo b0 > b
+  $ hg -q commit -A -m 'commit 0'
+
+  $ echo a1 > a
+  $ hg commit -m 'commit 1'
+  $ hg phase --public -r .
+  $ echo a2 > a
+  $ hg commit -m 'commit 2'
+
+  $ hg -q up -r 0
+  $ echo b1 > b
+  $ hg -q commit -m 'head 2 commit 1'
+  $ echo b2 > b
+  $ hg -q commit -m 'head 2 commit 2'
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ cd ..
+
+Test basic clone
+
+  $ hg --debug clone -U http://localhost:$HGPORT client-simple
+  using http://localhost:$HGPORT/
+  sending capabilities command
+  query 1; heads
+  sending 2 commands
+  sending command heads: {}
+  sending command known: {
+    'nodes': []
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  sending 1 commands
+  sending command changesetdata: {
+    'fields': set([
+      'bookmarks',
+      'parents',
+      'phase',
+      'revision'
+    ]),
+    'noderange': [
+      [],
+      [
+        '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
+        '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
+      ]
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=941; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  add changeset 3390ef850073
+  add changeset 4432d83626e8
+  add changeset cd2534766bec
+  add changeset e96ae20f4188
+  add changeset caa2a465451d
+  checking for updated bookmarks
+  sending 1 commands
+  sending command manifestdata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
+      '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
+      '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
+      '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
+      '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
+    ],
+    'tree': ''
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=992; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  sending 2 commands
+  sending command filedata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+      '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
+      '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
+    ],
+    'path': 'a'
+  }
+  sending command filedata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
+      '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
+      '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
+    ],
+    'path': 'b'
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=431; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  updating the branch cache
+  new changesets 3390ef850073:caa2a465451d (3 drafts)
+
+All changesets should have been transferred
+
+  $ hg -R client-simple debugindex -c
+     rev linkrev nodeid       p1           p2
+       0       0 3390ef850073 000000000000 000000000000
+       1       1 4432d83626e8 3390ef850073 000000000000
+       2       2 cd2534766bec 4432d83626e8 000000000000
+       3       3 e96ae20f4188 3390ef850073 000000000000
+       4       4 caa2a465451d e96ae20f4188 000000000000
+
+  $ hg -R client-simple log -G -T '{rev} {node} {phase}\n'
+  o  4 caa2a465451dd1facda0f5b12312c355584188a1 draft
+  |
+  o  3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft
+  |
+  | o  2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
+  | |
+  | o  1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
+  |/
+  o  0 3390ef850073fbc2f0dfff2244342c8e9229013a public
+  
+
+All manifests should have been transferred
+
+  $ hg -R client-simple debugindex -m
+     rev linkrev nodeid       p1           p2
+       0       0 992f4779029a 000000000000 000000000000
+       1       1 a988fb43583e 992f4779029a 000000000000
+       2       2 ec804e488c20 a988fb43583e 000000000000
+       3       3 045c7f3927da 992f4779029a 000000000000
+       4       4 379cb0c2e664 045c7f3927da 000000000000
+
+Cloning only a specific revision works
+
+  $ hg --debug clone -U -r 4432d83626e8 http://localhost:$HGPORT client-singlehead
+  using http://localhost:$HGPORT/
+  sending capabilities command
+  sending 1 commands
+  sending command lookup: {
+    'key': '4432d83626e8'
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=21; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  query 1; heads
+  sending 2 commands
+  sending command heads: {}
+  sending command known: {
+    'nodes': []
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  sending 1 commands
+  sending command changesetdata: {
+    'fields': set([
+      'bookmarks',
+      'parents',
+      'phase',
+      'revision'
+    ]),
+    'noderange': [
+      [],
+      [
+        'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
+      ]
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=381; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  add changeset 3390ef850073
+  add changeset 4432d83626e8
+  checking for updated bookmarks
+  sending 1 commands
+  sending command manifestdata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
+      '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8'
+    ],
+    'tree': ''
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=404; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  sending 2 commands
+  sending command filedata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+      '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc'
+    ],
+    'path': 'a'
+  }
+  sending command filedata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16'
+    ],
+    'path': 'b'
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=277; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=123; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  updating the branch cache
+  new changesets 3390ef850073:4432d83626e8
+
+  $ cd client-singlehead
+
+  $ hg log -G -T '{rev} {node} {phase}\n'
+  o  1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
+  |
+  o  0 3390ef850073fbc2f0dfff2244342c8e9229013a public
+  
+
+  $ hg debugindex -m
+     rev linkrev nodeid       p1           p2
+       0       0 992f4779029a 000000000000 000000000000
+       1       1 a988fb43583e 992f4779029a 000000000000
+
+Incremental pull works
+
+  $ hg --debug pull
+  pulling from http://localhost:$HGPORT/
+  using http://localhost:$HGPORT/
+  sending capabilities command
+  query 1; heads
+  sending 2 commands
+  sending command heads: {}
+  sending command known: {
+    'nodes': [
+      'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=2; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  searching for changes
+  all local heads known remotely
+  sending 1 commands
+  sending command changesetdata: {
+    'fields': set([
+      'bookmarks',
+      'parents',
+      'phase',
+      'revision'
+    ]),
+    'noderange': [
+      [
+        'D2\xd86&\xe8\xa9\x86U\xf0b\xec\x1f*C\xb0\x7f\x7f\xbb\xb0'
+      ],
+      [
+        '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
+        '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
+      ]
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=613; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  add changeset cd2534766bec
+  add changeset e96ae20f4188
+  add changeset caa2a465451d
+  checking for updated bookmarks
+  sending 1 commands
+  sending command manifestdata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
+      '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
+      '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
+    ],
+    'tree': ''
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=601; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  sending 2 commands
+  sending command filedata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+      '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
+    ],
+    'path': 'a'
+  }
+  sending command filedata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
+      '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
+      '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
+    ],
+    'path': 'b'
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=277; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  updating the branch cache
+  new changesets cd2534766bec:caa2a465451d (3 drafts)
+  (run 'hg update' to get a working copy)
+
+  $ hg log -G -T '{rev} {node} {phase}\n'
+  o  4 caa2a465451dd1facda0f5b12312c355584188a1 draft
+  |
+  o  3 e96ae20f4188487b9ae4ef3941c27c81143146e5 draft
+  |
+  | o  2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
+  | |
+  | o  1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
+  |/
+  o  0 3390ef850073fbc2f0dfff2244342c8e9229013a public
+  
+
+  $ hg debugindex -m
+     rev linkrev nodeid       p1           p2
+       0       0 992f4779029a 000000000000 000000000000
+       1       1 a988fb43583e 992f4779029a 000000000000
+       2       2 ec804e488c20 a988fb43583e 000000000000
+       3       3 045c7f3927da 992f4779029a 000000000000
+       4       4 379cb0c2e664 045c7f3927da 000000000000
+
+Phase-only update works
+
+  $ hg -R ../server-simple phase --public -r caa2a465451dd
+  $ hg --debug pull
+  pulling from http://localhost:$HGPORT/
+  using http://localhost:$HGPORT/
+  sending capabilities command
+  query 1; heads
+  sending 2 commands
+  sending command heads: {}
+  sending command known: {
+    'nodes': [
+      '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f',
+      '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1'
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=3; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  searching for changes
+  all remote heads known locally
+  sending 1 commands
+  sending command changesetdata: {
+    'fields': set([
+      'bookmarks',
+      'parents',
+      'phase',
+      'revision'
+    ]),
+    'noderange': [
+      [
+        '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
+        '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
+      ],
+      [
+        '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
+        '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
+      ]
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=92; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  checking for updated bookmarks
+  2 local changesets published
+  (run 'hg update' to get a working copy)
+
+  $ hg log -G -T '{rev} {node} {phase}\n'
+  o  4 caa2a465451dd1facda0f5b12312c355584188a1 public
+  |
+  o  3 e96ae20f4188487b9ae4ef3941c27c81143146e5 public
+  |
+  | o  2 cd2534766bece138c7c1afdc6825302f0f62d81f draft
+  | |
+  | o  1 4432d83626e8a98655f062ec1f2a43b07f7fbbb0 public
+  |/
+  o  0 3390ef850073fbc2f0dfff2244342c8e9229013a public
+  
+
+  $ cd ..
+
+Bookmarks are transferred on clone
+
+  $ hg -R server-simple bookmark -r 3390ef850073fbc2f0dfff2244342c8e9229013a book-1
+  $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-2
+
+  $ hg --debug clone -U http://localhost:$HGPORT/ client-bookmarks
+  using http://localhost:$HGPORT/
+  sending capabilities command
+  query 1; heads
+  sending 2 commands
+  sending command heads: {}
+  sending command known: {
+    'nodes': []
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=1; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  sending 1 commands
+  sending command changesetdata: {
+    'fields': set([
+      'bookmarks',
+      'parents',
+      'phase',
+      'revision'
+    ]),
+    'noderange': [
+      [],
+      [
+        '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
+        '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
+      ]
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=979; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  add changeset 3390ef850073
+  add changeset 4432d83626e8
+  add changeset cd2534766bec
+  add changeset e96ae20f4188
+  add changeset caa2a465451d
+  checking for updated bookmarks
+  adding remote bookmark book-1
+  adding remote bookmark book-2
+  sending 1 commands
+  sending command manifestdata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '\x99/Gy\x02\x9a=\xf8\xd0fm\x00\xbb\x92OicN&A',
+      '\xa9\x88\xfbCX>\x87\x1d\x1e\xd5u\x0e\xe0t\xc6\xd8@\xbb\xbf\xc8',
+      '\xec\x80NH\x8c \x88\xc25\t\x9a\x10 u\x13\xbe\xcd\xc3\xdd\xa5',
+      '\x04\\\x7f9\'\xda\x13\xe7Z\xf8\xf0\xe4\xf0HI\xe4a\xa9x\x0f',
+      '7\x9c\xb0\xc2\xe6d\\y\xdd\xc5\x9a\x1dG\'\xa9\xfb\x83\n\xeb&'
+    ],
+    'tree': ''
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=992; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  sending 2 commands
+  sending command filedata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '+N\xb0s\x19\xbf\xa0w\xa4\n/\x04\x916Y\xae\xf0\xdaB\xda',
+      '\x9a8\x12)\x97\xb3\xac\x97\xbe*\x9a\xa2\xe5V\x83\x83A\xfd\xf2\xcc',
+      '\xc2\xa2\x05\xc8\xb2\xad\xe2J\xf2`b\xe5<\xd5\xbc8\x01\xd6`\xda'
+    ],
+    'path': 'a'
+  }
+  sending command filedata: {
+    'fields': set([
+      'parents',
+      'revision'
+    ]),
+    'haveparents': True,
+    'nodes': [
+      '\x81\x9e%\x8d1\xa5\xe1`f)\xf3e\xbb\x90*\x1b!\xeeB\x16',
+      '\xb1zk\xd3g=\x9a\xb8\xce\xd5\x81\xa2\t\xf6/=\xa5\xccEx',
+      '\xc5\xb1\xf9\xd3n\x1c\xc18\xbf\xb6\xef\xb3\xde\xb7]\x8c\xcad\x94\xc3'
+    ],
+    'path': 'b'
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=431; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=431; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  updating the branch cache
+  new changesets 3390ef850073:caa2a465451d (1 drafts)
+
+  $ hg -R client-bookmarks bookmarks
+     book-1                    0:3390ef850073
+     book-2                    2:cd2534766bec
+
+Server-side bookmark moves are reflected during `hg pull`
+
+  $ hg -R server-simple bookmark -r cd2534766bece138c7c1afdc6825302f0f62d81f book-1
+  moving bookmark 'book-1' forward from 3390ef850073
+
+  $ hg -R client-bookmarks --debug pull
+  pulling from http://localhost:$HGPORT/
+  using http://localhost:$HGPORT/
+  sending capabilities command
+  query 1; heads
+  sending 2 commands
+  sending command heads: {}
+  sending command known: {
+    'nodes': [
+      '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f',
+      '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1'
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=43; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  received frame(size=11; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=3; request=3; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=3; stream=2; streamflags=; type=command-response; flags=eos)
+  searching for changes
+  all remote heads known locally
+  sending 1 commands
+  sending command changesetdata: {
+    'fields': set([
+      'bookmarks',
+      'parents',
+      'phase',
+      'revision'
+    ]),
+    'noderange': [
+      [
+        '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
+        '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
+      ],
+      [
+        '\xca\xa2\xa4eE\x1d\xd1\xfa\xcd\xa0\xf5\xb1#\x12\xc3UXA\x88\xa1',
+        '\xcd%4vk\xec\xe18\xc7\xc1\xaf\xdch%0/\x0fb\xd8\x1f'
+      ]
+    ]
+  }
+  received frame(size=11; request=1; stream=2; streamflags=stream-begin; type=command-response; flags=continuation)
+  received frame(size=144; request=1; stream=2; streamflags=; type=command-response; flags=continuation)
+  received frame(size=0; request=1; stream=2; streamflags=; type=command-response; flags=eos)
+  checking for updated bookmarks
+  updating bookmark book-1
+  (run 'hg update' to get a working copy)
+
+  $ hg -R client-bookmarks bookmarks
+     book-1                    2:cd2534766bec
+     book-2                    2:cd2534766bec
--- a/tests/test-wireproto-framing.py	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/test-wireproto-framing.py	Wed Sep 26 20:33:09 2018 +0900
@@ -44,9 +44,6 @@
         self.assertEqual(ffs(b"1 1 0 1 0 cbor:b'foo'"),
                          b'\x04\x00\x00\x01\x00\x01\x00\x10Cfoo')
 
-        self.assertEqual(ffs(b"1 1 0 1 0 cbor:u'foo'"),
-                         b'\x04\x00\x00\x01\x00\x01\x00\x10cfoo')
-
     def testcborlists(self):
         self.assertEqual(ffs(b"1 1 0 1 0 cbor:[None, True, False, 42, b'foo']"),
                          b'\n\x00\x00\x01\x00\x01\x00\x10\x85\xf6\xf5\xf4'
--- a/tests/wireprotohelpers.sh	Tue Sep 25 16:32:38 2018 -0400
+++ b/tests/wireprotohelpers.sh	Wed Sep 26 20:33:09 2018 +0900
@@ -1,4 +1,4 @@
-HTTPV2=exp-http-v2-0001
+HTTPV2=exp-http-v2-0002
 MEDIATYPE=application/mercurial-exp-framing-0005
 
 sendhttpraw() {
@@ -20,21 +20,21 @@
     wireprotov2server,
 )
 
-@wireprotov1server.wireprotocommand('customreadonly', permission='pull')
+@wireprotov1server.wireprotocommand(b'customreadonly', permission=b'pull')
 def customreadonlyv1(repo, proto):
     return wireprototypes.bytesresponse(b'customreadonly bytes response')
 
-@wireprotov2server.wireprotocommand('customreadonly', permission='pull')
+@wireprotov2server.wireprotocommand(b'customreadonly', permission=b'pull')
 def customreadonlyv2(repo, proto):
-    return wireprototypes.cborresponse(b'customreadonly bytes response')
+    yield b'customreadonly bytes response'
 
-@wireprotov1server.wireprotocommand('customreadwrite', permission='push')
+@wireprotov1server.wireprotocommand(b'customreadwrite', permission=b'push')
 def customreadwrite(repo, proto):
     return wireprototypes.bytesresponse(b'customreadwrite bytes response')
 
-@wireprotov2server.wireprotocommand('customreadwrite', permission='push')
+@wireprotov2server.wireprotocommand(b'customreadwrite', permission=b'push')
 def customreadwritev2(repo, proto):
-    return wireprototypes.cborresponse(b'customreadwrite bytes response')
+    yield b'customreadwrite bytes response'
 EOF
 
 cat >> $HGRCPATH << EOF
@@ -56,3 +56,10 @@
 web.api.http-v2 = true
 EOF
 }
+
+enablehttpv2client() {
+  cat >> $HGRCPATH << EOF
+[experimental]
+httppeer.advertise-v2 = true
+EOF
+}