changeset 35210:15d38e8fcb1e

merge with stable
author Augie Fackler <augie@google.com>
date Mon, 04 Dec 2017 11:28:29 -0500
parents 9153871d50e0 (diff) 759234670d19 (current diff)
children 1df2f0e1dfd2
files hgext/convert/bzr.py
diffstat 190 files changed, 5951 insertions(+), 1364 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Dec 01 23:27:08 2017 -0500
+++ b/.hgignore	Mon Dec 04 11:28:29 2017 -0500
@@ -24,6 +24,7 @@
 tests/.hypothesis
 tests/hypothesis-generated
 tests/annotated
+tests/exceptions
 tests/*.err
 tests/htmlcov
 build
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.jshintrc	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,11 @@
+{
+    // Enforcing
+    "eqeqeq"        : true,     // true: Require triple equals (===) for comparison
+    "forin"         : true,     // true: Require filtering for..in loops with obj.hasOwnProperty()
+    "freeze"        : true,     // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
+    "nonbsp"        : true,     // true: Prohibit "non-breaking whitespace" characters.
+    "undef"         : true,     // true: Require all non-global variables to be declared (prevents global leaks)
+
+    // Environments
+    "browser"       : true      // Web Browser (window, document, etc)
+}
--- a/contrib/check-code.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/contrib/check-code.py	Mon Dec 04 11:28:29 2017 -0500
@@ -148,7 +148,7 @@
     (r'\bsed\b.*[^\\]\\n', "don't use 'sed ... \\n', use a \\ and a newline"),
     (r'env.*-u', "don't use 'env -u VAR', use 'unset VAR'"),
     (r'cp.* -r ', "don't use 'cp -r', use 'cp -R'"),
-    (r'grep.* -[ABC] ', "don't use grep's context flags"),
+    (r'grep.* -[ABC]', "don't use grep's context flags"),
   ],
   # warnings
   [
@@ -197,7 +197,7 @@
      'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
     (r'^  [^$>].*27\.0\.0\.1',
      'use $LOCALIP not an explicit loopback address'),
-    (r'^  [^$>].*\$LOCALIP.*[^)]$',
+    (r'^  (?![>$] ).*\$LOCALIP.*[^)]$',
      'mark $LOCALIP output lines with (glob) to help tests in BSD jails'),
     (r'^  (cat|find): .*: No such file or directory',
      'use test -f to test for file existence'),
--- a/contrib/perf.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/contrib/perf.py	Mon Dec 04 11:28:29 2017 -0500
@@ -488,6 +488,122 @@
     timer(d)
     fm.end()
 
+@command('perfbundleread', formatteropts, 'BUNDLE')
+def perfbundleread(ui, repo, bundlepath, **opts):
+    """Benchmark reading of bundle files.
+
+    This command is meant to isolate the I/O part of bundle reading as
+    much as possible.
+    """
+    from mercurial import (
+        bundle2,
+        exchange,
+        streamclone,
+    )
+
+    def makebench(fn):
+        def run():
+            with open(bundlepath, 'rb') as fh:
+                bundle = exchange.readbundle(ui, fh, bundlepath)
+                fn(bundle)
+
+        return run
+
+    def makereadnbytes(size):
+        def run():
+            with open(bundlepath, 'rb') as fh:
+                bundle = exchange.readbundle(ui, fh, bundlepath)
+                while bundle.read(size):
+                    pass
+
+        return run
+
+    def makestdioread(size):
+        def run():
+            with open(bundlepath, 'rb') as fh:
+                while fh.read(size):
+                    pass
+
+        return run
+
+    # bundle1
+
+    def deltaiter(bundle):
+        for delta in bundle.deltaiter():
+            pass
+
+    def iterchunks(bundle):
+        for chunk in bundle.getchunks():
+            pass
+
+    # bundle2
+
+    def forwardchunks(bundle):
+        for chunk in bundle._forwardchunks():
+            pass
+
+    def iterparts(bundle):
+        for part in bundle.iterparts():
+            pass
+
+    def iterpartsseekable(bundle):
+        for part in bundle.iterparts(seekable=True):
+            pass
+
+    def seek(bundle):
+        for part in bundle.iterparts(seekable=True):
+            part.seek(0, os.SEEK_END)
+
+    def makepartreadnbytes(size):
+        def run():
+            with open(bundlepath, 'rb') as fh:
+                bundle = exchange.readbundle(ui, fh, bundlepath)
+                for part in bundle.iterparts():
+                    while part.read(size):
+                        pass
+
+        return run
+
+    benches = [
+        (makestdioread(8192), 'read(8k)'),
+        (makestdioread(16384), 'read(16k)'),
+        (makestdioread(32768), 'read(32k)'),
+        (makestdioread(131072), 'read(128k)'),
+    ]
+
+    with open(bundlepath, '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)'),
+            ])
+        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)'),
+            ])
+        elif isinstance(bundle, streamclone.streamcloneapplier):
+            raise error.Abort('stream clone bundles not supported')
+        else:
+            raise error.Abort('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')])
@@ -525,8 +641,8 @@
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
-        dirstate.dirs()
-        del dirstate._map.dirs
+        dirstate.hasdir('a')
+        del dirstate._map._dirs
     timer(d)
     fm.end()
 
@@ -545,8 +661,8 @@
     timer, fm = gettimer(ui, opts)
     "a" in repo.dirstate
     def d():
-        "a" in repo.dirstate._map.dirs
-        del repo.dirstate._map.dirs
+        repo.dirstate.hasdir("a")
+        del repo.dirstate._map._dirs
     timer(d)
     fm.end()
 
@@ -569,7 +685,7 @@
     def d():
         dirstate._map.dirfoldmap.get('a')
         del dirstate._map.dirfoldmap
-        del dirstate._map.dirs
+        del dirstate._map._dirs
     timer(d)
     fm.end()
 
--- a/contrib/python3-whitelist	Fri Dec 01 23:27:08 2017 -0500
+++ b/contrib/python3-whitelist	Mon Dec 04 11:28:29 2017 -0500
@@ -68,7 +68,6 @@
 test-run-tests.py
 test-show-stack.t
 test-status-terse.t
-test-terse-status.t
 test-unified-test.t
 test-update-issue1456.t
 test-update-reverse.t
--- a/contrib/wix/help.wxs	Fri Dec 01 23:27:08 2017 -0500
+++ b/contrib/wix/help.wxs	Mon Dec 04 11:28:29 2017 -0500
@@ -23,6 +23,7 @@
           <File Name="environment.txt" />
           <File Name="extensions.txt" />
           <File Name="filesets.txt" />
+          <File Name="flags.txt" />
           <File Name="glossary.txt" />
           <File Name="hgignore.txt" />
           <File Name="hgweb.txt" />
--- a/hgext/amend.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/amend.py	Mon Dec 04 11:28:29 2017 -0500
@@ -17,6 +17,7 @@
     cmdutil,
     commands,
     error,
+    pycompat,
     registrar,
 )
 
@@ -46,10 +47,11 @@
 
     See :hg:`help commit` for more details.
     """
+    opts = pycompat.byteskwargs(opts)
     if len(opts['note']) > 255:
         raise error.Abort(_("cannot store a note of more than 255 bytes"))
     with repo.wlock(), repo.lock():
         if not opts.get('logfile'):
             opts['message'] = opts.get('message') or repo['.'].description()
         opts['amend'] = True
-        return commands._docommit(ui, repo, *pats, **opts)
+        return commands._docommit(ui, repo, *pats, **pycompat.strkwargs(opts))
--- a/hgext/automv.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/automv.py	Mon Dec 04 11:28:29 2017 -0500
@@ -32,6 +32,7 @@
     copies,
     error,
     extensions,
+    pycompat,
     registrar,
     scmutil,
     similar
@@ -53,6 +54,7 @@
 
 def mvcheck(orig, ui, repo, *pats, **opts):
     """Hook to check for moves at commit time"""
+    opts = pycompat.byteskwargs(opts)
     renames = None
     disabled = opts.pop('no_automv', False)
     if not disabled:
@@ -68,7 +70,7 @@
     with repo.wlock():
         if renames is not None:
             scmutil._markchanges(repo, (), (), renames)
-        return orig(ui, repo, *pats, **opts)
+        return orig(ui, repo, *pats, **pycompat.strkwargs(opts))
 
 def _interestingfiles(repo, matcher):
     """Find what files were added or removed in this commit.
--- a/hgext/blackbox.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/blackbox.py	Mon Dec 04 11:28:29 2017 -0500
@@ -226,7 +226,7 @@
     if not repo.vfs.exists('blackbox.log'):
         return
 
-    limit = opts.get('limit')
+    limit = opts.get(r'limit')
     fp = repo.vfs('blackbox.log', 'r')
     lines = fp.read().split('\n')
 
--- a/hgext/bugzilla.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/bugzilla.py	Mon Dec 04 11:28:29 2017 -0500
@@ -580,7 +580,7 @@
             self.ui.warn(_("Bugzilla/MySQL cannot update bug state\n"))
 
         (user, userid) = self.get_bugzilla_user(committer)
-        now = time.strftime('%Y-%m-%d %H:%M:%S')
+        now = time.strftime(r'%Y-%m-%d %H:%M:%S')
         self.run('''insert into longdescs
                     (bug_id, who, bug_when, thetext)
                     values (%s, %s, %s, %s)''',
--- a/hgext/children.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/children.py	Mon Dec 04 11:28:29 2017 -0500
@@ -19,6 +19,7 @@
 from mercurial.i18n import _
 from mercurial import (
     cmdutil,
+    pycompat,
     registrar,
 )
 
@@ -55,6 +56,7 @@
     See :hg:`help log` and :hg:`help revsets.children`.
 
     """
+    opts = pycompat.byteskwargs(opts)
     rev = opts.get('rev')
     if file_:
         fctx = repo.filectx(file_, changeid=rev)
--- a/hgext/churn.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/churn.py	Mon Dec 04 11:28:29 2017 -0500
@@ -19,6 +19,7 @@
     cmdutil,
     encoding,
     patch,
+    pycompat,
     registrar,
     scmutil,
     util,
@@ -45,6 +46,7 @@
 
 def countrate(ui, repo, amap, *pats, **opts):
     """Calculate stats"""
+    opts = pycompat.byteskwargs(opts)
     if opts.get('dateformat'):
         def getkey(ctx):
             t, tz = ctx.date()
@@ -154,7 +156,7 @@
         return s + " " * (l - encoding.colwidth(s))
 
     amap = {}
-    aliases = opts.get('aliases')
+    aliases = opts.get(r'aliases')
     if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
         aliases = repo.wjoin('.hgchurn')
     if aliases:
@@ -172,7 +174,7 @@
     if not rate:
         return
 
-    if opts.get('sort'):
+    if opts.get(r'sort'):
         rate.sort()
     else:
         rate.sort(key=lambda x: (-sum(x[1]), x))
@@ -185,7 +187,7 @@
     ui.debug("assuming %i character terminal\n" % ttywidth)
     width = ttywidth - maxname - 2 - 2 - 2
 
-    if opts.get('diffstat'):
+    if opts.get(r'diffstat'):
         width -= 15
         def format(name, diffstat):
             added, removed = diffstat
--- a/hgext/commitextras.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/commitextras.py	Mon Dec 04 11:28:29 2017 -0500
@@ -46,7 +46,7 @@
     origcommit = repo.commit
     try:
         def _wrappedcommit(*innerpats, **inneropts):
-            extras = opts.get('extra')
+            extras = opts.get(r'extra')
             if extras:
                 for raw in extras:
                     if '=' not in raw:
@@ -65,7 +65,7 @@
                         msg = _("key '%s' is used internally, can't be set "
                                 "manually")
                         raise error.Abort(msg % k)
-                    inneropts['extra'][k] = v
+                    inneropts[r'extra'][k] = v
             return origcommit(*innerpats, **inneropts)
 
         # This __dict__ logic is needed because the normal
--- a/hgext/convert/bzr.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/bzr.py	Mon Dec 04 11:28:29 2017 -0500
@@ -44,8 +44,8 @@
 class bzr_source(common.converter_source):
     """Reads Bazaar repositories by using the Bazaar Python libraries"""
 
-    def __init__(self, ui, path, revs=None):
-        super(bzr_source, self).__init__(ui, path, revs=revs)
+    def __init__(self, ui, repotype, path, revs=None):
+        super(bzr_source, self).__init__(ui, repotype, path, revs=revs)
 
         if not os.path.exists(os.path.join(path, '.bzr')):
             raise common.NoRepo(_('%s does not look like a Bazaar repository')
--- a/hgext/convert/common.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/common.py	Mon Dec 04 11:28:29 2017 -0500
@@ -73,12 +73,13 @@
 class converter_source(object):
     """Conversion source interface"""
 
-    def __init__(self, ui, path=None, revs=None):
+    def __init__(self, ui, repotype, path=None, revs=None):
         """Initialize conversion source (or raise NoRepo("message")
         exception if path is not a valid repository)"""
         self.ui = ui
         self.path = path
         self.revs = revs
+        self.repotype = repotype
 
         self.encoding = 'utf-8'
 
@@ -218,7 +219,7 @@
 class converter_sink(object):
     """Conversion sink (target) interface"""
 
-    def __init__(self, ui, path):
+    def __init__(self, ui, repotype, path):
         """Initialize conversion sink (or raise NoRepo("message")
         exception if path is not a valid repository)
 
@@ -227,6 +228,7 @@
         self.ui = ui
         self.path = path
         self.created = []
+        self.repotype = repotype
 
     def revmapfile(self):
         """Path to a file that will contain lines
--- a/hgext/convert/convcmd.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/convcmd.py	Mon Dec 04 11:28:29 2017 -0500
@@ -15,6 +15,7 @@
     encoding,
     error,
     hg,
+    scmutil,
     util,
 )
 
@@ -114,7 +115,7 @@
     for name, source, sortmode in source_converters:
         try:
             if not type or name == type:
-                return source(ui, path, revs), sortmode
+                return source(ui, name, path, revs), sortmode
         except (NoRepo, MissingTool) as inst:
             exceptions.append(inst)
     if not ui.quiet:
@@ -128,7 +129,7 @@
     for name, sink in sink_converters:
         try:
             if not type or name == type:
-                return sink(ui, path)
+                return sink(ui, name, path)
         except NoRepo as inst:
             ui.note(_("convert: %s\n") % inst)
         except MissingTool as inst:
@@ -449,7 +450,7 @@
         commit = self.commitcache[rev]
         full = self.opts.get('full')
         changes = self.source.getchanges(rev, full)
-        if isinstance(changes, basestring):
+        if isinstance(changes, bytes):
             if changes == SKIPREV:
                 dest = SKIPREV
             else:
@@ -575,6 +576,7 @@
         ui.status(_("assuming destination %s\n") % dest)
 
     destc = convertsink(ui, dest, opts.get('dest_type'))
+    destc = scmutil.wrapconvertsink(destc)
 
     try:
         srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
--- a/hgext/convert/cvs.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/cvs.py	Mon Dec 04 11:28:29 2017 -0500
@@ -32,8 +32,8 @@
 NoRepo = common.NoRepo
 
 class convert_cvs(converter_source):
-    def __init__(self, ui, path, revs=None):
-        super(convert_cvs, self).__init__(ui, path, revs=revs)
+    def __init__(self, ui, repotype, path, revs=None):
+        super(convert_cvs, self).__init__(ui, repotype, path, revs=revs)
 
         cvs = os.path.join(path, "CVS")
         if not os.path.exists(cvs):
--- a/hgext/convert/darcs.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/darcs.py	Mon Dec 04 11:28:29 2017 -0500
@@ -40,8 +40,8 @@
                 pass
 
 class darcs_source(common.converter_source, common.commandline):
-    def __init__(self, ui, path, revs=None):
-        common.converter_source.__init__(self, ui, path, revs=revs)
+    def __init__(self, ui, repotype, path, revs=None):
+        common.converter_source.__init__(self, ui, repotype, path, revs=revs)
         common.commandline.__init__(self, ui, 'darcs')
 
         # check for _darcs, ElementTree so that we can easily skip
--- a/hgext/convert/filemap.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/filemap.py	Mon Dec 04 11:28:29 2017 -0500
@@ -172,7 +172,7 @@
 
 class filemap_source(common.converter_source):
     def __init__(self, ui, baseconverter, filemap):
-        super(filemap_source, self).__init__(ui)
+        super(filemap_source, self).__init__(ui, baseconverter.repotype)
         self.base = baseconverter
         self.filemapper = filemapper(ui, filemap)
         self.commits = {}
--- a/hgext/convert/git.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/git.py	Mon Dec 04 11:28:29 2017 -0500
@@ -66,8 +66,8 @@
     def gitpipe(self, *args, **kwargs):
         return self._gitcmd(self._run3, *args, **kwargs)
 
-    def __init__(self, ui, path, revs=None):
-        super(convert_git, self).__init__(ui, path, revs=revs)
+    def __init__(self, ui, repotype, path, revs=None):
+        super(convert_git, self).__init__(ui, repotype, path, revs=revs)
         common.commandline.__init__(self, ui, 'git')
 
         # Pass an absolute path to git to prevent from ever being interpreted
--- a/hgext/convert/gnuarch.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/gnuarch.py	Mon Dec 04 11:28:29 2017 -0500
@@ -36,8 +36,8 @@
             self.ren_files = {}
             self.ren_dirs = {}
 
-    def __init__(self, ui, path, revs=None):
-        super(gnuarch_source, self).__init__(ui, path, revs=revs)
+    def __init__(self, ui, repotype, path, revs=None):
+        super(gnuarch_source, self).__init__(ui, repotype, path, revs=revs)
 
         if not os.path.exists(os.path.join(path, '{arch}')):
             raise common.NoRepo(_("%s does not look like a GNU Arch repository")
--- a/hgext/convert/hg.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/hg.py	Mon Dec 04 11:28:29 2017 -0500
@@ -45,8 +45,8 @@
 sha1re = re.compile(r'\b[0-9a-f]{12,40}\b')
 
 class mercurial_sink(common.converter_sink):
-    def __init__(self, ui, path):
-        common.converter_sink.__init__(self, ui, path)
+    def __init__(self, ui, repotype, path):
+        common.converter_sink.__init__(self, ui, repotype, path)
         self.branchnames = ui.configbool('convert', 'hg.usebranchnames')
         self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
         self.tagsbranch = ui.config('convert', 'hg.tagsbranch')
@@ -444,8 +444,8 @@
         return rev in self.repo
 
 class mercurial_source(common.converter_source):
-    def __init__(self, ui, path, revs=None):
-        common.converter_source.__init__(self, ui, path, revs)
+    def __init__(self, ui, repotype, path, revs=None):
+        common.converter_source.__init__(self, ui, repotype, path, revs)
         self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
         self.ignored = set()
         self.saverev = ui.configbool('convert', 'hg.saverev')
--- a/hgext/convert/monotone.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/monotone.py	Mon Dec 04 11:28:29 2017 -0500
@@ -19,8 +19,8 @@
 from . import common
 
 class monotone_source(common.converter_source, common.commandline):
-    def __init__(self, ui, path=None, revs=None):
-        common.converter_source.__init__(self, ui, path, revs)
+    def __init__(self, ui, repotype, path=None, revs=None):
+        common.converter_source.__init__(self, ui, repotype, path, revs)
         if revs and len(revs) > 1:
             raise error.Abort(_('monotone source does not support specifying '
                                'multiple revs'))
--- a/hgext/convert/p4.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/p4.py	Mon Dec 04 11:28:29 2017 -0500
@@ -43,11 +43,11 @@
     return filename
 
 class p4_source(common.converter_source):
-    def __init__(self, ui, path, revs=None):
+    def __init__(self, ui, repotype, path, revs=None):
         # avoid import cycle
         from . import convcmd
 
-        super(p4_source, self).__init__(ui, path, revs=revs)
+        super(p4_source, self).__init__(ui, repotype, path, revs=revs)
 
         if "/" in path and not path.startswith('//'):
             raise common.NoRepo(_('%s does not look like a P4 repository') %
--- a/hgext/convert/subversion.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/convert/subversion.py	Mon Dec 04 11:28:29 2017 -0500
@@ -285,8 +285,8 @@
 # the parent module. A revision has at most one parent.
 #
 class svn_source(converter_source):
-    def __init__(self, ui, url, revs=None):
-        super(svn_source, self).__init__(ui, url, revs=revs)
+    def __init__(self, ui, repotype, url, revs=None):
+        super(svn_source, self).__init__(ui, repotype, url, revs=revs)
 
         if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
                 (os.path.exists(url) and
@@ -1112,9 +1112,9 @@
     def authorfile(self):
         return self.join('hg-authormap')
 
-    def __init__(self, ui, path):
+    def __init__(self, ui, repotype, path):
 
-        converter_sink.__init__(self, ui, path)
+        converter_sink.__init__(self, ui, repotype, path)
         commandline.__init__(self, ui, 'svn')
         self.delete = []
         self.setexec = []
--- a/hgext/extdiff.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/extdiff.py	Mon Dec 04 11:28:29 2017 -0500
@@ -338,6 +338,7 @@
     that revision is compared to the working directory, and, when no
     revisions are specified, the working directory files are compared
     to its parent.'''
+    opts = pycompat.byteskwargs(opts)
     program = opts.get('program')
     option = opts.get('option')
     if not program:
@@ -369,6 +370,7 @@
         self._cmdline = cmdline
 
     def __call__(self, ui, repo, *pats, **opts):
+        opts = pycompat.byteskwargs(opts)
         options = ' '.join(map(util.shellquote, opts['option']))
         if options:
             options = ' ' + options
--- a/hgext/fetch.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/fetch.py	Mon Dec 04 11:28:29 2017 -0500
@@ -19,6 +19,7 @@
     exchange,
     hg,
     lock,
+    pycompat,
     registrar,
     util,
 )
@@ -60,6 +61,7 @@
     Returns 0 on success.
     '''
 
+    opts = pycompat.byteskwargs(opts)
     date = opts.get('date')
     if date:
         opts['date'] = util.parsedate(date)
--- a/hgext/fsmonitor/__init__.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/fsmonitor/__init__.py	Mon Dec 04 11:28:29 2017 -0500
@@ -224,16 +224,21 @@
     Whenever full is False, ignored is False, and the Watchman client is
     available, use Watchman combined with saved state to possibly return only a
     subset of files.'''
-    def bail():
+    def bail(reason):
+        self._ui.debug('fsmonitor: fallback to core status, %s\n' % reason)
         return orig(match, subrepos, unknown, ignored, full=True)
 
-    if full or ignored or not self._watchmanclient.available():
-        return bail()
+    if full:
+        return bail('full rewalk requested')
+    if ignored:
+        return bail('listing ignored files')
+    if not self._watchmanclient.available():
+        return bail('client unavailable')
     state = self._fsmonitorstate
     clock, ignorehash, notefiles = state.get()
     if not clock:
         if state.walk_on_invalidate:
-            return bail()
+            return bail('no clock')
         # Initial NULL clock value, see
         # https://facebook.github.io/watchman/docs/clockspec.html
         clock = 'c:0:0'
@@ -263,7 +268,7 @@
         if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
             # ignore list changed -- can't rely on Watchman state any more
             if state.walk_on_invalidate:
-                return bail()
+                return bail('ignore rules changed')
             notefiles = []
             clock = 'c:0:0'
     else:
@@ -273,7 +278,11 @@
 
     matchfn = match.matchfn
     matchalways = match.always()
-    dmap = self._map._map
+    dmap = self._map
+    if util.safehasattr(dmap, '_map'):
+        # for better performance, directly access the inner dirstate map if the
+        # standard dirstate implementation is in use.
+        dmap = dmap._map
     nonnormalset = self._map.nonnormalset
 
     copymap = self._map.copymap
@@ -334,7 +343,7 @@
     except Exception as ex:
         _handleunavailable(self._ui, state, ex)
         self._watchmanclient.clearconnection()
-        return bail()
+        return bail('exception during run')
     else:
         # We need to propagate the last observed clock up so that we
         # can use it for our next query
@@ -342,7 +351,7 @@
         if result['is_fresh_instance']:
             if state.walk_on_invalidate:
                 state.invalidate()
-                return bail()
+                return bail('fresh instance')
             fresh_instance = True
             # Ignore any prior noteable files from the state info
             notefiles = []
--- a/hgext/gpg.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/gpg.py	Mon Dec 04 11:28:29 2017 -0500
@@ -106,7 +106,7 @@
 def newgpg(ui, **opts):
     """create a new gpg instance"""
     gpgpath = ui.config("gpg", "cmd")
-    gpgkey = opts.get('key')
+    gpgkey = opts.get(r'key')
     if not gpgkey:
         gpgkey = ui.config("gpg", "key")
     return gpg(gpgpath, gpgkey)
@@ -253,6 +253,7 @@
 
 def _dosign(ui, repo, *revs, **opts):
     mygpg = newgpg(ui, **opts)
+    opts = pycompat.byteskwargs(opts)
     sigver = "0"
     sigmessage = ""
 
@@ -312,7 +313,8 @@
                              % hgnode.short(n)
                              for n in nodes])
     try:
-        editor = cmdutil.getcommiteditor(editform='gpg.sign', **opts)
+        editor = cmdutil.getcommiteditor(editform='gpg.sign',
+                                         **pycompat.strkwargs(opts))
         repo.commit(message, opts['user'], opts['date'], match=msigs,
                     editor=editor)
     except ValueError as inst:
--- a/hgext/graphlog.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/graphlog.py	Mon Dec 04 11:28:29 2017 -0500
@@ -66,5 +66,5 @@
 
     This is an alias to :hg:`log -G`.
     """
-    opts['graph'] = True
+    opts[r'graph'] = True
     return commands.log(ui, repo, *pats, **opts)
--- a/hgext/hgk.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/hgk.py	Mon Dec 04 11:28:29 2017 -0500
@@ -48,6 +48,7 @@
     commands,
     obsolete,
     patch,
+    pycompat,
     registrar,
     scmutil,
     util,
@@ -79,6 +80,7 @@
     inferrepo=True)
 def difftree(ui, repo, node1=None, node2=None, *files, **opts):
     """diff trees from two commits"""
+
     def __difftree(repo, node1, node2, files=None):
         assert node2 is not None
         if files is None:
@@ -102,7 +104,7 @@
     ##
 
     while True:
-        if opts['stdin']:
+        if opts[r'stdin']:
             try:
                 line = util.bytesinput(ui.fin, ui.fout).split(' ')
                 node1 = line[0]
@@ -118,8 +120,8 @@
         else:
             node2 = node1
             node1 = repo.changelog.parents(node1)[0]
-        if opts['patch']:
-            if opts['pretty']:
+        if opts[r'patch']:
+            if opts[r'pretty']:
                 catcommit(ui, repo, node2, "")
             m = scmutil.match(repo[node1], files)
             diffopts = patch.difffeatureopts(ui)
@@ -130,7 +132,7 @@
                 ui.write(chunk)
         else:
             __difftree(repo, node1, node2, files=files)
-        if not opts['stdin']:
+        if not opts[r'stdin']:
             break
 
 def catcommit(ui, repo, n, prefix, ctx=None):
@@ -183,7 +185,7 @@
     # strings
     #
     prefix = ""
-    if opts['stdin']:
+    if opts[r'stdin']:
         try:
             (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
             prefix = "    "
@@ -201,7 +203,7 @@
             return 1
         n = repo.lookup(r)
         catcommit(ui, repo, n, prefix)
-        if opts['stdin']:
+        if opts[r'stdin']:
             try:
                 (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
             except EOFError:
@@ -340,7 +342,7 @@
     else:
         full = None
     copy = [x for x in revs]
-    revtree(ui, copy, repo, full, opts['max_count'], opts['parents'])
+    revtree(ui, copy, repo, full, opts[r'max_count'], opts[r'parents'])
 
 @command('view',
     [('l', 'limit', '',
@@ -348,6 +350,7 @@
     _('[-l LIMIT] [REVRANGE]'))
 def view(ui, repo, *etc, **opts):
     "start interactive history viewer"
+    opts = pycompat.byteskwargs(opts)
     os.chdir(repo.root)
     optstr = ' '.join(['--%s %s' % (k, v) for k, v in opts.iteritems() if v])
     if repo.filtername is None:
--- a/hgext/histedit.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/histedit.py	Mon Dec 04 11:28:29 2017 -0500
@@ -203,6 +203,7 @@
     mergeutil,
     node,
     obsolete,
+    pycompat,
     registrar,
     repair,
     scmutil,
@@ -541,9 +542,9 @@
     def commitfunc(**kwargs):
         overrides = {('phases', 'new-commit'): phasemin}
         with repo.ui.configoverride(overrides, 'histedit'):
-            extra = kwargs.get('extra', {}).copy()
+            extra = kwargs.get(r'extra', {}).copy()
             extra['histedit_source'] = src.hex()
-            kwargs['extra'] = extra
+            kwargs[r'extra'] = extra
             return repo.commit(**kwargs)
     return commitfunc
 
@@ -916,7 +917,8 @@
      ('o', 'outgoing', False, _('changesets not found in destination')),
      ('f', 'force', False,
       _('force outgoing even for unrelated repositories')),
-     ('r', 'rev', [], _('first revision to be edited'), _('REV'))],
+     ('r', 'rev', [], _('first revision to be edited'), _('REV'))] +
+    cmdutil.formatteropts,
      _("[OPTIONS] ([ANCESTOR] | --outgoing [URL])"))
 def histedit(ui, repo, *freeargs, **opts):
     """interactively edit changeset history
@@ -1093,6 +1095,9 @@
                     _('histedit requires exactly one ancestor revision'))
 
 def _histedit(ui, repo, state, *freeargs, **opts):
+    opts = pycompat.byteskwargs(opts)
+    fm = ui.formatter('histedit', opts)
+    fm.startitem()
     goal = _getgoal(opts)
     revs = opts.get('rev', [])
     rules = opts.get('commands', '')
@@ -1115,7 +1120,8 @@
         _newhistedit(ui, repo, state, revs, freeargs, opts)
 
     _continuehistedit(ui, repo, state)
-    _finishhistedit(ui, repo, state)
+    _finishhistedit(ui, repo, state, fm)
+    fm.end()
 
 def _continuehistedit(ui, repo, state):
     """This function runs after either:
@@ -1162,7 +1168,7 @@
     state.write()
     ui.progress(_("editing"), None)
 
-def _finishhistedit(ui, repo, state):
+def _finishhistedit(ui, repo, state, fm):
     """This action runs when histedit is finishing its session"""
     repo.ui.pushbuffer()
     hg.update(repo, state.parentctxnode, quietempty=True)
@@ -1196,6 +1202,13 @@
     mapping = {k: v for k, v in mapping.items()
                if k in nodemap and all(n in nodemap for n in v)}
     scmutil.cleanupnodes(repo, mapping, 'histedit')
+    hf = fm.hexfunc
+    fl = fm.formatlist
+    fd = fm.formatdict
+    nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
+                      for oldn, newn in mapping.iteritems()},
+                     key="oldnode", value="newnodes")
+    fm.data(nodechanges=nodechanges)
 
     state.clear()
     if os.path.exists(repo.sjoin('undo')):
--- a/hgext/journal.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/journal.py	Mon Dec 04 11:28:29 2017 -0500
@@ -30,6 +30,7 @@
     localrepo,
     lock,
     node,
+    pycompat,
     registrar,
     util,
 )
@@ -133,7 +134,7 @@
 
     Note that by default entries go from most recent to oldest.
     """
-    order = kwargs.pop('order', max)
+    order = kwargs.pop(r'order', max)
     iterables = [iter(it) for it in iterables]
     # this tracks still active iterables; iterables are deleted as they are
     # exhausted, which is why this is a dictionary and why each entry also
@@ -303,7 +304,7 @@
             # default to 600 seconds timeout
             l = lock.lock(
                 vfs, 'namejournal.lock',
-                int(self.ui.config("ui", "timeout")), desc=desc)
+                self.ui.configint("ui", "timeout"), desc=desc)
             self.ui.warn(_("got lock after %s seconds\n") % l.delay)
         self._lockref = weakref.ref(l)
         return l
@@ -458,6 +459,7 @@
     `hg journal -T json` can be used to produce machine readable output.
 
     """
+    opts = pycompat.byteskwargs(opts)
     name = '.'
     if opts.get('all'):
         if args:
--- a/hgext/keyword.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/keyword.py	Mon Dec 04 11:28:29 2017 -0500
@@ -104,6 +104,7 @@
     match,
     patch,
     pathutil,
+    pycompat,
     registrar,
     scmutil,
     templatefilters,
@@ -380,6 +381,7 @@
     '''Bails out if [keyword] configuration is not active.
     Returns status of working directory.'''
     if kwt:
+        opts = pycompat.byteskwargs(opts)
         return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
                            unknown=opts.get('unknown') or opts.get('all'))
     if ui.configitems('keyword'):
@@ -436,16 +438,16 @@
     ui.setconfig('keywordset', 'svn', svn, 'keyword')
 
     uikwmaps = ui.configitems('keywordmaps')
-    if args or opts.get('rcfile'):
+    if args or opts.get(r'rcfile'):
         ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
         if uikwmaps:
             ui.status(_('\textending current template maps\n'))
-        if opts.get('default') or not uikwmaps:
+        if opts.get(r'default') or not uikwmaps:
             if svn:
                 ui.status(_('\toverriding default svn keywordset\n'))
             else:
                 ui.status(_('\toverriding default cvs keywordset\n'))
-        if opts.get('rcfile'):
+        if opts.get(r'rcfile'):
             ui.readconfig(opts.get('rcfile'))
         if args:
             # simulate hgrc parsing
@@ -453,7 +455,7 @@
             repo.vfs.write('hgrc', rcmaps)
             ui.readconfig(repo.vfs.join('hgrc'))
         kwmaps = dict(ui.configitems('keywordmaps'))
-    elif opts.get('default'):
+    elif opts.get(r'default'):
         if svn:
             ui.status(_('\n\tconfiguration using default svn keywordset\n'))
         else:
@@ -543,6 +545,7 @@
     else:
         cwd = ''
     files = []
+    opts = pycompat.byteskwargs(opts)
     if not opts.get('unknown') or opts.get('all'):
         files = sorted(status.modified + status.added + status.clean)
     kwfiles = kwt.iskwfile(files, wctx)
--- a/hgext/largefiles/lfcommands.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/largefiles/lfcommands.py	Mon Dec 04 11:28:29 2017 -0500
@@ -177,7 +177,7 @@
             convcmd.converter = converter
 
             try:
-                convcmd.convert(ui, src, dest)
+                convcmd.convert(ui, src, dest, source_type='hg', dest_type='hg')
             finally:
                 convcmd.converter = orig
         success = True
--- a/hgext/largefiles/reposetup.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/largefiles/reposetup.py	Mon Dec 04 11:28:29 2017 -0500
@@ -138,7 +138,7 @@
                         sf = lfutil.standin(f)
                         if sf in dirstate:
                             newfiles.append(sf)
-                        elif sf in dirstate.dirs():
+                        elif dirstate.hasdir(sf):
                             # Directory entries could be regular or
                             # standin, check both
                             newfiles.extend((f, sf))
@@ -156,7 +156,7 @@
                     def sfindirstate(f):
                         sf = lfutil.standin(f)
                         dirstate = self.dirstate
-                        return sf in dirstate or sf in dirstate.dirs()
+                        return sf in dirstate or dirstate.hasdir(sf)
 
                     match._files = [f for f in match._files
                                     if sfindirstate(f)]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/lfs/__init__.py	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,180 @@
+# lfs - hash-preserving large file support using Git-LFS protocol
+#
+# 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.
+
+"""lfs - large file support (EXPERIMENTAL)
+
+Configs::
+
+    [lfs]
+    # Remote endpoint. Multiple protocols are supported:
+    # - http(s)://user:pass@example.com/path
+    #   git-lfs endpoint
+    # - file:///tmp/path
+    #   local filesystem, usually for testing
+    # if unset, lfs will prompt setting this when it must use this value.
+    # (default: unset)
+    url = https://example.com/lfs
+
+    # size of a file to make it use LFS
+    threshold = 10M
+
+    # how many times to retry before giving up on transferring an object
+    retry = 5
+"""
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+
+from mercurial import (
+    bundle2,
+    changegroup,
+    context,
+    exchange,
+    extensions,
+    filelog,
+    localrepo,
+    registrar,
+    revlog,
+    scmutil,
+    vfs as vfsmod,
+)
+
+from . import (
+    blobstore,
+    wrapper,
+)
+
+# 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'
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('lfs', 'url',
+    default=configitem.dynamicdefault,
+)
+configitem('lfs', 'threshold',
+    default=None,
+)
+configitem('lfs', 'retry',
+    default=5,
+)
+# Deprecated
+configitem('lfs', 'remotestore',
+    default=None,
+)
+# Deprecated
+configitem('lfs', 'dummy',
+    default=None,
+)
+# Deprecated
+configitem('lfs', 'git-lfs',
+    default=None,
+)
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+templatekeyword = registrar.templatekeyword()
+
+def featuresetup(ui, supported):
+    # don't die on seeing a repo with the lfs requirement
+    supported |= {'lfs'}
+
+def uisetup(ui):
+    localrepo.localrepository.featuresetupfuncs.add(featuresetup)
+
+def reposetup(ui, repo):
+    # Nothing to do with a remote repo
+    if not repo.local():
+        return
+
+    threshold = repo.ui.configbytes('lfs', 'threshold')
+
+    repo.svfs.options['lfsthreshold'] = threshold
+    repo.svfs.lfslocalblobstore = blobstore.local(repo)
+    repo.svfs.lfsremoteblobstore = blobstore.remote(repo)
+
+    # Push hook
+    repo.prepushoutgoinghooks.add('lfs', wrapper.prepush)
+
+    if 'lfs' not in repo.requirements:
+        def checkrequireslfs(ui, repo, **kwargs):
+            if 'lfs' not in repo.requirements:
+                ctx = repo[kwargs['node']]
+                # TODO: is there a way to just walk the files in the commit?
+                if any(ctx[f].islfs() for f in ctx.files()):
+                    repo.requirements.add('lfs')
+                    repo._writerequirements()
+
+        ui.setconfig('hooks', 'commit.lfs', checkrequireslfs, 'lfs')
+
+def wrapfilelog(filelog):
+    wrapfunction = extensions.wrapfunction
+
+    wrapfunction(filelog, 'addrevision', wrapper.filelogaddrevision)
+    wrapfunction(filelog, 'renamed', wrapper.filelogrenamed)
+    wrapfunction(filelog, 'size', wrapper.filelogsize)
+
+def extsetup(ui):
+    wrapfilelog(filelog.filelog)
+
+    wrapfunction = extensions.wrapfunction
+
+    wrapfunction(scmutil, 'wrapconvertsink', wrapper.convertsink)
+
+    wrapfunction(changegroup,
+                 'supportedoutgoingversions',
+                 wrapper.supportedoutgoingversions)
+    wrapfunction(changegroup,
+                 'allsupportedversions',
+                 wrapper.allsupportedversions)
+
+    wrapfunction(context.basefilectx, 'cmp', wrapper.filectxcmp)
+    wrapfunction(context.basefilectx, 'isbinary', wrapper.filectxisbinary)
+    context.basefilectx.islfs = wrapper.filectxislfs
+
+    revlog.addflagprocessor(
+        revlog.REVIDX_EXTSTORED,
+        (
+            wrapper.readfromstore,
+            wrapper.writetostore,
+            wrapper.bypasscheckhash,
+        ),
+    )
+
+    # Make bundle choose changegroup3 instead of changegroup2. This affects
+    # "hg bundle" command. Note: it does not cover all bundle formats like
+    # "packed1". Using "packed1" with lfs will likely cause trouble.
+    names = [k for k, v in exchange._bundlespeccgversions.items() if v == '02']
+    for k in names:
+        exchange._bundlespeccgversions[k] = '03'
+
+    # bundlerepo uses "vfsmod.readonlyvfs(othervfs)", we need to make sure lfs
+    # options and blob stores are passed from othervfs to the new readonlyvfs.
+    wrapfunction(vfsmod.readonlyvfs, '__init__', wrapper.vfsinit)
+
+    # when writing a bundle via "hg bundle" command, upload related LFS blobs
+    wrapfunction(bundle2, 'writenewbundle', wrapper.writenewbundle)
+
+@templatekeyword('lfs_files')
+def lfsfiles(repo, ctx, **args):
+    """List of strings. LFS files added or modified by the changeset."""
+    pointers = wrapper.pointersfromctx(ctx) # {path: pointer}
+    return sorted(pointers.keys())
+
+@command('debuglfsupload',
+         [('r', 'rev', [], _('upload large files introduced by REV'))])
+def debuglfsupload(ui, repo, **opts):
+    """upload lfs blobs added by the working copy parent or given revisions"""
+    revs = opts.get('rev', [])
+    pointers = wrapper.extractpointers(repo, scmutil.revrange(repo, revs))
+    wrapper.uploadblobs(repo, pointers)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/lfs/blobstore.py	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,347 @@
+# blobstore.py - local and remote (speaking Git-LFS protocol) blob storages
+#
+# 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.
+
+from __future__ import absolute_import
+
+import json
+import os
+import re
+
+from mercurial.i18n import _
+
+from mercurial import (
+    error,
+    url as urlmod,
+    util,
+    vfs as vfsmod,
+)
+
+# 64 bytes for SHA256
+_lfsre = re.compile(r'\A[a-f0-9]{64}\Z')
+
+class lfsvfs(vfsmod.vfs):
+    def join(self, path):
+        """split the path at first two characters, like: XX/XXXXX..."""
+        if not _lfsre.match(path):
+            raise error.ProgrammingError('unexpected lfs path: %s' % path)
+        return super(lfsvfs, self).join(path[0:2], path[2:])
+
+class filewithprogress(object):
+    """a file-like object that supports __len__ and read.
+
+    Useful to provide progress information for how many bytes are read.
+    """
+
+    def __init__(self, fp, callback):
+        self._fp = fp
+        self._callback = callback # func(readsize)
+        fp.seek(0, os.SEEK_END)
+        self._len = fp.tell()
+        fp.seek(0)
+
+    def __len__(self):
+        return self._len
+
+    def read(self, size):
+        if self._fp is None:
+            return b''
+        data = self._fp.read(size)
+        if data:
+            if self._callback:
+                self._callback(len(data))
+        else:
+            self._fp.close()
+            self._fp = None
+        return data
+
+class local(object):
+    """Local blobstore for large file contents.
+
+    This blobstore is used both as a cache and as a staging area for large blobs
+    to be uploaded to the remote blobstore.
+    """
+
+    def __init__(self, repo):
+        fullpath = repo.svfs.join('lfs/objects')
+        self.vfs = lfsvfs(fullpath)
+
+    def write(self, oid, data):
+        """Write blob to local blobstore."""
+        with self.vfs(oid, 'wb', atomictemp=True) as fp:
+            fp.write(data)
+
+    def read(self, oid):
+        """Read blob from local blobstore."""
+        return self.vfs.read(oid)
+
+    def has(self, oid):
+        """Returns True if the local blobstore contains the requested blob,
+        False otherwise."""
+        return self.vfs.exists(oid)
+
+class _gitlfsremote(object):
+
+    def __init__(self, repo, url):
+        ui = repo.ui
+        self.ui = ui
+        baseurl, authinfo = url.authinfo()
+        self.baseurl = baseurl.rstrip('/')
+        self.urlopener = urlmod.opener(ui, authinfo)
+        self.retry = ui.configint('lfs', 'retry')
+
+    def writebatch(self, pointers, fromstore):
+        """Batch upload from local to remote blobstore."""
+        self._batch(pointers, fromstore, 'upload')
+
+    def readbatch(self, pointers, tostore):
+        """Batch download from remote to local blostore."""
+        self._batch(pointers, tostore, 'download')
+
+    def _batchrequest(self, pointers, action):
+        """Get metadata about objects pointed by pointers for given action
+
+        Return decoded JSON object like {'objects': [{'oid': '', 'size': 1}]}
+        See https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md
+        """
+        objects = [{'oid': p.oid(), 'size': p.size()} for p in pointers]
+        requestdata = json.dumps({
+            'objects': objects,
+            'operation': action,
+        })
+        batchreq = util.urlreq.request('%s/objects/batch' % self.baseurl,
+                                       data=requestdata)
+        batchreq.add_header('Accept', 'application/vnd.git-lfs+json')
+        batchreq.add_header('Content-Type', 'application/vnd.git-lfs+json')
+        try:
+            rawjson = self.urlopener.open(batchreq).read()
+        except util.urlerr.httperror as ex:
+            raise LfsRemoteError(_('LFS HTTP error: %s (action=%s)')
+                                 % (ex, action))
+        try:
+            response = json.loads(rawjson)
+        except ValueError:
+            raise LfsRemoteError(_('LFS server returns invalid JSON: %s')
+                                 % rawjson)
+        return response
+
+    def _checkforservererror(self, pointers, responses):
+        """Scans errors from objects
+
+        Returns LfsRemoteError if any objects has an error"""
+        for response in responses:
+            error = response.get('error')
+            if error:
+                ptrmap = {p.oid(): p for p in pointers}
+                p = ptrmap.get(response['oid'], None)
+                if error['code'] == 404 and p:
+                    filename = getattr(p, 'filename', 'unknown')
+                    raise LfsRemoteError(
+                        _(('LFS server error. Remote object '
+                          'for file %s not found: %r')) % (filename, response))
+                raise LfsRemoteError(_('LFS server error: %r') % response)
+
+    def _extractobjects(self, response, pointers, action):
+        """extract objects from response of the batch API
+
+        response: parsed JSON object returned by batch API
+        return response['objects'] filtered by action
+        raise if any object has an error
+        """
+        # Scan errors from objects - fail early
+        objects = response.get('objects', [])
+        self._checkforservererror(pointers, objects)
+
+        # Filter objects with given action. Practically, this skips uploading
+        # objects which exist in the server.
+        filteredobjects = [o for o in objects if action in o.get('actions', [])]
+        # But for downloading, we want all objects. Therefore missing objects
+        # should be considered an error.
+        if action == 'download':
+            if len(filteredobjects) < len(objects):
+                missing = [o.get('oid', '?')
+                           for o in objects
+                           if action not in o.get('actions', [])]
+                raise LfsRemoteError(
+                    _('LFS server claims required objects do not exist:\n%s')
+                    % '\n'.join(missing))
+
+        return filteredobjects
+
+    def _basictransfer(self, obj, action, localstore, progress=None):
+        """Download or upload a single object using basic transfer protocol
+
+        obj: dict, an object description returned by batch API
+        action: string, one of ['upload', 'download']
+        localstore: blobstore.local
+
+        See https://github.com/git-lfs/git-lfs/blob/master/docs/api/\
+        basic-transfers.md
+        """
+        oid = str(obj['oid'])
+
+        href = str(obj['actions'][action].get('href'))
+        headers = obj['actions'][action].get('header', {}).items()
+
+        request = util.urlreq.request(href)
+        if action == 'upload':
+            # If uploading blobs, read data from local blobstore.
+            request.data = filewithprogress(localstore.vfs(oid), progress)
+            request.get_method = lambda: 'PUT'
+
+        for k, v in headers:
+            request.add_header(k, v)
+
+        response = b''
+        try:
+            req = self.urlopener.open(request)
+            while True:
+                data = req.read(1048576)
+                if not data:
+                    break
+                if action == 'download' and progress:
+                    progress(len(data))
+                response += data
+        except util.urlerr.httperror as ex:
+            raise LfsRemoteError(_('HTTP error: %s (oid=%s, action=%s)')
+                                 % (ex, oid, action))
+
+        if action == 'download':
+            # If downloading blobs, store downloaded data to local blobstore
+            localstore.write(oid, response)
+
+    def _batch(self, pointers, localstore, action):
+        if action not in ['upload', 'download']:
+            raise error.ProgrammingError('invalid Git-LFS action: %s' % action)
+
+        response = self._batchrequest(pointers, action)
+        prunningsize = [0]
+        objects = self._extractobjects(response, pointers, action)
+        total = sum(x.get('size', 0) for x in objects)
+        topic = {'upload': _('lfs uploading'),
+                 'download': _('lfs downloading')}[action]
+        if self.ui.verbose and len(objects) > 1:
+            self.ui.write(_('lfs: need to transfer %d objects (%s)\n')
+                          % (len(objects), util.bytecount(total)))
+        self.ui.progress(topic, 0, total=total)
+        def progress(size):
+            # advance progress bar by "size" bytes
+            prunningsize[0] += size
+            self.ui.progress(topic, prunningsize[0], total=total)
+        for obj in sorted(objects, key=lambda o: o.get('oid')):
+            objsize = obj.get('size', 0)
+            if self.ui.verbose:
+                if action == 'download':
+                    msg = _('lfs: downloading %s (%s)\n')
+                elif action == 'upload':
+                    msg = _('lfs: uploading %s (%s)\n')
+                self.ui.write(msg % (obj.get('oid'), util.bytecount(objsize)))
+            origrunningsize = prunningsize[0]
+            retry = self.retry
+            while True:
+                prunningsize[0] = origrunningsize
+                try:
+                    self._basictransfer(obj, action, localstore,
+                                        progress=progress)
+                    break
+                except Exception as ex:
+                    if retry > 0:
+                        if self.ui.verbose:
+                            self.ui.write(
+                                _('lfs: failed: %r (remaining retry %d)\n')
+                                % (ex, retry))
+                        retry -= 1
+                        continue
+                    raise
+
+        self.ui.progress(topic, pos=None, total=total)
+
+    def __del__(self):
+        # copied from mercurial/httppeer.py
+        urlopener = getattr(self, 'urlopener', None)
+        if urlopener:
+            for h in urlopener.handlers:
+                h.close()
+                getattr(h, "close_all", lambda : None)()
+
+class _dummyremote(object):
+    """Dummy store storing blobs to temp directory."""
+
+    def __init__(self, repo, url):
+        fullpath = repo.vfs.join('lfs', url.path)
+        self.vfs = lfsvfs(fullpath)
+
+    def writebatch(self, pointers, fromstore):
+        for p in pointers:
+            content = fromstore.read(p.oid())
+            with self.vfs(p.oid(), 'wb', atomictemp=True) as fp:
+                fp.write(content)
+
+    def readbatch(self, pointers, tostore):
+        for p in pointers:
+            content = self.vfs.read(p.oid())
+            tostore.write(p.oid(), content)
+
+class _nullremote(object):
+    """Null store storing blobs to /dev/null."""
+
+    def __init__(self, repo, url):
+        pass
+
+    def writebatch(self, pointers, fromstore):
+        pass
+
+    def readbatch(self, pointers, tostore):
+        pass
+
+class _promptremote(object):
+    """Prompt user to set lfs.url when accessed."""
+
+    def __init__(self, repo, url):
+        pass
+
+    def writebatch(self, pointers, fromstore, ui=None):
+        self._prompt()
+
+    def readbatch(self, pointers, tostore, ui=None):
+        self._prompt()
+
+    def _prompt(self):
+        raise error.Abort(_('lfs.url needs to be configured'))
+
+_storemap = {
+    'https': _gitlfsremote,
+    'http': _gitlfsremote,
+    'file': _dummyremote,
+    'null': _nullremote,
+    None: _promptremote,
+}
+
+def remote(repo):
+    """remotestore factory. return a store in _storemap depending on config"""
+    defaulturl = ''
+
+    # convert deprecated configs to the new url. TODO: remove this if other
+    # places are migrated to the new url config.
+    # deprecated config: lfs.remotestore
+    deprecatedstore = repo.ui.config('lfs', 'remotestore')
+    if deprecatedstore == 'dummy':
+        # deprecated config: lfs.remotepath
+        defaulturl = 'file://' + repo.ui.config('lfs', 'remotepath')
+    elif deprecatedstore == 'git-lfs':
+        # deprecated config: lfs.remoteurl
+        defaulturl = repo.ui.config('lfs', 'remoteurl')
+    elif deprecatedstore == 'null':
+        defaulturl = 'null://'
+
+    url = util.url(repo.ui.config('lfs', 'url', defaulturl))
+    scheme = url.scheme
+    if scheme not in _storemap:
+        raise error.Abort(_('lfs: unknown url scheme: %s') % scheme)
+    return _storemap[scheme](repo, url)
+
+class LfsRemoteError(error.RevlogError):
+    pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/lfs/pointer.py	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,73 @@
+# pointer.py - Git-LFS pointer serialization
+#
+# 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.
+
+from __future__ import absolute_import
+
+import re
+
+from mercurial.i18n import _
+
+from mercurial import (
+    error,
+)
+
+class InvalidPointer(error.RevlogError):
+    pass
+
+class gitlfspointer(dict):
+    VERSION = 'https://git-lfs.github.com/spec/v1'
+
+    def __init__(self, *args, **kwargs):
+        self['version'] = self.VERSION
+        super(gitlfspointer, self).__init__(*args, **kwargs)
+
+    @classmethod
+    def deserialize(cls, text):
+        try:
+            return cls(l.split(' ', 1) for l in text.splitlines()).validate()
+        except ValueError: # l.split returns 1 item instead of 2
+            raise InvalidPointer(_('cannot parse git-lfs text: %r') % text)
+
+    def serialize(self):
+        sortkeyfunc = lambda x: (x[0] != 'version', x)
+        items = sorted(self.validate().iteritems(), key=sortkeyfunc)
+        return ''.join('%s %s\n' % (k, v) for k, v in items)
+
+    def oid(self):
+        return self['oid'].split(':')[-1]
+
+    def size(self):
+        return int(self['size'])
+
+    # regular expressions used by _validate
+    # see https://github.com/git-lfs/git-lfs/blob/master/docs/spec.md
+    _keyre = re.compile(r'\A[a-z0-9.-]+\Z')
+    _valuere = re.compile(r'\A[^\n]*\Z')
+    _requiredre = {
+        'size': re.compile(r'\A[0-9]+\Z'),
+        'oid': re.compile(r'\Asha256:[0-9a-f]{64}\Z'),
+        'version': re.compile(r'\A%s\Z' % re.escape(VERSION)),
+    }
+
+    def validate(self):
+        """raise InvalidPointer on error. return self if there is no error"""
+        requiredcount = 0
+        for k, v in self.iteritems():
+            if k in self._requiredre:
+                if not self._requiredre[k].match(v):
+                    raise InvalidPointer(_('unexpected value: %s=%r') % (k, v))
+                requiredcount += 1
+            elif not self._keyre.match(k):
+                raise InvalidPointer(_('unexpected key: %s') % k)
+            if not self._valuere.match(v):
+                raise InvalidPointer(_('unexpected value: %s=%r') % (k, v))
+        if len(self._requiredre) != requiredcount:
+            miss = sorted(set(self._requiredre.keys()).difference(self.keys()))
+            raise InvalidPointer(_('missed keys: %s') % ', '.join(miss))
+        return self
+
+deserialize = gitlfspointer.deserialize
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/lfs/wrapper.py	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,273 @@
+# wrapper.py - methods wrapping core mercurial logic
+#
+# 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.
+
+from __future__ import absolute_import
+
+import hashlib
+
+from mercurial.i18n import _
+from mercurial.node import bin, nullid, short
+
+from mercurial import (
+    error,
+    filelog,
+    revlog,
+    util,
+)
+
+from . import (
+    blobstore,
+    pointer,
+)
+
+def supportedoutgoingversions(orig, repo):
+    versions = orig(repo)
+    versions.discard('01')
+    versions.discard('02')
+    versions.add('03')
+    return versions
+
+def allsupportedversions(orig, ui):
+    versions = orig(ui)
+    versions.add('03')
+    return versions
+
+def bypasscheckhash(self, text):
+    return False
+
+def readfromstore(self, text):
+    """Read filelog content from local blobstore transform for flagprocessor.
+
+    Default tranform for flagprocessor, returning contents from blobstore.
+    Returns a 2-typle (text, validatehash) where validatehash is True as the
+    contents of the blobstore should be checked using checkhash.
+    """
+    p = pointer.deserialize(text)
+    oid = p.oid()
+    store = self.opener.lfslocalblobstore
+    if not store.has(oid):
+        p.filename = getattr(self, 'indexfile', None)
+        self.opener.lfsremoteblobstore.readbatch([p], store)
+    text = store.read(oid)
+
+    # pack hg filelog metadata
+    hgmeta = {}
+    for k in p.keys():
+        if k.startswith('x-hg-'):
+            name = k[len('x-hg-'):]
+            hgmeta[name] = p[k]
+    if hgmeta or text.startswith('\1\n'):
+        text = filelog.packmeta(hgmeta, text)
+
+    return (text, True)
+
+def writetostore(self, text):
+    # hg filelog metadata (includes rename, etc)
+    hgmeta, offset = filelog.parsemeta(text)
+    if offset and offset > 0:
+        # lfs blob does not contain hg filelog metadata
+        text = text[offset:]
+
+    # git-lfs only supports sha256
+    oid = hashlib.sha256(text).hexdigest()
+    self.opener.lfslocalblobstore.write(oid, text)
+
+    # replace contents with metadata
+    longoid = 'sha256:%s' % oid
+    metadata = pointer.gitlfspointer(oid=longoid, size=str(len(text)))
+
+    # by default, we expect the content to be binary. however, LFS could also
+    # be used for non-binary content. add a special entry for non-binary data.
+    # this will be used by filectx.isbinary().
+    if not util.binary(text):
+        # not hg filelog metadata (affecting commit hash), no "x-hg-" prefix
+        metadata['x-is-binary'] = '0'
+
+    # translate hg filelog metadata to lfs metadata with "x-hg-" prefix
+    if hgmeta is not None:
+        for k, v in hgmeta.iteritems():
+            metadata['x-hg-%s' % k] = v
+
+    rawtext = metadata.serialize()
+    return (rawtext, False)
+
+def _islfs(rlog, node=None, rev=None):
+    if rev is None:
+        if node is None:
+            # both None - likely working copy content where node is not ready
+            return False
+        rev = rlog.rev(node)
+    else:
+        node = rlog.node(rev)
+    if node == nullid:
+        return False
+    flags = rlog.flags(rev)
+    return bool(flags & revlog.REVIDX_EXTSTORED)
+
+def filelogaddrevision(orig, self, text, transaction, link, p1, p2,
+                       cachedelta=None, node=None,
+                       flags=revlog.REVIDX_DEFAULT_FLAGS, **kwds):
+    threshold = self.opener.options['lfsthreshold']
+    textlen = len(text)
+    # exclude hg rename meta from file size
+    meta, offset = filelog.parsemeta(text)
+    if offset:
+        textlen -= offset
+
+    if threshold and textlen > threshold:
+        flags |= revlog.REVIDX_EXTSTORED
+
+    return orig(self, text, transaction, link, p1, p2, cachedelta=cachedelta,
+                node=node, flags=flags, **kwds)
+
+def filelogrenamed(orig, self, node):
+    if _islfs(self, node):
+        rawtext = self.revision(node, raw=True)
+        if not rawtext:
+            return False
+        metadata = pointer.deserialize(rawtext)
+        if 'x-hg-copy' in metadata and 'x-hg-copyrev' in metadata:
+            return metadata['x-hg-copy'], bin(metadata['x-hg-copyrev'])
+        else:
+            return False
+    return orig(self, node)
+
+def filelogsize(orig, self, rev):
+    if _islfs(self, rev=rev):
+        # fast path: use lfs metadata to answer size
+        rawtext = self.revision(rev, raw=True)
+        metadata = pointer.deserialize(rawtext)
+        return int(metadata['size'])
+    return orig(self, rev)
+
+def filectxcmp(orig, self, fctx):
+    """returns True if text is different than fctx"""
+    # some fctx (ex. hg-git) is not based on basefilectx and do not have islfs
+    if self.islfs() and getattr(fctx, 'islfs', lambda: False)():
+        # fast path: check LFS oid
+        p1 = pointer.deserialize(self.rawdata())
+        p2 = pointer.deserialize(fctx.rawdata())
+        return p1.oid() != p2.oid()
+    return orig(self, fctx)
+
+def filectxisbinary(orig, self):
+    if self.islfs():
+        # fast path: use lfs metadata to answer isbinary
+        metadata = pointer.deserialize(self.rawdata())
+        # if lfs metadata says nothing, assume it's binary by default
+        return bool(int(metadata.get('x-is-binary', 1)))
+    return orig(self)
+
+def filectxislfs(self):
+    return _islfs(self.filelog(), self.filenode())
+
+def convertsink(orig, sink):
+    sink = orig(sink)
+    if sink.repotype == 'hg':
+        class lfssink(sink.__class__):
+            def putcommit(self, files, copies, parents, commit, source, revmap,
+                          full, cleanp2):
+                pc = super(lfssink, self).putcommit
+                node = pc(files, copies, parents, commit, source, revmap, full,
+                          cleanp2)
+
+                if 'lfs' not in self.repo.requirements:
+                    ctx = self.repo[node]
+
+                    # The file list may contain removed files, so check for
+                    # membership before assuming it is in the context.
+                    if any(f in ctx and ctx[f].islfs() for f, n in files):
+                        self.repo.requirements.add('lfs')
+                        self.repo._writerequirements()
+
+                return node
+
+        sink.__class__ = lfssink
+
+    return sink
+
+def vfsinit(orig, self, othervfs):
+    orig(self, othervfs)
+    # copy lfs related options
+    for k, v in othervfs.options.items():
+        if k.startswith('lfs'):
+            self.options[k] = v
+    # also copy lfs blobstores. note: this can run before reposetup, so lfs
+    # blobstore attributes are not always ready at this time.
+    for name in ['lfslocalblobstore', 'lfsremoteblobstore']:
+        if util.safehasattr(othervfs, name):
+            setattr(self, name, getattr(othervfs, name))
+
+def _canskipupload(repo):
+    # if remotestore is a null store, upload is a no-op and can be skipped
+    return isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote)
+
+def candownload(repo):
+    # if remotestore is a null store, downloads will lead to nothing
+    return not isinstance(repo.svfs.lfsremoteblobstore, blobstore._nullremote)
+
+def uploadblobsfromrevs(repo, revs):
+    '''upload lfs blobs introduced by revs
+
+    Note: also used by other extensions e. g. infinitepush. avoid renaming.
+    '''
+    if _canskipupload(repo):
+        return
+    pointers = extractpointers(repo, revs)
+    uploadblobs(repo, pointers)
+
+def prepush(pushop):
+    """Prepush hook.
+
+    Read through the revisions to push, looking for filelog entries that can be
+    deserialized into metadata so that we can block the push on their upload to
+    the remote blobstore.
+    """
+    return uploadblobsfromrevs(pushop.repo, pushop.outgoing.missing)
+
+def writenewbundle(orig, ui, repo, source, filename, bundletype, outgoing,
+                   *args, **kwargs):
+    """upload LFS blobs added by outgoing revisions on 'hg bundle'"""
+    uploadblobsfromrevs(repo, outgoing.missing)
+    return orig(ui, repo, source, filename, bundletype, outgoing, *args,
+                **kwargs)
+
+def extractpointers(repo, revs):
+    """return a list of lfs pointers added by given revs"""
+    ui = repo.ui
+    if ui.debugflag:
+        ui.write(_('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 pointers.values()
+
+def pointersfromctx(ctx):
+    """return a dict {path: pointer} for given single changectx"""
+    result = {}
+    for f in ctx.files():
+        if f not in ctx:
+            continue
+        fctx = ctx[f]
+        if not _islfs(fctx.filelog(), fctx.filenode()):
+            continue
+        try:
+            result[f] = pointer.deserialize(fctx.rawdata())
+        except pointer.InvalidPointer as ex:
+            raise error.Abort(_('lfs: corrupted pointer (%s@%s): %s\n')
+                              % (f, short(ctx.node()), ex))
+    return result
+
+def uploadblobs(repo, pointers):
+    """upload given pointers from local blobstore"""
+    if not pointers:
+        return
+
+    remoteblob = repo.svfs.lfsremoteblobstore
+    remoteblob.writebatch(pointers, repo.svfs.lfslocalblobstore)
--- a/hgext/logtoprocess.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/logtoprocess.py	Mon Dec 04 11:28:29 2017 -0500
@@ -124,8 +124,6 @@
                 env = dict(itertools.chain(encoding.environ.items(),
                                            msgpairs, optpairs),
                            EVENT=event, HGPID=str(os.getpid()))
-                # Connect stdin to /dev/null to prevent child processes messing
-                # with mercurial's stdin.
                 runshellcommand(script, env)
             return super(logtoprocessui, self).log(event, *msg, **opts)
 
--- a/hgext/mq.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/mq.py	Mon Dec 04 11:28:29 2017 -0500
@@ -565,7 +565,7 @@
                 return index
         return None
 
-    guard_re = re.compile(r'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
+    guard_re = re.compile(br'\s?#([-+][^-+# \t\r\n\f][^# \t\r\n\f]*)')
 
     def parseseries(self):
         self.series = []
--- a/hgext/patchbomb.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/patchbomb.py	Mon Dec 04 11:28:29 2017 -0500
@@ -89,6 +89,7 @@
     mail,
     node as nodemod,
     patch,
+    pycompat,
     registrar,
     repair,
     scmutil,
@@ -318,7 +319,7 @@
     tmpfn = os.path.join(tmpdir, 'bundle')
     btype = ui.config('patchbomb', 'bundletype')
     if btype:
-        opts['type'] = btype
+        opts[r'type'] = btype
     try:
         commands.bundle(ui, repo, tmpfn, dest, **opts)
         return util.readfile(tmpfn)
@@ -338,8 +339,8 @@
     the user through the editor.
     """
     ui = repo.ui
-    if opts.get('desc'):
-        body = open(opts.get('desc')).read()
+    if opts.get(r'desc'):
+        body = open(opts.get(r'desc')).read()
     else:
         ui.write(_('\nWrite the introductory message for the '
                    'patch series.\n\n'))
@@ -359,21 +360,21 @@
     """
     ui = repo.ui
     _charsets = mail._charsets(ui)
-    subj = (opts.get('subject')
+    subj = (opts.get(r'subject')
             or prompt(ui, 'Subject:', 'A bundle for your repository'))
 
     body = _getdescription(repo, '', sender, **opts)
     msg = emailmod.MIMEMultipart.MIMEMultipart()
     if body:
-        msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
+        msg.attach(mail.mimeencode(ui, body, _charsets, opts.get(r'test')))
     datapart = emailmod.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
     datapart.set_payload(bundle)
-    bundlename = '%s.hg' % opts.get('bundlename', 'bundle')
+    bundlename = '%s.hg' % opts.get(r'bundlename', 'bundle')
     datapart.add_header('Content-Disposition', 'attachment',
                         filename=bundlename)
     emailmod.Encoders.encode_base64(datapart)
     msg.attach(datapart)
-    msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
+    msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get(r'test'))
     return [(msg, subj, None)]
 
 def _makeintro(repo, sender, revs, patches, **opts):
@@ -384,9 +385,9 @@
     _charsets = mail._charsets(ui)
 
     # use the last revision which is likely to be a bookmarked head
-    prefix = _formatprefix(ui, repo, revs.last(), opts.get('flag'),
+    prefix = _formatprefix(ui, repo, revs.last(), opts.get(r'flag'),
                            0, len(patches), numbered=True)
-    subj = (opts.get('subject') or
+    subj = (opts.get(r'subject') or
             prompt(ui, '(optional) Subject: ', rest=prefix, default=''))
     if not subj:
         return None         # skip intro if the user doesn't bother
@@ -394,7 +395,7 @@
     subj = prefix + ' ' + subj
 
     body = ''
-    if opts.get('diffstat'):
+    if opts.get(r'diffstat'):
         # generate a cumulative diffstat of the whole patch series
         diffstat = patch.diffstat(sum(patches, []))
         body = '\n' + diffstat
@@ -402,9 +403,9 @@
         diffstat = None
 
     body = _getdescription(repo, body, sender, **opts)
-    msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
+    msg = mail.mimeencode(ui, body, _charsets, opts.get(r'test'))
     msg['Subject'] = mail.headencode(ui, subj, _charsets,
-                                     opts.get('test'))
+                                     opts.get(r'test'))
     return (msg, subj, diffstat)
 
 def _getpatchmsgs(repo, sender, revs, patchnames=None, **opts):
@@ -414,6 +415,7 @@
 
     This function returns a list of "email" tuples (subject, content, None).
     """
+    bytesopts = pycompat.byteskwargs(opts)
     ui = repo.ui
     _charsets = mail._charsets(ui)
     patches = list(_getpatches(repo, revs, **opts))
@@ -423,7 +425,7 @@
              % len(patches))
 
     # build the intro message, or skip it if the user declines
-    if introwanted(ui, opts, len(patches)):
+    if introwanted(ui, bytesopts, len(patches)):
         msg = _makeintro(repo, sender, revs, patches, **opts)
         if msg:
             msgs.append(msg)
@@ -437,8 +439,8 @@
     for i, (r, p) in enumerate(zip(revs, patches)):
         if patchnames:
             name = patchnames[i]
-        msg = makepatch(ui, repo, r, p, opts, _charsets, i + 1,
-                        len(patches), numbered, name)
+        msg = makepatch(ui, repo, r, p, bytesopts, _charsets,
+                        i + 1, len(patches), numbered, name)
         msgs.append(msg)
 
     return msgs
@@ -579,6 +581,7 @@
     Before using this command, you will need to enable email in your
     hgrc. See the [email] section in hgrc(5) for details.
     '''
+    opts = pycompat.byteskwargs(opts)
 
     _charsets = mail._charsets(ui)
 
@@ -672,12 +675,13 @@
               prompt(ui, 'From', ui.username()))
 
     if bundle:
-        bundledata = _getbundle(repo, dest, **opts)
-        bundleopts = opts.copy()
-        bundleopts.pop('bundle', None)  # already processed
+        stropts = pycompat.strkwargs(opts)
+        bundledata = _getbundle(repo, dest, **stropts)
+        bundleopts = stropts.copy()
+        bundleopts.pop(r'bundle', None)  # already processed
         msgs = _getbundlemsgs(repo, sender, bundledata, **bundleopts)
     else:
-        msgs = _getpatchmsgs(repo, sender, revs, **opts)
+        msgs = _getpatchmsgs(repo, sender, revs, **pycompat.strkwargs(opts))
 
     showaddrs = []
 
--- a/hgext/rebase.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/rebase.py	Mon Dec 04 11:28:29 2017 -0500
@@ -21,7 +21,6 @@
 
 from mercurial.i18n import _
 from mercurial.node import (
-    hex,
     nullid,
     nullrev,
     short,
@@ -43,6 +42,7 @@
     obsutil,
     patch,
     phases,
+    pycompat,
     registrar,
     repair,
     revset,
@@ -53,7 +53,6 @@
 )
 
 release = lock.release
-templateopts = cmdutil.templateopts
 
 # The following constants are used throughout the rebase module. The ordering of
 # their values must be maintained.
@@ -179,6 +178,7 @@
         # other extensions
         self.keepopen = opts.get('keepopen', False)
         self.obsoletenotrebased = {}
+        self.obsoletewithoutsuccessorindestination = set()
 
     @property
     def repo(self):
@@ -311,9 +311,10 @@
         if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
             return
         obsoleteset = set(obsoleterevs)
-        self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
-                                    obsoleteset, destmap)
+        self.obsoletenotrebased, self.obsoletewithoutsuccessorindestination = \
+            _computeobsoletenotrebased(self.repo, obsoleteset, destmap)
         skippedset = set(self.obsoletenotrebased)
+        skippedset.update(self.obsoletewithoutsuccessorindestination)
         _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
 
     def _prepareabortorcontinue(self, isabort):
@@ -419,12 +420,26 @@
     def _performrebasesubset(self, tr, subset, pos, total):
         repo, ui, opts = self.repo, self.ui, self.opts
         sortedrevs = repo.revs('sort(%ld, -topo)', subset)
+        allowdivergence = self.ui.configbool(
+            'experimental', 'evolution.allowdivergence')
+        if not allowdivergence:
+            sortedrevs -= repo.revs(
+                'descendants(%ld) and not %ld',
+                self.obsoletewithoutsuccessorindestination,
+                self.obsoletewithoutsuccessorindestination,
+            )
         for rev in sortedrevs:
             dest = self.destmap[rev]
             ctx = repo[rev]
             desc = _ctxdesc(ctx)
             if self.state[rev] == rev:
                 ui.status(_('already rebased %s\n') % desc)
+            elif (not allowdivergence
+                  and rev in self.obsoletewithoutsuccessorindestination):
+                msg = _('note: not rebasing %s and its descendants as '
+                        'this would cause divergence\n') % desc
+                repo.ui.status(msg)
+                self.skipped.add(rev)
             elif rev in self.obsoletenotrebased:
                 succ = self.obsoletenotrebased[rev]
                 if succ is None:
@@ -594,7 +609,7 @@
     ('t', 'tool', '', _('specify merge tool')),
     ('c', 'continue', False, _('continue an interrupted rebase')),
     ('a', 'abort', False, _('abort an interrupted rebase'))] +
-     templateopts,
+    cmdutil.formatteropts,
     _('[-s REV | -b REV] [-d REV] [OPTION]'))
 def rebase(ui, repo, **opts):
     """move changeset (and descendants) to a different branch
@@ -699,6 +714,7 @@
     unresolved conflicts.
 
     """
+    opts = pycompat.byteskwargs(opts)
     rbsrt = rebaseruntime(repo, ui, opts)
 
     with repo.wlock(), repo.lock():
@@ -1546,22 +1562,26 @@
                 replacements[oldnode] = succs
     scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
     if fm:
-        nodechanges = {hex(oldn): [hex(n) for n in newn]
-                       for oldn, newn in replacements.iteritems()}
+        hf = fm.hexfunc
+        fl = fm.formatlist
+        fd = fm.formatdict
+        nodechanges = fd({hf(oldn): fl([hf(n) for n in newn], name='node')
+                          for oldn, newn in replacements.iteritems()},
+                         key="oldnode", value="newnodes")
         fm.data(nodechanges=nodechanges)
 
 def pullrebase(orig, ui, repo, *args, **opts):
     'Call rebase after pull if the latter has been invoked with --rebase'
     ret = None
-    if opts.get('rebase'):
+    if opts.get(r'rebase'):
         if ui.configbool('commands', 'rebase.requiredest'):
             msg = _('rebase destination required by configuration')
             hint = _('use hg pull followed by hg rebase -d DEST')
             raise error.Abort(msg, hint=hint)
 
         with repo.wlock(), repo.lock():
-            if opts.get('update'):
-                del opts['update']
+            if opts.get(r'update'):
+                del opts[r'update']
                 ui.debug('--update and --rebase are not compatible, ignoring '
                          'the update flag\n')
 
@@ -1582,15 +1602,15 @@
             if revspostpull > revsprepull:
                 # --rev option from pull conflict with rebase own --rev
                 # dropping it
-                if 'rev' in opts:
-                    del opts['rev']
+                if r'rev' in opts:
+                    del opts[r'rev']
                 # positional argument from pull conflicts with rebase's own
                 # --source.
-                if 'source' in opts:
-                    del opts['source']
+                if r'source' in opts:
+                    del opts[r'source']
                 # revsprepull is the len of the repo, not revnum of tip.
                 destspace = list(repo.changelog.revs(start=revsprepull))
-                opts['_destspace'] = destspace
+                opts[r'_destspace'] = destspace
                 try:
                     rebase(ui, repo, **opts)
                 except error.NoMergeDestAbort:
@@ -1604,7 +1624,7 @@
                         # with warning and trumpets
                         commands.update(ui, repo)
     else:
-        if opts.get('tool'):
+        if opts.get(r'tool'):
             raise error.Abort(_('--tool can only be used with --rebase'))
         ret = orig(ui, repo, *args, **opts)
 
@@ -1615,11 +1635,16 @@
     return set(r for r in revs if repo[r].obsolete())
 
 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
-    """return a mapping obsolete => successor for all obsolete nodes to be
-    rebased that have a successors in the destination
+    """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
+
+    `obsoletenotrebased` is a mapping mapping obsolete => successor for all
+    obsolete nodes to be rebased given in `rebaseobsrevs`.
 
-    obsolete => None entries in the mapping indicate nodes with no successor"""
+    `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
+    without a successor in destination.
+    """
     obsoletenotrebased = {}
+    obsoletewithoutsuccessorindestination = set([])
 
     assert repo.filtername is None
     cl = repo.changelog
@@ -1640,8 +1665,15 @@
                 if cl.isancestor(succnode, destnode):
                     obsoletenotrebased[srcrev] = nodemap[succnode]
                     break
+            else:
+                # 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(nodemap[s] in destmap
+                       for s in successors if s != srcnode):
+                    obsoletewithoutsuccessorindestination.add(srcrev)
 
-    return obsoletenotrebased
+    return obsoletenotrebased, obsoletewithoutsuccessorindestination
 
 def summaryhook(ui, repo):
     if not repo.vfs.exists('rebasestate'):
--- a/hgext/releasenotes.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/releasenotes.py	Mon Dec 04 11:28:29 2017 -0500
@@ -25,6 +25,7 @@
     error,
     minirst,
     node,
+    pycompat,
     registrar,
     scmutil,
     util,
@@ -570,6 +571,8 @@
     admonitions along with their title. This also includes the custom
     admonitions (if any).
     """
+
+    opts = pycompat.byteskwargs(opts)
     sections = releasenotessections(ui, repo)
 
     listflag = opts.get('list')
--- a/hgext/shelve.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/shelve.py	Mon Dec 04 11:28:29 2017 -0500
@@ -43,6 +43,7 @@
     node as nodemod,
     patch,
     phases,
+    pycompat,
     registrar,
     repair,
     scmutil,
@@ -380,7 +381,7 @@
             editor_ = False
             if editor:
                 editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
-                                                  **opts)
+                                                  **pycompat.strkwargs(opts))
             with repo.ui.configoverride(overrides):
                 return repo.commit(message, shelveuser, opts.get('date'),
                                    match, editor=editor_, extra=extra)
@@ -389,6 +390,7 @@
                 repo.mq.checkapplied = saved
 
     def interactivecommitfunc(ui, repo, *pats, **opts):
+        opts = pycompat.byteskwargs(opts)
         match = scmutil.match(repo['.'], pats, {})
         message = opts['message']
         return commitfunc(ui, repo, message, match, opts)
@@ -465,7 +467,7 @@
         else:
             node = cmdutil.dorecord(ui, repo, commitfunc, None,
                                     False, cmdutil.recordfilter, *pats,
-                                    **opts)
+                                    **pycompat.strkwargs(opts))
         if not node:
             _nothingtoshelvemessaging(ui, repo, pats, opts)
             return 1
@@ -852,6 +854,7 @@
         return _dounshelve(ui, repo, *shelved, **opts)
 
 def _dounshelve(ui, repo, *shelved, **opts):
+    opts = pycompat.byteskwargs(opts)
     abortf = opts.get('abort')
     continuef = opts.get('continue')
     if not abortf and not continuef:
@@ -1010,6 +1013,7 @@
     To delete specific shelved changes, use ``--delete``. To delete
     all shelved changes, use ``--cleanup``.
     '''
+    opts = pycompat.byteskwargs(opts)
     allowables = [
         ('addremove', {'create'}), # 'create' is pseudo action
         ('unknown', {'create'}),
--- a/hgext/sparse.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/sparse.py	Mon Dec 04 11:28:29 2017 -0500
@@ -82,6 +82,7 @@
     extensions,
     hg,
     match as matchmod,
+    pycompat,
     registrar,
     sparse,
     util,
@@ -286,6 +287,7 @@
 
     Returns 0 if editing the sparse checkout succeeds.
     """
+    opts = pycompat.byteskwargs(opts)
     include = opts.get('include')
     exclude = opts.get('exclude')
     force = opts.get('force')
--- a/hgext/uncommit.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/hgext/uncommit.py	Mon Dec 04 11:28:29 2017 -0500
@@ -29,6 +29,8 @@
     error,
     node,
     obsolete,
+    obsutil,
+    pycompat,
     registrar,
     scmutil,
 )
@@ -96,15 +98,13 @@
         newid = repo.commitctx(new)
     return newid
 
-def _uncommitdirstate(repo, oldctx, match):
-    """Fix the dirstate after switching the working directory from
-    oldctx to a copy of oldctx not containing changed files matched by
-    match.
+def _fixdirstate(repo, oldctx, newctx, status):
+    """ fix the dirstate after switching the working directory from oldctx to
+    newctx which can be result of either unamend or uncommit.
     """
-    ctx = repo['.']
     ds = repo.dirstate
     copies = dict(ds.copies())
-    s = repo.status(oldctx.p1(), oldctx, match=match)
+    s = status
     for f in s.modified:
         if ds[f] == 'r':
             # modified + removed -> removed
@@ -136,7 +136,7 @@
                   for dst, src in oldcopies.iteritems())
     # Adjust the dirstate copies
     for dst, src in copies.iteritems():
-        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
+        if (src not in newctx or dst in newctx or ds[dst] != 'a'):
             src = None
         ds.copy(src, dst)
 
@@ -152,6 +152,7 @@
     deleted in the changeset will be left unchanged, and so will remain
     modified in the working directory.
     """
+    opts = pycompat.byteskwargs(opts)
 
     with repo.wlock(), repo.lock():
         wctx = repo[None]
@@ -191,4 +192,79 @@
 
             with repo.dirstate.parentchange():
                 repo.dirstate.setparents(newid, node.nullid)
-                _uncommitdirstate(repo, old, match)
+                s = repo.status(old.p1(), old, match=match)
+                _fixdirstate(repo, old, repo[newid], s)
+
+def predecessormarkers(ctx):
+    """yields the obsolete markers marking the given changeset as a successor"""
+    for data in ctx.repo().obsstore.predecessors.get(ctx.node(), ()):
+        yield obsutil.marker(ctx.repo(), data)
+
+@command('^unamend', [])
+def unamend(ui, repo, **opts):
+    """
+    undo the most recent amend operation on a current changeset
+
+    This command will roll back to the previous version of a changeset,
+    leaving working directory in state in which it was before running
+    `hg amend` (e.g. files modified as part of an amend will be
+    marked as modified `hg status`)
+    """
+
+    unfi = repo.unfiltered()
+    with repo.wlock(), repo.lock(), repo.transaction('unamend'):
+
+        # identify the commit from which to unamend
+        curctx = repo['.']
+
+        if not curctx.mutable():
+            raise error.Abort(_('cannot unamend public changesets'))
+
+        # identify the commit to which to unamend
+        markers = list(predecessormarkers(curctx))
+        if len(markers) != 1:
+            e = _("changeset must have one predecessor, found %i predecessors")
+            raise error.Abort(e % len(markers))
+
+        prednode = markers[0].prednode()
+        predctx = unfi[prednode]
+
+        if curctx.children():
+            raise error.Abort(_("cannot unamend a changeset with children"))
+
+        # add an extra so that we get a new hash
+        # note: allowing unamend to undo an unamend is an intentional feature
+        extras = predctx.extra()
+        extras['unamend_source'] = curctx.hex()
+
+        def filectxfn(repo, ctx_, path):
+            try:
+                return predctx.filectx(path)
+            except KeyError:
+                return None
+
+        # Make a new commit same as predctx
+        newctx = context.memctx(repo,
+                                parents=(predctx.p1(), predctx.p2()),
+                                text=predctx.description(),
+                                files=predctx.files(),
+                                filectxfn=filectxfn,
+                                user=predctx.user(),
+                                date=predctx.date(),
+                                extra=extras)
+        # phase handling
+        commitphase = curctx.phase()
+        overrides = {('phases', 'new-commit'): commitphase}
+        with repo.ui.configoverride(overrides, 'uncommit'):
+            newprednode = repo.commitctx(newctx)
+
+        newpredctx = repo[newprednode]
+        dirstate = repo.dirstate
+
+        with dirstate.parentchange():
+            dirstate.setparents(newprednode, node.nullid)
+            s = repo.status(predctx, curctx)
+            _fixdirstate(repo, curctx, newpredctx, s)
+
+        mapping = {curctx.node(): (newprednode,)}
+        scmutil.cleanupnodes(repo, mapping, 'unamend')
--- a/mercurial/archival.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/archival.py	Mon Dec 04 11:28:29 2017 -0500
@@ -262,6 +262,7 @@
     def __init__(self, name, mtime):
         self.basedir = name
         self.opener = vfsmod.vfs(self.basedir)
+        self.mtime = mtime
 
     def addfile(self, name, mode, islink, data):
         if islink:
@@ -272,6 +273,8 @@
         f.close()
         destfile = os.path.join(self.basedir, name)
         os.chmod(destfile, mode)
+        if self.mtime is not None:
+            os.utime(destfile, (self.mtime, self.mtime))
 
     def done(self):
         pass
@@ -299,7 +302,12 @@
 
     matchfn is function to filter names of files to write to archive.
 
-    prefix is name of path to put before every archive member.'''
+    prefix is name of path to put before every archive member.
+
+    mtime is the modified time, in seconds, or None to use the changeset time.
+
+    subrepos tells whether to include subrepos.
+    '''
 
     if kind == 'files':
         if prefix:
--- a/mercurial/bundle2.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/bundle2.py	Mon Dec 04 11:28:29 2017 -0500
@@ -148,6 +148,7 @@
 from __future__ import absolute_import, division
 
 import errno
+import os
 import re
 import string
 import struct
@@ -362,7 +363,7 @@
                 self.count = count
                 self.current = p
                 yield p
-                p.seek(0, 2)
+                p.consume()
                 self.current = None
         self.iterator = func()
         return self.iterator
@@ -384,11 +385,11 @@
             try:
                 if self.current:
                     # consume the part content to not corrupt the stream.
-                    self.current.seek(0, 2)
+                    self.current.consume()
 
                 for part in self.iterator:
                     # consume the bundle content
-                    part.seek(0, 2)
+                    part.consume()
             except Exception:
                 seekerror = True
 
@@ -844,8 +845,9 @@
             yield self._readexact(size)
 
 
-    def iterparts(self):
+    def iterparts(self, seekable=False):
         """yield all parts contained in the stream"""
+        cls = seekableunbundlepart if seekable else unbundlepart
         # make sure param have been loaded
         self.params
         # From there, payload need to be decompressed
@@ -853,13 +855,12 @@
         indebug(self.ui, 'start extraction of bundle2 parts')
         headerblock = self._readpartheader()
         while headerblock is not None:
-            part = unbundlepart(self.ui, headerblock, self._fp)
+            part = cls(self.ui, headerblock, self._fp)
             yield part
-            # Seek to the end of the part to force it's consumption so the next
-            # part can be read. But then seek back to the beginning so the
-            # code consuming this generator has a part that starts at 0.
-            part.seek(0, 2)
-            part.seek(0)
+            # Ensure part is fully consumed so we can start reading the next
+            # part.
+            part.consume()
+
             headerblock = self._readpartheader()
         indebug(self.ui, 'end of bundle2 stream')
 
@@ -1164,7 +1165,7 @@
             raise
         finally:
             if not hardabort:
-                part.seek(0, 2)
+                part.consume()
         self.ui.debug('bundle2-input-stream-interrupt:'
                       ' closing out of band context\n')
 
@@ -1186,6 +1187,55 @@
     def gettransaction(self):
         raise TransactionUnavailable('no repo access from stream interruption')
 
+def decodepayloadchunks(ui, fh):
+    """Reads bundle2 part payload data into chunks.
+
+    Part payload data consists of framed chunks. This function takes
+    a file handle and emits those chunks.
+    """
+    dolog = ui.configbool('devel', 'bundle2.debug')
+    debug = ui.debug
+
+    headerstruct = struct.Struct(_fpayloadsize)
+    headersize = headerstruct.size
+    unpack = headerstruct.unpack
+
+    readexactly = changegroup.readexactly
+    read = fh.read
+
+    chunksize = unpack(readexactly(fh, headersize))[0]
+    indebug(ui, 'payload chunk size: %i' % chunksize)
+
+    # changegroup.readexactly() is inlined below for performance.
+    while chunksize:
+        if chunksize >= 0:
+            s = read(chunksize)
+            if len(s) < chunksize:
+                raise error.Abort(_('stream ended unexpectedly '
+                                    ' (got %d bytes, expected %d)') %
+                                  (len(s), chunksize))
+
+            yield s
+        elif chunksize == flaginterrupt:
+            # Interrupt "signal" detected. The regular stream is interrupted
+            # and a bundle2 part follows. Consume it.
+            interrupthandler(ui, fh)()
+        else:
+            raise error.BundleValueError(
+                'negative payload chunk size: %s' % chunksize)
+
+        s = read(headersize)
+        if len(s) < headersize:
+            raise error.Abort(_('stream ended unexpectedly '
+                                ' (got %d bytes, expected %d)') %
+                              (len(s), chunksize))
+
+        chunksize = unpack(s)[0]
+
+        # indebug() inlined for performance.
+        if dolog:
+            debug('bundle2-input: payload chunk size: %i\n' % chunksize)
+
 class unbundlepart(unpackermixin):
     """a bundle part read from a bundle"""
 
@@ -1206,10 +1256,8 @@
         self.advisoryparams = None
         self.params = None
         self.mandatorykeys = ()
-        self._payloadstream = None
         self._readheader()
         self._mandatory = None
-        self._chunkindex = [] #(payload, file) position tuples for chunk starts
         self._pos = 0
 
     def _fromheader(self, size):
@@ -1236,46 +1284,6 @@
         self.params.update(self.advisoryparams)
         self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
 
-    def _payloadchunks(self, chunknum=0):
-        '''seek to specified chunk and start yielding data'''
-        if len(self._chunkindex) == 0:
-            assert chunknum == 0, 'Must start with chunk 0'
-            self._chunkindex.append((0, self._tellfp()))
-        else:
-            assert chunknum < len(self._chunkindex), \
-                   'Unknown chunk %d' % chunknum
-            self._seekfp(self._chunkindex[chunknum][1])
-
-        pos = self._chunkindex[chunknum][0]
-        payloadsize = self._unpack(_fpayloadsize)[0]
-        indebug(self.ui, 'payload chunk size: %i' % payloadsize)
-        while payloadsize:
-            if payloadsize == flaginterrupt:
-                # interruption detection, the handler will now read a
-                # single part and process it.
-                interrupthandler(self.ui, self._fp)()
-            elif payloadsize < 0:
-                msg = 'negative payload chunk size: %i' %  payloadsize
-                raise error.BundleValueError(msg)
-            else:
-                result = self._readexact(payloadsize)
-                chunknum += 1
-                pos += payloadsize
-                if chunknum == len(self._chunkindex):
-                    self._chunkindex.append((pos, self._tellfp()))
-                yield result
-            payloadsize = self._unpack(_fpayloadsize)[0]
-            indebug(self.ui, 'payload chunk size: %i' % payloadsize)
-
-    def _findchunk(self, pos):
-        '''for a given payload position, return a chunk number and offset'''
-        for chunk, (ppos, fpos) in enumerate(self._chunkindex):
-            if ppos == pos:
-                return chunk, 0
-            elif ppos > pos:
-                return chunk - 1, pos - self._chunkindex[chunk - 1][0]
-        raise ValueError('Unknown chunk')
-
     def _readheader(self):
         """read the header and setup the object"""
         typesize = self._unpackheader(_fparttypesize)[0]
@@ -1311,6 +1319,24 @@
         # we read the data, tell it
         self._initialized = True
 
+    def _payloadchunks(self):
+        """Generator of decoded chunks in the payload."""
+        return decodepayloadchunks(self.ui, self._fp)
+
+    def consume(self):
+        """Read the part payload until completion.
+
+        By consuming the part data, the underlying stream read offset will
+        be advanced to the next part (or end of stream).
+        """
+        if self.consumed:
+            return
+
+        chunk = self.read(32768)
+        while chunk:
+            self._pos += len(chunk)
+            chunk = self.read(32768)
+
     def read(self, size=None):
         """read payload data"""
         if not self._initialized:
@@ -1327,23 +1353,82 @@
             self.consumed = True
         return data
 
+class seekableunbundlepart(unbundlepart):
+    """A bundle2 part in a bundle that is seekable.
+
+    Regular ``unbundlepart`` instances can only be read once. This class
+    extends ``unbundlepart`` to enable bi-directional seeking within the
+    part.
+
+    Bundle2 part data consists of framed chunks. Offsets when seeking
+    refer to the decoded data, not the offsets in the underlying bundle2
+    stream.
+
+    To facilitate quickly seeking within the decoded data, instances of this
+    class maintain a mapping between offsets in the underlying stream and
+    the decoded payload. This mapping will consume memory in proportion
+    to the number of chunks within the payload (which almost certainly
+    increases in proportion with the size of the part).
+    """
+    def __init__(self, ui, header, fp):
+        # (payload, file) offsets for chunk starts.
+        self._chunkindex = []
+
+        super(seekableunbundlepart, self).__init__(ui, header, fp)
+
+    def _payloadchunks(self, chunknum=0):
+        '''seek to specified chunk and start yielding data'''
+        if len(self._chunkindex) == 0:
+            assert chunknum == 0, 'Must start with chunk 0'
+            self._chunkindex.append((0, self._tellfp()))
+        else:
+            assert chunknum < len(self._chunkindex), \
+                   'Unknown chunk %d' % chunknum
+            self._seekfp(self._chunkindex[chunknum][1])
+
+        pos = self._chunkindex[chunknum][0]
+
+        for chunk in decodepayloadchunks(self.ui, self._fp):
+            chunknum += 1
+            pos += len(chunk)
+            if chunknum == len(self._chunkindex):
+                self._chunkindex.append((pos, self._tellfp()))
+
+            yield chunk
+
+    def _findchunk(self, pos):
+        '''for a given payload position, return a chunk number and offset'''
+        for chunk, (ppos, fpos) in enumerate(self._chunkindex):
+            if ppos == pos:
+                return chunk, 0
+            elif ppos > pos:
+                return chunk - 1, pos - self._chunkindex[chunk - 1][0]
+        raise ValueError('Unknown chunk')
+
     def tell(self):
         return self._pos
 
-    def seek(self, offset, whence=0):
-        if whence == 0:
+    def seek(self, offset, whence=os.SEEK_SET):
+        if whence == os.SEEK_SET:
             newpos = offset
-        elif whence == 1:
+        elif whence == os.SEEK_CUR:
             newpos = self._pos + offset
-        elif whence == 2:
+        elif whence == os.SEEK_END:
             if not self.consumed:
-                self.read()
+                # Can't use self.consume() here because it advances self._pos.
+                chunk = self.read(32768)
+                while chunk:
+                    chunk = self.read(32768)
             newpos = self._chunkindex[-1][0] - offset
         else:
             raise ValueError('Unknown whence value: %r' % (whence,))
 
         if newpos > self._chunkindex[-1][0] and not self.consumed:
-            self.read()
+            # Can't use self.consume() here because it advances self._pos.
+            chunk = self.read(32768)
+            while chunk:
+                chunk = self.read(32668)
+
         if not 0 <= newpos <= self._chunkindex[-1][0]:
             raise ValueError('Offset out of range')
 
--- a/mercurial/bundlerepo.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/bundlerepo.py	Mon Dec 04 11:28:29 2017 -0500
@@ -42,7 +42,7 @@
 )
 
 class bundlerevlog(revlog.revlog):
-    def __init__(self, opener, indexfile, bundle, linkmapper):
+    def __init__(self, opener, indexfile, cgunpacker, linkmapper):
         # How it works:
         # To retrieve a revision, we need to know the offset of the revision in
         # the bundle (an unbundle object). We store this offset in the index
@@ -52,15 +52,15 @@
         # check revision against repotiprev.
         opener = vfsmod.readonlyvfs(opener)
         revlog.revlog.__init__(self, opener, indexfile)
-        self.bundle = bundle
+        self.bundle = cgunpacker
         n = len(self)
         self.repotiprev = n - 1
         self.bundlerevs = set() # used by 'bundle()' revset expression
-        for deltadata in bundle.deltaiter():
+        for deltadata in cgunpacker.deltaiter():
             node, p1, p2, cs, deltabase, delta, flags = deltadata
 
             size = len(delta)
-            start = bundle.tell() - size
+            start = cgunpacker.tell() - size
 
             link = linkmapper(cs)
             if node in self.nodemap:
@@ -86,7 +86,7 @@
             self.bundlerevs.add(n)
             n += 1
 
-    def _chunk(self, rev):
+    def _chunk(self, rev, df=None):
         # Warning: in case of bundle, the diff is against what we stored as
         # delta base, not against rev - 1
         # XXX: could use some caching
@@ -108,7 +108,7 @@
         return mdiff.textdiff(self.revision(rev1, raw=True),
                               self.revision(rev2, raw=True))
 
-    def revision(self, nodeorrev, raw=False):
+    def revision(self, nodeorrev, _df=None, raw=False):
         """return an uncompressed revision of a given node or revision
         number.
         """
@@ -152,20 +152,23 @@
         # needs to override 'baserevision' and make more specific call here.
         return revlog.revlog.revision(self, nodeorrev, raw=True)
 
-    def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
+    def addrevision(self, *args, **kwargs):
+        raise NotImplementedError
+
+    def addgroup(self, *args, **kwargs):
         raise NotImplementedError
-    def addgroup(self, deltas, transaction, addrevisioncb=None):
+
+    def strip(self, *args, **kwargs):
         raise NotImplementedError
-    def strip(self, rev, minlink):
-        raise NotImplementedError
+
     def checksize(self):
         raise NotImplementedError
 
 class bundlechangelog(bundlerevlog, changelog.changelog):
-    def __init__(self, opener, bundle):
+    def __init__(self, opener, cgunpacker):
         changelog.changelog.__init__(self, opener)
         linkmapper = lambda x: x
-        bundlerevlog.__init__(self, opener, self.indexfile, bundle,
+        bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
                               linkmapper)
 
     def baserevision(self, nodeorrev):
@@ -183,9 +186,10 @@
             self.filteredrevs = oldfilter
 
 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
-    def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
+    def __init__(self, opener, cgunpacker, linkmapper, dirlogstarts=None,
+                 dir=''):
         manifest.manifestrevlog.__init__(self, opener, dir=dir)
-        bundlerevlog.__init__(self, opener, self.indexfile, bundle,
+        bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
                               linkmapper)
         if dirlogstarts is None:
             dirlogstarts = {}
@@ -214,9 +218,9 @@
         return super(bundlemanifest, self).dirlog(d)
 
 class bundlefilelog(bundlerevlog, filelog.filelog):
-    def __init__(self, opener, path, bundle, linkmapper):
+    def __init__(self, opener, path, cgunpacker, linkmapper):
         filelog.filelog.__init__(self, opener, path)
-        bundlerevlog.__init__(self, opener, self.indexfile, bundle,
+        bundlerevlog.__init__(self, opener, self.indexfile, cgunpacker,
                               linkmapper)
 
     def baserevision(self, nodeorrev):
@@ -243,82 +247,106 @@
         self.invalidate()
         self.dirty = True
 
-def _getfilestarts(bundle):
-    bundlefilespos = {}
-    for chunkdata in iter(bundle.filelogheader, {}):
+def _getfilestarts(cgunpacker):
+    filespos = {}
+    for chunkdata in iter(cgunpacker.filelogheader, {}):
         fname = chunkdata['filename']
-        bundlefilespos[fname] = bundle.tell()
-        for chunk in iter(lambda: bundle.deltachunk(None), {}):
+        filespos[fname] = cgunpacker.tell()
+        for chunk in iter(lambda: cgunpacker.deltachunk(None), {}):
             pass
-    return bundlefilespos
+    return filespos
 
 class bundlerepository(localrepo.localrepository):
-    def __init__(self, ui, path, bundlename):
+    """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
+    with the contents of a bundle file applied. The repository instance is
+    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.
+    """
+    def __init__(self, ui, repopath, bundlepath):
         self._tempparent = None
         try:
-            localrepo.localrepository.__init__(self, ui, path)
+            localrepo.localrepository.__init__(self, ui, repopath)
         except error.RepoError:
             self._tempparent = tempfile.mkdtemp()
             localrepo.instance(ui, self._tempparent, 1)
             localrepo.localrepository.__init__(self, ui, self._tempparent)
         self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
 
-        if path:
-            self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
+        if repopath:
+            self._url = 'bundle:' + util.expandpath(repopath) + '+' + bundlepath
         else:
-            self._url = 'bundle:' + bundlename
+            self._url = 'bundle:' + bundlepath
 
         self.tempfile = None
-        f = util.posixfile(bundlename, "rb")
-        self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
+        f = util.posixfile(bundlepath, "rb")
+        bundle = exchange.readbundle(ui, f, bundlepath)
 
-        if isinstance(self.bundle, bundle2.unbundle20):
-            hadchangegroup = False
-            for part in self.bundle.iterparts():
+        if isinstance(bundle, bundle2.unbundle20):
+            self._bundlefile = bundle
+            self._cgunpacker = None
+
+            cgpart = None
+            for part in bundle.iterparts(seekable=True):
                 if part.type == 'changegroup':
-                    if hadchangegroup:
+                    if cgpart:
                         raise NotImplementedError("can't process "
                                                   "multiple changegroups")
-                    hadchangegroup = True
+                    cgpart = part
 
-                self._handlebundle2part(part)
+                self._handlebundle2part(bundle, part)
 
-            if not hadchangegroup:
+            if not cgpart:
                 raise error.Abort(_("No changegroups found"))
 
-        elif self.bundle.compressed():
-            f = self._writetempbundle(self.bundle.read, '.hg10un',
-                                      header='HG10UN')
-            self.bundlefile = self.bundle = exchange.readbundle(ui, f,
-                                                                bundlename,
-                                                                self.vfs)
+            # This is required to placate a later consumer, which expects
+            # the payload offset to be at the beginning of the changegroup.
+            # We need to do this after the iterparts() generator advances
+            # because iterparts() will seek to end of payload after the
+            # generator returns control to iterparts().
+            cgpart.seek(0, os.SEEK_SET)
 
-        # dict with the mapping 'filename' -> position in the bundle
-        self.bundlefilespos = {}
+        elif isinstance(bundle, changegroup.cg1unpacker):
+            if bundle.compressed():
+                f = self._writetempbundle(bundle.read, '.hg10un',
+                                          header='HG10UN')
+                bundle = exchange.readbundle(ui, f, bundlepath, self.vfs)
+
+            self._bundlefile = bundle
+            self._cgunpacker = bundle
+        else:
+            raise error.Abort(_('bundle type %s cannot be read') %
+                              type(bundle))
+
+        # dict with the mapping 'filename' -> position in the changegroup.
+        self._cgfilespos = {}
 
         self.firstnewrev = self.changelog.repotiprev + 1
         phases.retractboundary(self, None, phases.draft,
                                [ctx.node() for ctx in self[self.firstnewrev:]])
 
-    def _handlebundle2part(self, part):
-        if part.type == 'changegroup':
-            cgstream = part
-            version = part.params.get('version', '01')
-            legalcgvers = changegroup.supportedincomingversions(self)
-            if version not in legalcgvers:
-                msg = _('Unsupported changegroup version: %s')
-                raise error.Abort(msg % version)
-            if self.bundle.compressed():
-                cgstream = self._writetempbundle(part.read,
-                                                 ".cg%sun" % version)
+    def _handlebundle2part(self, bundle, part):
+        if part.type != 'changegroup':
+            return
 
-            self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
+        cgstream = part
+        version = part.params.get('version', '01')
+        legalcgvers = changegroup.supportedincomingversions(self)
+        if version not in legalcgvers:
+            msg = _('Unsupported changegroup version: %s')
+            raise error.Abort(msg % version)
+        if bundle.compressed():
+            cgstream = self._writetempbundle(part.read, '.cg%sun' % version)
+
+        self._cgunpacker = changegroup.getunbundler(version, cgstream, 'UN')
 
     def _writetempbundle(self, readfn, suffix, header=''):
         """Write a temporary file to disk
         """
         fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
-                                        suffix=".hg10un")
+                                        suffix=suffix)
         self.tempfile = temp
 
         with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
@@ -338,20 +366,29 @@
     @localrepo.unfilteredpropertycache
     def changelog(self):
         # consume the header if it exists
-        self.bundle.changelogheader()
-        c = bundlechangelog(self.svfs, self.bundle)
-        self.manstart = self.bundle.tell()
+        self._cgunpacker.changelogheader()
+        c = bundlechangelog(self.svfs, self._cgunpacker)
+        self.manstart = self._cgunpacker.tell()
         return c
 
     def _constructmanifest(self):
-        self.bundle.seek(self.manstart)
+        self._cgunpacker.seek(self.manstart)
         # consume the header if it exists
-        self.bundle.manifestheader()
+        self._cgunpacker.manifestheader()
         linkmapper = self.unfiltered().changelog.rev
-        m = bundlemanifest(self.svfs, self.bundle, linkmapper)
-        self.filestart = self.bundle.tell()
+        m = bundlemanifest(self.svfs, self._cgunpacker, linkmapper)
+        self.filestart = self._cgunpacker.tell()
         return m
 
+    def _consumemanifest(self):
+        """Consumes the manifest portion of the bundle, setting filestart so the
+        file portion can be read."""
+        self._cgunpacker.seek(self.manstart)
+        self._cgunpacker.manifestheader()
+        for delta in self._cgunpacker.deltaiter():
+            pass
+        self.filestart = self._cgunpacker.tell()
+
     @localrepo.unfilteredpropertycache
     def manstart(self):
         self.changelog
@@ -360,26 +397,34 @@
     @localrepo.unfilteredpropertycache
     def filestart(self):
         self.manifestlog
+
+        # If filestart was not set by self.manifestlog, that means the
+        # manifestlog implementation did not consume the manifests from the
+        # changegroup (ex: it might be consuming trees from a separate bundle2
+        # part instead). So we need to manually consume it.
+        if 'filestart' not in self.__dict__:
+            self._consumemanifest()
+
         return self.filestart
 
     def url(self):
         return self._url
 
     def file(self, f):
-        if not self.bundlefilespos:
-            self.bundle.seek(self.filestart)
-            self.bundlefilespos = _getfilestarts(self.bundle)
+        if not self._cgfilespos:
+            self._cgunpacker.seek(self.filestart)
+            self._cgfilespos = _getfilestarts(self._cgunpacker)
 
-        if f in self.bundlefilespos:
-            self.bundle.seek(self.bundlefilespos[f])
+        if f in self._cgfilespos:
+            self._cgunpacker.seek(self._cgfilespos[f])
             linkmapper = self.unfiltered().changelog.rev
-            return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
+            return bundlefilelog(self.svfs, f, self._cgunpacker, linkmapper)
         else:
             return filelog.filelog(self.svfs, f)
 
     def close(self):
         """Close assigned bundle file immediately."""
-        self.bundlefile.close()
+        self._bundlefile.close()
         if self.tempfile is not None:
             self.vfs.unlink(self.tempfile)
         if self._tempparent:
--- a/mercurial/changegroup.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/changegroup.py	Mon Dec 04 11:28:29 2017 -0500
@@ -692,7 +692,7 @@
         # Callback for the manifest, used to collect linkrevs for filelog
         # revisions.
         # Returns the linkrev node (collected in lookupcl).
-        def makelookupmflinknode(dir):
+        def makelookupmflinknode(dir, nodes):
             if fastpathlinkrev:
                 assert not dir
                 return mfs.__getitem__
@@ -713,7 +713,7 @@
                 the client before you can trust the list of files and
                 treemanifests to send.
                 """
-                clnode = tmfnodes[dir][x]
+                clnode = nodes[x]
                 mdata = mfl.get(dir, x).readfast(shallow=True)
                 for p, n, fl in mdata.iterentries():
                     if fl == 't': # subdirectory manifest
@@ -733,15 +733,13 @@
 
         size = 0
         while tmfnodes:
-            dir = min(tmfnodes)
-            nodes = tmfnodes[dir]
+            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)):
+                                             makelookupmflinknode(dir, nodes)):
                     size += len(x)
                     yield x
-            del tmfnodes[dir]
         self._verbosenote(_('%8.i (manifests)\n') % size)
         yield self._manifestsdone()
 
--- a/mercurial/cmdutil.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/cmdutil.py	Mon Dec 04 11:28:29 2017 -0500
@@ -823,9 +823,9 @@
                   total=None, seqno=None, revwidth=None, pathname=None):
     node_expander = {
         'H': lambda: hex(node),
-        'R': lambda: str(repo.changelog.rev(node)),
+        'R': lambda: '%d' % repo.changelog.rev(node),
         'h': lambda: short(node),
-        'm': lambda: re.sub('[^\w]', '_', str(desc))
+        'm': lambda: re.sub('[^\w]', '_', desc or '')
         }
     expander = {
         '%': lambda: '%',
@@ -837,13 +837,13 @@
             expander.update(node_expander)
         if node:
             expander['r'] = (lambda:
-                    str(repo.changelog.rev(node)).zfill(revwidth or 0))
+                    ('%d' % repo.changelog.rev(node)).zfill(revwidth or 0))
         if total is not None:
-            expander['N'] = lambda: str(total)
+            expander['N'] = lambda: '%d' % total
         if seqno is not None:
-            expander['n'] = lambda: str(seqno)
+            expander['n'] = lambda: '%d' % seqno
         if total is not None and seqno is not None:
-            expander['n'] = lambda: str(seqno).zfill(len(str(total)))
+            expander['n'] = (lambda: ('%d' % seqno).zfill(len('%d' % total)))
         if pathname is not None:
             expander['s'] = lambda: os.path.basename(pathname)
             expander['d'] = lambda: os.path.dirname(pathname) or '.'
@@ -1850,7 +1850,13 @@
         self.ui.write("\n }")
 
 class changeset_templater(changeset_printer):
-    '''format changeset information.'''
+    '''format changeset information.
+
+    Note: there are a variety of convenience functions to build a
+    changeset_templater for common cases. See functions such as:
+    makelogtemplater, show_changeset, buildcommittemplate, or other
+    functions that use changesest_templater.
+    '''
 
     # Arguments before "buffered" used to be positional. Consider not
     # adding/removing arguments before "buffered" to not break callers.
@@ -1972,7 +1978,8 @@
     return formatter.lookuptemplate(ui, 'changeset', tmpl)
 
 def makelogtemplater(ui, repo, tmpl, buffered=False):
-    """Create a changeset_templater from a literal template 'tmpl'"""
+    """Create a changeset_templater from a literal template 'tmpl'
+    byte-string."""
     spec = logtemplatespec(tmpl, None)
     return changeset_templater(ui, repo, spec, buffered=buffered)
 
@@ -2975,8 +2982,9 @@
         for f in remaining:
             count += 1
             ui.progress(_('skipping'), count, total=total, unit=_('files'))
-            warnings.append(_('not removing %s: file still exists\n')
-                    % m.rel(f))
+            if ui.verbose or (f in files):
+                warnings.append(_('not removing %s: file still exists\n')
+                                % m.rel(f))
             ret = 1
         ui.progress(_('skipping'), None)
     else:
@@ -3029,6 +3037,11 @@
         if fntemplate:
             filename = makefilename(repo, fntemplate, ctx.node(),
                                     pathname=os.path.join(prefix, path))
+            # attempt to create the directory if it does not already exist
+            try:
+                os.makedirs(os.path.dirname(filename))
+            except OSError:
+                pass
         with formatter.maybereopen(basefm, filename, opts) as fm:
             data = ctx[path].data()
             if opts.get('decode'):
@@ -3124,6 +3137,8 @@
         # base     o - first parent of the changeset to amend
         wctx = repo[None]
 
+        # Copy to avoid mutating input
+        extra = extra.copy()
         # Update extra dict from amended commit (e.g. to preserve graft
         # source)
         extra.update(old.extra())
@@ -3445,6 +3460,7 @@
     return repo.status(match=scmutil.match(repo[None], pats, opts))
 
 def revert(ui, repo, ctx, parents, *pats, **opts):
+    opts = pycompat.byteskwargs(opts)
     parent, p2 = parents
     node = ctx.node()
 
@@ -3706,7 +3722,7 @@
                                 else:
                                     util.rename(target, bakname)
                     if ui.verbose or not exact:
-                        if not isinstance(msg, basestring):
+                        if not isinstance(msg, bytes):
                             msg = msg(abs)
                         ui.status(msg % rel)
                 elif exact:
@@ -3722,7 +3738,8 @@
             # Revert the subrepos on the revert list
             for sub in targetsubs:
                 try:
-                    wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
+                    wctx.sub(sub).revert(ctx.substate[sub], *pats,
+                                         **pycompat.strkwargs(opts))
                 except KeyError:
                     raise error.Abort("subrepository '%s' does not exist in %s!"
                                       % (sub, short(ctx.node())))
@@ -3802,9 +3819,8 @@
         operation = 'discard'
         reversehunks = True
         if node != parent:
-            operation = 'revert'
-            reversehunks = repo.ui.configbool('experimental',
-                'revertalternateinteractivemode')
+            operation = 'apply'
+            reversehunks = False
         if reversehunks:
             diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
         else:
@@ -3869,6 +3885,7 @@
             repo.dirstate.copy(copied[f], f)
 
 class command(registrar.command):
+    """deprecated: used registrar.command instead"""
     def _doregister(self, func, name, *args, **kwargs):
         func._deprecatedregistrar = True  # flag for deprecwarn in extensions.py
         return super(command, self)._doregister(func, name, *args, **kwargs)
--- a/mercurial/color.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/color.py	Mon Dec 04 11:28:29 2017 -0500
@@ -181,7 +181,7 @@
         configstyles(ui)
 
 def _modesetup(ui):
-    if ui.plain():
+    if ui.plain('color'):
         return None
     config = ui.config('ui', 'color')
     if config == 'debug':
--- a/mercurial/commands.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/commands.py	Mon Dec 04 11:28:29 2017 -0500
@@ -65,6 +65,7 @@
 table.update(debugcommandsmod.command._table)
 
 command = registrar.command(table)
+readonly = registrar.command.readonly
 
 # common command options
 
@@ -857,7 +858,7 @@
                 ui.status(_('changeset %d:%s: %s\n') % (ctx, ctx, transition))
                 hbisect.checkstate(state)
                 # bisect
-                nodes, changesets, bgood = hbisect.bisect(repo.changelog, state)
+                nodes, changesets, bgood = hbisect.bisect(repo, state)
                 # update to next check
                 node = nodes[0]
                 mayupdate(repo, node, show_stats=False)
@@ -870,7 +871,7 @@
     hbisect.checkstate(state)
 
     # actually bisect
-    nodes, changesets, good = hbisect.bisect(repo.changelog, state)
+    nodes, changesets, good = hbisect.bisect(repo, state)
     if extend:
         if not changesets:
             extendnode = hbisect.extendrange(repo, state, nodes, good)
@@ -1064,7 +1065,7 @@
       _('show only branches that have unmerged heads (DEPRECATED)')),
      ('c', 'closed', False, _('show normal and closed branches')),
     ] + formatteropts,
-    _('[-c]'))
+    _('[-c]'), cmdtype=readonly)
 def branches(ui, repo, active=False, closed=False, **opts):
     """list repository named branches
 
@@ -1258,7 +1259,7 @@
     ('', 'decode', None, _('apply any matching decode filter')),
     ] + walkopts + formatteropts,
     _('[OPTION]... FILE...'),
-    inferrepo=True)
+    inferrepo=True, cmdtype=readonly)
 def cat(ui, repo, file1, *pats, **opts):
     """output the current or given revision of files
 
@@ -1604,7 +1605,7 @@
      ('l', 'local', None, _('edit repository config')),
      ('g', 'global', None, _('edit global config'))] + formatteropts,
     _('[-u] [NAME]...'),
-    optionalrepo=True)
+    optionalrepo=True, cmdtype=readonly)
 def config(ui, repo, *values, **opts):
     """show combined config settings from all hgrc files
 
@@ -1777,7 +1778,7 @@
     ('c', 'change', '', _('change made by revision'), _('REV'))
     ] + diffopts + diffopts2 + walkopts + subrepoopts,
     _('[OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...'),
-    inferrepo=True)
+    inferrepo=True, cmdtype=readonly)
 def diff(ui, repo, *pats, **opts):
     """diff repository (or selected files)
 
@@ -1867,7 +1868,7 @@
     ('', 'switch-parent', None, _('diff against the second parent')),
     ('r', 'rev', [], _('revisions to export'), _('REV')),
     ] + diffopts,
-    _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'))
+    _('[OPTION]... [-o OUTFILESPEC] [-r] [REV]...'), cmdtype=readonly)
 def export(ui, repo, *changesets, **opts):
     """dump the header and diffs for one or more changesets
 
@@ -1948,7 +1949,7 @@
     [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
      ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
     ] + walkopts + formatteropts + subrepoopts,
-    _('[OPTION]... [FILE]...'))
+    _('[OPTION]... [FILE]...'), cmdtype=readonly)
 def files(ui, repo, *pats, **opts):
     """list tracked files
 
@@ -2321,7 +2322,7 @@
     ('d', 'date', None, _('list the date (short with -q)')),
     ] + formatteropts + walkopts,
     _('[OPTION]... PATTERN [FILE]...'),
-    inferrepo=True)
+    inferrepo=True, cmdtype=readonly)
 def grep(ui, repo, pattern, *pats, **opts):
     """search revision history for a pattern in specified files
 
@@ -2564,7 +2565,7 @@
     ('a', 'active', False, _('show active branchheads only (DEPRECATED)')),
     ('c', 'closed', False, _('show normal and closed branch heads')),
     ] + templateopts,
-    _('[-ct] [-r STARTREV] [REV]...'))
+    _('[-ct] [-r STARTREV] [REV]...'), cmdtype=readonly)
 def heads(ui, repo, *branchrevs, **opts):
     """show branch heads
 
@@ -2637,7 +2638,7 @@
      ('s', 'system', [], _('show help for specific platform(s)')),
      ],
     _('[-ecks] [TOPIC]'),
-    norepo=True)
+    norepo=True, cmdtype=readonly)
 def help_(ui, name=None, **opts):
     """show help for a given topic or a help overview
 
@@ -2679,7 +2680,7 @@
     ('B', 'bookmarks', None, _('show bookmarks')),
     ] + remoteopts + formatteropts,
     _('[-nibtB] [-r REV] [SOURCE]'),
-    optionalrepo=True)
+    optionalrepo=True, cmdtype=readonly)
 def identify(ui, repo, source=None, rev=None,
              num=None, id=None, branch=None, tags=None, bookmarks=None, **opts):
     """identify the working directory or specified revision
@@ -3253,7 +3254,7 @@
      _('do not display revision or any of its ancestors'), _('REV')),
     ] + logopts + walkopts,
     _('[OPTION]... [FILE]'),
-    inferrepo=True)
+    inferrepo=True, cmdtype=readonly)
 def log(ui, repo, *pats, **opts):
     """show revision history of entire repository or files
 
@@ -3461,7 +3462,7 @@
     [('r', 'rev', '', _('revision to display'), _('REV')),
      ('', 'all', False, _("list files from all revisions"))]
          + formatteropts,
-    _('[-r REV]'))
+    _('[-r REV]'), cmdtype=readonly)
 def manifest(ui, repo, node=None, rev=None, **opts):
     """output the current or given revision of the project manifest
 
@@ -3725,7 +3726,8 @@
             displayer.show(repo[n])
     displayer.close()
 
-@command('paths', formatteropts, _('[NAME]'), optionalrepo=True)
+@command('paths', formatteropts, _('[NAME]'), optionalrepo=True,
+    cmdtype=readonly)
 def paths(ui, repo, search=None, **opts):
     """show aliases for remote repositories
 
@@ -3922,7 +3924,7 @@
 
 @command('^pull',
     [('u', 'update', None,
-     _('update to new branch head if changesets were pulled')),
+     _('update to new branch head if new descendants were pulled')),
     ('f', 'force', None, _('run even when remote repository is unrelated')),
     ('r', 'rev', [], _('a remote changeset intended to be added'), _('REV')),
     ('B', 'bookmark', [], _("bookmark to pull"), _('BOOKMARK')),
@@ -3977,12 +3979,13 @@
             # not ending up with the name of the bookmark because of a race
             # condition on the server. (See issue 4689 for details)
             remotebookmarks = other.listkeys('bookmarks')
+            remotebookmarks = bookmarks.unhexlifybookmarks(remotebookmarks)
             pullopargs['remotebookmarks'] = remotebookmarks
             for b in opts['bookmark']:
                 b = repo._bookmarks.expandname(b)
                 if b not in remotebookmarks:
                     raise error.Abort(_('remote bookmark %s not found!') % b)
-                revs.append(remotebookmarks[b])
+                revs.append(hex(remotebookmarks[b]))
 
         if revs:
             try:
@@ -4521,8 +4524,7 @@
     ('d', 'date', '', _('tipmost revision matching date'), _('DATE')),
     ('r', 'rev', '', _('revert to the specified revision'), _('REV')),
     ('C', 'no-backup', None, _('do not save backup copies of files')),
-    ('i', 'interactive', None,
-            _('interactively select the changes (EXPERIMENTAL)')),
+    ('i', 'interactive', None, _('interactively select the changes')),
     ] + walkopts + dryrunopts,
     _('[OPTION]... [-r REV] [NAME]...'))
 def revert(ui, repo, *pats, **opts):
@@ -4562,6 +4564,7 @@
     Returns 0 on success.
     """
 
+    opts = pycompat.byteskwargs(opts)
     if opts.get("date"):
         if opts.get("rev"):
             raise error.Abort(_("you can't specify a revision and a date"))
@@ -4597,7 +4600,8 @@
             hint = _("use --all to revert all files")
         raise error.Abort(msg, hint=hint)
 
-    return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats, **opts)
+    return cmdutil.revert(ui, repo, ctx, (parent, p2), *pats,
+                          **pycompat.strkwargs(opts))
 
 @command('rollback', dryrunopts +
          [('f', 'force', False, _('ignore safety measures'))])
@@ -4652,7 +4656,7 @@
     return repo.rollback(dryrun=opts.get(r'dry_run'),
                          force=opts.get(r'force'))
 
-@command('root', [])
+@command('root', [], cmdtype=readonly)
 def root(ui, repo):
     """print the root (top) of the current working directory
 
@@ -4700,7 +4704,7 @@
 
     Please note that the server does not implement access control.
     This means that, by default, anybody can read from the server and
-    nobody can write to it by default. Set the ``web.allow_push``
+    nobody can write to it by default. Set the ``web.allow-push``
     option to ``*`` to allow everybody to push to the server. You
     should use a real web server if you need to authenticate users.
 
@@ -4746,7 +4750,7 @@
     ('', 'change', '', _('list the changed files of a revision'), _('REV')),
     ] + walkopts + subrepoopts + formatteropts,
     _('[OPTION]... [FILE]...'),
-    inferrepo=True)
+    inferrepo=True, cmdtype=readonly)
 def status(ui, repo, *pats, **opts):
     """show changed files in the working directory
 
@@ -4911,7 +4915,8 @@
     fm.end()
 
 @command('^summary|sum',
-    [('', 'remote', None, _('check for push and pull'))], '[--remote]')
+    [('', 'remote', None, _('check for push and pull'))],
+    '[--remote]', cmdtype=readonly)
 def summary(ui, repo, **opts):
     """summarize working directory state
 
@@ -5312,7 +5317,7 @@
     finally:
         release(lock, wlock)
 
-@command('tags', formatteropts, '')
+@command('tags', formatteropts, '', cmdtype=readonly)
 def tags(ui, repo, **opts):
     """list repository tags
 
@@ -5535,7 +5540,7 @@
     """
     return hg.verify(repo)
 
-@command('version', [] + formatteropts, norepo=True)
+@command('version', [] + formatteropts, norepo=True, cmdtype=readonly)
 def version_(ui, **opts):
     """output version and copyright information"""
     opts = pycompat.byteskwargs(opts)
--- a/mercurial/configitems.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/configitems.py	Mon Dec 04 11:28:29 2017 -0500
@@ -469,7 +469,7 @@
     default=None,
 )
 coreconfigitem('experimental', 'evolution.effect-flags',
-    default=False,
+    default=True,
     alias=[('experimental', 'effect-flags')]
 )
 coreconfigitem('experimental', 'evolution.exchange',
@@ -532,12 +532,12 @@
 coreconfigitem('experimental', 'rebase.multidest',
     default=False,
 )
-coreconfigitem('experimental', 'revertalternateinteractivemode',
-    default=True,
-)
 coreconfigitem('experimental', 'revlogv2',
     default=None,
 )
+coreconfigitem('experimental', 'single-head-per-branch',
+    default=False,
+)
 coreconfigitem('experimental', 'spacemovesdown',
     default=False,
 )
@@ -1060,6 +1060,9 @@
 coreconfigitem('ui', 'ssh',
     default='ssh',
 )
+coreconfigitem('ui', 'ssherrorhint',
+    default=None,
+)
 coreconfigitem('ui', 'statuscopies',
     default=False,
 )
@@ -1078,6 +1081,9 @@
 coreconfigitem('ui', 'timeout',
     default='600',
 )
+coreconfigitem('ui', 'timeout.warn',
+    default=0,
+)
 coreconfigitem('ui', 'traceback',
     default=False,
 )
@@ -1102,10 +1108,12 @@
 coreconfigitem('web', 'allowgz',
     default=False,
 )
-coreconfigitem('web', 'allowpull',
+coreconfigitem('web', 'allow-pull',
+    alias=[('web', 'allowpull')],
     default=True,
 )
-coreconfigitem('web', 'allow_push',
+coreconfigitem('web', 'allow-push',
+    alias=[('web', 'allow_push')],
     default=list,
 )
 coreconfigitem('web', 'allowzip',
--- a/mercurial/context.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/context.py	Mon Dec 04 11:28:29 2017 -0500
@@ -615,10 +615,13 @@
     def closesbranch(self):
         return 'close' in self._changeset.extra
     def extra(self):
+        """Return a dict of extra information."""
         return self._changeset.extra
     def tags(self):
+        """Return a list of byte tag names"""
         return self._repo.nodetags(self._node)
     def bookmarks(self):
+        """Return a list of byte bookmark names."""
         return self._repo.nodebookmarks(self._node)
     def phase(self):
         return self._repo._phasecache.phase(self._repo, self._rev)
@@ -629,7 +632,11 @@
         return False
 
     def children(self):
-        """return contexts for each child changeset"""
+        """return list of changectx contexts for each child changeset.
+
+        This returns only the immediate child changesets. Use descendants() to
+        recursively walk children.
+        """
         c = self._repo.changelog.children(self._node)
         return [changectx(self._repo, x) for x in c]
 
@@ -638,6 +645,10 @@
             yield changectx(self._repo, a)
 
     def descendants(self):
+        """Recursively yield all children of the changeset.
+
+        For just the immediate children, use children()
+        """
         for d in self._repo.changelog.descendants([self._rev]):
             yield changectx(self._repo, d)
 
@@ -819,6 +830,10 @@
         return self._changectx.phase()
     def phasestr(self):
         return self._changectx.phasestr()
+    def obsolete(self):
+        return self._changectx.obsolete()
+    def instabilities(self):
+        return self._changectx.instabilities()
     def manifest(self):
         return self._changectx.manifest()
     def changectx(self):
--- a/mercurial/crecord.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/crecord.py	Mon Dec 04 11:28:29 2017 -0500
@@ -555,7 +555,7 @@
     return chunkselector.opts
 
 _headermessages = { # {operation: text}
-    'revert': _('Select hunks to revert'),
+    'apply': _('Select hunks to apply'),
     'discard': _('Select hunks to discard'),
     None: _('Select hunks to record'),
 }
--- a/mercurial/debugcommands.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/debugcommands.py	Mon Dec 04 11:28:29 2017 -0500
@@ -296,7 +296,7 @@
         msg %= indent_string, exc.version, len(data)
         ui.write(msg)
     else:
-        msg = "%sversion: %s (%d bytes)\n"
+        msg = "%sversion: %d (%d bytes)\n"
         msg %= indent_string, version, len(data)
         ui.write(msg)
         fm = ui.formatter('debugobsolete', opts)
@@ -360,6 +360,24 @@
             return _debugbundle2(ui, gen, all=all, **opts)
         _debugchangegroup(ui, gen, all=all, **opts)
 
+@command('debugcapabilities',
+        [], _('PATH'),
+        norepo=True)
+def debugcapabilities(ui, path, **opts):
+    """lists the capabilities of a remote peer"""
+    peer = hg.peer(ui, opts, path)
+    caps = peer.capabilities()
+    ui.write(('Main capabilities:\n'))
+    for c in sorted(caps):
+        ui.write(('  %s\n') % c)
+    b2caps = bundle2.bundle2caps(peer)
+    if b2caps:
+        ui.write(('Bundle2 capabilities:\n'))
+        for key, values in sorted(b2caps.iteritems()):
+            ui.write(('  %s\n') % key)
+            for v in values:
+                ui.write(('    %s\n') % v)
+
 @command('debugcheckstate', [], '')
 def debugcheckstate(ui, repo):
     """validate the correctness of the current dirstate"""
@@ -569,11 +587,22 @@
                     the delta chain for this revision
     :``extraratio``: extradist divided by chainsize; another representation of
                     how much unrelated data is needed to load this delta chain
+
+    If the repository is configured to use the sparse read, additional keywords
+    are available:
+
+    :``readsize``:     total size of data read from the disk for a revision
+                       (sum of the sizes of all the blocks)
+    :``largestblock``: size of the largest block of data read from the disk
+    :``readdensity``:  density of useful bytes in the data read from the disk
+
+    The sparse read can be enabled with experimental.sparse-read = True
     """
     opts = pycompat.byteskwargs(opts)
     r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
     index = r.index
     generaldelta = r.version & revlog.FLAG_GENERALDELTA
+    withsparseread = getattr(r, '_withsparseread', False)
 
     def revinfo(rev):
         e = index[rev]
@@ -609,15 +638,20 @@
 
     fm.plain('    rev  chain# chainlen     prev   delta       '
              'size    rawsize  chainsize     ratio   lindist extradist '
-             'extraratio\n')
+             'extraratio')
+    if withsparseread:
+        fm.plain('   readsize largestblk rddensity')
+    fm.plain('\n')
 
     chainbases = {}
     for rev in r:
         comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
         chainbase = chain[0]
         chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
-        basestart = r.start(chainbase)
-        revstart = r.start(rev)
+        start = r.start
+        length = r.length
+        basestart = start(chainbase)
+        revstart = start(rev)
         lineardist = revstart + comp - basestart
         extradist = lineardist - chainsize
         try:
@@ -632,7 +666,7 @@
         fm.write('rev chainid chainlen prevrev deltatype compsize '
                  'uncompsize chainsize chainratio lindist extradist '
                  'extraratio',
-                 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
+                 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f',
                  rev, chainid, len(chain), prevrev, deltatype, comp,
                  uncomp, chainsize, chainratio, lineardist, extradist,
                  extraratio,
@@ -641,6 +675,26 @@
                  uncompsize=uncomp, chainsize=chainsize,
                  chainratio=chainratio, lindist=lineardist,
                  extradist=extradist, extraratio=extraratio)
+        if withsparseread:
+            readsize = 0
+            largestblock = 0
+            for revschunk in revlog._slicechunk(r, chain):
+                blkend = start(revschunk[-1]) + length(revschunk[-1])
+                blksize = blkend - start(revschunk[0])
+
+                readsize += blksize
+                if largestblock < blksize:
+                    largestblock = blksize
+
+            readdensity = float(chainsize) / float(readsize)
+
+            fm.write('readsize largestblock readdensity',
+                     ' %10d %10d %9.5f',
+                     readsize, largestblock, readdensity,
+                     readsize=readsize, largestblock=largestblock,
+                     readdensity=readdensity)
+
+        fm.plain('\n')
 
     fm.end()
 
@@ -665,8 +719,9 @@
         elif nodates:
             timestr = 'set                 '
         else:
-            timestr = time.strftime("%Y-%m-%d %H:%M:%S ",
+            timestr = time.strftime(r"%Y-%m-%d %H:%M:%S ",
                                     time.localtime(ent[3]))
+            timestr = encoding.strtolocal(timestr)
         if ent[1] & 0o20000:
             mode = 'lnk'
         else:
--- a/mercurial/dirstate.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/dirstate.py	Mon Dec 04 11:28:29 2017 -0500
@@ -80,6 +80,7 @@
         self._plchangecallbacks = {}
         self._origpl = None
         self._updatedfiles = set()
+        self._mapcls = dirstatemap
 
     @contextlib.contextmanager
     def parentchange(self):
@@ -127,9 +128,8 @@
 
     @propertycache
     def _map(self):
-        '''Return the dirstate contents as a map from filename to
-        (state, mode, size, time).'''
-        self._map = dirstatemap(self._ui, self._opener, self._root)
+        """Return the dirstate contents (see documentation for dirstatemap)."""
+        self._map = self._mapcls(self._ui, self._opener, self._root)
         return self._map
 
     @property
@@ -158,8 +158,8 @@
     def _pl(self):
         return self._map.parents()
 
-    def dirs(self):
-        return self._map.dirs
+    def hasdir(self, d):
+        return self._map.hastrackeddir(d)
 
     @rootcache('.hgignore')
     def _ignore(self):
@@ -387,40 +387,23 @@
     def copies(self):
         return self._map.copymap
 
-    def _droppath(self, f):
-        if self[f] not in "?r" and "dirs" in self._map.__dict__:
-            self._map.dirs.delpath(f)
-
-        if "filefoldmap" in self._map.__dict__:
-            normed = util.normcase(f)
-            if normed in self._map.filefoldmap:
-                del self._map.filefoldmap[normed]
-
-        self._updatedfiles.add(f)
-
     def _addpath(self, f, state, mode, size, mtime):
         oldstate = self[f]
         if state == 'a' or oldstate == 'r':
             scmutil.checkfilename(f)
-            if f in self._map.dirs:
+            if self._map.hastrackeddir(f):
                 raise error.Abort(_('directory %r already in dirstate') % f)
             # shadows
             for d in util.finddirs(f):
-                if d in self._map.dirs:
+                if self._map.hastrackeddir(d):
                     break
                 entry = self._map.get(d)
                 if entry is not None and entry[0] != 'r':
                     raise error.Abort(
                         _('file %r in dirstate clashes with %r') % (d, f))
-        if oldstate in "?r" and "dirs" in self._map.__dict__:
-            self._map.dirs.addpath(f)
         self._dirty = True
         self._updatedfiles.add(f)
-        self._map[f] = dirstatetuple(state, mode, size, mtime)
-        if state != 'n' or mtime == -1:
-            self._map.nonnormalset.add(f)
-        if size == -2:
-            self._map.otherparentset.add(f)
+        self._map.addfile(f, oldstate, state, mode, size, mtime)
 
     def normal(self, f):
         '''Mark a file normal and clean.'''
@@ -458,8 +441,6 @@
                     return
         self._addpath(f, 'n', 0, -1, -1)
         self._map.copymap.pop(f, None)
-        if f in self._map.nonnormalset:
-            self._map.nonnormalset.remove(f)
 
     def otherparent(self, f):
         '''Mark as coming from the other parent, always dirty.'''
@@ -482,7 +463,7 @@
     def remove(self, f):
         '''Mark a file removed.'''
         self._dirty = True
-        self._droppath(f)
+        oldstate = self[f]
         size = 0
         if self._pl[1] != nullid:
             entry = self._map.get(f)
@@ -493,8 +474,8 @@
                 elif entry[0] == 'n' and entry[2] == -2: # other parent
                     size = -2
                     self._map.otherparentset.add(f)
-        self._map[f] = dirstatetuple('r', 0, size, 0)
-        self._map.nonnormalset.add(f)
+        self._updatedfiles.add(f)
+        self._map.removefile(f, oldstate, size)
         if size == 0:
             self._map.copymap.pop(f, None)
 
@@ -506,12 +487,10 @@
 
     def drop(self, f):
         '''Drop a file from the dirstate'''
-        if f in self._map:
+        oldstate = self[f]
+        if self._map.dropfile(f, oldstate):
             self._dirty = True
-            self._droppath(f)
-            del self._map[f]
-            if f in self._map.nonnormalset:
-                self._map.nonnormalset.remove(f)
+            self._updatedfiles.add(f)
             self._map.copymap.pop(f, None)
 
     def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
@@ -635,12 +614,7 @@
 
             # emulate dropping timestamp in 'parsers.pack_dirstate'
             now = _getfsnow(self._opener)
-            dmap = self._map
-            for f in self._updatedfiles:
-                e = dmap.get(f)
-                if e is not None and e[0] == 'n' and e[3] == now:
-                    dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
-                    self._map.nonnormalset.add(f)
+            self._map.clearambiguoustimes(self._updatedfiles, now)
 
             # emulate that all 'dirstate.normal' results are written out
             self._lastnormaltime = 0
@@ -797,7 +771,6 @@
         results = dict.fromkeys(subrepos)
         results['.hg'] = None
 
-        alldirs = None
         for ff in files:
             # constructing the foldmap is expensive, so don't do it for the
             # common case where files is ['.']
@@ -828,9 +801,7 @@
                 if nf in dmap: # does it exactly match a missing file?
                     results[nf] = None
                 else: # does it match a missing directory?
-                    if alldirs is None:
-                        alldirs = util.dirs(dmap._map)
-                    if nf in alldirs:
+                    if self._map.hasdir(nf):
                         if matchedir:
                             matchedir(nf)
                         notfoundadd(nf)
@@ -1198,6 +1169,39 @@
         self._opener.unlink(backupname)
 
 class dirstatemap(object):
+    """Map encapsulating the dirstate's contents.
+
+    The dirstate contains the following state:
+
+    - `identity` is the identity of the dirstate file, which can be used to
+      detect when changes have occurred to the dirstate file.
+
+    - `parents` is a pair containing the parents of the working copy. The
+      parents are updated by calling `setparents`.
+
+    - the state map maps filenames to tuples of (state, mode, size, mtime),
+      where state is a single character representing 'normal', 'added',
+      'removed', or 'merged'. It is read by treating the dirstate as a
+      dict.  File state is updated by calling the `addfile`, `removefile` and
+      `dropfile` methods.
+
+    - `copymap` maps destination filenames to their source filename.
+
+    The dirstate also provides the following views onto the state:
+
+    - `nonnormalset` is a set of the filenames that have state other
+      than 'normal', or are normal but have an mtime of -1 ('normallookup').
+
+    - `otherparentset` is a set of the filenames that are marked as coming
+      from the second parent when the dirstate is currently being merged.
+
+    - `filefoldmap` is a dict mapping normalized filenames to the denormalized
+      form that they appear as in the dirstate.
+
+    - `dirfoldmap` is a dict mapping normalized directory names to the
+      denormalized form that they appear as in the dirstate.
+    """
+
     def __init__(self, ui, opener, root):
         self._ui = ui
         self._opener = opener
@@ -1226,6 +1230,12 @@
         self._map.clear()
         self.copymap.clear()
         self.setparents(nullid, nullid)
+        util.clearcachedproperty(self, "_dirs")
+        util.clearcachedproperty(self, "_alldirs")
+        util.clearcachedproperty(self, "filefoldmap")
+        util.clearcachedproperty(self, "dirfoldmap")
+        util.clearcachedproperty(self, "nonnormalset")
+        util.clearcachedproperty(self, "otherparentset")
 
     def iteritems(self):
         return self._map.iteritems()
@@ -1242,15 +1252,9 @@
     def __contains__(self, key):
         return key in self._map
 
-    def __setitem__(self, key, value):
-        self._map[key] = value
-
     def __getitem__(self, key):
         return self._map[key]
 
-    def __delitem__(self, key):
-        del self._map[key]
-
     def keys(self):
         return self._map.keys()
 
@@ -1258,6 +1262,60 @@
         """Loads the underlying data, if it's not already loaded"""
         self._map
 
+    def addfile(self, f, oldstate, state, mode, size, mtime):
+        """Add a tracked file to the dirstate."""
+        if oldstate in "?r" and "_dirs" in self.__dict__:
+            self._dirs.addpath(f)
+        if oldstate == "?" and "_alldirs" in self.__dict__:
+            self._alldirs.addpath(f)
+        self._map[f] = dirstatetuple(state, mode, size, mtime)
+        if state != 'n' or mtime == -1:
+            self.nonnormalset.add(f)
+        if size == -2:
+            self.otherparentset.add(f)
+
+    def removefile(self, f, oldstate, size):
+        """
+        Mark a file as removed in the dirstate.
+
+        The `size` parameter is used to store sentinel values that indicate
+        the file's previous state.  In the future, we should refactor this
+        to be more explicit about what that state is.
+        """
+        if oldstate not in "?r" and "_dirs" in self.__dict__:
+            self._dirs.delpath(f)
+        if oldstate == "?" and "_alldirs" in self.__dict__:
+            self._alldirs.addpath(f)
+        if "filefoldmap" in self.__dict__:
+            normed = util.normcase(f)
+            self.filefoldmap.pop(normed, None)
+        self._map[f] = dirstatetuple('r', 0, size, 0)
+        self.nonnormalset.add(f)
+
+    def dropfile(self, f, oldstate):
+        """
+        Remove a file from the dirstate.  Returns True if the file was
+        previously recorded.
+        """
+        exists = self._map.pop(f, None) is not None
+        if exists:
+            if oldstate != "r" and "_dirs" in self.__dict__:
+                self._dirs.delpath(f)
+            if "_alldirs" in self.__dict__:
+                self._alldirs.delpath(f)
+        if "filefoldmap" in self.__dict__:
+            normed = util.normcase(f)
+            self.filefoldmap.pop(normed, None)
+        self.nonnormalset.discard(f)
+        return exists
+
+    def clearambiguoustimes(self, files, now):
+        for f in files:
+            e = self.get(f)
+            if e is not None and e[0] == 'n' and e[3] == now:
+                self._map[f] = dirstatetuple(e[0], e[1], e[2], -1)
+                self.nonnormalset.add(f)
+
     def nonnormalentries(self):
         '''Compute the nonnormal dirstate entries from the dmap'''
         try:
@@ -1293,13 +1351,28 @@
         f['.'] = '.' # prevents useless util.fspath() invocation
         return f
 
+    def hastrackeddir(self, d):
+        """
+        Returns True if the dirstate contains a tracked (not removed) file
+        in this directory.
+        """
+        return d in self._dirs
+
+    def hasdir(self, d):
+        """
+        Returns True if the dirstate contains a file (tracked or removed)
+        in this directory.
+        """
+        return d in self._alldirs
+
     @propertycache
-    def dirs(self):
-        """Returns a set-like object containing all the directories in the
-        current dirstate.
-        """
+    def _dirs(self):
         return util.dirs(self._map, 'r')
 
+    @propertycache
+    def _alldirs(self):
+        return util.dirs(self._map)
+
     def _opendirstatefile(self):
         fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
         if self._pendingmode is not None and self._pendingmode != mode:
@@ -1387,8 +1460,6 @@
         # Avoid excess attribute lookups by fast pathing certain checks
         self.__contains__ = self._map.__contains__
         self.__getitem__ = self._map.__getitem__
-        self.__setitem__ = self._map.__setitem__
-        self.__delitem__ = self._map.__delitem__
         self.get = self._map.get
 
     def write(self, st, now):
@@ -1419,6 +1490,6 @@
     def dirfoldmap(self):
         f = {}
         normcase = util.normcase
-        for name in self.dirs:
+        for name in self._dirs:
             f[normcase(name)] = name
         return f
--- a/mercurial/discovery.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/discovery.py	Mon Dec 04 11:28:29 2017 -0500
@@ -21,6 +21,7 @@
     branchmap,
     error,
     phases,
+    scmutil,
     setdiscovery,
     treediscovery,
     util,
@@ -365,11 +366,8 @@
             if None in unsyncedheads:
                 # old remote, no heads data
                 heads = None
-            elif len(unsyncedheads) <= 4 or repo.ui.verbose:
-                heads = ' '.join(short(h) for h in unsyncedheads)
             else:
-                heads = (' '.join(short(h) for h in unsyncedheads[:4]) +
-                         ' ' + _("and %s others") % (len(unsyncedheads) - 4))
+                heads = scmutil.nodesummaries(repo, unsyncedheads)
             if heads is None:
                 repo.ui.status(_("remote has heads that are "
                                  "not known locally\n"))
--- a/mercurial/dispatch.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/dispatch.py	Mon Dec 04 11:28:29 2017 -0500
@@ -410,7 +410,7 @@
     # tokenize each argument into exactly one word.
     replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
     # escape '\$' for regex
-    regex = '|'.join(replacemap.keys()).replace('$', r'\$')
+    regex = '|'.join(replacemap.keys()).replace('$', br'\$')
     r = re.compile(regex)
     return r.sub(lambda x: replacemap[x.group()], cmd)
 
@@ -455,7 +455,7 @@
                                  "of %i variable in alias '%s' definition."
                                  % (int(m.groups()[0]), self.name))
                         return ''
-                cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
+                cmd = re.sub(br'\$(\d+|\$)', _checkvar, self.definition[1:])
                 cmd = aliasinterpolate(self.name, args, cmd)
                 return ui.system(cmd, environ=env,
                                  blockedtag='alias_%s' % self.name)
@@ -1006,7 +1006,7 @@
                     if not func.optionalrepo:
                         if func.inferrepo and args and not path:
                             # try to infer -R from command args
-                            repos = map(cmdutil.findrepo, args)
+                            repos = pycompat.maplist(cmdutil.findrepo, args)
                             guess = repos[0]
                             if guess and repos.count(guess) == len(repos):
                                 req.args = ['--repository', guess] + fullargs
--- a/mercurial/error.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/error.py	Mon Dec 04 11:28:29 2017 -0500
@@ -301,3 +301,7 @@
 
 class PeerTransportError(Abort):
     """Transport-level I/O error when communicating with a peer repo."""
+
+class InMemoryMergeConflictsError(Exception):
+    """Exception raised when merge conflicts arose during an in-memory merge."""
+    __bytes__ = _tobytes
--- a/mercurial/exchange.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/exchange.py	Mon Dec 04 11:28:29 2017 -0500
@@ -1348,7 +1348,8 @@
         # all known bundle2 servers now support listkeys, but lets be nice with
         # new implementation.
         return
-    pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
+    books = pullop.remote.listkeys('bookmarks')
+    pullop.remotebookmarks = bookmod.unhexlifybookmarks(books)
 
 
 @pulldiscovery('changegroup')
@@ -1459,7 +1460,7 @@
     # processing bookmark update
     for namespace, value in op.records['listkeys']:
         if namespace == 'bookmarks':
-            pullop.remotebookmarks = value
+            pullop.remotebookmarks = bookmod.unhexlifybookmarks(value)
 
     # bookmark data were either already there or pulled in the bundle
     if pullop.remotebookmarks is not None:
@@ -1552,7 +1553,6 @@
     pullop.stepsdone.add('bookmarks')
     repo = pullop.repo
     remotebookmarks = pullop.remotebookmarks
-    remotebookmarks = bookmod.unhexlifybookmarks(remotebookmarks)
     bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
                              pullop.remote.url(),
                              pullop.gettransaction,
@@ -1785,17 +1785,6 @@
     outgoing = _computeoutgoing(repo, heads, common)
     bundle2.addparttagsfnodescache(repo, bundler, outgoing)
 
-def _getbookmarks(repo, **kwargs):
-    """Returns bookmark to node mapping.
-
-    This function is primarily used to generate `bookmarks` bundle2 part.
-    It is a separate function in order to make it easy to wrap it
-    in extensions. Passing `kwargs` to the function makes it easy to
-    add new parameters in extensions.
-    """
-
-    return dict(bookmod.listbinbookmarks(repo))
-
 def check_heads(repo, their_heads, context):
     """check if the heads of a repo have been modified
 
--- a/mercurial/hbisect.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/hbisect.py	Mon Dec 04 11:28:29 2017 -0500
@@ -21,7 +21,7 @@
     error,
 )
 
-def bisect(changelog, state):
+def bisect(repo, state):
     """find the next node (if any) for testing during a bisect search.
     returns a (nodes, number, good) tuple.
 
@@ -32,33 +32,15 @@
     if searching for a first bad one.
     """
 
+    changelog = repo.changelog
     clparents = changelog.parentrevs
     skip = set([changelog.rev(n) for n in state['skip']])
 
     def buildancestors(bad, good):
-        # only the earliest bad revision matters
         badrev = min([changelog.rev(n) for n in bad])
-        goodrevs = [changelog.rev(n) for n in good]
-        goodrev = min(goodrevs)
-        # build visit array
-        ancestors = [None] * (len(changelog) + 1) # an extra for [-1]
-
-        # set nodes descended from goodrevs
-        for rev in goodrevs:
+        ancestors = collections.defaultdict(lambda: None)
+        for rev in repo.revs("descendants(%ln) - ancestors(%ln)", good, good):
             ancestors[rev] = []
-        for rev in changelog.revs(goodrev + 1):
-            for prev in clparents(rev):
-                if ancestors[prev] == []:
-                    ancestors[rev] = []
-
-        # clear good revs from array
-        for rev in goodrevs:
-            ancestors[rev] = None
-        for rev in changelog.revs(len(changelog), goodrev):
-            if ancestors[rev] is None:
-                for prev in clparents(rev):
-                    ancestors[prev] = None
-
         if ancestors[badrev] is None:
             return badrev, None
         return badrev, ancestors
--- a/mercurial/help.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/help.py	Mon Dec 04 11:28:29 2017 -0500
@@ -226,6 +226,7 @@
     (['color'], _("Colorizing Outputs"), loaddoc('color')),
     (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
     (["dates"], _("Date Formats"), loaddoc('dates')),
+    (["flags"], _("Command-line flags"), loaddoc('flags')),
     (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
     (['environment', 'env'], _('Environment Variables'),
      loaddoc('environment')),
--- a/mercurial/help/config.txt	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/help/config.txt	Mon Dec 04 11:28:29 2017 -0500
@@ -2188,6 +2188,10 @@
 ``ssh``
     Command to use for SSH connections. (default: ``ssh``)
 
+``ssherrorhint``
+    A hint shown to the user in the case of SSH error (e.g.
+    ``Please see http://company/internalwiki/ssh.html``)
+
 ``strict``
     Require exact command names, instead of allowing unambiguous
     abbreviations. (default: False)
@@ -2211,6 +2215,10 @@
     The timeout used when a lock is held (in seconds), a negative value
     means no timeout. (default: 600)
 
+``timeout.warn``
+    Time (in seconds) before a warning is printed about held lock. A negative
+    value means no warning. (default: 0)
+
 ``traceback``
     Mercurial always prints a traceback when an unknown exception
     occurs. Setting this to True will make Mercurial print a traceback
@@ -2260,7 +2268,7 @@
 you want it to accept pushes from anybody, you can use the following
 command line::
 
-    $ hg --config web.allow_push=* --config web.push_ssl=False serve
+    $ hg --config web.allow-push=* --config web.push_ssl=False serve
 
 Note that this will allow anybody to push anything to the server and
 that this should not be used for public servers.
@@ -2287,16 +2295,16 @@
     revisions.
     (default: False)
 
-``allowpull``
+``allow-pull``
     Whether to allow pulling from the repository. (default: True)
 
-``allow_push``
+``allow-push``
     Whether to allow pushing to the repository. If empty or not set,
     pushing is not allowed. If the special value ``*``, any remote
     user can push, including unauthenticated users. Otherwise, the
     remote user must have been authenticated, and the authenticated
     user name must be present in this list. The contents of the
-    allow_push list are examined after the deny_push list.
+    allow-push list are examined after the deny_push list.
 
 ``allow_read``
     If the user has not already been denied repository access due to
@@ -2390,7 +2398,7 @@
     push is not denied. If the special value ``*``, all remote users are
     denied push. Otherwise, unauthenticated users are all denied, and
     any authenticated user name present in this list is also denied. The
-    contents of the deny_push list are examined before the allow_push list.
+    contents of the deny_push list are examined before the allow-push list.
 
 ``deny_read``
     Whether to deny reading/viewing of the repository. If this list is
--- a/mercurial/help/environment.txt	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/help/environment.txt	Mon Dec 04 11:28:29 2017 -0500
@@ -73,6 +73,8 @@
 
     ``alias``
         Don't remove aliases.
+    ``color``
+        Don't disable colored output.
     ``i18n``
         Preserve internationalization.
     ``revsetalias``
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/help/flags.txt	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,104 @@
+Most Mercurial commands accept various flags.
+
+Flag names
+==========
+
+Flags for each command are listed in :hg:`help` for that command.
+Additionally, some flags, such as --repository, are global and can be used with
+any command - those are seen in :hg:`help -v`, and can be specified before or
+after the command.
+
+Every flag has at least a long name, such as --repository. Some flags may also
+have a short one-letter name, such as the equivalent -R. Using the short or long
+name is equivalent and has the same effect.
+
+Flags that have a short name can also be bundled together - for instance, to
+specify both --edit (short -e) and --interactive (short -i), one could use::
+
+    hg commit -ei
+
+If any of the bundled flags takes a value (i.e. is not a boolean), it must be
+last, followed by the value::
+
+    hg commit -im 'Message'
+
+Flag types
+==========
+
+Mercurial command-line flags can be strings, numbers, booleans, or lists of
+strings.
+
+Specifying flag values
+======================
+
+The following syntaxes are allowed, assuming a flag 'flagname' with short name
+'f'::
+
+    --flagname=foo
+    --flagname foo
+    -f foo
+    -ffoo
+
+This syntax applies to all non-boolean flags (strings, numbers or lists).
+
+Specifying boolean flags
+========================
+
+Boolean flags do not take a value parameter. To specify a boolean, use the flag
+name to set it to true, or the same name prefixed with 'no-' to set it to
+false::
+
+    hg commit --interactive
+    hg commit --no-interactive
+
+Specifying list flags
+=====================
+
+List flags take multiple values. To specify them, pass the flag multiple times::
+
+    hg files --include mercurial --include tests
+
+Setting flag defaults
+=====================
+
+In order to set a default value for a flag in an hgrc file, it is recommended to
+use aliases::
+
+    [alias]
+    commit = commit --interactive
+
+For more information on hgrc files, see :hg:`help config`.
+
+Overriding flags on the command line
+====================================
+
+If the same non-list flag is specified multiple times on the command line, the
+latest specification is used::
+
+    hg commit -m "Ignored value" -m "Used value"
+
+This includes the use of aliases - e.g., if one has::
+
+    [alias]
+    committemp = commit -m "Ignored value"
+
+then the following command will override that -m::
+
+    hg committemp -m "Used value"
+
+Overriding flag defaults
+========================
+
+Every flag has a default value, and you may also set your own defaults in hgrc
+as described above.
+Except for list flags, defaults can be overridden on the command line simply by
+specifying the flag in that location.
+
+Hidden flags
+============
+
+Some flags are not shown in a command's help by default - specifically, those
+that are deemed to be experimental, deprecated or advanced. To show all flags,
+add the --verbose flag for the help command::
+
+    hg help --verbose commit
--- a/mercurial/hgweb/common.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/hgweb/common.py	Mon Dec 04 11:28:29 2017 -0500
@@ -75,7 +75,7 @@
     if deny and (not user or ismember(hgweb.repo.ui, user, deny)):
         raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
 
-    allow = hgweb.configlist('web', 'allow_push')
+    allow = hgweb.configlist('web', 'allow-push')
     if not (allow and ismember(hgweb.repo.ui, user, allow)):
         raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
 
--- a/mercurial/hgweb/hgweb_mod.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/hgweb/hgweb_mod.py	Mon Dec 04 11:28:29 2017 -0500
@@ -114,7 +114,7 @@
         self.stripecount = self.configint('web', 'stripes')
         self.maxshortchanges = self.configint('web', 'maxshortchanges')
         self.maxfiles = self.configint('web', 'maxfiles')
-        self.allowpull = self.configbool('web', 'allowpull')
+        self.allowpull = self.configbool('web', 'allow-pull')
 
         # we use untrusted=False to prevent a repo owner from using
         # web.templates in .hg/hgrc to get access to any file readable
--- a/mercurial/hgweb/webcommands.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/hgweb/webcommands.py	Mon Dec 04 11:28:29 2017 -0500
@@ -1241,51 +1241,49 @@
                              max([edge[1] for edge in edges] or [0]))
         return cols
 
-    def graphdata(usetuples, encodestr):
+    def graphdata(usetuples):
+        # {jsdata} will be passed to |json, so it must be in utf-8
+        encodestr = encoding.fromlocal
         data = []
 
         row = 0
         for (id, type, ctx, vtx, edges) in tree:
             if type != graphmod.CHANGESET:
                 continue
-            node = pycompat.bytestr(ctx)
-            age = encodestr(templatefilters.age(ctx.date()))
-            desc = templatefilters.firstline(encodestr(ctx.description()))
-            desc = url.escape(templatefilters.nonempty(desc))
-            user = url.escape(templatefilters.person(encodestr(ctx.user())))
-            branch = url.escape(encodestr(ctx.branch()))
-            try:
-                branchnode = web.repo.branchtip(branch)
-            except error.RepoLookupError:
-                branchnode = None
-            branch = branch, branchnode == ctx.node()
 
             if usetuples:
+                node = pycompat.bytestr(ctx)
+                age = encodestr(templatefilters.age(ctx.date()))
+                desc = templatefilters.firstline(encodestr(ctx.description()))
+                desc = url.escape(templatefilters.nonempty(desc))
+                user = templatefilters.person(encodestr(ctx.user()))
+                user = url.escape(user)
+                branch = url.escape(encodestr(ctx.branch()))
+                try:
+                    branchnode = web.repo.branchtip(ctx.branch())
+                except error.RepoLookupError:
+                    branchnode = None
+                branch = branch, branchnode == ctx.node()
+
                 data.append((node, vtx, edges, desc, user, age, branch,
                              [url.escape(encodestr(x)) for x in ctx.tags()],
                              [url.escape(encodestr(x))
                               for x in ctx.bookmarks()]))
             else:
+                entry = webutil.commonentry(web.repo, ctx)
                 edgedata = [{'col': edge[0], 'nextcol': edge[1],
                              'color': (edge[2] - 1) % 6 + 1,
                              'width': edge[3], 'bcolor': edge[4]}
                             for edge in edges]
 
-                data.append(
-                    {'node': node,
-                     'col': vtx[0],
+                entry.update(
+                    {'col': vtx[0],
                      'color': (vtx[1] - 1) % 6 + 1,
                      'edges': edgedata,
                      'row': row,
-                     'nextrow': row + 1,
-                     'desc': desc,
-                     'user': user,
-                     'age': age,
-                     'bookmarks': webutil.nodebookmarksdict(
-                         web.repo, ctx.node()),
-                     'branches': webutil.nodebranchdict(web.repo, ctx),
-                     'inbranch': webutil.nodeinbranch(web.repo, ctx),
-                     'tags': webutil.nodetagsdict(web.repo, ctx.node())})
+                     'nextrow': row + 1})
+
+                data.append(entry)
 
             row += 1
 
@@ -1302,9 +1300,8 @@
                 canvaswidth=(cols + 1) * bg_height,
                 truecanvasheight=rows * bg_height,
                 canvasheight=canvasheight, bg_height=bg_height,
-                # {jsdata} will be passed to |json, so it must be in utf-8
-                jsdata=lambda **x: graphdata(True, encoding.fromlocal),
-                nodes=lambda **x: graphdata(False, pycompat.bytestr),
+                jsdata=lambda **x: graphdata(True),
+                nodes=lambda **x: graphdata(False),
                 node=ctx.hex(), changenav=changenav)
 
 def _getdoc(e):
--- a/mercurial/hgweb/webutil.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/hgweb/webutil.py	Mon Dec 04 11:28:29 2017 -0500
@@ -361,6 +361,8 @@
         'date': ctx.date(),
         'extra': ctx.extra(),
         'phase': ctx.phasestr(),
+        'obsolete': ctx.obsolete(),
+        'instabilities': [{"instability": i} for i in ctx.instabilities()],
         'branch': nodebranchnodefault(ctx),
         'inbranch': nodeinbranch(repo, ctx),
         'branches': nodebranchdict(repo, ctx),
--- a/mercurial/httppeer.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/httppeer.py	Mon Dec 04 11:28:29 2017 -0500
@@ -222,7 +222,7 @@
             if not data:
                 data = strargs
             else:
-                if isinstance(data, basestring):
+                if isinstance(data, bytes):
                     i = io.BytesIO(data)
                     i.length = len(data)
                     data = i
--- a/mercurial/localrepo.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/localrepo.py	Mon Dec 04 11:28:29 2017 -0500
@@ -364,11 +364,14 @@
         self.root = self.wvfs.base
         self.path = self.wvfs.join(".hg")
         self.origroot = path
-        # These auditor are not used by the vfs,
-        # only used when writing this comment: basectx.match
-        self.auditor = pathutil.pathauditor(self.root, self._checknested)
-        self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
-                                                realfs=False, cached=True)
+        # 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
@@ -1244,6 +1247,8 @@
             # gating.
             tracktags(tr2)
             repo = reporef()
+            if repo.ui.configbool('experimental', 'single-head-per-branch'):
+                scmutil.enforcesinglehead(repo, tr2, desc)
             if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
                 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
                     args = tr.hookargs.copy()
@@ -1587,29 +1592,18 @@
         # determine whether it can be inherited
         if parentenvvar is not None:
             parentlock = encoding.environ.get(parentenvvar)
-        try:
-            l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
-                             acquirefn=acquirefn, desc=desc,
-                             inheritchecker=inheritchecker,
-                             parentlock=parentlock)
-        except error.LockHeld as inst:
-            if not wait:
-                raise
-            # show more details for new-style locks
-            if ':' in inst.locker:
-                host, pid = inst.locker.split(":", 1)
-                self.ui.warn(
-                    _("waiting for lock on %s held by process %r "
-                      "on host %r\n") % (desc, pid, host))
-            else:
-                self.ui.warn(_("waiting for lock on %s held by %r\n") %
-                             (desc, inst.locker))
-            # default to 600 seconds timeout
-            l = lockmod.lock(vfs, lockname,
-                             int(self.ui.config("ui", "timeout")),
-                             releasefn=releasefn, acquirefn=acquirefn,
-                             desc=desc)
-            self.ui.warn(_("got lock after %s seconds\n") % l.delay)
+
+        timeout = 0
+        warntimeout = 0
+        if wait:
+            timeout = self.ui.configint("ui", "timeout")
+            warntimeout = self.ui.configint("ui", "timeout.warn")
+
+        l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
+                            releasefn=releasefn,
+                            acquirefn=acquirefn, desc=desc,
+                            inheritchecker=inheritchecker,
+                            parentlock=parentlock)
         return l
 
     def _afterlock(self, callback):
--- a/mercurial/lock.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/lock.py	Mon Dec 04 11:28:29 2017 -0500
@@ -14,6 +14,8 @@
 import time
 import warnings
 
+from .i18n import _
+
 from . import (
     encoding,
     error,
@@ -39,6 +41,58 @@
                 raise
     return result
 
+def trylock(ui, vfs, lockname, timeout, warntimeout, *args, **kwargs):
+    """return an acquired lock or raise an a LockHeld exception
+
+    This function is responsible to issue warnings and or debug messages about
+    the held lock while trying to acquires it."""
+
+    def printwarning(printer, locker):
+        """issue the usual "waiting on lock" message through any channel"""
+        # show more details for new-style locks
+        if ':' in locker:
+            host, pid = locker.split(":", 1)
+            msg = _("waiting for lock on %s held by process %r "
+                    "on host %r\n") % (l.desc, pid, host)
+        else:
+            msg = _("waiting for lock on %s held by %r\n") % (l.desc, locker)
+        printer(msg)
+
+    l = lock(vfs, lockname, 0, *args, dolock=False, **kwargs)
+
+    debugidx = 0 if (warntimeout and timeout) else -1
+    warningidx = 0
+    if not timeout:
+        warningidx = -1
+    elif warntimeout:
+        warningidx = warntimeout
+
+    delay = 0
+    while True:
+        try:
+            l._trylock()
+            break
+        except error.LockHeld as inst:
+            if delay == debugidx:
+                printwarning(ui.debug, inst.locker)
+            if delay == warningidx:
+                printwarning(ui.warn, inst.locker)
+            if timeout <= delay:
+                raise error.LockHeld(errno.ETIMEDOUT, inst.filename,
+                                     l.desc, inst.locker)
+            time.sleep(1)
+            delay += 1
+
+    l.delay = delay
+    if l.delay:
+        if 0 <= warningidx <= l.delay:
+            ui.warn(_("got lock after %s seconds\n") % l.delay)
+        else:
+            ui.debug("got lock after %s seconds\n" % l.delay)
+    if l.acquirefn:
+        l.acquirefn()
+    return l
+
 class lock(object):
     '''An advisory lock held by one process to control access to a set
     of files.  Non-cooperating processes or incorrectly written scripts
@@ -60,7 +114,8 @@
     _host = None
 
     def __init__(self, vfs, file, timeout=-1, releasefn=None, acquirefn=None,
-                 desc=None, inheritchecker=None, parentlock=None):
+                 desc=None, inheritchecker=None, parentlock=None,
+                 dolock=True):
         self.vfs = vfs
         self.f = file
         self.held = 0
@@ -74,9 +129,10 @@
         self._inherited = False
         self.postrelease  = []
         self.pid = self._getpid()
-        self.delay = self.lock()
-        if self.acquirefn:
-            self.acquirefn()
+        if dolock:
+            self.delay = self.lock()
+            if self.acquirefn:
+                self.acquirefn()
 
     def __enter__(self):
         return self
--- a/mercurial/mail.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/mail.py	Mon Dec 04 11:28:29 2017 -0500
@@ -152,7 +152,7 @@
     fp = open(mbox, 'ab+')
     # Should be time.asctime(), but Windows prints 2-characters day
     # of month instead of one. Make them print the same thing.
-    date = time.strftime('%a %b %d %H:%M:%S %Y', time.localtime())
+    date = time.strftime(r'%a %b %d %H:%M:%S %Y', time.localtime())
     fp.write('From %s %s\n' % (sender, date))
     fp.write(msg)
     fp.write('\n\n')
--- a/mercurial/match.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/match.py	Mon Dec 04 11:28:29 2017 -0500
@@ -305,9 +305,6 @@
         Returns the string 'all' if the given directory and all subdirectories
         should be visited. Otherwise returns True or False indicating whether
         the given directory should be visited.
-
-        This function's behavior is undefined if it has returned False for
-        one of the dir's parent directories.
         '''
         return True
 
--- a/mercurial/obsolete.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/obsolete.py	Mon Dec 04 11:28:29 2017 -0500
@@ -994,10 +994,10 @@
     public = phases.public
     cl = repo.changelog
     torev = cl.nodemap.get
-    for ctx in repo.set('(not public()) and (not obsolete())'):
-        rev = ctx.rev()
+    tonode = cl.node
+    for rev in repo.revs('(not public()) and (not obsolete())'):
         # We only evaluate mutable, non-obsolete revision
-        node = ctx.node()
+        node = tonode(rev)
         # (future) A cache of predecessors may worth if split is very common
         for pnode in obsutil.allpredecessors(repo.obsstore, [node],
                                    ignoreflags=bumpedfix):
@@ -1023,8 +1023,10 @@
     divergent = set()
     obsstore = repo.obsstore
     newermap = {}
-    for ctx in repo.set('(not public()) - obsolete()'):
-        mark = obsstore.predecessors.get(ctx.node(), ())
+    tonode = repo.changelog.node
+    for rev in repo.revs('(not public()) - obsolete()'):
+        node = tonode(rev)
+        mark = obsstore.predecessors.get(node, ())
         toprocess = set(mark)
         seen = set()
         while toprocess:
@@ -1036,7 +1038,7 @@
                 obsutil.successorssets(repo, prec, cache=newermap)
             newer = [n for n in newermap[prec] if n]
             if len(newer) > 1:
-                divergent.add(ctx.rev())
+                divergent.add(rev)
                 break
             toprocess.update(obsstore.predecessors.get(prec, ()))
     return divergent
--- a/mercurial/obsutil.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/obsutil.py	Mon Dec 04 11:28:29 2017 -0500
@@ -751,8 +751,9 @@
 
     return values
 
-def successorsetverb(successorset):
-    """ Return the verb summarizing the successorset
+def obsfateverb(successorset, markers):
+    """ Return the verb summarizing the successorset and potentially using
+    information from the markers
     """
     if not successorset:
         verb = 'pruned'
@@ -795,7 +796,7 @@
     line = []
 
     # Verb
-    line.append(successorsetverb(successors))
+    line.append(obsfateverb(successors, markers))
 
     # Operations
     operations = markersoperations(markers)
--- a/mercurial/patch.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/patch.py	Mon Dec 04 11:28:29 2017 -0500
@@ -149,6 +149,8 @@
                 raise StopIteration
             return l
 
+        __next__ = next
+
     inheader = False
     cur = []
 
@@ -203,7 +205,7 @@
 
     # attempt to detect the start of a patch
     # (this heuristic is borrowed from quilt)
-    diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
+    diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]-|RCS file: |'
                         br'retrieving revision [0-9]+(\.[0-9]+)*$|'
                         br'---[ \t].*?^\+\+\+[ \t]|'
                         br'\*\*\*[ \t].*?^---[ \t])',
@@ -997,16 +999,26 @@
 def getmessages():
     return {
         'multiple': {
+            'apply': _("apply change %d/%d to '%s'?"),
             'discard': _("discard change %d/%d to '%s'?"),
             'record': _("record change %d/%d to '%s'?"),
-            'revert': _("revert change %d/%d to '%s'?"),
         },
         'single': {
+            'apply': _("apply this change to '%s'?"),
             'discard': _("discard this change to '%s'?"),
             'record': _("record this change to '%s'?"),
-            'revert': _("revert this change to '%s'?"),
         },
         'help': {
+            'apply': _('[Ynesfdaq?]'
+                         '$$ &Yes, apply this change'
+                         '$$ &No, skip this change'
+                         '$$ &Edit this change manually'
+                         '$$ &Skip remaining changes to this file'
+                         '$$ Apply remaining changes to this &file'
+                         '$$ &Done, skip remaining changes and files'
+                         '$$ Apply &all changes to all remaining files'
+                         '$$ &Quit, applying no changes'
+                         '$$ &? (display help)'),
             'discard': _('[Ynesfdaq?]'
                          '$$ &Yes, discard this change'
                          '$$ &No, skip this change'
@@ -1027,16 +1039,6 @@
                         '$$ Record &all changes to all remaining files'
                         '$$ &Quit, recording no changes'
                         '$$ &? (display help)'),
-            'revert': _('[Ynesfdaq?]'
-                        '$$ &Yes, revert this change'
-                        '$$ &No, skip this change'
-                        '$$ &Edit this change manually'
-                        '$$ &Skip remaining changes to this file'
-                        '$$ Revert remaining changes to this &file'
-                        '$$ &Done, skip remaining changes and files'
-                        '$$ Revert &all changes to all remaining files'
-                        '$$ &Quit, reverting no changes'
-                        '$$ &? (display help)')
         }
     }
 
@@ -1990,14 +1992,16 @@
     return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
                       prefix=prefix, eolmode=eolmode)
 
+def _canonprefix(repo, prefix):
+    if prefix:
+        prefix = pathutil.canonpath(repo.root, repo.getcwd(), prefix)
+        if prefix != '':
+            prefix += '/'
+    return prefix
+
 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
                eolmode='strict'):
-
-    if prefix:
-        prefix = pathutil.canonpath(backend.repo.root, backend.repo.getcwd(),
-                                    prefix)
-        if prefix != '':
-            prefix += '/'
+    prefix = _canonprefix(backend.repo, prefix)
     def pstrip(p):
         return pathtransform(p, strip - 1, prefix)[1]
 
@@ -2183,20 +2187,22 @@
     return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
                          similarity)
 
-def changedfiles(ui, repo, patchpath, strip=1):
+def changedfiles(ui, repo, patchpath, strip=1, prefix=''):
     backend = fsbackend(ui, repo.root)
+    prefix = _canonprefix(repo, prefix)
     with open(patchpath, 'rb') as fp:
         changed = set()
         for state, values in iterhunks(fp):
             if state == 'file':
                 afile, bfile, first_hunk, gp = values
                 if gp:
-                    gp.path = pathtransform(gp.path, strip - 1, '')[1]
+                    gp.path = pathtransform(gp.path, strip - 1, prefix)[1]
                     if gp.oldpath:
-                        gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
+                        gp.oldpath = pathtransform(gp.oldpath, strip - 1,
+                                                   prefix)[1]
                 else:
                     gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
-                                       '')
+                                       prefix)
                 changed.add(gp.path)
                 if gp.op == 'RENAME':
                     changed.add(gp.oldpath)
--- a/mercurial/registrar.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/registrar.py	Mon Dec 04 11:28:29 2017 -0500
@@ -112,35 +112,53 @@
     The created object can be used as a decorator for adding commands to
     that command table. This accepts multiple arguments to define a command.
 
-    The first argument is the command name.
+    The first argument is the command name (as bytes).
 
-    The options argument is an iterable of tuples defining command arguments.
-    See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
+    The `options` keyword argument is an iterable of tuples defining command
+    arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
+    tuple.
 
-    The synopsis argument defines a short, one line summary of how to use the
+    The `synopsis` argument defines a short, one line summary of how to use the
     command. This shows up in the help output.
 
-    The norepo argument defines whether the command does not require a
+    There are three arguments that control what repository (if any) is found
+    and passed to the decorated function: `norepo`, `optionalrepo`, and
+    `inferrepo`.
+
+    The `norepo` argument defines whether the command does not require a
     local repository. Most commands operate against a repository, thus the
-    default is False.
+    default is False. When True, no repository will be passed.
 
-    The optionalrepo argument defines whether the command optionally requires
-    a local repository.
+    The `optionalrepo` argument defines whether the command optionally requires
+    a local repository. If no repository can be found, None will be passed
+    to the decorated function.
 
-    The inferrepo argument defines whether to try to find a repository from the
-    command line arguments. If True, arguments will be examined for potential
-    repository locations. See ``findrepo()``. If a repository is found, it
-    will be used.
+    The `inferrepo` argument defines whether to try to find a repository from
+    the command line arguments. If True, arguments will be examined for
+    potential repository locations. See ``findrepo()``. If a repository is
+    found, it will be used and passed to the decorated function.
 
     There are three constants in the class which tells what type of the command
     that is. That information will be helpful at various places. It will be also
     be used to decide what level of access the command has on hidden commits.
     The constants are:
 
-    unrecoverablewrite is for those write commands which can't be recovered like
-    push.
-    recoverablewrite is for write commands which can be recovered like commit.
-    readonly is for commands which are read only.
+    `unrecoverablewrite` is for those write commands which can't be recovered
+    like push.
+    `recoverablewrite` is for write commands which can be recovered like commit.
+    `readonly` is for commands which are read only.
+
+    The signature of the decorated function looks like this:
+        def cmd(ui[, repo] [, <args>] [, <options>])
+
+      `repo` is required if `norepo` is False.
+      `<args>` are positional args (or `*args`) arguments, of non-option
+      arguments from the command line.
+      `<options>` are keyword arguments (or `**options`) of option arguments
+      from the command line.
+
+    See the WritingExtensions and MercurialApi documentation for more exhaustive
+    descriptions and examples.
     """
 
     unrecoverablewrite = "unrecoverable"
--- a/mercurial/scmutil.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/scmutil.py	Mon Dec 04 11:28:29 2017 -0500
@@ -1279,3 +1279,30 @@
             else:
                 revrange = '%s:%s' % (minrev, maxrev)
             repo.ui.status(_('new changesets %s\n') % revrange)
+
+def nodesummaries(repo, nodes, maxnumnodes=4):
+    if len(nodes) <= maxnumnodes or repo.ui.verbose:
+        return ' '.join(short(h) for h in nodes)
+    first = ' '.join(short(h) for h in nodes[:maxnumnodes])
+    return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
+
+def enforcesinglehead(repo, tr, desc):
+    """check that no named branch has multiple heads"""
+    if desc in ('strip', 'repair'):
+        # skip the logic during strip
+        return
+    visible = repo.filtered('visible')
+    # possible improvement: we could restrict the check to affected branch
+    for name, heads in visible.branchmap().iteritems():
+        if len(heads) > 1:
+            msg = _('rejecting multiple heads on branch "%s"')
+            msg %= name
+            hint = _('%d heads: %s')
+            hint %= (len(heads), nodesummaries(repo, heads))
+            raise error.Abort(msg, hint=hint)
+
+def wrapconvertsink(sink):
+    """Allow extensions to wrap the sink returned by convcmd.convertsink()
+    before it is used, whether or not the convert extension was formally loaded.
+    """
+    return sink
--- a/mercurial/sshpeer.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/sshpeer.py	Mon Dec 04 11:28:29 2017 -0500
@@ -204,8 +204,9 @@
         self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee)
 
         def badresponse():
-            self._abort(error.RepoError(_('no suitable response from '
-                                          'remote hg')))
+            msg = _("no suitable response from remote hg")
+            hint = self.ui.config("ui", "ssherrorhint")
+            self._abort(error.RepoError(msg, hint=hint))
 
         try:
             # skip any noise generated by remote shell
--- a/mercurial/subrepo.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/subrepo.py	Mon Dec 04 11:28:29 2017 -0500
@@ -1154,24 +1154,24 @@
         # 2. update the subrepo to the revision specified in
         #    the corresponding substate dictionary
         self.ui.status(_('reverting subrepo %s\n') % substate[0])
-        if not opts.get('no_backup'):
+        if not opts.get(r'no_backup'):
             # Revert all files on the subrepo, creating backups
             # Note that this will not recursively revert subrepos
             # We could do it if there was a set:subrepos() predicate
             opts = opts.copy()
-            opts['date'] = None
-            opts['rev'] = substate[1]
+            opts[r'date'] = None
+            opts[r'rev'] = substate[1]
 
             self.filerevert(*pats, **opts)
 
         # Update the repo to the revision specified in the given substate
-        if not opts.get('dry_run'):
+        if not opts.get(r'dry_run'):
             self.get(substate, overwrite=True)
 
     def filerevert(self, *pats, **opts):
-        ctx = self._repo[opts['rev']]
+        ctx = self._repo[opts[r'rev']]
         parents = self._repo.dirstate.parents()
-        if opts.get('all'):
+        if opts.get(r'all'):
             pats = ['set:modified()']
         else:
             pats = []
--- a/mercurial/templatekw.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templatekw.py	Mon Dec 04 11:28:29 2017 -0500
@@ -608,6 +608,7 @@
     # the verbosity templatekw available.
     succsandmarkers = showsuccsandmarkers(**args)
 
+    args = pycompat.byteskwargs(args)
     ui = args['ui']
 
     values = []
@@ -816,7 +817,7 @@
 
 @templatekeyword('phaseidx')
 def showphaseidx(repo, ctx, templ, **args):
-    """Integer. The changeset phase index."""
+    """Integer. The changeset phase index. (ADVANCED)"""
     return ctx.phase()
 
 @templatekeyword('rev')
@@ -860,12 +861,6 @@
     """List of strings. Any tags associated with the changeset."""
     return shownames('tags', **args)
 
-def loadkeyword(ui, extname, registrarobj):
-    """Load template keyword from specified registrarobj
-    """
-    for name, func in registrarobj._table.iteritems():
-        keywords[name] = func
-
 @templatekeyword('termwidth')
 def showtermwidth(repo, ctx, templ, **args):
     """Integer. The width of the current terminal."""
@@ -891,5 +886,24 @@
     return showlist('instability', args['ctx'].instabilities(), args,
                     plural='instabilities')
 
+@templatekeyword('verbosity')
+def showverbosity(ui, **args):
+    """String. The current output verbosity in 'debug', 'quiet', 'verbose',
+    or ''."""
+    # see cmdutil.changeset_templater for priority of these flags
+    if ui.debugflag:
+        return 'debug'
+    elif ui.quiet:
+        return 'quiet'
+    elif ui.verbose:
+        return 'verbose'
+    return ''
+
+def loadkeyword(ui, extname, registrarobj):
+    """Load template keyword from specified registrarobj
+    """
+    for name, func in registrarobj._table.iteritems():
+        keywords[name] = func
+
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = keywords.values()
--- a/mercurial/templater.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templater.py	Mon Dec 04 11:28:29 2017 -0500
@@ -1005,17 +1005,18 @@
                 "obsmakers")
         raise error.ParseError(msg)
 
-@templatefunc('obsfateverb(successors)')
+@templatefunc('obsfateverb(successors, markers)')
 def obsfateverb(context, mapping, args):
     """Compute obsfate related information based on successors (EXPERIMENTAL)"""
-    if len(args) != 1:
+    if len(args) != 2:
         # i18n: "obsfateverb" is a keyword
-        raise error.ParseError(_("obsfateverb expects one arguments"))
+        raise error.ParseError(_("obsfateverb expects two arguments"))
 
     successors = evalfuncarg(context, mapping, args[0])
+    markers = evalfuncarg(context, mapping, args[1])
 
     try:
-        return obsutil.successorsetverb(successors)
+        return obsutil.obsfateverb(successors, markers)
     except TypeError:
         # i18n: "obsfateverb" is a keyword
         errmsg = _("obsfateverb first argument should be countable")
--- a/mercurial/templates/gitweb/changelogentry.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/gitweb/changelogentry.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -1,5 +1,9 @@
 <div>
-<a class="title" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}"><span class="age">{date|rfc822date}</span>{desc|strip|firstline|escape|nonempty}<span class="logtags"> {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a>
+ <a class="title" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+  <span class="age">{date|rfc822date}</span>
+  {desc|strip|firstline|escape|nonempty}
+  {alltags}
+ </a>
 </div>
 <div class="title_text">
 <div class="log_link">
--- a/mercurial/templates/gitweb/changeset.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/gitweb/changeset.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -30,7 +30,10 @@
 </div>
 
 <div>
-<a class="title" href="{url|urlescape}raw-rev/{node|short}">{desc|strip|escape|firstline|nonempty} <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a>
+ <a class="title" href="{url|urlescape}raw-rev/{node|short}">
+  {desc|strip|escape|firstline|nonempty}
+  {alltags}
+ </a>
 </div>
 <div class="title_text">
 <table cellspacing="0">
--- a/mercurial/templates/gitweb/filelog.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/gitweb/filelog.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -36,7 +36,7 @@
 
 <div class="title" >
   {file|urlescape}{if(linerange,
-' (following lines {linerange}{if(descend, ', descending')} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">back to filelog</a>)')}
+' (following lines {linerange}{if(descend, ', descending')} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">all revisions for this file</a>)')}
 </div>
 
 <table>
--- a/mercurial/templates/gitweb/graph.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/gitweb/graph.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -49,7 +49,7 @@
 var graph = new Graph();
 graph.scale({bg_height});
 
-graph.vertex = function(x, y, color, parity, cur) \{
+graph.vertex = function(x, y, radius, color, parity, cur) \{
 	
 	this.ctx.beginPath();
 	color = this.setColor(color, 0.25, 0.75);
--- a/mercurial/templates/gitweb/manifest.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/gitweb/manifest.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -28,7 +28,7 @@
 {searchform}
 </div>
 
-<div class="title">{path|escape} <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></div>
+<div class="title">{path|escape} {alltags}</div>
 <table cellspacing="0">
 <tr class="parity{upparity}">
 <td style="font-family:monospace">drwxr-xr-x</td>
--- a/mercurial/templates/gitweb/map	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/gitweb/map	Mon Dec 04 11:28:29 2017 -0500
@@ -262,10 +262,14 @@
   </tr>'
 shortlog = shortlog.tmpl
 graph = graph.tmpl
+phasetag = '{ifeq(phase, 'public', '', '<span class="phasetag" title="{phase|escape}">{phase|escape}</span> ')}'
+obsoletetag = '{if(obsolete, '<span class="obsoletetag" title="obsolete">obsolete</span> ')}'
+instabilitytag = '<span class="instabilitytag" title="{instability|escape}">{instability|escape}</span> '
 tagtag = '<span class="tagtag" title="{name|escape}">{name|escape}</span> '
 branchtag = '<span class="branchtag" title="{name|escape}">{name|escape}</span> '
 inbranchtag = '<span class="inbranchtag" title="{name|escape}">{name|escape}</span> '
 bookmarktag = '<span class="bookmarktag" title="{name|escape}">{name|escape}</span> '
+alltags = '<span class="logtags">{phasetag}{obsoletetag}{instabilities%instabilitytag}{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>'
 shortlogentry = '
   <tr class="parity{parity}">
     <td class="age"><i class="age">{date|rfc822date}</i></td>
@@ -273,7 +277,7 @@
     <td>
       <a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
         <b>{desc|strip|firstline|escape|nonempty}</b>
-        <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
+        {alltags}
       </a>
     </td>
     <td class="link" nowrap>
@@ -288,7 +292,7 @@
     <td>
       <a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
         <b>{desc|strip|firstline|escape|nonempty}</b>
-        <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
+        {alltags}
       </a>
     </td>
     <td class="link">
--- a/mercurial/templates/gitweb/summary.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/gitweb/summary.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -31,7 +31,7 @@
 <table cellspacing="0">
 <tr><td>description</td><td>{desc}</td></tr>
 <tr><td>owner</td><td>{owner|obfuscate}</td></tr>
-<tr><td>last change</td><td>{lastchange|rfc822date}</td></tr>
+<tr><td>last change</td><td class="date age">{lastchange|rfc822date}</td></tr>
 </table>
 
 <div><a  class="title" href="{url|urlescape}shortlog{sessionvars%urlparameter}">changes</a></div>
--- a/mercurial/templates/monoblue/changelogentry.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/monoblue/changelogentry.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -1,4 +1,9 @@
-<h3 class="changelog"><a class="title" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}<span class="logtags"> {inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a></h3>
+<h3 class="changelog">
+    <a class="title" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
+        {desc|strip|firstline|escape|nonempty}
+        {alltags}
+    </a>
+</h3>
 <ul class="changelog-entry">
     <li class="age">{date|rfc822date}</li>
     <li>by <span class="name">{author|obfuscate}</span> <span class="revdate">[{date|rfc822date}] rev {rev}</span></li>
--- a/mercurial/templates/monoblue/changeset.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/monoblue/changeset.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -32,14 +32,19 @@
 
     <h2 class="no-link no-border">changeset</h2>
 
-    <h3 class="changeset"><a href="{url|urlescape}raw-rev/{node|short}">{desc|strip|escape|firstline|nonempty} <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></a></h3>
+    <h3 class="changeset">
+        <a href="{url|urlescape}raw-rev/{node|short}">
+            {desc|strip|escape|firstline|nonempty}
+            {alltags}
+        </a>
+    </h3>
     <p class="changeset-age"><span class="age">{date|rfc822date}</span></p>
 
     <dl class="overview">
         <dt>author</dt>
         <dd>{author|obfuscate}</dd>
         <dt>date</dt>
-        <dd>{date|rfc822date}</dd>
+        <dd class="date age">{date|rfc822date}</dd>
         {branch%changesetbranch}
         <dt>changeset {rev}</dt>
         <dd><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></dd>
--- a/mercurial/templates/monoblue/fileannotate.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/monoblue/fileannotate.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -42,7 +42,7 @@
         <dt>author</dt>
         <dd>{author|obfuscate}</dd>
         <dt>date</dt>
-        <dd>{date|rfc822date}</dd>
+        <dd class="date age">{date|rfc822date}</dd>
         {branch%filerevbranch}
         <dt>changeset {rev}</dt>
         <dd><a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></dd>
--- a/mercurial/templates/monoblue/filerevision.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/monoblue/filerevision.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -42,7 +42,7 @@
         <dt>author</dt>
         <dd>{author|obfuscate}</dd>
         <dt>date</dt>
-        <dd>{date|rfc822date}</dd>
+        <dd class="date age">{date|rfc822date}</dd>
         {branch%filerevbranch}
         <dt>changeset {rev}</dt>
         <dd><a class="list" href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></dd>
--- a/mercurial/templates/monoblue/graph.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/monoblue/graph.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -43,7 +43,7 @@
     var graph = new Graph();
     graph.scale({bg_height});
 
-    graph.vertex = function(x, y, color, parity, cur) \{
+    graph.vertex = function(x, y, radius, color, parity, cur) \{
 
         this.ctx.beginPath();
         color = this.setColor(color, 0.25, 0.75);
--- a/mercurial/templates/monoblue/manifest.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/monoblue/manifest.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -30,7 +30,7 @@
     </ul>
 
     <h2 class="no-link no-border">files</h2>
-    <p class="files">{path|escape} <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span></p>
+    <p class="files">{path|escape} {alltags}</p>
 
     <table>
         <tr class="parity{upparity}">
--- a/mercurial/templates/monoblue/map	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/monoblue/map	Mon Dec 04 11:28:29 2017 -0500
@@ -221,10 +221,14 @@
   <dt>child {rev}</dt>
   <dd><a href="{url|urlescape}comparison/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></dd>'
 shortlog = shortlog.tmpl
+phasetag = '{ifeq(phase, 'public', '', '<span class="phasetag" title="{phase|escape}">{phase|escape}</span> ')}'
+obsoletetag = '{if(obsolete, '<span class="obsoletetag" title="obsolete">obsolete</span> ')}'
+instabilitytag = '<span class="instabilitytag" title="{instability|escape}">{instability|escape}</span> '
 tagtag = '<span class="tagtag" title="{name|escape}">{name|escape}</span> '
 branchtag = '<span class="branchtag" title="{name|escape}">{name|escape}</span> '
 inbranchtag = '<span class="inbranchtag" title="{name|escape}">{name|escape}</span> '
 bookmarktag = '<span class="bookmarktag" title="{name|escape}">{name|escape}</span> '
+alltags = '<span class="logtags">{phasetag}{obsoletetag}{instabilities%instabilitytag}{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>'
 shortlogentry = '
   <tr class="parity{parity}">
     <td class="nowrap age">{date|rfc822date}</td>
@@ -232,7 +236,7 @@
     <td>
       <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
         {desc|strip|firstline|escape|nonempty}
-        <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
+        {alltags}
       </a>
     </td>
     <td class="nowrap">
@@ -247,7 +251,7 @@
     <td>
       <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">
         {desc|strip|firstline|escape|nonempty}
-        <span class="logtags">{inbranch%inbranchtag}{branches%branchtag}{tags%tagtag}{bookmarks%bookmarktag}</span>
+        {alltags}
       </a>
     </td>
     <td class="nowrap">
--- a/mercurial/templates/monoblue/summary.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/monoblue/summary.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -34,7 +34,7 @@
         <dt>owner</dt>
         <dd>{owner|obfuscate}</dd>
         <dt>last change</dt>
-        <dd>{lastchange|rfc822date}</dd>
+        <dd class="date age">{lastchange|rfc822date}</dd>
     </dl>
 
     <h2><a href="{url|urlescape}shortlog{sessionvars%urlparameter}">Changes</a></h2>
--- a/mercurial/templates/paper/changeset.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/changeset.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -33,7 +33,7 @@
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 <h3>
  changeset {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
- {changesetbranch%changelogbranchname}{changesettag}{changesetbookmark}
+ {alltags}
 </h3>
 
 {searchform}
--- a/mercurial/templates/paper/fileannotate.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/fileannotate.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -39,7 +39,7 @@
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 <h3>
  annotate {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
- {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+ {alltags}
 </h3>
 
 {searchform}
--- a/mercurial/templates/paper/filecomparison.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/filecomparison.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -38,7 +38,7 @@
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 <h3>
  comparison {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
- {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+ {alltags}
 </h3>
 
 {searchform}
--- a/mercurial/templates/paper/filediff.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/filediff.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -38,7 +38,7 @@
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 <h3>
  diff {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
- {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+ {alltags}
 </h3>
 
 {searchform}
--- a/mercurial/templates/paper/filelog.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/filelog.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -46,9 +46,9 @@
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 <h3>
  log {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
- {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+ {alltags}
  {if(linerange,
-' (following lines {linerange}{if(descend, ', descending')} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">back to filelog</a>)')}
+' (following lines {linerange}{if(descend, ', descending')} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">all revisions for this file</a>)')}
 </h3>
 
 {searchform}
--- a/mercurial/templates/paper/filelogentry.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/filelogentry.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -3,7 +3,7 @@
   <td class="author">{author|person}</td>
   <td class="description">
    <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>
-   {inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag}{rename%filelogrename}
+   {alltags}{rename%filelogrename}
   </td>
  </tr>
  {if(patch, '<tr><td colspan="3">{diff}</td></tr>')}
--- a/mercurial/templates/paper/filerevision.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/filerevision.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -38,7 +38,7 @@
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 <h3>
  view {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
- {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+ {alltags}
 </h3>
 
 {searchform}
--- a/mercurial/templates/paper/graph.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/graph.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -62,7 +62,7 @@
 var graph = new Graph();
 graph.scale({bg_height});
 
-graph.vertex = function(x, y, color, parity, cur) \{
+graph.vertex = function(x, y, radius, color, parity, cur) \{
 	
 	this.ctx.beginPath();
 	color = this.setColor(color, 0.25, 0.75);
--- a/mercurial/templates/paper/manifest.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/manifest.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -32,7 +32,7 @@
 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
 <h3>
  directory {path|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
- {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
+ {alltags}
 </h3>
 
 {searchform}
--- a/mercurial/templates/paper/map	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/map	Mon Dec 04 11:28:29 2017 -0500
@@ -198,11 +198,15 @@
       </a>
     </td>
   </tr>'
+phasetag = '{ifeq(phase, 'public', '', '<span class="phase">{phase|escape}</span> ')}'
+obsoletetag = '{if(obsolete, '<span class="obsolete">obsolete</span> ')}'
+instabilitytag = '<span class="instability">{instability|escape}</span> '
 changelogtag = '<span class="tag">{name|escape}</span> '
 changesettag = '<span class="tag">{tag|escape}</span> '
 changesetbookmark = '<span class="tag">{bookmark|escape}</span> '
 changelogbranchhead = '<span class="branchhead">{name|escape}</span> '
 changelogbranchname = '<span class="branchname">{name|escape}</span> '
+alltags = '{phasetag}{obsoletetag}{instabilities%instabilitytag}{inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag}'
 
 filediffparent = '
   <tr>
--- a/mercurial/templates/paper/shortlogentry.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/paper/shortlogentry.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -3,6 +3,6 @@
   <td class="author">{author|person}</td>
   <td class="description">
    <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>
-   {inbranch%changelogbranchname}{branches%changelogbranchhead}{tags%changelogtag}{bookmarks%changelogtag}
+   {alltags}
   </td>
  </tr>
--- a/mercurial/templates/raw/graphnode.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/raw/graphnode.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -1,7 +1,7 @@
-changeset:   {node}
-user:        {user}
-date:        {age}
-summary:     {desc}
+changeset:   {node|short}
+user:        {author|person}
+date:        {date|age}
+summary:     {desc|firstline|nonempty}
 {branches%branchname}{tags%tagname}{bookmarks%bookmarkname}
 node:        ({col}, {row}) (color {color})
 {edges%graphedge}
--- a/mercurial/templates/spartan/changelogentry.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/spartan/changelogentry.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -18,6 +18,18 @@
   <th class="date">date:</th>
   <td class="date">{date|rfc822date}</td>
  </tr>
+ {ifeq(phase, 'public', '', '<tr>
+  <th class="phase">phase:</th>
+  <td class="phase">{phase|escape}</td>
+ </tr>')}
+ {if(obsolete, '<tr>
+  <th class="obsolete">obsolete:</th>
+  <td class="obsolete">yes</td>
+ </tr>')}
+ {ifeq(count(instabilities), '0', '', '<tr>
+  <th class="instabilities">instabilities:</th>
+  <td class="instabilities">{instabilities%"{instability} "|escape}</td>
+ </tr>')}
  <tr>
   <th class="files"><a href="{url|urlescape}file/{node|short}{sessionvars%urlparameter}">files</a>:</th>
   <td class="files">{files}</td>
--- a/mercurial/templates/spartan/changeset.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/spartan/changeset.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -33,6 +33,18 @@
  <th class="date">date:</th>
  <td class="date age">{date|rfc822date}</td>
 </tr>
+{ifeq(phase, 'public', '', '<tr>
+ <th class="phase">phase:</th>
+ <td class="phase">{phase|escape}</td>
+</tr>')}
+{if(obsolete, '<tr>
+ <th class="obsolete">obsolete:</th>
+ <td class="obsolete">yes</td>
+</tr>')}
+{ifeq(count(instabilities), '0', '', '<tr>
+ <th class="instabilities">instabilities:</th>
+ <td class="instabilities">{instabilities%"{instability} "|escape}</td>
+</tr>')}
 <tr>
  <th class="files">files:</th>
  <td class="files">{files}</td>
--- a/mercurial/templates/spartan/graph.tmpl	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/spartan/graph.tmpl	Mon Dec 04 11:28:29 2017 -0500
@@ -43,7 +43,7 @@
 var graph = new Graph();
 graph.scale({bg_height});
 
-graph.vertex = function(x, y, color, parity, cur) \{
+graph.vertex = function(x, y, radius, color, parity, cur) \{
 	
 	this.ctx.beginPath();
 	color = this.setColor(color, 0.25, 0.75);
--- a/mercurial/templates/static/followlines.js	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/static/followlines.js	Mon Dec 04 11:28:29 2017 -0500
@@ -13,7 +13,7 @@
     }
     // URL to complement with "linerange" query parameter
     var targetUri = sourcelines.dataset.logurl;
-    if (typeof targetUri === 'undefined') {
+    if (typeof targetUri === 'undefined') {
         return;
     }
 
@@ -38,7 +38,7 @@
     // element
     var selectableElements = Array.prototype.filter.call(
         sourcelines.children,
-        function(x) { return x.tagName === selectableTag });
+        function(x) { return x.tagName === selectableTag; });
 
     var btnTitleStart = 'start following lines history from here';
     var btnTitleEnd = 'terminate line block selection here';
@@ -62,7 +62,7 @@
     }
 
     // extend DOM with CSS class for selection highlight and action buttons
-    var followlinesButtons = []
+    var followlinesButtons = [];
     for (var i = 0; i < selectableElements.length; i++) {
         selectableElements[i].classList.add('followlines-select');
         var btn = createButton();
@@ -114,7 +114,7 @@
         if (parent === null) {
             return null;
         }
-        if (element.tagName == selectableTag && parent.isSameNode(sourcelines)) {
+        if (element.tagName === selectableTag && parent.isSameNode(sourcelines)) {
             return element;
         }
         return selectableParent(parent);
@@ -182,7 +182,7 @@
 
             // compute line range (startId, endId)
             var endId = parseInt(endElement.id.slice(1));
-            if (endId == startId) {
+            if (endId === startId) {
                 // clicked twice the same line, cancel and reset initial state
                 // (CSS, event listener for selection start)
                 removeSelectedCSSClass();
--- a/mercurial/templates/static/mercurial.js	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/static/mercurial.js	Mon Dec 04 11:28:29 2017 -0500
@@ -30,11 +30,9 @@
 	this.ctx.strokeStyle = 'rgb(0, 0, 0)';
 	this.ctx.fillStyle = 'rgb(0, 0, 0)';
 	this.cur = [0, 0];
-	this.line_width = 3;
 	this.bg = [0, 4];
 	this.cell = [2, 0];
 	this.columns = 0;
-	this.revlink = '';
 
 	this.reset = function() {
 		this.bg = [0, 4];
@@ -42,13 +40,13 @@
 		this.columns = 0;
 		document.getElementById('nodebgs').innerHTML = '';
 		document.getElementById('graphnodes').innerHTML = '';
-	}
+	};
 
 	this.scale = function(height) {
 		this.bg_height = height;
 		this.box_size = Math.floor(this.bg_height / 1.2);
 		this.cell_height = this.box_size;
-	}
+	};
 
 	this.setColor = function(color, bg, fg) {
 
@@ -62,9 +60,9 @@
 		// provides the multiplier that should be applied to
 		// the foreground colours.
 		var s;
-		if(typeof color == "string") {
+		if(typeof color === "string") {
 			s = "#" + color;
-		} else { //typeof color == "number"
+		} else { //typeof color === "number"
 			color %= colors.length;
 			var red = (colors[color][0] * fg) || bg;
 			var green = (colors[color][1] * fg) || bg;
@@ -78,7 +76,7 @@
 		this.ctx.fillStyle = s;
 		return s;
 
-	}
+	};
 
 	this.edge = function(x0, y0, x1, y1, color, width) {
 
@@ -90,14 +88,15 @@
 		this.ctx.lineTo(x1, y1);
 		this.ctx.stroke();
 
-	}
+	};
 
 	this.render = function(data) {
 
 		var backgrounds = '';
 		var nodedata = '';
+		var line, start, end, color, x, y, x0, y0, x1, y1, column, radius;
 
-		for (var i in data) {
+		for (var i = 0; i < data.length; i++) {
 
 			var parity = i % 2;
 			this.cell[1] += this.bg_height;
@@ -109,7 +108,7 @@
 			var fold = false;
 
 			var prevWidth = this.ctx.lineWidth;
-			for (var j in edges) {
+			for (var j = 0; j < edges.length; j++) {
 
 				line = edges[j];
 				start = line[0];
@@ -126,8 +125,8 @@
 					this.columns += 1;
 				}
 
-				if (start == this.columns && start > end) {
-					var fold = true;
+				if (start === this.columns && start > end) {
+					fold = true;
 				}
 
 				x0 = this.cell[0] + this.box_size * start + this.box_size / 2;
@@ -142,13 +141,13 @@
 
 			// Draw the revision node in the right column
 
-			column = node[0]
-			color = node[1]
+			column = node[0];
+			color = node[1];
 
 			radius = this.box_size / 8;
 			x = this.cell[0] + this.box_size * column + this.box_size / 2;
 			y = this.bg[1] - this.bg_height / 2;
-			var add = this.vertex(x, y, color, parity, cur);
+			var add = this.vertex(x, y, radius, color, parity, cur);
 			backgrounds += add[0];
 			nodedata += add[1];
 
@@ -159,7 +158,7 @@
 		document.getElementById('nodebgs').innerHTML += backgrounds;
 		document.getElementById('graphnodes').innerHTML += nodedata;
 
-	}
+	};
 
 }
 
@@ -228,10 +227,11 @@
 			return shortdate(once);
 		}
 
-		for (unit in scales){
+		for (var unit in scales){
+			if (!scales.hasOwnProperty(unit)) { continue; }
 			var s = scales[unit];
 			var n = Math.floor(delta / s);
-			if ((n >= 2) || (s == 1)){
+			if ((n >= 2) || (s === 1)){
 				if (future){
 					return format(n, unit) + ' from now';
 				} else {
@@ -259,7 +259,7 @@
 
 function toggleDiffstat() {
     var curdetails = document.getElementById('diffstatdetails').style.display;
-    var curexpand = curdetails == 'none' ? 'inline' : 'none';
+    var curexpand = curdetails === 'none' ? 'inline' : 'none';
     document.getElementById('diffstatdetails').style.display = curexpand;
     document.getElementById('diffstatexpand').style.display = curdetails;
 }
@@ -273,7 +273,8 @@
 
     function setLinewrap(enable) {
         var nodes = document.getElementsByClassName('sourcelines');
-        for (var i = 0; i < nodes.length; i++) {
+        var i;
+        for (i = 0; i < nodes.length; i++) {
             if (enable) {
                 nodes[i].classList.add('wrap');
             } else {
@@ -282,7 +283,7 @@
         }
 
         var links = document.getElementsByClassName('linewraplink');
-        for (var i = 0; i < links.length; i++) {
+        for (i = 0; i < links.length; i++) {
             links[i].innerHTML = enable ? 'on' : 'off';
         }
     }
@@ -297,12 +298,12 @@
 }
 
 function makeRequest(url, method, onstart, onsuccess, onerror, oncomplete) {
-    xfr = new XMLHttpRequest();
-    xfr.onreadystatechange = function() {
-        if (xfr.readyState === 4) {
+    var xhr = new XMLHttpRequest();
+    xhr.onreadystatechange = function() {
+        if (xhr.readyState === 4) {
             try {
-                if (xfr.status === 200) {
-                    onsuccess(xfr.responseText);
+                if (xhr.status === 200) {
+                    onsuccess(xhr.responseText);
                 } else {
                     throw 'server error';
                 }
@@ -314,11 +315,11 @@
         }
     };
 
-    xfr.open(method, url);
-    xfr.overrideMimeType("text/xhtml; charset=" + document.characterSet.toLowerCase());
-    xfr.send();
+    xhr.open(method, url);
+    xhr.overrideMimeType("text/xhtml; charset=" + document.characterSet.toLowerCase());
+    xhr.send();
     onstart();
-    return xfr;
+    return xhr;
 }
 
 function removeByClassName(className) {
@@ -344,8 +345,8 @@
                         containerSelector,
                         messageFormat,
                         mode) {
-    updateInitiated = false;
-    container = document.querySelector(containerSelector);
+    var updateInitiated = false;
+    var container = document.querySelector(containerSelector);
 
     function scrollHandler() {
         if (updateInitiated) {
@@ -354,8 +355,7 @@
 
         var scrollHeight = document.documentElement.scrollHeight;
         var clientHeight = document.documentElement.clientHeight;
-        var scrollTop = document.body.scrollTop
-            || document.documentElement.scrollTop;
+        var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
 
         if (scrollHeight - (scrollTop + clientHeight) < 50) {
             updateInitiated = true;
@@ -382,7 +382,8 @@
                     appendFormatHTML(container, messageFormat, message);
                 },
                 function onsuccess(htmlText) {
-                    if (mode == 'graph') {
+                    if (mode === 'graph') {
+                        var graph = window.graph;
                         var sizes = htmlText.match(/^\s*<canvas id="graph" width="(\d+)" height="(\d+)"><\/canvas>$/m);
                         var addWidth = sizes[1];
                         var addHeight = sizes[2];
@@ -450,7 +451,7 @@
         "ignoreblanklines",
     ];
 
-    var urlParams = new URLSearchParams(window.location.search);
+    var urlParams = new window.URLSearchParams(window.location.search);
 
     function updateAndRefresh(e) {
         var checkbox = e.target;
@@ -459,7 +460,7 @@
         window.location.search = urlParams.toString();
     }
 
-    var allChecked = form.getAttribute("data-ignorews") == "1";
+    var allChecked = form.getAttribute("data-ignorews") === "1";
 
     for (var i = 0; i < KEYS.length; i++) {
         var key = KEYS[i];
@@ -469,11 +470,11 @@
             continue;
         }
 
-        currentValue = form.getAttribute("data-" + key);
-        checkbox.checked = currentValue != "0";
+        var currentValue = form.getAttribute("data-" + key);
+        checkbox.checked = currentValue !== "0";
 
         // ignorews implies ignorewsamount and ignorewseol.
-        if (allChecked && (key == "ignorewsamount" || key == "ignorewseol")) {
+        if (allChecked && (key === "ignorewsamount" || key === "ignorewseol")) {
             checkbox.checked = true;
             checkbox.disabled = true;
         }
--- a/mercurial/templates/static/style-gitweb.css	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/static/style-gitweb.css	Mon Dec 04 11:28:29 2017 -0500
@@ -61,8 +61,6 @@
 }
 td.indexlinks a:hover { background-color: #6666aa; }
 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
-div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
-div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
 
 .search {
     margin-right: 8px;
@@ -122,6 +120,18 @@
 	background-color: #ffaaff;
 	border-color: #ffccff #ff00ee #ff00ee #ffccff;
 }
+span.logtags span.phasetag {
+	background-color: #dfafff;
+	border-color: #e2b8ff #ce48ff #ce48ff #e2b8ff;
+}
+span.logtags span.obsoletetag {
+	background-color: #dddddd;
+	border-color: #e4e4e4 #a3a3a3 #a3a3a3 #e4e4e4;
+}
+span.logtags span.instabilitytag {
+	background-color: #ffb1c0;
+	border-color: #ffbbc8 #ff4476 #ff4476 #ffbbc8;
+}
 span.logtags span.tagtag {
 	background-color: #ffffaa;
 	border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
@@ -191,10 +201,9 @@
 }
 
 div#followlines {
-  background-color: #B7B7B7;
-  border: 1px solid #CCC;
-  border-radius: 5px;
-  padding: 4px;
+  background-color: #FFF;
+  border: 1px solid #d9d8d1;
+  padding: 5px;
   position: fixed;
 }
 
@@ -315,8 +324,6 @@
 ul#graphnodes li .info {
 	display: block;
 	font-size: 100%;
-	position: relative;
-	top: -3px;
 	font-style: italic;
 }
 
--- a/mercurial/templates/static/style-monoblue.css	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/static/style-monoblue.css	Mon Dec 04 11:28:29 2017 -0500
@@ -233,6 +233,18 @@
   background-color: #ffaaff;
   border-color: #ffccff #ff00ee #ff00ee #ffccff;
 }
+span.logtags span.phasetag {
+  background-color: #dfafff;
+  border-color: #e2b8ff #ce48ff #ce48ff #e2b8ff;
+}
+span.logtags span.obsoletetag {
+  background-color: #dddddd;
+  border-color: #e4e4e4 #a3a3a3 #a3a3a3 #e4e4e4;
+}
+span.logtags span.instabilitytag {
+  background-color: #ffb1c0;
+  border-color: #ffbbc8 #ff4476 #ff4476 #ffbbc8;
+}
 span.logtags span.tagtag {
   background-color: #ffffaa;
   border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
@@ -309,6 +321,7 @@
 pre.sourcelines.stripes > :nth-child(4n+1):hover + :nth-child(4n+2),
 pre.sourcelines.stripes > :nth-child(4n+3):hover + :nth-child(4n+4) { background-color: #D5E1E6; }
 
+tr:target td,
 pre.sourcelines > span:target,
 pre.sourcelines.stripes > span:target {
     background-color: #bfdfff;
@@ -490,7 +503,6 @@
 
 ul#graphnodes li .info {
 	display: block;
-	position: relative;
 }
 /** end of canvas **/
 
--- a/mercurial/templates/static/style-paper.css	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/static/style-paper.css	Mon Dec 04 11:28:29 2017 -0500
@@ -137,6 +137,33 @@
   margin: 1em 0;
 }
 
+.phase {
+  color: #999;
+  font-size: 70%;
+  border-bottom: 1px dotted #999;
+  font-weight: normal;
+  margin-left: .5em;
+  vertical-align: baseline;
+}
+
+.obsolete {
+  color: #999;
+  font-size: 70%;
+  border-bottom: 1px dashed #999;
+  font-weight: normal;
+  margin-left: .5em;
+  vertical-align: baseline;
+}
+
+.instability {
+  color: #000;
+  font-size: 70%;
+  border-bottom: 1px solid #000;
+  font-weight: normal;
+  margin-left: .5em;
+  vertical-align: baseline;
+}
+
 .tag {
   color: #999;
   font-size: 70%;
@@ -165,10 +192,6 @@
   vertical-align: baseline;
 }
 
-h3 .branchname {
-  font-size: 80%;
-}
-
 /* Common */
 pre { margin: 0; }
 
@@ -295,10 +318,9 @@
 }
 
 div#followlines {
-  background-color: #B7B7B7;
-  border: 1px solid #CCC;
-  border-radius: 5px;
-  padding: 4px;
+  background-color: #FFF;
+  border: 1px solid #999;
+  padding: 5px;
   position: fixed;
 }
 
@@ -459,8 +481,6 @@
 ul#graphnodes li .info {
 	display: block;
 	font-size: 70%;
-	position: relative;
-	top: -3px;
 }
 
 /* Comparison */
--- a/mercurial/templates/static/style.css	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/templates/static/style.css	Mon Dec 04 11:28:29 2017 -0500
@@ -117,6 +117,4 @@
 ul#graphnodes li .info {
 	display: block;
 	font-size: 70%;
-	position: relative;
-	top: -1px;
 }
--- a/mercurial/ui.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/ui.py	Mon Dec 04 11:28:29 2017 -0500
@@ -49,6 +49,8 @@
 [ui]
 # The rollback command is dangerous. As a rule, don't use it.
 rollback = False
+# Make `hg status` report copy information
+statuscopies = yes
 
 [commands]
 # Make `hg status` emit cwd-relative paths by default.
@@ -695,6 +697,9 @@
         >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
         >>> u.configlist(s, b'list1')
         ['this', 'is', 'a small', 'test']
+        >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
+        >>> u.configlist(s, b'list2')
+        ['this', 'is', 'a small', 'test']
         """
         # default is not always a list
         v = self.configwith(config.parselist, section, name, default,
@@ -1602,7 +1607,7 @@
         stack.
         """
         if not self.configbool('devel', 'all-warnings'):
-            if config is not None and not self.configbool('devel', config):
+            if config is None or not self.configbool('devel', config):
                 return
         msg = 'devel-warn: ' + msg
         stacklevel += 1 # get in develwarn
--- a/mercurial/util.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/mercurial/util.py	Mon Dec 04 11:28:29 2017 -0500
@@ -931,6 +931,11 @@
         # __dict__ assignment required to bypass __setattr__ (eg: repoview)
         obj.__dict__[self.name] = value
 
+def clearcachedproperty(obj, prop):
+    '''clear a cached property value, if one has been set'''
+    if prop in obj.__dict__:
+        del obj.__dict__[prop]
+
 def pipefilter(s, cmd):
     '''filter string S through command CMD, returning its output'''
     p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
@@ -2662,7 +2667,7 @@
         else:
             prefix_char = prefix
         mapping[prefix_char] = prefix_char
-    r = remod.compile(r'%s(%s)' % (prefix, patterns))
+    r = remod.compile(br'%s(%s)' % (prefix, patterns))
     return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
 
 def getport(port):
--- a/setup.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/setup.py	Mon Dec 04 11:28:29 2017 -0500
@@ -767,7 +767,7 @@
             'mercurial.thirdparty.attr',
             'hgext', 'hgext.convert', 'hgext.fsmonitor',
             'hgext.fsmonitor.pywatchman', 'hgext.highlight',
-            'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd',
+            'hgext.largefiles', 'hgext.lfs', 'hgext.zeroconf', 'hgext3rd',
             'hgdemandimport']
 
 common_depends = ['mercurial/bitmanipulation.h',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/common-pattern.py	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,39 @@
+# common patterns in test at can safely be replaced
+from __future__ import absolute_import
+
+substitutions = [
+    # list of possible compressions
+    (br'zstd,zlib,none,bzip2',
+     br'$USUAL_COMPRESSIONS$'
+    ),
+    # capabilities sent through http
+    (br'bundlecaps=HG20%2Cbundle2%3DHG20%250A'
+     br'changegroup%253D01%252C02%250A'
+     br'digests%253Dmd5%252Csha1%252Csha512%250A'
+     br'error%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250A'
+     br'hgtagsfnodes%250A'
+     br'listkeys%250A'
+     br'phases%253Dheads%250A'
+     br'pushkey%250A'
+     br'remote-changegroup%253Dhttp%252Chttps',
+     # (the replacement patterns)
+     br'$USUAL_BUNDLE_CAPS$'
+    ),
+    # bundle2 capabilities sent through ssh
+    (br'bundle2=HG20%0A'
+     br'changegroup%3D01%2C02%0A'
+     br'digests%3Dmd5%2Csha1%2Csha512%0A'
+     br'error%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0A'
+     br'hgtagsfnodes%0A'
+     br'listkeys%0A'
+     br'phases%3Dheads%0A'
+     br'pushkey%0A'
+     br'remote-changegroup%3Dhttp%2Chttps',
+     # (replacement patterns)
+     br'$USUAL_BUNDLE2_CAPS$'
+    ),
+    # HTTP log dates
+    (br' - - \[\d\d/.../2\d\d\d \d\d:\d\d:\d\d] "GET',
+     br' - - [$LOGDATE$] "GET'
+    ),
+]
--- a/tests/hghave.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/hghave.py	Mon Dec 04 11:28:29 2017 -0500
@@ -284,6 +284,17 @@
         return (0, 0)
     return (int(m.group(1)), int(m.group(2)))
 
+# https://github.com/git-lfs/lfs-test-server
+@check("lfs-test-server", "git-lfs test server")
+def has_lfsserver():
+    exe = 'lfs-test-server'
+    if has_windows():
+        exe = 'lfs-test-server.exe'
+    return any(
+        os.access(os.path.join(path, exe), os.X_OK)
+        for path in os.environ["PATH"].split(os.pathsep)
+    )
+
 @checkvers("git", "git client (with ext::sh support) version >= %s", (1.9,))
 def has_git_range(v):
     major, minor = v.split('.')[0:2]
@@ -444,6 +455,10 @@
     return matchoutput("clang-format --help",
                        br"^OVERVIEW: A tool to format C/C\+\+[^ ]+ code.")
 
+@check("jshint", "JSHint static code analysis tool")
+def has_jshint():
+    return matchoutput("jshint --version 2>&1", br"jshint v")
+
 @check("pygments", "Pygments source highlighting library")
 def has_pygments():
     try:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/logexceptions.py	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,73 @@
+# logexceptions.py - Write files containing info about Mercurial exceptions
+#
+# Copyright 2017 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
+
+import inspect
+import os
+import sys
+import traceback
+import uuid
+
+from mercurial import (
+    dispatch,
+    extensions,
+)
+
+def handleexception(orig, ui):
+    res = orig(ui)
+
+    if not ui.environ.get(b'HGEXCEPTIONSDIR'):
+        return res
+
+    dest = os.path.join(ui.environ[b'HGEXCEPTIONSDIR'],
+                        str(uuid.uuid4()).encode('ascii'))
+
+    exc_type, exc_value, exc_tb = sys.exc_info()
+
+    stack = []
+    tb = exc_tb
+    while tb:
+        stack.append(tb)
+        tb = tb.tb_next
+    stack.reverse()
+
+    hgframe = 'unknown'
+    hgline = 'unknown'
+
+    # Find the first Mercurial frame in the stack.
+    for tb in stack:
+        mod = inspect.getmodule(tb)
+        if not mod.__name__.startswith(('hg', 'mercurial')):
+            continue
+
+        frame = tb.tb_frame
+
+        try:
+            with open(inspect.getsourcefile(tb), 'r') as fh:
+                hgline = fh.readlines()[frame.f_lineno - 1].strip()
+        except (IndexError, OSError):
+            pass
+
+        hgframe = '%s:%d' % (frame.f_code.co_filename, frame.f_lineno)
+        break
+
+    primary = traceback.extract_tb(exc_tb)[-1]
+    primaryframe = '%s:%d' % (primary.filename, primary.lineno)
+
+    with open(dest, 'wb') as fh:
+        parts = [
+            str(exc_value),
+            primaryframe,
+            hgframe,
+            hgline,
+        ]
+        fh.write(b'\0'.join(p.encode('utf-8', 'replace') for p in parts))
+
+def extsetup(ui):
+    extensions.wrapfunction(dispatch, 'handlecommandexception',
+                            handleexception)
--- a/tests/run-tests.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/run-tests.py	Mon Dec 04 11:28:29 2017 -0500
@@ -45,11 +45,12 @@
 
 from __future__ import absolute_import, print_function
 
+import argparse
+import collections
 import difflib
 import distutils.version as version
 import errno
 import json
-import optparse
 import os
 import random
 import re
@@ -296,122 +297,132 @@
 
 def getparser():
     """Obtain the OptionParser used by the CLI."""
-    parser = optparse.OptionParser("%prog [options] [tests]")
-
-    # keep these sorted
-    parser.add_option("--blacklist", action="append",
+    parser = argparse.ArgumentParser(usage='%(prog)s [options] [tests]')
+
+    selection = parser.add_argument_group('Test Selection')
+    selection.add_argument('--allow-slow-tests', action='store_true',
+        help='allow extremely slow tests')
+    selection.add_argument("--blacklist", action="append",
         help="skip tests listed in the specified blacklist file")
-    parser.add_option("--whitelist", action="append",
+    selection.add_argument("--changed",
+        help="run tests that are changed in parent rev or working directory")
+    selection.add_argument("-k", "--keywords",
+        help="run tests matching keywords")
+    selection.add_argument("-r", "--retest", action="store_true",
+        help = "retest failed tests")
+    selection.add_argument("--test-list", action="append",
+        help="read tests to run from the specified file")
+    selection.add_argument("--whitelist", action="append",
         help="always run tests listed in the specified whitelist file")
-    parser.add_option("--test-list", action="append",
-                      help="read tests to run from the specified file")
-    parser.add_option("--changed", type="string",
-        help="run tests that are changed in parent rev or working directory")
-    parser.add_option("-C", "--annotate", action="store_true",
-        help="output files annotated with coverage")
-    parser.add_option("-c", "--cover", action="store_true",
-        help="print a test coverage report")
-    parser.add_option("--color", choices=["always", "auto", "never"],
-                      default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
-                      help="colorisation: always|auto|never (default: auto)")
-    parser.add_option("-d", "--debug", action="store_true",
+    selection.add_argument('tests', metavar='TESTS', nargs='*',
+                        help='Tests to run')
+
+    harness = parser.add_argument_group('Test Harness Behavior')
+    harness.add_argument('--bisect-repo',
+                        metavar='bisect_repo',
+                        help=("Path of a repo to bisect. Use together with "
+                              "--known-good-rev"))
+    harness.add_argument("-d", "--debug", action="store_true",
         help="debug mode: write output of test scripts to console"
              " rather than capturing and diffing it (disables timeout)")
-    parser.add_option("-f", "--first", action="store_true",
+    harness.add_argument("-f", "--first", action="store_true",
         help="exit on the first test failure")
-    parser.add_option("-H", "--htmlcov", action="store_true",
-        help="create an HTML report of the coverage of the files")
-    parser.add_option("-i", "--interactive", action="store_true",
+    harness.add_argument("-i", "--interactive", action="store_true",
         help="prompt to accept changed output")
-    parser.add_option("-j", "--jobs", type="int",
+    harness.add_argument("-j", "--jobs", type=int,
         help="number of jobs to run in parallel"
              " (default: $%s or %d)" % defaults['jobs'])
-    parser.add_option("--keep-tmpdir", action="store_true",
+    harness.add_argument("--keep-tmpdir", action="store_true",
         help="keep temporary directory after running tests")
-    parser.add_option("-k", "--keywords",
-        help="run tests matching keywords")
-    parser.add_option("--list-tests", action="store_true",
+    harness.add_argument('--known-good-rev',
+                        metavar="known_good_rev",
+                        help=("Automatically bisect any failures using this "
+                              "revision as a known-good revision."))
+    harness.add_argument("--list-tests", action="store_true",
         help="list tests instead of running them")
-    parser.add_option("-l", "--local", action="store_true",
+    harness.add_argument("--loop", action="store_true",
+        help="loop tests repeatedly")
+    harness.add_argument('--random', action="store_true",
+        help='run tests in random order')
+    harness.add_argument("-p", "--port", type=int,
+        help="port on which servers should listen"
+             " (default: $%s or %d)" % defaults['port'])
+    harness.add_argument('--profile-runner', action='store_true',
+                        help='run statprof on run-tests')
+    harness.add_argument("-R", "--restart", action="store_true",
+        help="restart at last error")
+    harness.add_argument("--runs-per-test", type=int, dest="runs_per_test",
+        help="run each test N times (default=1)", default=1)
+    harness.add_argument("--shell",
+        help="shell to use (default: $%s or %s)" % defaults['shell'])
+    harness.add_argument('--showchannels', action='store_true',
+                        help='show scheduling channels')
+    harness.add_argument("--slowtimeout", type=int,
+        help="kill errant slow tests after SLOWTIMEOUT seconds"
+             " (default: $%s or %d)" % defaults['slowtimeout'])
+    harness.add_argument("-t", "--timeout", type=int,
+        help="kill errant tests after TIMEOUT seconds"
+             " (default: $%s or %d)" % defaults['timeout'])
+    harness.add_argument("--tmpdir",
+        help="run tests in the given temporary directory"
+             " (implies --keep-tmpdir)")
+    harness.add_argument("-v", "--verbose", action="store_true",
+        help="output verbose messages")
+
+    hgconf = parser.add_argument_group('Mercurial Configuration')
+    hgconf.add_argument("--chg", action="store_true",
+        help="install and use chg wrapper in place of hg")
+    hgconf.add_argument("--compiler",
+        help="compiler to build with")
+    hgconf.add_argument('--extra-config-opt', action="append", default=[],
+        help='set the given config opt in the test hgrc')
+    hgconf.add_argument("-l", "--local", action="store_true",
         help="shortcut for --with-hg=<testdir>/../hg, "
              "and --with-chg=<testdir>/../contrib/chg/chg if --chg is set")
-    parser.add_option("--loop", action="store_true",
-        help="loop tests repeatedly")
-    parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
-        help="run each test N times (default=1)", default=1)
-    parser.add_option("-n", "--nodiff", action="store_true",
-        help="skip showing test changes")
-    parser.add_option("--outputdir", type="string",
-        help="directory to write error logs to (default=test directory)")
-    parser.add_option("-p", "--port", type="int",
-        help="port on which servers should listen"
-             " (default: $%s or %d)" % defaults['port'])
-    parser.add_option("--compiler", type="string",
-        help="compiler to build with")
-    parser.add_option("--pure", action="store_true",
+    hgconf.add_argument("--ipv6", action="store_true",
+        help="prefer IPv6 to IPv4 for network related tests")
+    hgconf.add_argument("--pure", action="store_true",
         help="use pure Python code instead of C extensions")
-    parser.add_option("-R", "--restart", action="store_true",
-        help="restart at last error")
-    parser.add_option("-r", "--retest", action="store_true",
-        help="retest failed tests")
-    parser.add_option("-S", "--noskips", action="store_true",
-        help="don't report skip tests verbosely")
-    parser.add_option("--shell", type="string",
-        help="shell to use (default: $%s or %s)" % defaults['shell'])
-    parser.add_option("-t", "--timeout", type="int",
-        help="kill errant tests after TIMEOUT seconds"
-             " (default: $%s or %d)" % defaults['timeout'])
-    parser.add_option("--slowtimeout", type="int",
-        help="kill errant slow tests after SLOWTIMEOUT seconds"
-             " (default: $%s or %d)" % defaults['slowtimeout'])
-    parser.add_option("--time", action="store_true",
-        help="time how long each test takes")
-    parser.add_option("--json", action="store_true",
-                      help="store test result data in 'report.json' file")
-    parser.add_option("--tmpdir", type="string",
-        help="run tests in the given temporary directory"
-             " (implies --keep-tmpdir)")
-    parser.add_option("-v", "--verbose", action="store_true",
-        help="output verbose messages")
-    parser.add_option("--xunit", type="string",
-                      help="record xunit results at specified path")
-    parser.add_option("--view", type="string",
-        help="external diff viewer")
-    parser.add_option("--with-hg", type="string",
+    hgconf.add_argument("-3", "--py3k-warnings", action="store_true",
+        help="enable Py3k warnings on Python 2.7+")
+    hgconf.add_argument("--with-chg", metavar="CHG",
+        help="use specified chg wrapper in place of hg")
+    hgconf.add_argument("--with-hg",
         metavar="HG",
         help="test using specified hg script rather than a "
              "temporary installation")
-    parser.add_option("--chg", action="store_true",
-                      help="install and use chg wrapper in place of hg")
-    parser.add_option("--with-chg", metavar="CHG",
-                      help="use specified chg wrapper in place of hg")
-    parser.add_option("--ipv6", action="store_true",
-                      help="prefer IPv6 to IPv4 for network related tests")
-    parser.add_option("-3", "--py3k-warnings", action="store_true",
-        help="enable Py3k warnings on Python 2.7+")
     # This option should be deleted once test-check-py3-compat.t and other
     # Python 3 tests run with Python 3.
-    parser.add_option("--with-python3", metavar="PYTHON3",
-                      help="Python 3 interpreter (if running under Python 2)"
-                           " (TEMPORARY)")
-    parser.add_option('--extra-config-opt', action="append",
-                      help='set the given config opt in the test hgrc')
-    parser.add_option('--random', action="store_true",
-                      help='run tests in random order')
-    parser.add_option('--profile-runner', action='store_true',
-                      help='run statprof on run-tests')
-    parser.add_option('--allow-slow-tests', action='store_true',
-                      help='allow extremely slow tests')
-    parser.add_option('--showchannels', action='store_true',
-                      help='show scheduling channels')
-    parser.add_option('--known-good-rev', type="string",
-                      metavar="known_good_rev",
-                      help=("Automatically bisect any failures using this "
-                            "revision as a known-good revision."))
-    parser.add_option('--bisect-repo', type="string",
-                      metavar='bisect_repo',
-                      help=("Path of a repo to bisect. Use together with "
-                            "--known-good-rev"))
+    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",
+        help="output files annotated with coverage")
+    reporting.add_argument("--color", choices=["always", "auto", "never"],
+        default=os.environ.get('HGRUNTESTSCOLOR', 'auto'),
+        help="colorisation: always|auto|never (default: auto)")
+    reporting.add_argument("-c", "--cover", action="store_true",
+        help="print a test coverage report")
+    reporting.add_argument('--exceptions', action='store_true',
+        help='log all exceptions and generate an exception report')
+    reporting.add_argument("-H", "--htmlcov", action="store_true",
+        help="create an HTML report of the coverage of the files")
+    reporting.add_argument("--json", action="store_true",
+        help="store test result data in 'report.json' file")
+    reporting.add_argument("--outputdir",
+        help="directory to write error logs to (default=test directory)")
+    reporting.add_argument("-n", "--nodiff", action="store_true",
+        help="skip showing test changes")
+    reporting.add_argument("-S", "--noskips", action="store_true",
+        help="don't report skip tests verbosely")
+    reporting.add_argument("--time", action="store_true",
+        help="time how long each test takes")
+    reporting.add_argument("--view",
+        help="external diff viewer")
+    reporting.add_argument("--xunit",
+        help="record xunit results at specified path")
 
     for option, (envvar, default) in defaults.items():
         defaults[option] = type(default)(os.environ.get(envvar, default))
@@ -421,7 +432,7 @@
 
 def parseargs(args, parser):
     """Parse arguments with our OptionParser and validate results."""
-    (options, args) = parser.parse_args(args)
+    options = parser.parse_args(args)
 
     # jython is always pure
     if 'java' in sys.platform or '__pypy__' in sys.modules:
@@ -550,7 +561,7 @@
     if options.showchannels:
         options.nodiff = True
 
-    return (options, args)
+    return options
 
 def rename(src, dst):
     """Like os.rename(), trade atomicity and opened files friendliness
@@ -968,6 +979,15 @@
             ]
         r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
 
+        replacementfile = os.path.join(self._testdir, b'common-pattern.py')
+
+        if os.path.exists(replacementfile):
+            data = {}
+            with open(replacementfile, mode='rb') as source:
+                # the intermediate 'compile' step help with debugging
+                code = compile(source.read(), replacementfile, 'exec')
+                exec(code, data)
+                r.extend(data.get('substitutions', ()))
         return r
 
     def _escapepath(self, p):
@@ -1087,9 +1107,9 @@
         hgrc.write(b'ipv6 = %s\n' % str(self._useipv6).encode('ascii'))
 
         for opt in self._extraconfigopts:
-            section, key = opt.split('.', 1)
-            assert '=' in key, ('extra config opt %s must '
-                                'have an = for assignment' % opt)
+            section, key = opt.encode('utf-8').split(b'.', 1)
+            assert b'=' in key, ('extra config opt %s must '
+                                 'have an = for assignment' % opt)
             hgrc.write(b'[%s]\n%s\n' % (section, key))
         hgrc.close()
 
@@ -1197,9 +1217,7 @@
 
     def __init__(self, path, *args, **kwds):
         # accept an extra "case" parameter
-        case = None
-        if 'case' in kwds:
-            case = kwds.pop('case')
+        case = kwds.pop('case', None)
         self._case = case
         self._allcases = parsettestcases(path)
         super(TTest, self).__init__(path, *args, **kwds)
@@ -1517,6 +1535,7 @@
     @staticmethod
     def rematch(el, l):
         try:
+            el = b'(?:' + el + b')'
             # use \Z to ensure that the regex matches to the end of the string
             if os.name == 'nt':
                 return re.match(el + br'\r?\n\Z', l)
@@ -2096,6 +2115,18 @@
                     os.environ['PYTHONHASHSEED'])
             if self._runner.options.time:
                 self.printtimes(result.times)
+
+            if self._runner.options.exceptions:
+                exceptions = aggregateexceptions(
+                    os.path.join(self._runner._outputdir, b'exceptions'))
+                total = sum(exceptions.values())
+
+                self.stream.writeln('Exceptions Report:')
+                self.stream.writeln('%d total from %d frames' %
+                                    (total, len(exceptions)))
+                for (frame, line, exc), count in exceptions.most_common():
+                    self.stream.writeln('%d\t%s: %s' % (count, frame, exc))
+
             self.stream.flush()
 
         return result
@@ -2287,18 +2318,16 @@
         oldmask = os.umask(0o22)
         try:
             parser = parser or getparser()
-            options, args = parseargs(args, parser)
-            # positional arguments are paths to test files to run, so
-            # we make sure they're all bytestrings
-            args = [_bytespath(a) for a in args]
+            options = parseargs(args, parser)
+            tests = [_bytespath(a) for a in options.tests]
             if options.test_list is not None:
                 for listfile in options.test_list:
                     with open(listfile, 'rb') as f:
-                        args.extend(t for t in f.read().splitlines() if t)
+                        tests.extend(t for t in f.read().splitlines() if t)
             self.options = options
 
             self._checktools()
-            testdescs = self.findtests(args)
+            testdescs = self.findtests(tests)
             if options.profile_runner:
                 import statprof
                 statprof.start()
@@ -2353,10 +2382,18 @@
 
         self._testdir = osenvironb[b'TESTDIR'] = getattr(
             os, 'getcwdb', os.getcwd)()
+        # assume all tests in same folder for now
+        if testdescs:
+            pathname = os.path.dirname(testdescs[0]['path'])
+            if pathname:
+                osenvironb[b'TESTDIR'] = os.path.join(osenvironb[b'TESTDIR'],
+                                                      pathname)
         if self.options.outputdir:
             self._outputdir = canonpath(_bytespath(self.options.outputdir))
         else:
             self._outputdir = self._testdir
+            if testdescs and pathname:
+                self._outputdir = os.path.join(self._outputdir, pathname)
 
         if 'PYTHONHASHSEED' not in os.environ:
             # use a random python hash seed all the time
@@ -2476,6 +2513,23 @@
 
         self._coveragefile = os.path.join(self._testdir, b'.coverage')
 
+        if self.options.exceptions:
+            exceptionsdir = os.path.join(self._outputdir, b'exceptions')
+            try:
+                os.makedirs(exceptionsdir)
+            except OSError as e:
+                if e.errno != errno.EEXIST:
+                    raise
+
+            # Remove all existing exception reports.
+            for f in os.listdir(exceptionsdir):
+                os.unlink(os.path.join(exceptionsdir, f))
+
+            osenvironb[b'HGEXCEPTIONSDIR'] = exceptionsdir
+            logexceptions = os.path.join(self._testdir, b'logexceptions.py')
+            self.options.extra_config_opt.append(
+                'extensions.logexceptions=%s' % logexceptions.decode('utf-8'))
+
         vlog("# Using TESTDIR", self._testdir)
         vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
         vlog("# Using HGTMP", self._hgtmp)
@@ -2504,6 +2558,16 @@
             else:
                 args = os.listdir(b'.')
 
+        expanded_args = []
+        for arg in args:
+            if os.path.isdir(arg):
+                if not arg.endswith(b'/'):
+                    arg += b'/'
+                expanded_args.extend([arg + a for a in os.listdir(arg)])
+            else:
+                expanded_args.append(arg)
+        args = expanded_args
+
         tests = []
         for t in args:
             if not (os.path.basename(t).startswith(b'test-')
@@ -2918,6 +2982,24 @@
                 print("WARNING: Did not find prerequisite tool: %s " %
                       p.decode("utf-8"))
 
+def aggregateexceptions(path):
+    exceptions = collections.Counter()
+
+    for f in os.listdir(path):
+        with open(os.path.join(path, f), 'rb') as fh:
+            data = fh.read().split(b'\0')
+            if len(data) != 4:
+                continue
+
+            exc, mainframe, hgframe, hgline = data
+            exc = exc.decode('utf-8')
+            mainframe = mainframe.decode('utf-8')
+            hgframe = hgframe.decode('utf-8')
+            hgline = hgline.decode('utf-8')
+            exceptions[(hgframe, hgline, exc)] += 1
+
+    return exceptions
+
 if __name__ == '__main__':
     runner = TestRunner()
 
--- a/tests/seq.py	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/seq.py	Mon Dec 04 11:28:29 2017 -0500
@@ -10,6 +10,9 @@
 from __future__ import absolute_import, print_function
 import sys
 
+if sys.version_info[0] >= 3:
+    xrange = range
+
 start = 1
 if len(sys.argv) > 2:
     start = int(sys.argv[1])
--- a/tests/test-amend.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-amend.t	Mon Dec 04 11:28:29 2017 -0500
@@ -203,8 +203,8 @@
   [255]
   $ hg amend --note "adding bar"
   $ hg debugobsolete -r .
-  112478962961147124edd43549aedd1a335e44bf be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 16084da537dd8f84cfdb3055c633772269d62e1b 0 (Thu Jan 01 00:00:00 1970 +0000) {'note': 'adding bar', 'operation': 'amend', 'user': 'test'}
+  112478962961147124edd43549aedd1a335e44bf be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
+  be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 16084da537dd8f84cfdb3055c633772269d62e1b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'note': 'adding bar', 'operation': 'amend', 'user': 'test'}
 #endif
 
 Cannot amend public changeset
--- a/tests/test-bookmarks-pushpull.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-bookmarks-pushpull.t	Mon Dec 04 11:28:29 2017 -0500
@@ -103,12 +103,98 @@
 delete a remote bookmark
 
   $ hg book -d W
-  $ hg push -B W ../a --config "$TESTHOOK"
+  $ hg push -B W ../a --config "$TESTHOOK" --debug --config devel.bundle2.debug=yes
   pushing to ../a
+  query 1; heads
   searching for changes
+  all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   no changes found
+  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output: start emission of HG20 stream
+  bundle2-output: bundle parameter: 
+  bundle2-output: start of parts
+  bundle2-output: bundle part: "replycaps"
+  bundle2-output-part: "replycaps" 185 bytes payload
+  bundle2-output: part 0: "REPLYCAPS"
+  bundle2-output: header chunk size: 16
+  bundle2-output: payload chunk size: 185
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "check:phases"
+  bundle2-output-part: "check:phases" 48 bytes payload
+  bundle2-output: part 1: "CHECK:PHASES"
+  bundle2-output: header chunk size: 19
+  bundle2-output: payload chunk size: 48
+  bundle2-output: closing payload chunk
+  bundle2-output: bundle part: "pushkey"
+  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output: part 2: "PUSHKEY"
+  bundle2-output: header chunk size: 90
+  bundle2-output: closing payload chunk
+  bundle2-output: end of bundle
+  bundle2-input: start processing of HG20 stream
+  bundle2-input: reading bundle2 stream parameters
+  bundle2-input-bundle: with-transaction
+  bundle2-input: start extraction of bundle2 parts
+  bundle2-input: part header size: 16
+  bundle2-input: part type: "REPLYCAPS"
+  bundle2-input: part id: "0"
+  bundle2-input: part parameters: 0
+  bundle2-input: found a handler for part replycaps
+  bundle2-input-part: "replycaps" supported
+  bundle2-input: payload chunk size: 185
+  bundle2-input: payload chunk size: 0
+  bundle2-input-part: total payload size 185
+  bundle2-input: part header size: 19
+  bundle2-input: part type: "CHECK:PHASES"
+  bundle2-input: part id: "1"
+  bundle2-input: part parameters: 0
+  bundle2-input: found a handler for part check:phases
+  bundle2-input-part: "check:phases" supported
+  bundle2-input: payload chunk size: 48
+  bundle2-input: payload chunk size: 0
+  bundle2-input-part: total payload size 48
+  bundle2-input: part header size: 90
+  bundle2-input: part type: "PUSHKEY"
+  bundle2-input: part id: "2"
+  bundle2-input: part parameters: 4
+  bundle2-input: found a handler for part pushkey
+  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
+  pushing key for "bookmarks:W"
+  bundle2-input: payload chunk size: 0
+  bundle2-input: part header size: 0
+  bundle2-input: end of bundle2 stream
+  bundle2-input-bundle: 2 parts total
+  running hook txnclose-bookmark.test: sh $TESTTMP/hook.sh
   test-hook-bookmark: W:  0000000000000000000000000000000000000000 -> 
+  bundle2-output-bundle: "HG20", 1 parts total
+  bundle2-output: start emission of HG20 stream
+  bundle2-output: bundle parameter: 
+  bundle2-output: start of parts
+  bundle2-output: bundle part: "reply:pushkey"
+  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
+  bundle2-output: part 0: "REPLY:PUSHKEY"
+  bundle2-output: header chunk size: 43
+  bundle2-output: closing payload chunk
+  bundle2-output: end of bundle
+  bundle2-input: start processing of HG20 stream
+  bundle2-input: reading bundle2 stream parameters
+  bundle2-input-bundle: no-transaction
+  bundle2-input: start extraction of bundle2 parts
+  bundle2-input: part header size: 43
+  bundle2-input: part type: "REPLY:PUSHKEY"
+  bundle2-input: part id: "0"
+  bundle2-input: part parameters: 2
+  bundle2-input: found a handler for part reply:pushkey
+  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
+  bundle2-input: payload chunk size: 0
+  bundle2-input: part header size: 0
+  bundle2-input: end of bundle2 stream
+  bundle2-input-bundle: 0 parts total
   deleting remote bookmark W
+  listing keys for "phases"
   [1]
 
 export the active bookmark
--- a/tests/test-cat.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-cat.t	Mon Dec 04 11:28:29 2017 -0500
@@ -119,3 +119,13 @@
   $ PATTERN='t4' hg log -r '.' -T "{envvars % '{key} -> {value}\n'}" \
   >                 --config "experimental.exportableenviron=PATTERN"
   PATTERN -> t4
+
+Test behavior of output when directory structure does not already exist
+
+  $ mkdir foo
+  $ echo a > foo/a
+  $ hg add foo/a
+  $ hg commit -qm "add foo/a"
+  $ hg cat --output "output/%p" foo/a
+  $ cat output/foo/a
+  a
--- a/tests/test-check-code.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-check-code.t	Mon Dec 04 11:28:29 2017 -0500
@@ -44,6 +44,7 @@
   .hgignore
   .hgsigs
   .hgtags
+  .jshintrc
   CONTRIBUTING
   CONTRIBUTORS
   COPYING
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-jshint.t	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,12 @@
+#require test-repo jshint hg10
+
+  $ . "$TESTDIR/helpers-testrepo.sh"
+
+run jshint on all tracked files ending in .js except vendored dependencies
+
+  $ cd "`dirname "$TESTDIR"`"
+
+  $ testrepohg locate 'set:**.js' \
+  > -X mercurial/templates/static/excanvas.js \
+  > 2>/dev/null \
+  > | xargs jshint
--- a/tests/test-clonebundles.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-clonebundles.t	Mon Dec 04 11:28:29 2017 -0500
@@ -32,8 +32,8 @@
 
   $ cat server/access.log
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "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$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
 
 Empty manifest file results in retrieval
 (the extension only checks if the manifest file exists)
--- a/tests/test-command-template.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-command-template.t	Mon Dec 04 11:28:29 2017 -0500
@@ -2876,6 +2876,17 @@
   @@ -0,0 +1,1 @@
   +second
 
+ui verbosity:
+
+  $ hg log -l1 -T '{verbosity}\n'
+  
+  $ hg log -l1 -T '{verbosity}\n' --debug
+  debug
+  $ hg log -l1 -T '{verbosity}\n' --quiet
+  quiet
+  $ hg log -l1 -T '{verbosity}\n' --verbose
+  verbose
+
   $ cd ..
 
 
--- a/tests/test-completion.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-completion.t	Mon Dec 04 11:28:29 2017 -0500
@@ -72,6 +72,7 @@
   debugapplystreamclonebundle
   debugbuilddag
   debugbundle
+  debugcapabilities
   debugcheckstate
   debugcolor
   debugcommands
@@ -249,6 +250,7 @@
   debugapplystreamclonebundle: 
   debugbuilddag: mergeable-file, overwritten-file, new-file
   debugbundle: all, part-type, spec
+  debugcapabilities: 
   debugcheckstate: 
   debugcolor: style
   debugcommands: 
--- a/tests/test-contrib-perf.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-contrib-perf.t	Mon Dec 04 11:28:29 2017 -0500
@@ -55,6 +55,8 @@
                  benchmark parsing bookmarks from disk to memory
    perfbranchmap
                  benchmark the update of a branchmap
+   perfbundleread
+                 Benchmark reading of bundle files.
    perfcca       (no help text available)
    perfchangegroupchangelog
                  Benchmark producing a changelog group for a changegroup.
@@ -173,3 +175,7 @@
   $ (testrepohg files -r 1.2 glob:mercurial/*.c glob:mercurial/*.py;
   >  testrepohg files -r tip glob:mercurial/*.c glob:mercurial/*.py) |
   > "$TESTDIR"/check-perf-code.py contrib/perf.py
+  contrib/perf.py:498:
+   >     from mercurial import (
+   import newer module separately in try clause for early Mercurial
+  [1]
--- a/tests/test-debugcommands.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-debugcommands.t	Mon Dec 04 11:28:29 2017 -0500
@@ -77,6 +77,40 @@
    }
   ]
 
+debugdelta chain with sparse read enabled
+
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > sparse-read = True
+  > EOF
+  $ hg debugdeltachain -m
+      rev  chain# chainlen     prev   delta       size    rawsize  chainsize     ratio   lindist extradist extraratio   readsize largestblk rddensity
+        0       1        1       -1    base         44         43         44   1.02326        44         0    0.00000         44         44   1.00000
+
+  $ hg debugdeltachain -m -T '{rev} {chainid} {chainlen} {readsize} {largestblock} {readdensity}\n'
+  0 1 1 44 44 1.0
+
+  $ hg debugdeltachain -m -Tjson
+  [
+   {
+    "chainid": 1,
+    "chainlen": 1,
+    "chainratio": 1.02325581395,
+    "chainsize": 44,
+    "compsize": 44,
+    "deltatype": "base",
+    "extradist": 0,
+    "extraratio": 0.0,
+    "largestblock": 44,
+    "lindist": 44,
+    "prevrev": -1,
+    "readdensity": 1.0,
+    "readsize": 44,
+    "rev": 0,
+    "uncompsize": 43
+   }
+  ]
+
 Test max chain len
   $ cat >> $HGRCPATH << EOF
   > [format]
@@ -156,3 +190,37 @@
   from h hidden in g at:
    debugstacktrace.py:6 in f
    debugstacktrace.py:9 in g
+
+Test debugcapabilities command:
+
+  $ hg debugcapabilities ./debugrevlog/
+  Main capabilities:
+    branchmap
+    $USUAL_BUNDLE2_CAPS$
+    getbundle
+    known
+    lookup
+    pushkey
+    unbundle
+  Bundle2 capabilities:
+    HG20
+    changegroup
+      01
+      02
+    digests
+      md5
+      sha1
+      sha512
+    error
+      abort
+      unsupportedcontent
+      pushraced
+      pushkey
+    hgtagsfnodes
+    listkeys
+    phases
+      heads
+    pushkey
+    remote-changegroup
+      http
+      https
--- a/tests/test-dispatch.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-dispatch.t	Mon Dec 04 11:28:29 2017 -0500
@@ -83,11 +83,12 @@
   $ mkdir -p badrepo/.hg
   $ echo 'invalid-syntax' > badrepo/.hg/hgrc
   $ hg log -b -Rbadrepo default
-  hg: parse error at badrepo/.hg/hgrc:1: invalid-syntax
+  hg: parse error at badrepo/.hg/hgrc:1: invalid-syntax (glob)
   [255]
 
   $ hg log -b --cwd=inexistent default
-  abort: No such file or directory: 'inexistent'
+  abort: No such file or directory: 'inexistent' (no-windows !)
+  abort: The system cannot find the file specified: 'inexistent' (windows !)
   [255]
 
   $ hg log -b '--config=ui.traceback=yes' 2>&1 | grep '^Traceback'
--- a/tests/test-drawdag.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-drawdag.t	Mon Dec 04 11:28:29 2017 -0500
@@ -227,11 +227,11 @@
   o  A 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
   
   $ hg debugobsolete
-  112478962961147124edd43549aedd1a335e44bf 7fb047a69f220c21711122dfd94305a9efb60cba 64a8289d249234b9886244d379f15e6b650b28e3 711f53bbef0bebd12eb6f0511d5e2e998b984846 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'split', 'user': 'test'}
-  26805aba1e600a82e93661149f2313866a221a7b be0ef73c17ade3fc89dc41701eb9fc3a91b58282 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
-  be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
-  64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'prune', 'user': 'test'}
-  58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'prune', 'user': 'test'}
+  112478962961147124edd43549aedd1a335e44bf 7fb047a69f220c21711122dfd94305a9efb60cba 64a8289d249234b9886244d379f15e6b650b28e3 711f53bbef0bebd12eb6f0511d5e2e998b984846 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'split', 'user': 'test'}
+  26805aba1e600a82e93661149f2313866a221a7b be0ef73c17ade3fc89dc41701eb9fc3a91b58282 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'replace', 'user': 'test'}
+  be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'replace', 'user': 'test'}
+  64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'prune', 'user': 'test'}
+  58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'prune', 'user': 'test'}
 
 Change file contents via comments
 
--- a/tests/test-getbundle.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-getbundle.t	Mon Dec 04 11:28:29 2017 -0500
@@ -264,9 +264,9 @@
 
   $ cat access.log
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=700b7e19db54103633c4bf4a6a6b6d55f4d50c03+d5f6e1ea452285324836a49d7d3c2a63cfed1d31&heads=13c0170174366b441dc68e8e33757232fa744458+bac16991d12ff45f9dc43c52da1946dfadb83e80 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=700b7e19db54103633c4bf4a6a6b6d55f4d50c03+d5f6e1ea452285324836a49d7d3c2a63cfed1d31&heads=13c0170174366b441dc68e8e33757232fa744458+bac16991d12ff45f9dc43c52da1946dfadb83e80 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
 
   $ cat error.log
 
--- a/tests/test-globalopts.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-globalopts.t	Mon Dec 04 11:28:29 2017 -0500
@@ -355,6 +355,7 @@
    environment   Environment Variables
    extensions    Using Additional Features
    filesets      Specifying File Sets
+   flags         Command-line flags
    glossary      Glossary
    hgignore      Syntax for Mercurial Ignore Files
    hgweb         Configuring hgweb
@@ -439,6 +440,7 @@
    environment   Environment Variables
    extensions    Using Additional Features
    filesets      Specifying File Sets
+   flags         Command-line flags
    glossary      Glossary
    hgignore      Syntax for Mercurial Ignore Files
    hgweb         Configuring hgweb
--- a/tests/test-help.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-help.t	Mon Dec 04 11:28:29 2017 -0500
@@ -110,6 +110,7 @@
    environment   Environment Variables
    extensions    Using Additional Features
    filesets      Specifying File Sets
+   flags         Command-line flags
    glossary      Glossary
    hgignore      Syntax for Mercurial Ignore Files
    hgweb         Configuring hgweb
@@ -188,6 +189,7 @@
    environment   Environment Variables
    extensions    Using Additional Features
    filesets      Specifying File Sets
+   flags         Command-line flags
    glossary      Glossary
    hgignore      Syntax for Mercurial Ignore Files
    hgweb         Configuring hgweb
@@ -865,6 +867,7 @@
    environment   Environment Variables
    extensions    Using Additional Features
    filesets      Specifying File Sets
+   flags         Command-line flags
    glossary      Glossary
    hgignore      Syntax for Mercurial Ignore Files
    hgweb         Configuring hgweb
@@ -895,6 +898,8 @@
                  builds a repo with a given DAG from scratch in the current
                  empty repo
    debugbundle   lists the contents of a bundle
+   debugcapabilities
+                 lists the capabilities of a remote peer
    debugcheckstate
                  validate the correctness of the current dirstate
    debugcolor    show available color, effects or style
@@ -2011,6 +2016,13 @@
   Specifying File Sets
   </td></tr>
   <tr><td>
+  <a href="/help/flags">
+  flags
+  </a>
+  </td><td>
+  Command-line flags
+  </td></tr>
+  <tr><td>
   <a href="/help/glossary">
   glossary
   </a>
--- a/tests/test-hgweb-commands.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb-commands.t	Mon Dec 04 11:28:29 2017 -0500
@@ -775,7 +775,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/cad8025a2e87">branch commit with null character: </a>
-     <span class="branchhead">unstable</span> <span class="tag">tip</span> <span class="tag">something</span> 
+     <span class="phase">draft</span> <span class="branchhead">unstable</span> <span class="tag">tip</span> <span class="tag">something</span> 
     </td>
    </tr>
    <tr>
@@ -783,7 +783,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/1d22e65f027e">branch</a>
-     <span class="branchhead">stable</span> 
+     <span class="phase">draft</span> <span class="branchhead">stable</span> 
     </td>
    </tr>
    <tr>
@@ -791,7 +791,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/a4f92ed23982">Added tag 1.0 for changeset 2ef0ac749a14</a>
-     <span class="branchhead">default</span> 
+     <span class="phase">draft</span> <span class="branchhead">default</span> 
     </td>
    </tr>
    <tr>
@@ -799,7 +799,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/2ef0ac749a14">base</a>
-     <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
+     <span class="phase">draft</span> <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
     </td>
    </tr>
   
@@ -880,7 +880,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    changeset 0:<a href="/rev/2ef0ac749a14">2ef0ac749a14</a>
-   <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
+   <span class="phase">draft</span> <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
   </h3>
   
   
@@ -1054,7 +1054,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/2ef0ac749a14">base</a>
-     <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
+     <span class="phase">draft</span> <span class="tag">1.0</span> <span class="tag">anotherthing</span> 
     </td>
    </tr>
   
@@ -1312,7 +1312,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    view foo @ 1:<a href="/rev/a4f92ed23982">a4f92ed23982</a>
-   
+   <span class="phase">draft</span> <span class="branchhead">default</span> 
   </h3>
   
   
@@ -1446,7 +1446,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    view foo @ 2:<a href="/rev/1d22e65f027e">1d22e65f027e</a>
-   <span class="branchname">stable</span> 
+   <span class="phase">draft</span> <span class="branchhead">stable</span> 
   </h3>
   
   
@@ -1572,7 +1572,7 @@
   <table cellspacing="0">
   <tr><td>description</td><td>unknown</td></tr>
   <tr><td>owner</td><td>&#70;&#111;&#111;&#32;&#66;&#97;&#114;&#32;&#60;&#102;&#111;&#111;&#46;&#98;&#97;&#114;&#64;&#101;&#120;&#97;&#109;&#112;&#108;&#101;&#46;&#99;&#111;&#109;&#62;</td></tr>
-  <tr><td>last change</td><td>Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
+  <tr><td>last change</td><td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td></tr>
   </table>
   
   <div><a  class="title" href="/shortlog?style=gitweb">changes</a></div>
@@ -1584,7 +1584,7 @@
   <td>
   <a class="list" href="/rev/cad8025a2e87?style=gitweb">
   <b>branch commit with null character: </b>
-  <span class="logtags"><span class="branchtag" title="unstable">unstable</span> <span class="tagtag" title="tip">tip</span> <span class="bookmarktag" title="something">something</span> </span>
+  <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="branchtag" title="unstable">unstable</span> <span class="tagtag" title="tip">tip</span> <span class="bookmarktag" title="something">something</span> </span>
   </a>
   </td>
   <td class="link" nowrap>
@@ -1598,7 +1598,7 @@
   <td>
   <a class="list" href="/rev/1d22e65f027e?style=gitweb">
   <b>branch</b>
-  <span class="logtags"><span class="branchtag" title="stable">stable</span> </span>
+  <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="branchtag" title="stable">stable</span> </span>
   </a>
   </td>
   <td class="link" nowrap>
@@ -1612,7 +1612,7 @@
   <td>
   <a class="list" href="/rev/a4f92ed23982?style=gitweb">
   <b>Added tag 1.0 for changeset 2ef0ac749a14</b>
-  <span class="logtags"><span class="branchtag" title="default">default</span> </span>
+  <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="branchtag" title="default">default</span> </span>
   </a>
   </td>
   <td class="link" nowrap>
@@ -1626,7 +1626,7 @@
   <td>
   <a class="list" href="/rev/2ef0ac749a14?style=gitweb">
   <b>base</b>
-  <span class="logtags"><span class="tagtag" title="1.0">1.0</span> <span class="bookmarktag" title="anotherthing">anotherthing</span> </span>
+  <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="tagtag" title="1.0">1.0</span> <span class="bookmarktag" title="anotherthing">anotherthing</span> </span>
   </a>
   </td>
   <td class="link" nowrap>
@@ -1792,7 +1792,7 @@
   var graph = new Graph();
   graph.scale(39);
   
-  graph.vertex = function(x, y, color, parity, cur) {
+  graph.vertex = function(x, y, radius, color, parity, cur) {
   	
   	this.ctx.beginPath();
   	color = this.setColor(color, 0.25, 0.75);
@@ -1926,7 +1926,7 @@
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*zlib (glob)
+  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch $USUAL_BUNDLE2_CAPS$ unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=zstd,zlib
 
 heads
 
@@ -2093,8 +2093,6 @@
   ul#graphnodes li .info {
   	display: block;
   	font-size: 70%;
-  	position: relative;
-  	top: -1px;
   }
 
 Stop and restart the server at the directory different from the repository
@@ -2126,27 +2124,6 @@
   
   
 
-Stop and restart with HGENCODING=cp932 and preferuncompressed
-
-  $ killdaemons.py
-  $ HGENCODING=cp932 hg serve --config server.preferuncompressed=True -n test \
-  >     -p $HGPORT -d --pid-file=hg.pid -E errors.log
-  $ cat hg.pid >> $DAEMON_PIDS
-
-commit message with Japanese Kanji 'Noh', which ends with '\x5c'
-
-  $ echo foo >> foo
-  $ HGENCODING=cp932 hg ci -m `$PYTHON -c 'print("\x94\x5c")'`
-
-Graph json escape of multibyte character
-
-  $ get-with-headers.py $LOCALIP:$HGPORT 'graph/' > out
-  >>> from __future__ import print_function
-  >>> for line in open("out"):
-  ...     if line.startswith("var data ="):
-  ...         print(line, end='')
-  var data = [["061dd13ba3c3", [0, 1], [[0, 0, 1, -1, ""]], "\u80fd", "test", "1970-01-01", ["unstable", true], ["tip"], ["something"]], ["cad8025a2e87", [0, 1], [[0, 0, 1, 3, "FF0000"]], "branch commit with null character: \u0000", "test", "1970-01-01", ["unstable", false], [], []], ["1d22e65f027e", [0, 1], [[0, 0, 1, 3, ""]], "branch", "test", "1970-01-01", ["stable", true], [], []], ["a4f92ed23982", [0, 1], [[0, 0, 1, 3, ""]], "Added tag 1.0 for changeset 2ef0ac749a14", "test", "1970-01-01", ["default", true], [], []], ["2ef0ac749a14", [0, 1], [], "base", "test", "1970-01-01", ["default", false], ["1.0"], ["anotherthing"]]];
-
 capabilities
 
 (plain version to check the format)
@@ -2174,7 +2151,7 @@
   batch
   stream-preferred
   streamreqs=generaldelta,revlogv1
-  bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
+  $USUAL_BUNDLE2_CAPS$
   unbundle=HG10GZ,HG10BZ,HG10UN
   httpheader=1024
   httpmediatype=0.1rx,0.1tx,0.2tx
--- a/tests/test-hgweb-descend-empties.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb-descend-empties.t	Mon Dec 04 11:28:29 2017 -0500
@@ -73,7 +73,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    directory / @ 0:<a href="/rev/c9f45f7a1659">c9f45f7a1659</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
@@ -193,7 +193,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    directory / @ 0:<a href="/rev/c9f45f7a1659?style=coal">c9f45f7a1659</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
@@ -317,7 +317,7 @@
       </ul>
   
       <h2 class="no-link no-border">files</h2>
-      <p class="files">/ <span class="logtags"><span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></p>
+      <p class="files">/ <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></p>
   
       <table>
           <tr class="parity0">
@@ -431,7 +431,7 @@
   </div>
   </div>
   
-  <div class="title">/ <span class="logtags"><span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></div>
+  <div class="title">/ <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></div>
   <table cellspacing="0">
   <tr class="parity0">
   <td style="font-family:monospace">drwxr-xr-x</td>
--- a/tests/test-hgweb-diffs.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb-diffs.t	Mon Dec 04 11:28:29 2017 -0500
@@ -81,7 +81,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    changeset 0:<a href="/rev/0cd96de13884">0cd96de13884</a>
-   
+   <span class="phase">draft</span> 
   </h3>
   
   
@@ -254,7 +254,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    diff b @ 1:<a href="/rev/559edbd9ed20">559edbd9ed20</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
@@ -359,7 +359,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    changeset 0:<a href="/rev/0cd96de13884">0cd96de13884</a>
-   
+   <span class="phase">draft</span> 
   </h3>
   
   
@@ -536,7 +536,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    diff a @ 1:<a href="/rev/559edbd9ed20">559edbd9ed20</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
@@ -642,7 +642,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    comparison a @ 0:<a href="/rev/0cd96de13884">0cd96de13884</a>
-   
+   <span class="phase">draft</span> 
   </h3>
   
   
@@ -772,7 +772,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    comparison a @ 2:<a href="/rev/d73db4d812ff">d73db4d812ff</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
@@ -904,7 +904,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    comparison a @ 3:<a href="/rev/20e80271eb7a">20e80271eb7a</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
@@ -1042,7 +1042,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    comparison e @ 5:<a href="/rev/41d9fc4a6ae1">41d9fc4a6ae1</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
--- a/tests/test-hgweb-empty.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb-empty.t	Mon Dec 04 11:28:29 2017 -0500
@@ -306,7 +306,7 @@
   var graph = new Graph();
   graph.scale(39);
   
-  graph.vertex = function(x, y, color, parity, cur) {
+  graph.vertex = function(x, y, radius, color, parity, cur) {
   	
   	this.ctx.beginPath();
   	color = this.setColor(color, 0.25, 0.75);
--- a/tests/test-hgweb-filelog.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb-filelog.t	Mon Dec 04 11:28:29 2017 -0500
@@ -189,7 +189,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
-   <span class="branchname">a-branch</span> 
+   <span class="phase">draft</span> <span class="branchname">a-branch</span> 
    
   </h3>
   
@@ -220,7 +220,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/3f41bc784e7e">second a</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    
@@ -229,7 +229,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/5ed941583260">first a</a>
-     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+     <span class="phase">draft</span> <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
     </td>
    </tr>
    
@@ -312,7 +312,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
-   <span class="branchname">a-branch</span> 
+   <span class="phase">draft</span> <span class="branchname">a-branch</span> 
    
   </h3>
   
@@ -343,7 +343,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/3f41bc784e7e">second a</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    
@@ -352,7 +352,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/5ed941583260">first a</a>
-     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+     <span class="phase">draft</span> <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
     </td>
    </tr>
    
@@ -435,7 +435,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
-   <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+   <span class="phase">draft</span> <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
    
   </h3>
   
@@ -466,7 +466,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/5ed941583260">first a</a>
-     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+     <span class="phase">draft</span> <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
     </td>
    </tr>
    
@@ -549,7 +549,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
-   <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+   <span class="phase">draft</span> <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
    
   </h3>
   
@@ -580,7 +580,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/5ed941583260">first a</a>
-     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+     <span class="phase">draft</span> <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
     </td>
    </tr>
    
@@ -740,8 +740,8 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log c @ 7:<a href="/rev/46c1a66bd8fc">46c1a66bd8fc</a>
-   <span class="branchname">a-branch</span> <span class="tag">tip</span> 
-    (following lines 1:2 <a href="/log/tip/c">back to filelog</a>)
+   <span class="phase">draft</span> <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
+    (following lines 1:2 <a href="/log/tip/c">all revisions for this file</a>)
   </h3>
   
   
@@ -771,7 +771,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/46c1a66bd8fc">change c</a>
-     <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
+     <span class="phase">draft</span> <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
     </td>
    </tr>
    
@@ -780,7 +780,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/6563da9dcf87">b</a>
-     
+     <span class="phase">draft</span> 
     </td>
    </tr>
    
@@ -860,8 +860,8 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log c @ 7:<a href="/rev/46c1a66bd8fc?revcount=1">46c1a66bd8fc</a>
-   <span class="branchname">a-branch</span> <span class="tag">tip</span> 
-    (following lines 1:2 <a href="/log/tip/c?revcount=1">back to filelog</a>)
+   <span class="phase">draft</span> <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
+    (following lines 1:2 <a href="/log/tip/c?revcount=1">all revisions for this file</a>)
   </h3>
   
   
@@ -891,7 +891,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/46c1a66bd8fc?revcount=1">change c</a>
-     <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
+     <span class="phase">draft</span> <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
     </td>
    </tr>
    
@@ -1097,7 +1097,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
-   <span class="branchname">a-branch</span> 
+   <span class="phase">draft</span> <span class="branchname">a-branch</span> 
    
   </h3>
   
@@ -1128,7 +1128,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/3f41bc784e7e">second a</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
@@ -1141,7 +1141,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/5ed941583260">first a</a>
-     <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
+     <span class="phase">draft</span> <span class="tag">a-tag</span> <span class="tag">a-bookmark</span> 
     </td>
    </tr>
    <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
@@ -1379,8 +1379,8 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log c @ 12:<a href="/rev/6e4182052f7b">6e4182052f7b</a>
-   <span class="branchname">a-branch</span> <span class="tag">tip</span> 
-    (following lines 3:4 <a href="/log/tip/c">back to filelog</a>)
+   <span class="phase">draft</span> <span class="branchhead">a-branch</span> <span class="tag">tip</span> 
+    (following lines 3:4 <a href="/log/tip/c">all revisions for this file</a>)
   </h3>
   
   
@@ -1410,7 +1410,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/fb9bc322513a">touching beginning and end of c</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
@@ -1429,7 +1429,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/e95928d60479">touch beginning of c</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
@@ -1449,7 +1449,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/5c6574614c37">make c bigger and touch its beginning</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
@@ -1473,7 +1473,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/46c1a66bd8fc">change c</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
@@ -1487,7 +1487,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/6563da9dcf87">b</a>
-     
+     <span class="phase">draft</span> 
     </td>
    </tr>
    <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
@@ -1636,8 +1636,8 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    log c @ 8:<a href="/rev/5c6574614c37">5c6574614c37</a>
-   <span class="branchname">a-branch</span> 
-    (following lines 3:4, descending <a href="/log/8/c">back to filelog</a>)
+   <span class="phase">draft</span> <span class="branchname">a-branch</span> 
+    (following lines 3:4, descending <a href="/log/8/c">all revisions for this file</a>)
   </h3>
   
   
@@ -1667,7 +1667,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/5c6574614c37">make c bigger and touch its beginning</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    
@@ -1676,7 +1676,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/e95928d60479">touch beginning of c</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    
@@ -1685,7 +1685,7 @@
     <td class="author">test</td>
     <td class="description">
      <a href="/rev/fb9bc322513a">touching beginning and end of c</a>
-     <span class="branchname">a-branch</span> 
+     <span class="phase">draft</span> <span class="branchname">a-branch</span> 
     </td>
    </tr>
    
--- a/tests/test-hgweb-json.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb-json.t	Mon Dec 04 11:28:29 2017 -0500
@@ -1581,6 +1581,10 @@
         "topic": "filesets"
       },
       {
+        "summary": "Command-line flags",
+        "topic": "flags"
+      },
+      {
         "summary": "Glossary",
         "topic": "glossary"
       },
@@ -1644,3 +1648,28 @@
     "rawdoc": "Working with Phases\n*", (glob)
     "topic": "phases"
   }
+
+Commit message with Japanese Kanji 'Noh', which ends with '\x5c'
+
+  $ echo foo >> da/foo
+  $ HGENCODING=cp932 hg ci -m `$PYTHON -c 'print("\x94\x5c")'`
+
+Commit message with null character
+
+  $ echo foo >> da/foo
+  >>> open('msg', 'wb').write('commit with null character: \0\n')
+  $ hg ci -l msg
+  $ rm msg
+
+Stop and restart with HGENCODING=cp932
+
+  $ killdaemons.py
+  $ HGENCODING=cp932 hg serve -p $HGPORT -d --pid-file=hg.pid \
+  > -A access.log -E error.log
+  $ cat hg.pid >> $DAEMON_PIDS
+
+Test json escape of multibyte characters
+
+  $ request json-filelog/tip/da/foo?revcount=2 | grep '"desc":'
+        "desc": "commit with null character: \u0000",
+        "desc": "\u80fd",
--- a/tests/test-hgweb-removed.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb-removed.t	Mon Dec 04 11:28:29 2017 -0500
@@ -62,7 +62,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    changeset 1:<a href="/rev/c78f6c5cbea9">c78f6c5cbea9</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
@@ -190,7 +190,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    diff a @ 1:<a href="/rev/c78f6c5cbea9">c78f6c5cbea9</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
--- a/tests/test-hgweb-symrev.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb-symrev.t	Mon Dec 04 11:28:29 2017 -0500
@@ -468,11 +468,11 @@
   <a href="/graph/tip?style=gitweb">graph</a> |
   <a href="/file/tip?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
   <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
-  <a class="title" href="/rev/9d8c40cba617?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>third<span class="logtags"> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></a>
+   <a class="title" href="/rev/9d8c40cba617?style=gitweb">
   <a href="/rev/9d8c40cba617?style=gitweb">changeset</a><br/>
-  <a class="title" href="/rev/a7c1559b7bba?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a>
+   <a class="title" href="/rev/a7c1559b7bba?style=gitweb">
   <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a><br/>
-  <a class="title" href="/rev/43c799df6e75?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>first<span class="logtags"> </span></a>
+   <a class="title" href="/rev/43c799df6e75?style=gitweb">
   <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
   <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
 
@@ -518,11 +518,11 @@
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=gitweb&rev=all()' | egrep $REVLINKS
   <a href="/file?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a> 
-  <a class="title" href="/rev/9d8c40cba617?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>third<span class="logtags"> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></a>
+   <a class="title" href="/rev/9d8c40cba617?style=gitweb">
   <a href="/rev/9d8c40cba617?style=gitweb">changeset</a><br/>
-  <a class="title" href="/rev/a7c1559b7bba?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a>
+   <a class="title" href="/rev/a7c1559b7bba?style=gitweb">
   <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a><br/>
-  <a class="title" href="/rev/43c799df6e75?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>first<span class="logtags"> </span></a>
+   <a class="title" href="/rev/43c799df6e75?style=gitweb">
   <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=gitweb' | egrep $REVLINKS
@@ -531,7 +531,7 @@
   <a href="/graph/xyzzy?style=gitweb">graph</a> |
   <a href="/file/xyzzy?style=gitweb">files</a> |
   <a href="/raw-rev/xyzzy">raw</a>  | <a href="/archive/xyzzy.zip">zip</a>  |
-  <a class="title" href="/raw-rev/a7c1559b7bba">second <span class="logtags"><span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a>
+   <a class="title" href="/raw-rev/a7c1559b7bba">
    <td style="font-family:monospace"><a class="list" href="/rev/a7c1559b7bba?style=gitweb">a7c1559b7bba</a></td>
   <a class="list" href="/rev/43c799df6e75?style=gitweb">43c799df6e75</a>
   <a class="list" href="/rev/9d8c40cba617?style=gitweb">9d8c40cba617</a>
@@ -560,9 +560,9 @@
   <a href="/graph/xyzzy?style=gitweb">graph</a> |
   <a href="/file/xyzzy?style=gitweb">files</a> | <a href="/archive/xyzzy.zip">zip</a>  |
   <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
-  <a class="title" href="/rev/a7c1559b7bba?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a>
+   <a class="title" href="/rev/a7c1559b7bba?style=gitweb">
   <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a><br/>
-  <a class="title" href="/rev/43c799df6e75?style=gitweb"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>first<span class="logtags"> </span></a>
+   <a class="title" href="/rev/43c799df6e75?style=gitweb">
   <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
   <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
 
@@ -709,9 +709,9 @@
               <li><a href="/graph/tip?style=monoblue">graph</a></li>
               <li><a href="/file/tip?style=monoblue">files</a></li>
               <li><a href="/archive/tip.zip">zip</a></li>
-      <h3 class="changelog"><a class="title" href="/rev/9d8c40cba617?style=monoblue">third<span class="logtags"> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></a></h3>
-  <h3 class="changelog"><a class="title" href="/rev/a7c1559b7bba?style=monoblue">second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a></h3>
-  <h3 class="changelog"><a class="title" href="/rev/43c799df6e75?style=monoblue">first<span class="logtags"> </span></a></h3>
+      <a class="title" href="/rev/9d8c40cba617?style=monoblue">
+      <a class="title" href="/rev/a7c1559b7bba?style=monoblue">
+      <a class="title" href="/rev/43c799df6e75?style=monoblue">
   <a href="/log/43c799df6e75?style=monoblue">(0)</a>  <a href="/log/tip?style=monoblue">tip</a> 
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=monoblue' | egrep $REVLINKS
@@ -753,16 +753,16 @@
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=monoblue&rev=all()' | egrep $REVLINKS
               <li><a href="/archive/tip.zip">zip</a></li>
-      <h3 class="changelog"><a class="title" href="/rev/9d8c40cba617?style=monoblue">third<span class="logtags"> <span class="branchtag" title="default">default</span> <span class="tagtag" title="tip">tip</span> </span></a></h3>
-  <h3 class="changelog"><a class="title" href="/rev/a7c1559b7bba?style=monoblue">second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a></h3>
-  <h3 class="changelog"><a class="title" href="/rev/43c799df6e75?style=monoblue">first<span class="logtags"> </span></a></h3>
+      <a class="title" href="/rev/9d8c40cba617?style=monoblue">
+      <a class="title" href="/rev/a7c1559b7bba?style=monoblue">
+      <a class="title" href="/rev/43c799df6e75?style=monoblue">
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=monoblue' | egrep $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
           <li><a href="/raw-rev/xyzzy">raw</a></li>
           <li><a href="/archive/xyzzy.zip">zip</a></li>
-      <h3 class="changeset"><a href="/raw-rev/a7c1559b7bba">second <span class="logtags"><span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a></h3>
+          <a href="/raw-rev/a7c1559b7bba">
           <dd><a href="/rev/a7c1559b7bba?style=monoblue">a7c1559b7bba</a></dd>
   <dd><a href="/rev/43c799df6e75?style=monoblue">43c799df6e75</a></dd>
   <dd><a href="/rev/9d8c40cba617?style=monoblue">9d8c40cba617</a></dd>
@@ -789,8 +789,8 @@
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
               <li><a href="/archive/xyzzy.zip">zip</a></li>
-      <h3 class="changelog"><a class="title" href="/rev/a7c1559b7bba?style=monoblue">second<span class="logtags"> <span class="bookmarktag" title="xyzzy">xyzzy</span> </span></a></h3>
-  <h3 class="changelog"><a class="title" href="/rev/43c799df6e75?style=monoblue">first<span class="logtags"> </span></a></h3>
+      <a class="title" href="/rev/a7c1559b7bba?style=monoblue">
+      <a class="title" href="/rev/43c799df6e75?style=monoblue">
   <a href="/log/43c799df6e75?style=monoblue">(0)</a>  <a href="/log/tip?style=monoblue">tip</a> 
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=monoblue' | egrep $REVLINKS
--- a/tests/test-hgweb.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-hgweb.t	Mon Dec 04 11:28:29 2017 -0500
@@ -267,7 +267,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    directory / @ 0:<a href="/rev/2ef0ac749a14">2ef0ac749a14</a>
-   <span class="tag">tip</span> <span class="tag">@</span> <span class="tag">a b c</span> <span class="tag">d/e/f</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> <span class="tag">@</span> <span class="tag">a b c</span> <span class="tag">d/e/f</span> 
   </h3>
   
   
@@ -340,7 +340,7 @@
 
   $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
   200 Script output follows
-  content-length: 9066
+  content-length: 9152
   content-type: text/css
   
   body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
@@ -406,8 +406,6 @@
   }
   td.indexlinks a:hover { background-color: #6666aa; }
   div.pre { font-family:monospace; font-size:12px; white-space:pre; }
-  div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
-  div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
   
   .search {
       margin-right: 8px;
@@ -467,6 +465,18 @@
   	background-color: #ffaaff;
   	border-color: #ffccff #ff00ee #ff00ee #ffccff;
   }
+  span.logtags span.phasetag {
+  	background-color: #dfafff;
+  	border-color: #e2b8ff #ce48ff #ce48ff #e2b8ff;
+  }
+  span.logtags span.obsoletetag {
+  	background-color: #dddddd;
+  	border-color: #e4e4e4 #a3a3a3 #a3a3a3 #e4e4e4;
+  }
+  span.logtags span.instabilitytag {
+  	background-color: #ffb1c0;
+  	border-color: #ffbbc8 #ff4476 #ff4476 #ffbbc8;
+  }
   span.logtags span.tagtag {
   	background-color: #ffffaa;
   	border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
@@ -536,10 +546,9 @@
   }
   
   div#followlines {
-    background-color: #B7B7B7;
-    border: 1px solid #CCC;
-    border-radius: 5px;
-    padding: 4px;
+    background-color: #FFF;
+    border: 1px solid #d9d8d1;
+    padding: 5px;
     position: fixed;
   }
   
@@ -660,8 +669,6 @@
   ul#graphnodes li .info {
   	display: block;
   	font-size: 100%;
-  	position: relative;
-  	top: -3px;
   	font-style: italic;
   }
   
--- a/tests/test-highlight.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-highlight.t	Mon Dec 04 11:28:29 2017 -0500
@@ -113,7 +113,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    view primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
@@ -252,7 +252,7 @@
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
    annotate primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
-   <span class="tag">tip</span> 
+   <span class="phase">draft</span> <span class="branchhead">default</span> <span class="tag">tip</span> 
   </h3>
   
   
--- a/tests/test-histedit-obsolete.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-histedit-obsolete.t	Mon Dec 04 11:28:29 2017 -0500
@@ -51,9 +51,9 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
+  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
+  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
 
 With some node gone missing during the edit.
 
@@ -80,13 +80,13 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
+  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
+  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '12', 'operation': 'histedit', 'user': 'test'}
+  76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
+  46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '5', 'operation': 'histedit', 'user': 'test'}
   $ cd ..
 
 Base setup for the rest of the testing
@@ -170,13 +170,13 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
-  b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
+  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
+  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'histedit', 'user': 'test'}
+  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'histedit', 'user': 'test'}
+  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
+  96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
+  b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'histedit', 'user': 'test'}
 
 
 Ensure hidden revision does not prevent histedit
@@ -575,4 +575,4 @@
   o  0:cb9a9f314b8b (public) a
   
   $ hg debugobsolete --rev .
-  ee118ab9fa44ebb86be85996548b5517a39e5093 175d6b286a224c23f192e79a581ce83131a53fa2 0 (*) {'operation': 'histedit', 'user': 'test'} (glob)
+  ee118ab9fa44ebb86be85996548b5517a39e5093 175d6b286a224c23f192e79a581ce83131a53fa2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'histedit', 'user': 'test'}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-templates.t	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,54 @@
+Testing templating for histedit command
+
+Setup
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > histedit=
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ for ch in a b c d; do echo foo > $ch; hg commit -Aqm "Added "$ch; done
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  3:62615734edd5 Added d
+  |
+  o  2:28ad74487de9 Added c
+  |
+  o  1:29becc82797a Added b
+  |
+  o  0:18d04c59bb5d Added a
+  
+Getting the JSON output for nodechanges
+
+  $ hg histedit -Tjson --commands - 2>&1 <<EOF
+  > pick 28ad74487de9 Added c
+  > pick 62615734edd5 Added d
+  > pick 18d04c59bb5d Added a
+  > pick 29becc82797a Added b
+  > EOF
+  [
+   {
+    "nodechanges": {"18d04c59bb5d2d4090ad9a5b59bd6274adb63add": ["109f8ec895447f81b380ba8d4d8b66539ccdcb94"], "28ad74487de9599d00d81085be739c61fc340652": ["bff9e07c1807942b161dab768aa793b48e9a7f9d"], "29becc82797a4bc11ec8880b58eaecd2ab3e7760": ["f5dcf3b4db23f31f1aacf46c33d1393de303d26f"], "62615734edd52f06b6fb9c2beb429e4fe30d57b8": ["201423b441c84d9e6858daed653e0d22485c1cfa"]}
+   }
+  ]
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  7:f5dcf3b4db23 Added b
+  |
+  o  6:109f8ec89544 Added a
+  |
+  o  5:201423b441c8 Added d
+  |
+  o  4:bff9e07c1807 Added c
+  
+  $ hg histedit -T "{nodechanges|json}" --commands - 2>&1 <<EOF
+  > pick bff9e07c1807 Added c
+  > pick 201423b441c8 Added d
+  > pick 109f8ec89544 Added a
+  > roll f5dcf3b4db23 Added b
+  > EOF
+  {"109f8ec895447f81b380ba8d4d8b66539ccdcb94": ["8d01470bfeab64d3de13c49adb79d88790d38396"], "f3ec56a374bdbdf1953cacca505161442c6f3a3e": [], "f5dcf3b4db23f31f1aacf46c33d1393de303d26f": ["8d01470bfeab64d3de13c49adb79d88790d38396"]} (no-eol)
--- a/tests/test-http-bad-server.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-http-bad-server.t	Mon Dec 04 11:28:29 2017 -0500
@@ -130,7 +130,7 @@
   readline(184 from -1) -> (27) Accept-Encoding: identity\r\n
   readline(157 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(128 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
-  readline(87 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(87 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(39 from -1) -> (35) accept: application/mercurial-0.1\r\n
   readline(4 from -1) -> (4) host
   read limit reached; closing socket
@@ -172,7 +172,7 @@
   readline(266 from -1) -> (27) Accept-Encoding: identity\r\n
   readline(239 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(210 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
-  readline(169 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(169 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(121 from -1) -> (35) accept: application/mercurial-0.1\r\n
   readline(86 from -1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(6? from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n (glob)
@@ -231,7 +231,7 @@
   readline(261 from -1) -> (41) content-type: application/mercurial-0.1\r\n
   readline(220 from -1) -> (19) vary: X-HgProto-1\r\n
   readline(201 from -1) -> (19) x-hgargs-post: 28\r\n
-  readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(134 from -1) -> (35) accept: application/mercurial-0.1\r\n
   readline(99 from -1) -> (20) content-length: 28\r\n
   readline(79 from -1) -> (2?) host: localhost:$HGPORT\r\n (glob)
@@ -334,7 +334,7 @@
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
-  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(-1) -> (35) accept: application/mercurial-0.1\r\n
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
@@ -382,7 +382,7 @@
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
-  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(-1) -> (35) accept: application/mercurial-0.1\r\n
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
@@ -434,7 +434,7 @@
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
-  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(-1) -> (35) accept: application/mercurial-0.1\r\n
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
@@ -450,7 +450,7 @@
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
-  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(-1) -> (35) accept: application/mercurial-0.1\r\n
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
@@ -495,7 +495,7 @@
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
-  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(-1) -> (35) accept: application/mercurial-0.1\r\n
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
@@ -511,7 +511,7 @@
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
-  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(-1) -> (35) accept: application/mercurial-0.1\r\n
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
@@ -558,7 +558,7 @@
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
-  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(-1) -> (35) accept: application/mercurial-0.1\r\n
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
@@ -574,7 +574,7 @@
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
   readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
-  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=$USUAL_COMPRESSIONS$\r\n
   readline(-1) -> (35) accept: application/mercurial-0.1\r\n
   readline(-1) -> (2?) host: localhost:$HGPORT\r\n (glob)
   readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
--- a/tests/test-http-bundle1.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-http-bundle1.t	Mon Dec 04 11:28:29 2017 -0500
@@ -265,66 +265,66 @@
 
   $ sed 's/.*] "/"/' < ../access.log
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "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$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "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$
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524* (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
 
   $ cd ..
 
--- a/tests/test-http-proxy.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-http-proxy.t	Mon Dec 04 11:28:29 2017 -0500
@@ -107,19 +107,19 @@
   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)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=branchmap HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "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=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=branchmap HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (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$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  * - - [*] "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=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (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$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  * - - [*] "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=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (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$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  * - - [*] "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=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (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$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
-  * - - [*] "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=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (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$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
--- a/tests/test-http.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-http.t	Mon Dec 04 11:28:29 2017 -0500
@@ -256,63 +256,63 @@
 
   $ sed 's/.*] "/"/' < ../access.log
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "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$
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "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$
+  "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
 
   $ cd ..
 
--- a/tests/test-import.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-import.t	Mon Dec 04 11:28:29 2017 -0500
@@ -1349,6 +1349,93 @@
 
   $ cd ..
 
+commit message that looks like a diff header (issue1879)
+
+  $ hg init headerlikemsg
+  $ cd headerlikemsg
+  $ touch empty
+  $ echo nonempty >> nonempty
+  $ hg ci -qAl - <<EOF
+  > blah blah
+  > diff blah
+  > blah blah
+  > EOF
+  $ hg --config diff.git=1 log -pv
+  changeset:   0:c6ef204ef767
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  files:       empty nonempty
+  description:
+  blah blah
+  diff blah
+  blah blah
+  
+  
+  diff --git a/empty b/empty
+  new file mode 100644
+  diff --git a/nonempty b/nonempty
+  new file mode 100644
+  --- /dev/null
+  +++ b/nonempty
+  @@ -0,0 +1,1 @@
+  +nonempty
+  
+
+ (without --git, empty file is lost, but commit message should be preserved)
+
+  $ hg init plain
+  $ hg export 0 | hg -R plain import -
+  applying patch from stdin
+  $ hg --config diff.git=1 -R plain log -pv
+  changeset:   0:60a2d231e71f
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  files:       nonempty
+  description:
+  blah blah
+  diff blah
+  blah blah
+  
+  
+  diff --git a/nonempty b/nonempty
+  new file mode 100644
+  --- /dev/null
+  +++ b/nonempty
+  @@ -0,0 +1,1 @@
+  +nonempty
+  
+
+ (with --git, patch contents should be fully preserved)
+
+  $ hg init git
+  $ hg --config diff.git=1 export 0 | hg -R git import -
+  applying patch from stdin
+  $ hg --config diff.git=1 -R git log -pv
+  changeset:   0:c6ef204ef767
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  files:       empty nonempty
+  description:
+  blah blah
+  diff blah
+  blah blah
+  
+  
+  diff --git a/empty b/empty
+  new file mode 100644
+  diff --git a/nonempty b/nonempty
+  new file mode 100644
+  --- /dev/null
+  +++ b/nonempty
+  @@ -0,0 +1,1 @@
+  +nonempty
+  
+
+  $ cd ..
+
 no segfault while importing a unified diff which start line is zero but chunk
 size is non-zero
 
--- a/tests/test-largefiles-wireproto.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-largefiles-wireproto.t	Mon Dec 04 11:28:29 2017 -0500
@@ -352,7 +352,7 @@
   searching 2 changesets for largefiles
   verified existence of 2 revisions of 2 largefiles
   $ tail -1 access.log
-  $LOCALIP - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3D972a1a11f19934401291cc99117ec614933374ce%3Bstatlfile+sha%3Dc801c9cfe94400963fcb683246217d5db77f9a9a x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3D972a1a11f19934401291cc99117ec614933374ce%3Bstatlfile+sha%3Dc801c9cfe94400963fcb683246217d5db77f9a9a x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   $ hg -R batchverifyclone update
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -390,7 +390,7 @@
   searching 3 changesets for largefiles
   verified existence of 3 revisions of 3 largefiles
   $ tail -1 access.log
-  $LOCALIP - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3Dc8559c3c9cfb42131794b7d8009230403b9b454c x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3Dc8559c3c9cfb42131794b7d8009230403b9b454c x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
 
   $ killdaemons.py
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lfs-largefiles.t	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,351 @@
+This tests the interaction between the largefiles and lfs extensions, and
+conversion from largefiles -> lfs.
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > largefiles =
+  > 
+  > [lfs]
+  > # standin files are 41 bytes.  Stay bigger for clarity.
+  > threshold = 42
+  > EOF
+
+Setup a repo with a normal file and a largefile, above and below the lfs
+threshold to test lfconvert.  *.txt start life as a normal file; *.bin start as
+an lfs/largefile.
+
+  $ hg init largefiles
+  $ cd largefiles
+  $ echo 'normal' > normal.txt
+  $ echo 'normal above lfs threshold 0000000000000000000000000' > lfs.txt
+  $ hg ci -Am 'normal.txt'
+  adding lfs.txt
+  adding normal.txt
+  $ echo 'largefile' > large.bin
+  $ echo 'largefile above lfs threshold 0000000000000000000000' > lfs.bin
+  $ hg add --large large.bin lfs.bin
+  $ hg ci -m 'add largefiles'
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > lfs =
+  > EOF
+
+Add an lfs file and normal file that collide with files on the other branch.
+large.bin is added as a normal file, and is named as such only to clash with the
+largefile on the other branch.
+
+  $ hg up -q '.^'
+  $ echo 'below lfs threshold' > large.bin
+  $ echo 'lfs above the lfs threshold for length 0000000000000' > lfs.bin
+  $ hg ci -Am 'add with lfs extension'
+  adding large.bin
+  adding lfs.bin
+  created new head
+
+  $ hg log -G
+  @  changeset:   2:e989d0fa3764
+  |  tag:         tip
+  |  parent:      0:29361292f54d
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     add with lfs extension
+  |
+  | o  changeset:   1:6513aaab9ca0
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     add largefiles
+  |
+  o  changeset:   0:29361292f54d
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     normal.txt
+  
+--------------------------------------------------------------------------------
+Merge largefiles into lfs branch
+
+The largefiles extension will prompt to use the normal or largefile when merged
+into the lfs files.  `hg manifest` will show standins if present.  They aren't,
+because largefiles merge doesn't merge content.  If it did, selecting (n)ormal
+would convert to lfs on commit, if appropriate.
+
+BUG: Largefiles isn't running the merge tool, like when two lfs files are
+merged.  This is probably by design, but it should probably at least prompt if
+content should be taken from (l)ocal or (o)ther as well.
+
+  $ hg --config ui.interactive=True merge 6513aaab9ca0 <<EOF
+  > n
+  > n
+  > EOF
+  remote turned local normal file large.bin into a largefile
+  use (l)argefile or keep (n)ormal file? n
+  remote turned local normal file lfs.bin into a largefile
+  use (l)argefile or keep (n)ormal file? n
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m 'merge lfs with largefiles -> normal'
+  $ hg manifest
+  large.bin
+  lfs.bin
+  lfs.txt
+  normal.txt
+
+The merged lfs.bin resolved to lfs because the (n)ormal option was picked.  The
+lfs.txt file is unchanged by the merge, because it was added before lfs was
+enabled, and the content didn't change.
+  $ hg debugdata lfs.bin 0
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:81c7492b2c05e130431f65a87651b54a30c5da72c99ce35a1e9b9872a807312b
+  size 53
+  x-is-binary 0
+  $ hg debugdata lfs.txt 0
+  normal above lfs threshold 0000000000000000000000000
+
+Another filelog entry is NOT made by the merge, so nothing is committed as lfs.
+  $ hg log -r . -T '{join(lfs_files, ", ")}\n'
+  
+
+Replay the last merge, but pick (l)arge this time.  The manifest will show any
+standins.
+
+  $ hg up -Cq e989d0fa3764
+
+  $ hg --config ui.interactive=True merge 6513aaab9ca0 <<EOF
+  > l
+  > l
+  > EOF
+  remote turned local normal file large.bin into a largefile
+  use (l)argefile or keep (n)ormal file? l
+  remote turned local normal file lfs.bin into a largefile
+  use (l)argefile or keep (n)ormal file? l
+  getting changed largefiles
+  2 largefiles updated, 0 removed
+  2 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m 'merge lfs with largefiles -> large'
+  created new head
+  $ hg manifest
+  .hglf/large.bin
+  .hglf/lfs.bin
+  lfs.txt
+  normal.txt
+
+--------------------------------------------------------------------------------
+Merge lfs into largefiles branch
+
+  $ hg up -Cq 6513aaab9ca0
+  $ hg --config ui.interactive=True merge e989d0fa3764 <<EOF
+  > n
+  > n
+  > EOF
+  remote turned local largefile large.bin into a normal file
+  keep (l)argefile or use (n)ormal file? n
+  remote turned local largefile lfs.bin into a normal file
+  keep (l)argefile or use (n)ormal file? n
+  getting changed largefiles
+  0 largefiles updated, 0 removed
+  2 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m 'merge largefiles with lfs -> normal'
+  created new head
+  $ hg manifest
+  large.bin
+  lfs.bin
+  lfs.txt
+  normal.txt
+
+The merged lfs.bin got converted to lfs because the (n)ormal option was picked.
+The lfs.txt file is unchanged by the merge, because it was added before lfs was
+enabled.
+  $ hg debugdata lfs.bin 0
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:81c7492b2c05e130431f65a87651b54a30c5da72c99ce35a1e9b9872a807312b
+  size 53
+  x-is-binary 0
+  $ hg debugdata lfs.txt 0
+  normal above lfs threshold 0000000000000000000000000
+
+Another filelog entry is NOT made by the merge, so nothing is committed as lfs.
+  $ hg log -r . -T '{join(lfs_files, ", ")}\n'
+  
+
+Replay the last merge, but pick (l)arge this time.  The manifest will show the
+standins.
+
+  $ hg up -Cq 6513aaab9ca0
+
+  $ hg --config ui.interactive=True merge e989d0fa3764 <<EOF
+  > l
+  > l
+  > EOF
+  remote turned local largefile large.bin into a normal file
+  keep (l)argefile or use (n)ormal file? l
+  remote turned local largefile lfs.bin into a normal file
+  keep (l)argefile or use (n)ormal file? l
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m 'merge largefiles with lfs -> large'
+  created new head
+  $ hg manifest
+  .hglf/large.bin
+  .hglf/lfs.bin
+  lfs.txt
+  normal.txt
+
+--------------------------------------------------------------------------------
+
+When both largefiles and lfs are configured to add by size, the tie goes to
+largefiles since it hooks cmdutil.add() and lfs hooks the filelog write in the
+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
+  $ hg --config largefiles.minsize=1 ci -Am 'large by size'
+  adding large_by_size.bin as a largefile
+  $ hg manifest
+  .hglf/large.bin
+  .hglf/large_by_size.bin
+  .hglf/lfs.bin
+  lfs.txt
+  normal.txt
+
+  $ hg rm large_by_size.bin
+  $ hg ci -m 'remove large_by_size.bin'
+
+Largefiles doesn't do anything special with diff, so it falls back to diffing
+the standins.  Extdiff also is standin based comparison.  Diff and extdiff both
+work on the original file for lfs objects.
+
+Largefile -> lfs transition
+  $ hg diff -r 1 -r 3
+  diff -r 6513aaab9ca0 -r dcc5ce63e252 .hglf/large.bin
+  --- a/.hglf/large.bin	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -cef9a458373df9b0743a0d3c14d0c66fb19b8629
+  diff -r 6513aaab9ca0 -r dcc5ce63e252 .hglf/lfs.bin
+  --- a/.hglf/lfs.bin	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -557fb6309cef935e1ac2c8296508379e4b15a6e6
+  diff -r 6513aaab9ca0 -r dcc5ce63e252 large.bin
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/large.bin	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +below lfs threshold
+  diff -r 6513aaab9ca0 -r dcc5ce63e252 lfs.bin
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/lfs.bin	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +lfs above the lfs threshold for length 0000000000000
+
+lfs -> largefiles transition
+  $ hg diff -r 2 -r 6
+  diff -r e989d0fa3764 -r 95e1e80325c8 .hglf/large.bin
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hglf/large.bin	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +cef9a458373df9b0743a0d3c14d0c66fb19b8629
+  diff -r e989d0fa3764 -r 95e1e80325c8 .hglf/lfs.bin
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hglf/lfs.bin	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +557fb6309cef935e1ac2c8296508379e4b15a6e6
+  diff -r e989d0fa3764 -r 95e1e80325c8 large.bin
+  --- a/large.bin	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -below lfs threshold
+  diff -r e989d0fa3764 -r 95e1e80325c8 lfs.bin
+  --- a/lfs.bin	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -lfs above the lfs threshold for length 0000000000000
+
+A largefiles repo can be converted to lfs.  The lfconvert command uses the
+convert extension under the hood with --to-normal.  So the --config based
+parameters are available, but not --authormap, --branchmap, etc.
+
+  $ cd ..
+  $ hg lfconvert --to-normal largefiles nolargefiles 2>&1
+  initializing destination nolargefiles
+  0 additional largefiles cached
+  scanning source...
+  sorting...
+  converting...
+  8 normal.txt
+  7 add largefiles
+  6 add with lfs extension
+  5 merge lfs with largefiles -> normal
+  4 merge lfs with largefiles -> large
+  3 merge largefiles with lfs -> normal
+  2 merge largefiles with lfs -> large
+  1 large by size
+  0 remove large_by_size.bin
+  $ cd nolargefiles
+
+The requirement is added to the destination repo
+
+  $ cat .hg/requires
+  dotencode
+  fncache
+  generaldelta
+  lfs
+  revlogv1
+  store
+
+  $ hg log -r 'all()' -G -T '{rev} {join(lfs_files, ", ")} ({desc})\n'
+  o  8  (remove large_by_size.bin)
+  |
+  o  7 large_by_size.bin (large by size)
+  |
+  o    6  (merge largefiles with lfs -> large)
+  |\
+  +---o  5  (merge largefiles with lfs -> normal)
+  | |/
+  +---o  4 lfs.bin (merge lfs with largefiles -> large)
+  | |/
+  +---o  3  (merge lfs with largefiles -> normal)
+  | |/
+  | o  2 lfs.bin (add with lfs extension)
+  | |
+  o |  1 lfs.bin (add largefiles)
+  |/
+  o  0 lfs.txt (normal.txt)
+  
+  $ hg debugdata lfs.bin 0
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:2172a5bd492dd41ec533b9bb695f7691b6351719407ac797f0ccad5348c81e62
+  size 53
+  x-is-binary 0
+  $ hg debugdata lfs.bin 1
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:81c7492b2c05e130431f65a87651b54a30c5da72c99ce35a1e9b9872a807312b
+  size 53
+  x-is-binary 0
+  $ hg debugdata lfs.bin 2
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:2172a5bd492dd41ec533b9bb695f7691b6351719407ac797f0ccad5348c81e62
+  size 53
+  x-is-binary 0
+  $ hg debugdata lfs.bin 3
+  abort: invalid revision identifier 3
+  [255]
+
+No diffs when comparing merge and p1 that kept p1's changes.  Diff of lfs to
+largefiles no longer operates in standin files.
+
+  $ hg diff -r 2:3
+  $ hg diff -r 2:6
+  diff -r e989d0fa3764 -r 752e3a0d8488 large.bin
+  --- a/large.bin	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/large.bin	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -below lfs threshold
+  +largefile
+  diff -r e989d0fa3764 -r 752e3a0d8488 lfs.bin
+  --- a/lfs.bin	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/lfs.bin	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -lfs above the lfs threshold for length 0000000000000
+  +largefile above lfs threshold 0000000000000000000000
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lfs-pointer.py	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,41 @@
+from __future__ import absolute_import, print_function
+
+import os
+import sys
+
+# make it runnable using python directly without run-tests.py
+sys.path[0:0] = [os.path.join(os.path.dirname(__file__), '..')]
+
+from hgext.lfs import pointer
+
+def tryparse(text):
+    r = {}
+    try:
+        r = pointer.deserialize(text)
+        print('ok')
+    except Exception as ex:
+        print(ex)
+    if r:
+        text2 = r.serialize()
+        if text2 != text:
+            print('reconstructed text differs')
+    return r
+
+t = ('version https://git-lfs.github.com/spec/v1\n'
+     'oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1'
+     '258daaa5e2ca24d17e2393\n'
+     'size 12345\n'
+     'x-foo extra-information\n')
+
+tryparse('')
+tryparse(t)
+tryparse(t.replace('git-lfs', 'unknown'))
+tryparse(t.replace('v1\n', 'v1\n\n'))
+tryparse(t.replace('sha256', 'ahs256'))
+tryparse(t.replace('sha256:', ''))
+tryparse(t.replace('12345', '0x12345'))
+tryparse(t.replace('extra-information', 'extra\0information'))
+tryparse(t.replace('extra-information', 'extra\ninformation'))
+tryparse(t.replace('x-foo', 'x_foo'))
+tryparse(t.replace('oid', 'blobid'))
+tryparse(t.replace('size', 'size-bytes').replace('oid', 'object-id'))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lfs-pointer.py.out	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,12 @@
+missed keys: oid, size
+ok
+unexpected value: version='https://unknown.github.com/spec/v1'
+cannot parse git-lfs text: 'version https://git-lfs.github.com/spec/v1\n\noid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393\nsize 12345\nx-foo extra-information\n'
+unexpected value: oid='ahs256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393'
+unexpected value: oid='4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393'
+unexpected value: size='0x12345'
+ok
+cannot parse git-lfs text: 'version https://git-lfs.github.com/spec/v1\noid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393\nsize 12345\nx-foo extra\ninformation\n'
+unexpected key: x_foo
+missed keys: oid
+missed keys: oid, size
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lfs-test-server.t	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,126 @@
+#require lfs-test-server
+
+  $ LFS_LISTEN="tcp://:$HGPORT"
+  $ LFS_HOST="localhost:$HGPORT"
+  $ LFS_PUBLIC=1
+  $ export LFS_LISTEN LFS_HOST LFS_PUBLIC
+#if no-windows
+  $ lfs-test-server &> lfs-server.log &
+  $ echo $! >> $DAEMON_PIDS
+#else
+  $ cat >> $TESTTMP/spawn.py <<EOF
+  > import os
+  > import subprocess
+  > import sys
+  > 
+  > for path in os.environ["PATH"].split(os.pathsep):
+  >     exe = os.path.join(path, 'lfs-test-server.exe')
+  >     if os.path.exists(exe):
+  >         with open('lfs-server.log', 'wb') as out:
+  >             p = subprocess.Popen(exe, stdout=out, stderr=out)
+  >             sys.stdout.write('%s\n' % p.pid)
+  >             sys.exit(0)
+  > sys.exit(1)
+  > EOF
+  $ $PYTHON $TESTTMP/spawn.py >> $DAEMON_PIDS
+#endif
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > lfs=
+  > [lfs]
+  > url=http://foo:bar@$LFS_HOST/
+  > threshold=1
+  > EOF
+
+  $ hg init repo1
+  $ cd repo1
+  $ echo THIS-IS-LFS > a
+  $ hg commit -m a -A a
+
+  $ hg init ../repo2
+  $ hg push ../repo2 -v
+  pushing to ../repo2
+  searching for changes
+  lfs: uploading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
+  1 changesets found
+  uncompressed size of bundle content:
+       * (changelog) (glob)
+       * (manifests) (glob)
+       *  a (glob)
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+  $ cd ../repo2
+  $ hg update tip -v
+  resolving manifests
+  getting a
+  lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+When the server has some blobs already
+
+  $ hg mv a b
+  $ echo ANOTHER-LARGE-FILE > c
+  $ echo ANOTHER-LARGE-FILE2 > d
+  $ hg commit -m b-and-c -A b c d
+  $ hg push ../repo1 -v | grep -v '^  '
+  pushing to ../repo1
+  searching for changes
+  lfs: need to transfer 2 objects (39 bytes)
+  lfs: uploading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
+  lfs: uploading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
+  1 changesets found
+  uncompressed size of bundle content:
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 3 changes to 3 files
+
+  $ hg --repo ../repo1 update tip -v
+  resolving manifests
+  getting b
+  getting c
+  lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes)
+  getting d
+  lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes)
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Check error message when the remote missed a blob:
+
+  $ echo FFFFF > b
+  $ hg commit -m b -A b
+  $ echo FFFFF >> b
+  $ hg commit -m b b
+  $ rm -rf .hg/store/lfs
+  $ hg update -C '.^'
+  abort: LFS server claims required objects do not exist:
+  8e6ea5f6c066b44a0efa43bcce86aea73f17e6e23f0663df0251e7524e140a13!
+  [255]
+
+Check error message when object does not exist:
+
+  $ hg init test && cd test
+  $ echo "[extensions]" >> .hg/hgrc
+  $ echo "lfs=" >> .hg/hgrc
+  $ echo "[lfs]" >> .hg/hgrc
+  $ echo "threshold=1" >> .hg/hgrc
+  $ echo a > a
+  $ hg add a
+  $ hg commit -m 'test'
+  $ echo aaaaa > a
+  $ hg commit -m 'largefile'
+  $ hg debugdata .hg/store/data/a.i 1 # verify this is no the file content but includes "oid", the LFS "pointer".
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:bdc26931acfb734b142a8d675f205becf27560dc461f501822de13274fe6fc8a
+  size 6
+  x-is-binary 0
+  $ cd ..
+  $ hg --config 'lfs.url=https://dewey-lfs.vip.facebook.com/lfs' clone test test2
+  updating to branch default
+  abort: LFS server error. Remote object for file data/a.i not found:(.*)! (re)
+  [255]
+
+  $ $PYTHON $RUNTESTDIR/killdaemons.py $DAEMON_PIDS
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-lfs.t	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,638 @@
+# Initial setup
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > lfs=
+  > [lfs]
+  > threshold=1000B
+  > EOF
+
+  $ LONG=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+
+# Prepare server and enable extension
+  $ hg init server
+  $ hg clone -q server client
+  $ cd client
+
+# Commit small file
+  $ echo s > smallfile
+  $ hg commit -Aqm "add small file"
+
+# Commit large file
+  $ echo $LONG > largefile
+  $ grep lfs .hg/requires
+  [1]
+  $ hg commit --traceback -Aqm "add large file"
+  $ grep lfs .hg/requires
+  lfs
+
+# Ensure metadata is stored
+  $ hg debugdata largefile 0
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
+  size 1501
+  x-is-binary 0
+
+# Check the blobstore is populated
+  $ find .hg/store/lfs/objects | sort
+  .hg/store/lfs/objects
+  .hg/store/lfs/objects/f1
+  .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
+
+# Check the blob stored contains the actual contents of the file
+  $ cat .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
+  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+
+# Push changes to the server
+
+  $ hg push
+  pushing to $TESTTMP/server (glob)
+  searching for changes
+  abort: lfs.url needs to be configured
+  [255]
+
+  $ cat >> $HGRCPATH << EOF
+  > [lfs]
+  > url=file:$TESTTMP/dummy-remote/
+  > EOF
+
+  $ hg push -v | egrep -v '^(uncompressed| )'
+  pushing to $TESTTMP/server (glob)
+  searching for changes
+  2 changesets found
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+
+# Unknown URL scheme
+
+  $ hg push --config lfs.url=ftp://foobar
+  abort: lfs: unknown url scheme: ftp
+  [255]
+
+  $ cd ../
+
+# Initialize new client (not cloning) and setup extension
+  $ hg init client2
+  $ cd client2
+  $ cat >> .hg/hgrc <<EOF
+  > [paths]
+  > default = $TESTTMP/server
+  > EOF
+
+# Pull from server
+  $ hg pull default
+  pulling from $TESTTMP/server (glob)
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  new changesets b29ba743f89d:00c137947d30
+  (run 'hg update' to get a working copy)
+
+# Check the blobstore is not yet populated
+  $ [ -d .hg/store/lfs/objects ]
+  [1]
+
+# Update to the last revision containing the large file
+  $ hg update
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+# Check the blobstore has been populated on update
+  $ find .hg/store/lfs/objects | sort
+  .hg/store/lfs/objects
+  .hg/store/lfs/objects/f1
+  .hg/store/lfs/objects/f1/1e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b
+
+# Check the contents of the file are fetched from blobstore when requested
+  $ hg cat -r . largefile
+  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+
+# Check the file has been copied in the working copy
+  $ cat largefile
+  AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+
+  $ cd ..
+
+# Check rename, and switch between large and small files
+
+  $ hg init repo3
+  $ cd repo3
+  $ cat >> .hg/hgrc << EOF
+  > [lfs]
+  > threshold=10B
+  > EOF
+
+  $ echo LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS > large
+  $ echo SHORTER > small
+  $ hg add . -q
+  $ hg commit -m 'commit with lfs content'
+
+  $ hg mv large l
+  $ hg mv small s
+  $ hg commit -m 'renames'
+
+  $ echo SHORT > l
+  $ echo BECOME-LARGER-FROM-SHORTER > s
+  $ hg commit -m 'large to small, small to large'
+
+  $ echo 1 >> l
+  $ echo 2 >> s
+  $ hg commit -m 'random modifications'
+
+  $ echo RESTORE-TO-BE-LARGE > l
+  $ echo SHORTER > s
+  $ hg commit -m 'switch large and small again'
+
+# Test lfs_files template
+
+  $ hg log -r 'all()' -T '{rev} {join(lfs_files, ", ")}\n'
+  0 large
+  1 l
+  2 s
+  3 s
+  4 l
+
+# Push and pull the above repo
+
+  $ hg --cwd .. init repo4
+  $ hg push ../repo4
+  pushing to ../repo4
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 10 changes to 4 files
+
+  $ hg --cwd .. init repo5
+  $ hg --cwd ../repo5 pull ../repo3
+  pulling from ../repo3
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 10 changes to 4 files
+  new changesets fd47a419c4f7:5adf850972b9
+  (run 'hg update' to get a working copy)
+
+  $ cd ..
+
+# Test clone
+
+  $ hg init repo6
+  $ cd repo6
+  $ cat >> .hg/hgrc << EOF
+  > [lfs]
+  > threshold=30B
+  > EOF
+
+  $ echo LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES > large
+  $ echo SMALL > small
+  $ hg commit -Aqm 'create a lfs file' large small
+  $ hg debuglfsupload -r 'all()' -v
+
+  $ cd ..
+
+  $ hg clone repo6 repo7
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd repo7
+  $ cat large
+  LARGE-BECAUSE-IT-IS-MORE-THAN-30-BYTES
+  $ cat small
+  SMALL
+
+  $ cd ..
+
+# Test rename and status
+
+  $ hg init repo8
+  $ cd repo8
+  $ cat >> .hg/hgrc << EOF
+  > [lfs]
+  > threshold=10B
+  > EOF
+
+  $ echo THIS-IS-LFS-BECAUSE-10-BYTES > a1
+  $ echo SMALL > a2
+  $ hg commit -m a -A a1 a2
+  $ hg status
+  $ hg mv a1 b1
+  $ hg mv a2 a1
+  $ hg mv b1 a2
+  $ hg commit -m b
+  $ hg status
+  $ HEADER=$'\1\n'
+  $ printf '%sSTART-WITH-HG-FILELOG-METADATA' "$HEADER" > a2
+  $ printf '%sMETA\n' "$HEADER" > a1
+  $ hg commit -m meta
+  $ hg status
+  $ hg log -T '{rev}: {file_copies} | {file_dels} | {file_adds}\n'
+  2:  |  | 
+  1: a1 (a2)a2 (a1) |  | 
+  0:  |  | a1 a2
+
+  $ for n in a1 a2; do
+  >   for r in 0 1 2; do
+  >     printf '\n%s @ %s\n' $n $r
+  >     hg debugdata $n $r
+  >   done
+  > done
+  
+  a1 @ 0
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
+  size 29
+  x-is-binary 0
+  
+  a1 @ 1
+  \x01 (esc)
+  copy: a2
+  copyrev: 50470ad23cf937b1f4b9f80bfe54df38e65b50d9
+  \x01 (esc)
+  SMALL
+  
+  a1 @ 2
+  \x01 (esc)
+  \x01 (esc)
+  \x01 (esc)
+  META
+  
+  a2 @ 0
+  SMALL
+  
+  a2 @ 1
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
+  size 29
+  x-hg-copy a1
+  x-hg-copyrev be23af27908a582af43e5cda209a5a9b319de8d4
+  x-is-binary 0
+  
+  a2 @ 2
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:876dadc86a8542f9798048f2c47f51dbf8e4359aed883e8ec80c5db825f0d943
+  size 32
+  x-is-binary 0
+
+# Verify commit hashes include rename metadata
+
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  2:0fae949de7fa meta
+  1:9cd6bdffdac0 b
+  0:7f96794915f7 a
+
+  $ cd ..
+
+# Test bundle
+
+  $ hg init repo9
+  $ cd repo9
+  $ cat >> .hg/hgrc << EOF
+  > [lfs]
+  > threshold=10B
+  > [diff]
+  > git=1
+  > EOF
+
+  $ for i in 0 single two three 4; do
+  >   echo 'THIS-IS-LFS-'$i > a
+  >   hg commit -m a-$i -A a
+  > done
+
+  $ hg update 2 -q
+  $ echo 'THIS-IS-LFS-2-CHILD' > a
+  $ hg commit -m branching -q
+
+  $ hg bundle --base 1 bundle.hg -v
+  4 changesets found
+  uncompressed size of bundle content:
+       * (changelog) (glob)
+       * (manifests) (glob)
+       *  a (glob)
+  $ hg --config extensions.strip= strip -r 2 --no-backup --force -q
+  $ hg -R bundle.hg log -p -T '{rev} {desc}\n' a
+  5 branching
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-two
+  +THIS-IS-LFS-2-CHILD
+  
+  4 a-4
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-three
+  +THIS-IS-LFS-4
+  
+  3 a-three
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-two
+  +THIS-IS-LFS-three
+  
+  2 a-two
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-single
+  +THIS-IS-LFS-two
+  
+  1 a-single
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-0
+  +THIS-IS-LFS-single
+  
+  0 a-0
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,1 @@
+  +THIS-IS-LFS-0
+  
+  $ hg bundle -R bundle.hg --base 1 bundle-again.hg -q
+  $ hg -R bundle-again.hg log -p -T '{rev} {desc}\n' a
+  5 branching
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-two
+  +THIS-IS-LFS-2-CHILD
+  
+  4 a-4
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-three
+  +THIS-IS-LFS-4
+  
+  3 a-three
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-two
+  +THIS-IS-LFS-three
+  
+  2 a-two
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-single
+  +THIS-IS-LFS-two
+  
+  1 a-single
+  diff --git a/a b/a
+  --- a/a
+  +++ b/a
+  @@ -1,1 +1,1 @@
+  -THIS-IS-LFS-0
+  +THIS-IS-LFS-single
+  
+  0 a-0
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,1 @@
+  +THIS-IS-LFS-0
+  
+  $ cd ..
+
+# Test isbinary
+
+  $ hg init repo10
+  $ cd repo10
+  $ cat >> .hg/hgrc << EOF
+  > [extensions]
+  > lfs=
+  > [lfs]
+  > threshold=1
+  > EOF
+  $ $PYTHON <<'EOF'
+  > def write(path, content):
+  >     with open(path, 'wb') as f:
+  >         f.write(content)
+  > write('a', b'\0\0')
+  > write('b', b'\1\n')
+  > write('c', b'\1\n\0')
+  > write('d', b'xx')
+  > EOF
+  $ hg add a b c d
+  $ hg diff --stat
+   a |  Bin 
+   b |    1 +
+   c |  Bin 
+   d |    1 +
+   4 files changed, 2 insertions(+), 0 deletions(-)
+  $ hg commit -m binarytest
+  $ cat > $TESTTMP/dumpbinary.py << EOF
+  > def reposetup(ui, repo):
+  >     for n in 'abcd':
+  >         ui.write(('%s: binary=%s\n') % (n, repo['.'][n].isbinary()))
+  > EOF
+  $ hg --config extensions.dumpbinary=$TESTTMP/dumpbinary.py id --trace
+  a: binary=True
+  b: binary=False
+  c: binary=True
+  d: binary=False
+  b55353847f02 tip
+
+  $ cd ..
+
+# Test fctx.cmp fastpath - diff without LFS blobs
+
+  $ hg init repo11
+  $ cd repo11
+  $ cat >> .hg/hgrc <<EOF
+  > [lfs]
+  > threshold=1
+  > EOF
+  $ cat > ../patch.diff <<EOF
+  > # HG changeset patch
+  > 2
+  > 
+  > diff --git a/a b/a
+  > old mode 100644
+  > new mode 100755
+  > EOF
+
+  $ for i in 1 2 3; do
+  >     cp ../repo10/a a
+  >     if [ $i = 3 ]; then
+  >         # make a content-only change
+  >         hg import -q --bypass ../patch.diff
+  >         hg update -q
+  >         rm ../patch.diff
+  >     else
+  >         echo $i >> a
+  >         hg commit -m $i -A a
+  >     fi
+  > done
+  $ [ -d .hg/store/lfs/objects ]
+
+  $ cd ..
+
+  $ hg clone repo11 repo12 --noupdate
+  $ cd repo12
+  $ hg log --removed -p a -T '{desc}\n' --config diff.nobinary=1 --git
+  2
+  diff --git a/a b/a
+  old mode 100644
+  new mode 100755
+  
+  2
+  diff --git a/a b/a
+  Binary file a has changed
+  
+  1
+  diff --git a/a b/a
+  new file mode 100644
+  Binary file a has changed
+  
+  $ [ -d .hg/store/lfs/objects ]
+  [1]
+
+  $ cd ..
+
+# Verify the repos
+
+  $ cat > $TESTTMP/dumpflog.py << EOF
+  > # print raw revision sizes, flags, and hashes for certain files
+  > import hashlib
+  > from mercurial import revlog
+  > from mercurial.node import short
+  > def hash(rawtext):
+  >     h = hashlib.sha512()
+  >     h.update(rawtext)
+  >     return h.hexdigest()[:4]
+  > def reposetup(ui, repo):
+  >     # these 2 files are interesting
+  >     for name in ['l', 's']:
+  >         fl = repo.file(name)
+  >         if len(fl) == 0:
+  >             continue
+  >         sizes = [revlog.revlog.rawsize(fl, i) for i in fl]
+  >         texts = [fl.revision(i, raw=True) for i in fl]
+  >         flags = [int(fl.flags(i)) for i in fl]
+  >         hashes = [hash(t) for t in texts]
+  >         print('  %s: rawsizes=%r flags=%r hashes=%r'
+  >               % (name, sizes, flags, hashes))
+  > EOF
+
+  $ for i in client client2 server repo3 repo4 repo5 repo6 repo7 repo8 repo9 \
+  >          repo10; do
+  >   echo 'repo:' $i
+  >   hg --cwd $i verify --config extensions.dumpflog=$TESTTMP/dumpflog.py -q
+  > done
+  repo: client
+  repo: client2
+  repo: server
+  repo: repo3
+    l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
+    s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
+  repo: repo4
+    l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
+    s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
+  repo: repo5
+    l: rawsizes=[211, 6, 8, 141] flags=[8192, 0, 0, 8192] hashes=['d2b8', '948c', 'cc88', '724d']
+    s: rawsizes=[74, 141, 141, 8] flags=[0, 8192, 8192, 0] hashes=['3c80', 'fce0', '874a', '826b']
+  repo: repo6
+  repo: repo7
+  repo: repo8
+  repo: repo9
+  repo: repo10
+
+lfs -> normal -> lfs round trip conversions are possible.  The threshold for the
+lfs destination is specified here because it was originally listed in the local
+.hgrc, and the global one is too high to trigger lfs usage.  For lfs -> normal,
+there's no 'lfs' destination repo requirement.  For normal -> lfs, there is.
+
+XXX: There's not a great way to ensure that the conversion to normal files
+actually converts _everything_ to normal.  The extension needs to be loaded for
+the source, but there's no way to disable it for the destination.  The best that
+can be done is to raise the threshold so that lfs isn't used on the destination.
+It doesn't like using '!' to unset the value on the command line.
+
+  $ hg --config extensions.convert= --config lfs.threshold=1000M \
+  >    convert repo8 convert_normal
+  initializing destination convert_normal repository
+  scanning source...
+  sorting...
+  converting...
+  2 a
+  1 b
+  0 meta
+  $ grep 'lfs' convert_normal/.hg/requires
+  [1]
+  $ hg --cwd convert_normal debugdata a1 0
+  THIS-IS-LFS-BECAUSE-10-BYTES
+
+  $ hg --config extensions.convert= --config lfs.threshold=10B \
+  >    convert convert_normal convert_lfs
+  initializing destination convert_lfs repository
+  scanning source...
+  sorting...
+  converting...
+  2 a
+  1 b
+  0 meta
+  $ hg --cwd convert_lfs debugdata a1 0
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:5bb8341bee63b3649f222b2215bde37322bea075a30575aa685d8f8d21c77024
+  size 29
+  x-is-binary 0
+  $ grep 'lfs' convert_lfs/.hg/requires
+  lfs
+
+This convert is trickier, because it contains deleted files (via `hg mv`)
+
+  $ hg --config extensions.convert= --config lfs.threshold=1000M \
+  >    convert repo3 convert_normal2
+  initializing destination convert_normal2 repository
+  scanning source...
+  sorting...
+  converting...
+  4 commit with lfs content
+  3 renames
+  2 large to small, small to large
+  1 random modifications
+  0 switch large and small again
+  $ grep 'lfs' convert_normal2/.hg/requires
+  [1]
+  $ hg --cwd convert_normal2 debugdata large 0
+  LONGER-THAN-TEN-BYTES-WILL-TRIGGER-LFS
+
+  $ hg --config extensions.convert= --config lfs.threshold=10B \
+  >    convert convert_normal2 convert_lfs2
+  initializing destination convert_lfs2 repository
+  scanning source...
+  sorting...
+  converting...
+  4 commit with lfs content
+  3 renames
+  2 large to small, small to large
+  1 random modifications
+  0 switch large and small again
+  $ grep 'lfs' convert_lfs2/.hg/requires
+  lfs
+  $ hg --cwd convert_lfs2 debugdata large 0
+  version https://git-lfs.github.com/spec/v1
+  oid sha256:66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e
+  size 39
+  x-is-binary 0
--- a/tests/test-lock-badness.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-lock-badness.t	Mon Dec 04 11:28:29 2017 -0500
@@ -57,14 +57,77 @@
   $ echo b > b/b
   $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
   $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
-  > > preup 2>&1
+  > > preup-stdout 2>preup-stderr
   $ wait
-  $ cat preup
+  $ cat preup-stdout
+  $ cat preup-stderr
   waiting for lock on working directory of b held by process '*' on host '*' (glob)
   got lock after * seconds (glob)
   $ cat stdout
   adding b
 
+On processs waiting on another, warning after a long time.
+
+  $ echo b > b/c
+  $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
+  $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
+  > --config ui.timeout.warn=250 \
+  > > preup-stdout 2>preup-stderr
+  $ wait
+  $ cat preup-stdout
+  $ cat preup-stderr
+  $ cat stdout
+  adding c
+
+On processs waiting on another, warning disabled.
+
+  $ echo b > b/d
+  $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
+  $ hg -R b up -q --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
+  > --config ui.timeout.warn=-1 \
+  > > preup-stdout 2>preup-stderr
+  $ wait
+  $ cat preup-stdout
+  $ cat preup-stderr
+  $ cat stdout
+  adding d
+
+check we still print debug output
+
+On processs waiting on another, warning after a long time (debug output on)
+
+  $ echo b > b/e
+  $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
+  $ hg -R b up --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
+  > --config ui.timeout.warn=250 --debug\
+  > > preup-stdout 2>preup-stderr
+  $ wait
+  $ cat preup-stdout
+  calling hook pre-update: hghook_pre-update.sleephalf
+  waiting for lock on working directory of b held by process '*' on host '*' (glob)
+  got lock after * seconds (glob)
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat preup-stderr
+  $ cat stdout
+  adding e
+
+On processs waiting on another, warning disabled, (debug output on)
+
+  $ echo b > b/f
+  $ hg -R b ci -A -m b --config hooks.precommit="python:`pwd`/hooks.py:sleepone" > stdout &
+  $ hg -R b up --config hooks.pre-update="python:`pwd`/hooks.py:sleephalf" \
+  > --config ui.timeout.warn=-1 --debug\
+  > > preup-stdout 2>preup-stderr
+  $ wait
+  $ cat preup-stdout
+  calling hook pre-update: hghook_pre-update.sleephalf
+  waiting for lock on working directory of b held by process '*' on host '*' (glob)
+  got lock after * seconds (glob)
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat preup-stderr
+  $ cat stdout
+  adding f
+
 Pushing to a local read-only repo that can't be locked
 
   $ chmod 100 a/.hg/store
--- a/tests/test-obsmarker-template.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-obsmarker-template.t	Mon Dec 04 11:28:29 2017 -0500
@@ -13,7 +13,7 @@
   > evolution=true
   > [templates]
   > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
-  > obsfateverb = "{obsfateverb(successors)}"
+  > obsfateverb = "{obsfateverb(successors, markers)}"
   > obsfateoperations = "{if(obsfateoperations(markers), " using {join(obsfateoperations(markers), ", ")}")}"
   > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
   > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
@@ -248,9 +248,9 @@
   @  d004c8f274b9
   |
   | x  a468dc9b3633
-  |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["operation", "amend"], ["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
+  |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["ef1", "1"], ["operation", "amend"], ["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
   | x  471f378eab4c
-  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["operation", "amend"], ["user", "test"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["ef1", "9"], ["operation", "amend"], ["user", "test"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
   o  ea207398892e
   
 
@@ -975,11 +975,11 @@
   o  019fadeab383
   |
   | x  65b757b745b9
-  |/     Obsfate: [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]}]
+  |/     Obsfate: [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["ef1", "1"], ["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]}]
   | @  fdf9bde5129a
   |/
   | x  471f378eab4c
-  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"]}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"]}]
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["ef1", "1"], ["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"]}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["ef1", "1"], ["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"]}]
   o  ea207398892e
   
 
@@ -1287,7 +1287,7 @@
   | x  b7ea6d14e664
   | |    Obsfate: [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
   | | x  0dec01379d3b
-  | |/     Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"]}]
+  | |/     Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["ef1", "1"], ["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"]}]
   | x  471f378eab4c
   |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
   o  ea207398892e
@@ -1450,8 +1450,8 @@
   
 
   $ hg debugobsolete
-  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
 
 Check templates
 ---------------
@@ -2045,7 +2045,7 @@
   | o  ba2ed02b0c9a
   | |
   | x  4a004186e638
-  |/     Obsfate: [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"]}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"]}]
+  |/     Obsfate: [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["ef1", "1"], ["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"]}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["ef1", "1"], ["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"]}]
   o  dd800401bd8c
   |
   | x  9bd10a0775e4
--- a/tests/test-obsolete-distributed.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-obsolete-distributed.t	Mon Dec 04 11:28:29 2017 -0500
@@ -302,9 +302,9 @@
   o  0:e82fb8d02bbf ROOT
   
   $ hg debugobsolete
-  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
-  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'bob'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
   $ cd ..
 
 Celeste pulls from Bob and rewrites them again
@@ -349,12 +349,12 @@
   o  0:e82fb8d02bbf ROOT
   
   $ hg debugobsolete
-  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
-  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
-  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'bob'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'celeste'}
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
 
 Celeste now pushes to the server
 
@@ -391,12 +391,12 @@
   new changesets 9866d64649a5:77ae25d99ff0
   (run 'hg heads' to see heads)
   $ hg debugobsolete
-  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
-  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'bob'}
 
 Then, she pulls from Bob, pulling predecessors of the changeset she has
 already pulled. The changesets are not obsoleted in the Bob repo yet. Their
@@ -418,12 +418,12 @@
   @  0:e82fb8d02bbf ROOT
   
   $ hg debugobsolete
-  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
-  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'bob'}
 
 Same tests, but change coming from a bundle
 (testing with a bundle is interesting because absolutely no discovery or
@@ -439,12 +439,12 @@
   @  0:e82fb8d02bbf ROOT
   
   $ hg debugobsolete
-  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
-  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'bob'}
   $ hg -R ../repo-Bob bundle ../step-1.hg
   searching for changes
   2 changesets found
@@ -477,11 +477,11 @@
   @  0:e82fb8d02bbf ROOT
   
   $ hg debugobsolete
-  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
-  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
-  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
-  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'bob'}
 
   $ cd ..
--- a/tests/test-obsolete-divergent.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-obsolete-divergent.t	Mon Dec 04 11:28:29 2017 -0500
@@ -680,6 +680,6 @@
   o  0:426bada5c675 A
   
   $ hg debugobsolete
-  a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'test', 'user': 'test'}
-  ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'test', 'user': 'test'}
+  a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'test', 'user': 'test'}
+  ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'test', 'user': 'test'}
--- a/tests/test-obsolete.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-obsolete.t	Mon Dec 04 11:28:29 2017 -0500
@@ -1015,6 +1015,44 @@
   orphan: 2 changesets
   phase-divergent: 1 changesets
 
+#if serve
+
+  $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
+  $ cat hg.pid >> $DAEMON_PIDS
+
+check obsolete changeset
+
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(obsolete())&style=paper' | grep '<span class="obsolete">'
+     <span class="phase">draft</span> <span class="obsolete">obsolete</span> 
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(obsolete())&style=coal' | grep '<span class="obsolete">'
+     <span class="phase">draft</span> <span class="obsolete">obsolete</span> 
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(obsolete())&style=gitweb' | grep '<span class="logtags">'
+    <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="obsoletetag" title="obsolete">obsolete</span> </span>
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(obsolete())&style=monoblue' | grep '<span class="logtags">'
+          <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="obsoletetag" title="obsolete">obsolete</span> </span>
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(obsolete())&style=spartan' | grep 'class="obsolete"'
+    <th class="obsolete">obsolete:</th>
+    <td class="obsolete">yes</td>
+
+check changeset with instabilities
+
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(phasedivergent())&style=paper' | grep '<span class="instability">'
+     <span class="phase">draft</span> <span class="instability">orphan</span> <span class="instability">phase-divergent</span> 
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(phasedivergent())&style=coal' | grep '<span class="instability">'
+     <span class="phase">draft</span> <span class="instability">orphan</span> <span class="instability">phase-divergent</span> 
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(phasedivergent())&style=gitweb' | grep '<span class="logtags">'
+    <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="instabilitytag" title="orphan">orphan</span> <span class="instabilitytag" title="phase-divergent">phase-divergent</span> </span>
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(phasedivergent())&style=monoblue' | grep '<span class="logtags">'
+          <span class="logtags"><span class="phasetag" title="draft">draft</span> <span class="instabilitytag" title="orphan">orphan</span> <span class="instabilitytag" title="phase-divergent">phase-divergent</span> </span>
+  $ get-with-headers.py localhost:$HGPORT 'log?rev=first(phasedivergent())&style=spartan' | grep 'class="instabilities"'
+    <th class="instabilities">instabilities:</th>
+    <td class="instabilities">orphan phase-divergent </td>
+
+  $ killdaemons.py
+
+  $ rm hg.pid access.log errors.log
+#endif
+
 Test incoming/outcoming with changesets obsoleted remotely, known locally
 ===============================================================================
 
@@ -1333,7 +1371,7 @@
   $ hg strip --hidden -r 2 --config extensions.strip= --config devel.strip-obsmarkers=no
   saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-ede36964-backup.hg (glob)
   $ hg debugobsolete
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1360,7 +1398,7 @@
   searching for changes
   no changes found
   $ hg debugobsolete
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1394,8 +1432,8 @@
       e016b03fd86fcccc54817d120b90b751aaf367d6
       b0551702f918510f01ae838ab03a463054c67b46
   obsmarkers -- {}
-      version: 1 (86 bytes)
-      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+      version: 1 (92 bytes)
+      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
   phase-heads -- {}
       b0551702f918510f01ae838ab03a463054c67b46 draft
 
@@ -1408,7 +1446,7 @@
   new changesets e016b03fd86f:b0551702f918
   (run 'hg update' to get a working copy)
   $ hg debugobsolete | sort
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
   $ hg log -G
   o  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1444,15 +1482,15 @@
   adding d
   $ hg ci --amend -m dd --config experimental.evolution.track-operation=1
   $ hg debugobsolete --index --rev "3+7"
-  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'operation': 'amend', 'user': 'test'} (re)
+  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
   $ hg debugobsolete --index --rev "3+7" -Tjson
   [
    {
     "date": [0.0, 0],
     "flag": 0,
     "index": 1,
-    "metadata": {"operation": "amend", "user": "test"},
+    "metadata": {"ef1": "1", "operation": "amend", "user": "test"},
     "prednode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
     "succnodes": ["d27fb9b066076fd921277a4b9e8b9cb48c95bc6a"]
    },
@@ -1460,7 +1498,7 @@
     "date": [0.0, 0],
     "flag": 0,
     "index": 3,
-    "metadata": {"operation": "amend", "user": "test"},
+    "metadata": {"ef1": "1", "operation": "amend", "user": "test"},
     "prednode": "4715cf767440ed891755448016c2b8cf70760c30",
     "succnodes": ["7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d"]
    }
@@ -1468,15 +1506,15 @@
 
 Test the --delete option of debugobsolete command
   $ hg debugobsolete --index
-  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
   $ hg debugobsolete --delete 1 --delete 3
   deleted 2 obsolescence markers
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
-  1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+  1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
 
 Test adding changeset after obsmarkers affecting it
 (eg: during pull, or unbundle)
--- a/tests/test-rebase-obsolete.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-rebase-obsolete.t	Mon Dec 04 11:28:29 2017 -0500
@@ -6,7 +6,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > logtemplate= {rev}:{node|short} {desc|firstline}
+  > logtemplate= {rev}:{node|short} {desc|firstline}{if(obsolete,' ({obsfate})')}
   > [experimental]
   > evolution.createmarkers=True
   > evolution.allowunstable=True
@@ -94,18 +94,18 @@
   | |
   o |  4:9520eea781bc E
   |/
-  | x  3:32af7686d403 D
+  | x  3:32af7686d403 D (rewritten using rebase as 10:8eeb3c33ad33)
   | |
-  | x  2:5fddd98957c8 C
+  | x  2:5fddd98957c8 C (rewritten using rebase as 9:2327fea05063)
   | |
-  | x  1:42ccdea3bb16 B
+  | x  1:42ccdea3bb16 B (rewritten using rebase as 8:e4e5be0395b2)
   |/
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
 
 
   $ cd ..
@@ -164,18 +164,18 @@
   | |
   o |  4:9520eea781bc E
   |/
-  | x  3:32af7686d403 D
+  | x  3:32af7686d403 D (pruned using rebase)
   | |
-  | x  2:5fddd98957c8 C
+  | x  2:5fddd98957c8 C (rewritten using rebase as 10:5ae4c968c6ac)
   | |
-  | x  1:42ccdea3bb16 B
+  | x  1:42ccdea3bb16 B (pruned using rebase)
   |/
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
 
 
 More complex case where part of the rebase set were already rebased
@@ -183,16 +183,16 @@
   $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
   rebasing 9:08483444fef9 "D"
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
   $ hg log -G
   @  11:4596109a6a43 D
   |
   | o  10:5ae4c968c6ac C
   | |
-  | x  9:08483444fef9 D
+  | x  9:08483444fef9 D (rewritten using rebase as 11:4596109a6a43)
   | |
   | o  8:8877864f1edb B
   | |
@@ -211,12 +211,12 @@
   note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D" (tip)
   rebasing 10:5ae4c968c6ac "C"
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'rebase', 'user': 'test'}
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
   $ hg log --rev 'contentdivergent()'
   $ hg log -G
   o  13:98f6af4ee953 C
@@ -299,7 +299,7 @@
   | |
   o |  4:9520eea781bc E
   |/
-  | @  1:42ccdea3bb16 B
+  | @  1:42ccdea3bb16 B (pruned using rebase)
   |/
   o  0:cd010b8cd998 A
   
@@ -341,36 +341,70 @@
   | |
   o |  4:9520eea781bc E
   |/
-  | x  3:32af7686d403 D
+  | x  3:32af7686d403 D (rewritten using rebase as 8:4dc2197e807b)
   | |
-  | x  2:5fddd98957c8 C
+  | x  2:5fddd98957c8 C (rewritten using rebase as 8:4dc2197e807b)
   | |
-  | x  1:42ccdea3bb16 B
+  | x  1:42ccdea3bb16 B (rewritten using rebase as 8:4dc2197e807b)
   |/
   o  0:cd010b8cd998 A
   
   $ hg id --debug -r tip
   4dc2197e807bae9817f09905b50ab288be2dbbcf tip
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'rebase', 'user': 'test'}
 
   $ cd ..
 
 Rebase set has hidden descendants
 ---------------------------------
 
-We rebase a changeset which has a hidden changeset. The hidden changeset must
-not be rebased.
+We rebase a changeset which has hidden descendants. Hidden changesets must not
+be rebased.
 
   $ hg clone base hidden
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd hidden
+  $ hg log -G
+  @  7:02de42196ebe H
+  |
+  | o  6:eea13746799a G
+  |/|
+  o |  5:24b6387c8c8c F
+  | |
+  | o  4:9520eea781bc E
+  |/
+  | o  3:32af7686d403 D
+  | |
+  | o  2:5fddd98957c8 C
+  | |
+  | o  1:42ccdea3bb16 B
+  |/
+  o  0:cd010b8cd998 A
+  
   $ hg rebase -s 5fddd98957c8 -d eea13746799a
   rebasing 2:5fddd98957c8 "C"
   rebasing 3:32af7686d403 "D"
+  $ hg log -G
+  o  9:cf44d2f5a9f4 D
+  |
+  o  8:e273c5e7d2d2 C
+  |
+  | @  7:02de42196ebe H
+  | |
+  o |  6:eea13746799a G
+  |\|
+  | o  5:24b6387c8c8c F
+  | |
+  o |  4:9520eea781bc E
+  |/
+  | o  1:42ccdea3bb16 B
+  |/
+  o  0:cd010b8cd998 A
+  
   $ hg rebase -s 42ccdea3bb16 -d 02de42196ebe
   rebasing 1:42ccdea3bb16 "B"
   $ hg log -G
@@ -405,18 +439,18 @@
   | |
   | o  4:9520eea781bc E
   |/
-  | x  3:32af7686d403 D
+  | x  3:32af7686d403 D (rewritten using rebase as 9:cf44d2f5a9f4)
   | |
-  | x  2:5fddd98957c8 C
+  | x  2:5fddd98957c8 C (rewritten using rebase as 8:e273c5e7d2d2)
   | |
-  | x  1:42ccdea3bb16 B
+  | x  1:42ccdea3bb16 B (rewritten using rebase as 10:7c6027df6a99)
   |/
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
 
 Test that rewriting leaving instability behind is allowed
 ---------------------------------------------------------------------
@@ -432,7 +466,7 @@
   |
   | o  9:cf44d2f5a9f4 D
   | |
-  | x  8:e273c5e7d2d2 C
+  | x  8:e273c5e7d2d2 C (rewritten using rebase as 11:0d8f238b634c)
   | |
   @ |  7:02de42196ebe H
   | |
@@ -462,7 +496,7 @@
   |/
   | o  10:7c6027df6a99 B
   | |
-  | x  7:02de42196ebe H
+  | x  7:02de42196ebe H (rewritten using rebase as 13:bfe264faf697)
   | |
   +---o  6:eea13746799a G
   | |/
@@ -556,7 +590,7 @@
   |/
   | o    8:53a6a128b2b7 M
   | |\
-  | | x  7:02de42196ebe H
+  | | x  7:02de42196ebe H (rewritten using rebase as 11:6c11a6218c97)
   | | |
   o---+  6:eea13746799a G
   | | |
@@ -564,7 +598,7 @@
   | | |
   o---+  4:9520eea781bc E
    / /
-  x |  3:32af7686d403 D
+  x |  3:32af7686d403 D (rewritten using rebase as 10:b5313c85b22e)
   | |
   o |  2:5fddd98957c8 C
   | |
@@ -599,7 +633,7 @@
   |/
   | o    8:53a6a128b2b7 M
   | |\
-  | | x  7:02de42196ebe H
+  | | x  7:02de42196ebe H (rewritten using rebase as 11:6c11a6218c97)
   | | |
   o---+  6:eea13746799a G
   | | |
@@ -607,7 +641,7 @@
   | | |
   o---+  4:9520eea781bc E
    / /
-  x |  3:32af7686d403 D
+  x |  3:32af7686d403 D (rewritten using rebase as 10:b5313c85b22e)
   | |
   o |  2:5fddd98957c8 C
   | |
@@ -633,11 +667,11 @@
   |
   | o  17:97219452e4bd L
   | |
-  | x  16:fc37a630c901 K
+  | x  16:fc37a630c901 K (rewritten using amend as 18:bfaedf8eb73b)
   |/
   | o  15:5ae8a643467b J
   | |
-  | x  14:9ad579b4a5de I
+  | x  14:9ad579b4a5de I (rewritten using amend as 16:fc37a630c901)
   |/
   | o  12:acd174b7ab39 I
   | |
@@ -647,7 +681,7 @@
   |/
   | o    8:53a6a128b2b7 M
   | |\
-  | | x  7:02de42196ebe H
+  | | x  7:02de42196ebe H (rewritten using rebase as 11:6c11a6218c97)
   | | |
   o---+  6:eea13746799a G
   | | |
@@ -655,7 +689,7 @@
   | | |
   o---+  4:9520eea781bc E
    / /
-  x |  3:32af7686d403 D
+  x |  3:32af7686d403 D (rewritten using rebase as 10:b5313c85b22e)
   | |
   o |  2:5fddd98957c8 C
   | |
@@ -695,6 +729,15 @@
   $ echo C > C
   $ hg add C
   $ hg commit -m C
+  $ hg log -G
+  @  4:212cb178bcbb C
+  |
+  | o  3:261e70097290 B2
+  | |
+  x |  1:a8b11f55fb19 B0 (rewritten using amend as 3:261e70097290)
+  |/
+  o  0:4a2df7238c3b A
+  
 
 Rebase finds its way in a chain of marker
 
@@ -711,6 +754,17 @@
   $ hg commit -m D
   $ hg --hidden strip -r 'desc(B1)'
   saved backup bundle to $TESTTMP/obsskip/.hg/strip-backup/86f6414ccda7-b1c452ee-backup.hg (glob)
+  $ hg log -G
+  @  5:1a79b7535141 D
+  |
+  | o  4:ff2c4d47b71d C
+  | |
+  | o  2:261e70097290 B2
+  | |
+  x |  1:a8b11f55fb19 B0 (rewritten using amend as 2:261e70097290)
+  |/
+  o  0:4a2df7238c3b A
+  
 
   $ hg rebase -d 'desc(B2)'
   note: not rebasing 1:a8b11f55fb19 "B0", already in destination as 2:261e70097290 "B2"
@@ -766,6 +820,19 @@
   created new head
   $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=true
   obsoleted 1 changesets
+  $ hg log -G
+  @  11:f44da1f4954c nonrelevant (pruned)
+  |
+  | o  10:121d9e3bc4c6 P
+  |/
+  o  9:4be60e099a77 C
+  |
+  o  6:9c48361117de D
+  |
+  o  2:261e70097290 B2
+  |
+  o  0:4a2df7238c3b A
+  
   $ hg rebase -r . -d 10
   note: not rebasing 11:f44da1f4954c "nonrelevant" (tip), it has no successor
 
@@ -806,7 +873,7 @@
   | |
   | | o  12:3eb461388009 john doe
   | |/
-  x |  10:121d9e3bc4c6 P
+  x |  10:121d9e3bc4c6 P (rewritten using amend as 13:77d874d096a2)
   |/
   o  9:4be60e099a77 C
   |
@@ -835,7 +902,7 @@
   | |
   | | o  12:3eb461388009 john doe
   | |/
-  x |  10:121d9e3bc4c6 P
+  x |  10:121d9e3bc4c6 P (rewritten using amend as 13:77d874d096a2)
   |/
   o  9:4be60e099a77 C
   |
@@ -893,7 +960,7 @@
   $ hg log -G -r 16::
   @  21:7bdc8a87673d dummy change
   |
-  x  20:8b31da3c4919 dummy change
+  x  20:8b31da3c4919 dummy change (rewritten as 18:601db7a18f51)
   |
   o  19:b82fb57ea638 willconflict second version
   |
@@ -920,6 +987,208 @@
   rebasing 21:7bdc8a87673d "dummy change" (tip)
   $ cd ..
 
+Divergence cases due to obsolete changesets
+-------------------------------------------
+
+We should ignore branches with unstable changesets when they are based on an
+obsolete changeset which successor is in rebase set.
+
+  $ hg init divergence
+  $ cd divergence
+  $ cat >> .hg/hgrc << EOF
+  > [extensions]
+  > strip =
+  > [alias]
+  > strip = strip --no-backup --quiet
+  > [templates]
+  > instabilities = '{rev}:{node|short} {desc|firstline}{if(instabilities," ({instabilities})")}\n'
+  > EOF
+
+  $ hg debugdrawdag <<EOF
+  >   e   f
+  >   |   |
+  >   d'  d # replace: d -> d'
+  >    \ /
+  >     c
+  >     |
+  >   x b
+  >    \|
+  >     a
+  > EOF
+  $ hg log -G -r 'a'::
+  o  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
+  
+
+Changeset d and its descendants are excluded to avoid divergence of d, which
+would occur because the successor of d (d') is also in rebaseset. As a
+consequence f (descendant of d) is left behind.
+
+  $ hg rebase -b 'e' -d 'x'
+  rebasing 1:488e1b7e7341 "b" (b)
+  rebasing 3:a82ac2b38757 "c" (c)
+  rebasing 5:027ad6c5830d "d'" (d')
+  rebasing 6:d60ebfa0f1cb "e" (e)
+  note: not rebasing 4:76be324c128b "d" (d) and its descendants as this would cause divergence
+  $ hg log -G -r 'a'::
+  o  11:eb6d63fc4ed5 e
+  |
+  o  10:44d8c724a70c d'
+  |
+  o  9:d008e6b4d3fd c
+  |
+  o  8:67e8f4a16c49 b
+  |
+  | o  7:1143e9adc121 f
+  | |
+  | | x  6:d60ebfa0f1cb e (rewritten using rebase as 11:eb6d63fc4ed5)
+  | | |
+  | | x  5:027ad6c5830d d' (rewritten using rebase as 10:44d8c724a70c)
+  | | |
+  | x |  4:76be324c128b d (rewritten using replace as 5:027ad6c5830d)
+  | |/
+  | x  3:a82ac2b38757 c (rewritten using rebase as 9:d008e6b4d3fd)
+  | |
+  o |  2:630d7c95eff7 x
+  | |
+  | x  1:488e1b7e7341 b (rewritten using rebase as 8:67e8f4a16c49)
+  |/
+  o  0:b173517d0057 a
+  
+  $ hg strip -r 8:
+
+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.
+By allowing divergence, we can perform the rebase.
+
+  $ hg rebase -r 'c'::'f' -d 'x'
+  abort: this rebase will cause divergences from: 76be324c128b
+  (to force the rebase please set experimental.evolution.allowdivergence=True)
+  [255]
+  $ hg rebase --config experimental.evolution.allowdivergence=true -r 'c'::'f' -d 'x'
+  rebasing 3:a82ac2b38757 "c" (c)
+  rebasing 4:76be324c128b "d" (d)
+  rebasing 7:1143e9adc121 "f" (f tip)
+  $ hg log -G -r 'a':: -T instabilities
+  o  10:e1744ea07510 f
+  |
+  o  9:e2b36ea9a0a0 d (content-divergent)
+  |
+  o  8:6a0376de376e c
+  |
+  | x  7:1143e9adc121 f
+  | |
+  | | o  6:d60ebfa0f1cb e (orphan)
+  | | |
+  | | o  5:027ad6c5830d d' (orphan content-divergent)
+  | | |
+  | x |  4:76be324c128b d
+  | |/
+  | x  3:a82ac2b38757 c
+  | |
+  o |  2:630d7c95eff7 x
+  | |
+  | o  1:488e1b7e7341 b
+  |/
+  o  0:b173517d0057 a
+  
+  $ hg strip -r 8:
+
+(Not skipping obsoletes means that divergence is allowed.)
+
+  $ hg rebase --config experimental.rebaseskipobsolete=false -r 'c'::'f' -d 'x'
+  rebasing 3:a82ac2b38757 "c" (c)
+  rebasing 4:76be324c128b "d" (d)
+  rebasing 7:1143e9adc121 "f" (f tip)
+
+  $ hg strip -r 0:
+
+Similar test on a more complex graph
+
+  $ hg debugdrawdag <<EOF
+  >       g
+  >       |
+  >   f   e
+  >   |   |
+  >   e'  d # replace: e -> e'
+  >    \ /
+  >     c
+  >     |
+  >   x b
+  >    \|
+  >     a
+  > EOF
+  $ hg log -G -r 'a':
+  o  8:2876ce66c6eb g
+  |
+  | o  7:3ffec603ab53 f
+  | |
+  x |  6:e36fae928aec e (rewritten using replace as 5:63324dc512ea)
+  | |
+  | o  5:63324dc512ea e'
+  | |
+  o |  4:76be324c128b d
+  |/
+  o  3:a82ac2b38757 c
+  |
+  | o  2:630d7c95eff7 x
+  | |
+  o |  1:488e1b7e7341 b
+  |/
+  o  0:b173517d0057 a
+  
+  $ hg rebase -b 'f' -d 'x'
+  rebasing 1:488e1b7e7341 "b" (b)
+  rebasing 3:a82ac2b38757 "c" (c)
+  rebasing 5:63324dc512ea "e'" (e')
+  rebasing 7:3ffec603ab53 "f" (f)
+  rebasing 4:76be324c128b "d" (d)
+  note: not rebasing 6:e36fae928aec "e" (e) and its descendants as this would cause divergence
+  $ hg log -G -r 'a':
+  o  13:a1707a5b7c2c d
+  |
+  | o  12:ef6251596616 f
+  | |
+  | o  11:b6f172e64af9 e'
+  |/
+  o  10:d008e6b4d3fd c
+  |
+  o  9:67e8f4a16c49 b
+  |
+  | o  8:2876ce66c6eb g
+  | |
+  | | x  7:3ffec603ab53 f (rewritten using rebase as 12:ef6251596616)
+  | | |
+  | x |  6:e36fae928aec e (rewritten using replace as 5:63324dc512ea)
+  | | |
+  | | x  5:63324dc512ea e' (rewritten using rebase as 11:b6f172e64af9)
+  | | |
+  | x |  4:76be324c128b d (rewritten using rebase as 13:a1707a5b7c2c)
+  | |/
+  | x  3:a82ac2b38757 c (rewritten using rebase as 10:d008e6b4d3fd)
+  | |
+  o |  2:630d7c95eff7 x
+  | |
+  | x  1:488e1b7e7341 b (rewritten using rebase as 9:67e8f4a16c49)
+  |/
+  o  0:b173517d0057 a
+  
+
+  $ cd ..
+
 Rebase merge where successor of one parent is equal to destination (issue5198)
 
   $ hg init p1-succ-is-dest
@@ -939,11 +1208,11 @@
   $ hg log -G
   o    5:50e9d60b99c6 F
   |\
-  | | x  4:66f1a38021c9 F
+  | | x  4:66f1a38021c9 F (rewritten using rebase as 5:50e9d60b99c6)
   | |/|
   | o |  3:7fb047a69f22 E
   | | |
-  | | x  2:b18e25de2cf5 D
+  | | x  2:b18e25de2cf5 D (rewritten using replace as 1:112478962961)
   | |/
   o |  1:112478962961 B
   |/
@@ -970,9 +1239,9 @@
   $ hg log -G
   o    5:aae1787dacee F
   |\
-  | | x  4:66f1a38021c9 F
+  | | x  4:66f1a38021c9 F (rewritten using rebase as 5:aae1787dacee)
   | |/|
-  | | x  3:7fb047a69f22 E
+  | | x  3:7fb047a69f22 E (rewritten using replace as 1:112478962961)
   | | |
   | o |  2:b18e25de2cf5 D
   | |/
@@ -1002,13 +1271,13 @@
   $ hg log -G
   o    6:0913febf6439 F
   |\
-  +---x  5:66f1a38021c9 F
+  +---x  5:66f1a38021c9 F (rewritten using rebase as 6:0913febf6439)
   | | |
   | o |  4:26805aba1e60 C
   | | |
   o | |  3:7fb047a69f22 E
   | | |
-  +---x  2:b18e25de2cf5 D
+  +---x  2:b18e25de2cf5 D (rewritten using replace as 1:112478962961)
   | |
   | o  1:112478962961 B
   |/
@@ -1035,11 +1304,11 @@
   $ hg log -G
   o    6:c6ab0cc6d220 F
   |\
-  +---x  5:66f1a38021c9 F
+  +---x  5:66f1a38021c9 F (rewritten using rebase as 6:c6ab0cc6d220)
   | | |
   | o |  4:26805aba1e60 C
   | | |
-  | | x  3:7fb047a69f22 E
+  | | x  3:7fb047a69f22 E (rewritten using replace as 1:112478962961)
   | | |
   o---+  2:b18e25de2cf5 D
    / /
@@ -1070,13 +1339,13 @@
   $ hg log -G
   o  6:8f47515dda15 D
   |
-  | x    5:66f1a38021c9 F
+  | x    5:66f1a38021c9 F (pruned using rebase)
   | |\
   o | |  4:26805aba1e60 C
   | | |
-  | | x  3:7fb047a69f22 E
+  | | x  3:7fb047a69f22 E (rewritten using replace as 1:112478962961)
   | | |
-  | x |  2:b18e25de2cf5 D
+  | x |  2:b18e25de2cf5 D (rewritten using rebase as 6:8f47515dda15)
   | |/
   o /  1:112478962961 B
   |/
@@ -1106,13 +1375,13 @@
   $ hg log -G
   o  6:533690786a86 E
   |
-  | x    5:66f1a38021c9 F
+  | x    5:66f1a38021c9 F (pruned using rebase)
   | |\
   o | |  4:26805aba1e60 C
   | | |
-  | | x  3:7fb047a69f22 E
+  | | x  3:7fb047a69f22 E (rewritten using rebase as 6:533690786a86)
   | | |
-  | x |  2:b18e25de2cf5 D
+  | x |  2:b18e25de2cf5 D (rewritten using replace as 1:112478962961)
   | |/
   o /  1:112478962961 B
   |/
@@ -1224,7 +1493,7 @@
   $ hg book -r 2 mybook --hidden  # rev 2 has a bookmark on it now
   $ hg up 2 && hg log -r .  # working dir is at rev 2 again
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  2:1e9a3c00cbe9 b (no-eol)
+  2:1e9a3c00cbe9 b (rewritten using rebase as 3:be1832deae9a) (no-eol)
   $ hg rebase -r 2 -d 3 --config experimental.evolution.track-operation=1
   note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b" (tip)
 Check that working directory and bookmark was updated to rev 3 although rev 2
@@ -1234,7 +1503,7 @@
   $ hg bookmarks
      mybook                    3:be1832deae9a
   $ hg debugobsolete --rev tip
-  1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
 
 Obsoleted working parent and bookmark could be moved if an ancestor of working
 parent gets moved:
@@ -1338,7 +1607,7 @@
   $ hg log -G
   @  2:b18e25de2cf5 D
   |
-  | @  1:2ec65233581b B
+  | @  1:2ec65233581b B (pruned using prune)
   |/
   o  0:426bada5c675 A
   
--- a/tests/test-rebase-templates.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-rebase-templates.t	Mon Dec 04 11:28:29 2017 -0500
@@ -42,3 +42,16 @@
   
   $ hg rebase -s 1 -d 5 -q -T "{nodechanges|json}"
   {"29becc82797a4bc11ec8880b58eaecd2ab3e7760": ["d9d6773efc831c274eace04bc13e8e6412517139"]} (no-eol)
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  o  6:d9d6773efc83 Added b
+  |
+  @  5:df21b32134ba Added d
+  |
+  o  4:849767420fd5 Added c
+  |
+  o  0:18d04c59bb5d Added a
+  
+
+  $ hg rebase -s 6 -d 4 -q -T "{nodechanges % '{oldnode}:{newnodes % ' {node} '}'}"
+  d9d6773efc831c274eace04bc13e8e6412517139: f48cd65c6dc3d2acb55da54402a5b029546e546f  (no-eol)
--- a/tests/test-remove.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-remove.t	Mon Dec 04 11:28:29 2017 -0500
@@ -189,9 +189,9 @@
                                                               \r (no-eol) (esc)
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-21 state clean, options -A
+21 state clean, options -Av
 
-  $ remove -A foo
+  $ remove -Av foo
   \r (no-eol) (esc)
   deleting [===========================================>] 1/1\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
@@ -205,10 +205,10 @@
   ./foo
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-22 state modified, options -A
+22 state modified, options -Av
 
   $ echo b >> foo
-  $ remove -A foo
+  $ remove -Av foo
   \r (no-eol) (esc)
   deleting [===========================================>] 1/1\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
@@ -357,9 +357,32 @@
                                                               \r (no-eol) (esc)
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-dir, options -A
+dir, options -Av
 
   $ rm test/bar
+  $ remove -Av test
+  \r (no-eol) (esc)
+  deleting [===========================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  skipping [===========================================>] 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)
+  removing test/bar (glob)
+  not removing test/foo: file still exists (glob)
+  exit code: 1
+  R test/bar
+  ./foo
+  ./test/foo
+  \r (no-eol) (esc)
+  updating [===========================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+dir, options -A <dir>
+  $ rm test/bar
   $ remove -A test
   \r (no-eol) (esc)
   deleting [===========================================>] 1/1\r (no-eol) (esc)
@@ -371,7 +394,26 @@
   deleting [===========================================>] 1/1\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   removing test/bar (glob)
-  not removing test/foo: file still exists (glob)
+  exit code: 1
+  R test/bar
+  ./foo
+  ./test/foo
+  \r (no-eol) (esc)
+  updating [===========================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+without any files/dirs, options -A
+  $ rm test/bar
+  $ remove -A
+  \r (no-eol) (esc)
+  skipping [=====================>                      ] 1/2\r (no-eol) (esc)
+  skipping [===========================================>] 2/2\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  deleting [===========================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  removing test/bar (glob)
   exit code: 1
   R test/bar
   ./foo
--- a/tests/test-revert-interactive.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-revert-interactive.t	Mon Dec 04 11:28:29 2017 -0500
@@ -60,56 +60,56 @@
   2 hunks, 2 lines changed
   examine changes to 'f'? [Ynesfdaq?] y
   
-  @@ -1,5 +1,6 @@
-  +a
+  @@ -1,6 +1,5 @@
+  -a
    1
    2
    3
    4
    5
-  revert change 1/6 to 'f'? [Ynesfdaq?] y
+  apply change 1/6 to 'f'? [Ynesfdaq?] y
   
-  @@ -1,5 +2,6 @@
+  @@ -2,6 +1,5 @@
    1
    2
    3
    4
    5
-  +b
-  revert change 2/6 to 'f'? [Ynesfdaq?] y
+  -b
+  apply change 2/6 to 'f'? [Ynesfdaq?] y
   
   diff --git a/folder1/g b/folder1/g
   2 hunks, 2 lines changed
   examine changes to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -1,5 +1,6 @@
-  +c
+  @@ -1,6 +1,5 @@
+  -c
    1
    2
    3
    4
    5
-  revert change 3/6 to 'folder1/g'? [Ynesfdaq?] ?
+  apply change 3/6 to 'folder1/g'? [Ynesfdaq?] ?
   
-  y - yes, revert this change
+  y - yes, apply this change
   n - no, skip this change
   e - edit this change manually
   s - skip remaining changes to this file
-  f - revert remaining changes to this file
+  f - apply remaining changes to this file
   d - done, skip remaining changes and files
-  a - revert all changes to all remaining files
-  q - quit, reverting no changes
+  a - apply all changes to all remaining files
+  q - quit, applying no changes
   ? - ? (display help)
-  revert change 3/6 to 'folder1/g'? [Ynesfdaq?] y
+  apply change 3/6 to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -1,5 +2,6 @@
+  @@ -2,6 +1,5 @@
    1
    2
    3
    4
    5
-  +d
-  revert change 4/6 to 'folder1/g'? [Ynesfdaq?] n
+  -d
+  apply change 4/6 to 'folder1/g'? [Ynesfdaq?] n
   
   diff --git a/folder2/h b/folder2/h
   2 hunks, 2 lines changed
@@ -157,12 +157,12 @@
   1 hunks, 1 lines changed
   examine changes to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -3,3 +3,4 @@
+  @@ -3,4 +3,3 @@
    3
    4
    5
-  +d
-  revert this change to 'folder1/g'? [Ynesfdaq?] n
+  -d
+  apply this change to 'folder1/g'? [Ynesfdaq?] n
   
   $ ls folder1/
   g
@@ -173,12 +173,12 @@
   1 hunks, 1 lines changed
   examine changes to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -3,3 +3,4 @@
+  @@ -3,4 +3,3 @@
    3
    4
    5
-  +d
-  revert this change to 'folder1/g'? [Ynesfdaq?] y
+  -d
+  apply this change to 'folder1/g'? [Ynesfdaq?] y
   
   $ ls folder1/
   g
@@ -206,45 +206,45 @@
   2 hunks, 2 lines changed
   examine changes to 'f'? [Ynesfdaq?] y
   
-  @@ -1,5 +1,6 @@
-  +a
+  @@ -1,6 +1,5 @@
+  -a
    1
    2
    3
    4
    5
-  revert change 1/6 to 'f'? [Ynesfdaq?] y
+  apply change 1/6 to 'f'? [Ynesfdaq?] y
   
-  @@ -1,5 +2,6 @@
+  @@ -2,6 +1,5 @@
    1
    2
    3
    4
    5
-  +b
-  revert change 2/6 to 'f'? [Ynesfdaq?] y
+  -b
+  apply change 2/6 to 'f'? [Ynesfdaq?] y
   
   diff --git a/folder1/g b/folder1/g
   2 hunks, 2 lines changed
   examine changes to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -1,5 +1,6 @@
-  +c
+  @@ -1,6 +1,5 @@
+  -c
    1
    2
    3
    4
    5
-  revert change 3/6 to 'folder1/g'? [Ynesfdaq?] y
+  apply change 3/6 to 'folder1/g'? [Ynesfdaq?] y
   
-  @@ -1,5 +2,6 @@
+  @@ -2,6 +1,5 @@
    1
    2
    3
    4
    5
-  +d
-  revert change 4/6 to 'folder1/g'? [Ynesfdaq?] n
+  -d
+  apply change 4/6 to 'folder1/g'? [Ynesfdaq?] n
   
   diff --git a/folder2/h b/folder2/h
   2 hunks, 2 lines changed
@@ -368,77 +368,6 @@
   $ cat k
   42
 
-Check the experimental config to invert the selection:
-  $ cat <<EOF >> $HGRCPATH
-  > [experimental]
-  > revertalternateinteractivemode=False
-  > EOF
-
-
-  $ hg up -C .
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ printf 'firstline\nc\n1\n2\n3\n 3\n5\nd\nlastline\n' > folder1/g
-  $ hg diff --nodates
-  diff -r a3d963a027aa folder1/g
-  --- a/folder1/g
-  +++ b/folder1/g
-  @@ -1,7 +1,9 @@
-  +firstline
-   c
-   1
-   2
-   3
-  -4
-  + 3
-   5
-   d
-  +lastline
-  $ hg revert -i <<EOF
-  > y
-  > y
-  > y
-  > n
-  > EOF
-  reverting folder1/g (glob)
-  diff --git a/folder1/g b/folder1/g
-  3 hunks, 3 lines changed
-  examine changes to 'folder1/g'? [Ynesfdaq?] y
-  
-  @@ -1,4 +1,5 @@
-  +firstline
-   c
-   1
-   2
-   3
-  discard change 1/3 to 'folder1/g'? [Ynesfdaq?] y
-  
-  @@ -1,7 +2,7 @@
-   c
-   1
-   2
-   3
-  -4
-  + 3
-   5
-   d
-  discard change 2/3 to 'folder1/g'? [Ynesfdaq?] y
-  
-  @@ -6,2 +7,3 @@
-   5
-   d
-  +lastline
-  discard change 3/3 to 'folder1/g'? [Ynesfdaq?] n
-  
-  $ hg diff --nodates
-  diff -r a3d963a027aa folder1/g
-  --- a/folder1/g
-  +++ b/folder1/g
-  @@ -5,3 +5,4 @@
-   4
-   5
-   d
-  +lastline
-
   $ hg update -C .
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg purge
@@ -463,11 +392,6 @@
 
 When a line without EOL is selected during "revert -i" (issue5651)
 
-  $ cat <<EOF >> $HGRCPATH
-  > [experimental]
-  > %unset revertalternateinteractivemode
-  > EOF
-
   $ hg init $TESTTMP/revert-i-eol
   $ cd $TESTTMP/revert-i-eol
   $ echo 0 > a
@@ -487,11 +411,11 @@
   1 hunks, 1 lines changed
   examine changes to 'a'? [Ynesfdaq?] y
   
-  @@ -1,1 +1,2 @@
+  @@ -1,2 +1,1 @@
    0
-  +1
+  -1
   \ No newline at end of file
-  revert this change to 'a'? [Ynesfdaq?] y
+  apply this change to 'a'? [Ynesfdaq?] y
   
   $ cat a
   0
--- a/tests/test-run-tests.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-run-tests.t	Mon Dec 04 11:28:29 2017 -0500
@@ -32,8 +32,7 @@
 #if execbit
   $ touch hg
   $ run-tests.py --with-hg=./hg
-  Usage: run-tests.py [options] [tests]
-  
+  usage: run-tests.py [options] [tests]
   run-tests.py: error: --with-hg must specify an executable hg script
   [2]
   $ rm hg
@@ -98,19 +97,22 @@
 
 test churn with globs
   $ cat > test-failure.t <<EOF
-  >   $ echo "bar-baz"; echo "bar-bad"
+  >   $ echo "bar-baz"; echo "bar-bad"; echo foo
   >   bar*bad (glob)
   >   bar*baz (glob)
+  >   | fo (re)
   > EOF
   $ rt test-failure.t
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
-  @@ -1,3 +1,3 @@
-     $ echo "bar-baz"; echo "bar-bad"
+  @@ -1,4 +1,4 @@
+     $ echo "bar-baz"; echo "bar-bad"; echo foo
   +  bar*baz (glob)
      bar*bad (glob)
   -  bar*baz (glob)
+  -  | fo (re)
+  +  foo
   
   ERROR: test-failure.t output changed
   !
@@ -126,11 +128,13 @@
   
   \x1b[38;5;124m--- $TESTTMP/test-failure.t\x1b[39m (esc)
   \x1b[38;5;34m+++ $TESTTMP/test-failure.t.err\x1b[39m (esc)
-  \x1b[38;5;90;01m@@ -1,3 +1,3 @@\x1b[39;00m (esc)
-     $ echo "bar-baz"; echo "bar-bad"
+  \x1b[38;5;90;01m@@ -1,4 +1,4 @@\x1b[39;00m (esc)
+     $ echo "bar-baz"; echo "bar-bad"; echo foo
   \x1b[38;5;34m+  bar*baz (glob)\x1b[39m (esc)
      bar*bad (glob)
   \x1b[38;5;124m-  bar*baz (glob)\x1b[39m (esc)
+  \x1b[38;5;124m-  | fo (re)\x1b[39m (esc)
+  \x1b[38;5;34m+  foo\x1b[39m (esc)
   
   \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
   !
@@ -145,11 +149,13 @@
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
-  @@ -1,3 +1,3 @@
-     $ echo "bar-baz"; echo "bar-bad"
+  @@ -1,4 +1,4 @@
+     $ echo "bar-baz"; echo "bar-bad"; echo foo
   +  bar*baz (glob)
      bar*bad (glob)
   -  bar*baz (glob)
+  -  | fo (re)
+  +  foo
   
   ERROR: test-failure.t output changed
   !
@@ -1261,6 +1267,58 @@
   .
   # Ran 1 tests, 0 skipped, 0 failed.
 
+support for automatically discovering test if arg is a folder
+  $ mkdir tmp && cd tmp
+
+  $ cat > test-uno.t << EOF
+  >   $ echo line
+  >   line
+  > EOF
+
+  $ cp test-uno.t test-dos.t
+  $ cd ..
+  $ cp -R tmp tmpp
+  $ cp tmp/test-uno.t test-solo.t
+
+  $ $PYTHON $TESTDIR/run-tests.py tmp/ test-solo.t tmpp
+  .....
+  # Ran 5 tests, 0 skipped, 0 failed.
+  $ rm -rf tmp tmpp
+
+support for running run-tests.py from another directory
+  $ mkdir tmp && cd tmp
+
+  $ cat > useful-file.sh << EOF
+  > important command
+  > EOF
+
+  $ cat > test-folder.t << EOF
+  >   $ cat \$TESTDIR/useful-file.sh
+  >   important command
+  > EOF
+
+  $ cat > test-folder-fail.t << EOF
+  >   $ cat \$TESTDIR/useful-file.sh
+  >   important commando
+  > EOF
+
+  $ cd ..
+  $ $PYTHON $TESTDIR/run-tests.py tmp/test-*.t
+  
+  --- $TESTTMP/anothertests/tmp/test-folder-fail.t
+  +++ $TESTTMP/anothertests/tmp/test-folder-fail.t.err
+  @@ -1,2 +1,2 @@
+     $ cat $TESTDIR/useful-file.sh
+  -  important commando
+  +  important command
+  
+  ERROR: test-folder-fail.t output changed
+  !.
+  Failed test-folder-fail.t: output changed
+  # Ran 2 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
 support for bisecting failed tests automatically
   $ hg init bisect
   $ cd bisect
@@ -1324,8 +1382,7 @@
   [1]
 
   $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
-  Usage: run-tests.py [options] [tests]
-  
+  usage: run-tests.py [options] [tests]
   run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
   [2]
 
@@ -1469,3 +1526,54 @@
   # Ran 2 tests, 0 skipped, 1 failed.
   python hash seed: * (glob)
   [1]
+
+Test automatic pattern replacement
+
+  $ cat << EOF >> common-pattern.py
+  > substitutions = [
+  >     (br'foo-(.*)\\b',
+  >      br'\$XXX=\\1\$'),
+  >     (br'bar\\n',
+  >      br'\$YYY$\\n'),
+  > ]
+  > EOF
+
+  $ cat << EOF >> test-substitution.t
+  >   $ echo foo-12
+  >   \$XXX=12$
+  >   $ echo foo-42
+  >   \$XXX=42$
+  >   $ echo bar prior
+  >   bar prior
+  >   $ echo lastbar
+  >   last\$YYY$
+  >   $ echo foo-bar foo-baz
+  > EOF
+
+  $ rt test-substitution.t
+  
+  --- $TESTTMP/anothertests/cases/test-substitution.t
+  +++ $TESTTMP/anothertests/cases/test-substitution.t.err
+  @@ -7,3 +7,4 @@
+     $ echo lastbar
+     last$YYY$
+     $ echo foo-bar foo-baz
+  +  $XXX=bar foo-baz$
+  
+  ERROR: test-substitution.t output changed
+  !
+  Failed test-substitution.t: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+--extra-config-opt works
+
+  $ cat << EOF >> test-config-opt.t
+  >   $ hg init test-config-opt
+  >   $ hg -R test-config-opt purge
+  > EOF
+
+  $ rt --extra-config-opt extensions.purge= test-config-opt.t
+  .
+  # Ran 1 tests, 0 skipped, 0 failed.
--- a/tests/test-setdiscovery.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-setdiscovery.t	Mon Dec 04 11:28:29 2017 -0500
@@ -350,9 +350,9 @@
   $ killdaemons.py
   $ cut -d' ' -f6- access.log | grep -v cmd=known # cmd=known uses random sampling
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D513314ca8b3ae4dac8eec56966265b00fcf866db x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D513314ca8b3ae4dac8eec56966265b00fcf866db x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:$USUAL_BUNDLE_CAPS$&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   $ cat errors.log
 
   $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-single-head.t	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,203 @@
+=====================
+Test workflow options
+=====================
+
+  $ . "$TESTDIR/testlib/obsmarker-common.sh"
+
+Test single head enforcing - Setup
+=============================================
+
+  $ cat << EOF >> $HGRCPATH
+  > [experimental]
+  > evolution = all
+  > EOF
+  $ hg init single-head-server
+  $ cd single-head-server
+  $ cat <<EOF >> .hg/hgrc
+  > [phases]
+  > publish = no
+  > [experimental]
+  > single-head-per-branch = yes
+  > EOF
+  $ mkcommit ROOT
+  $ mkcommit c_dA0
+  $ cd ..
+
+  $ hg clone single-head-server client
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Test single head enforcing - with branch only
+---------------------------------------------
+
+  $ cd client
+
+continuing the current defaultbranch
+
+  $ mkcommit c_dB0
+  $ hg push
+  pushing to $TESTTMP/single-head-server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+creating a new branch
+
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg branch branch_A
+  marked working directory as branch branch_A
+  (branches are permanent and global, did you want a bookmark?)
+  $ mkcommit c_aC0
+  $ hg push --new-branch
+  pushing to $TESTTMP/single-head-server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+
+Create a new head on the default branch
+
+  $ hg up 'desc("c_dA0")'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_dD0
+  created new head
+  $ hg push -f
+  pushing to $TESTTMP/single-head-server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  transaction abort!
+  rollback completed
+  abort: rejecting multiple heads on branch "default"
+  (2 heads: 286d02a6e2a2 9bf953aa81f6)
+  [255]
+
+remerge them
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ mkcommit c_dE0
+  $ hg push
+  pushing to $TESTTMP/single-head-server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+
+Test single head enforcing - after rewrite
+------------------------------------------
+
+  $ mkcommit c_dF0
+  $ hg push
+  pushing to $TESTTMP/single-head-server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  $ hg commit --amend -m c_dF1
+  $ hg push
+  pushing to $TESTTMP/single-head-server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  obsoleted 1 changesets
+
+Check it does to interfer with strip
+------------------------------------
+
+setup
+
+  $ hg branch branch_A --force
+  marked working directory as branch branch_A
+  $ mkcommit c_aG0
+  created new head
+  $ hg update 'desc("c_dF1")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_dH0
+  $ hg update 'desc("c_aG0")'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ mkcommit c_aI0
+  $ hg log -G
+  @    changeset:   10:49003e504178
+  |\   branch:      branch_A
+  | |  tag:         tip
+  | |  parent:      8:a33fb808fb4b
+  | |  parent:      3:840af1c6bc88
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     c_aI0
+  | |
+  | | o  changeset:   9:fe47ea669cea
+  | | |  parent:      7:99a2dc242c5d
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     c_dH0
+  | | |
+  | o |  changeset:   8:a33fb808fb4b
+  | |/   branch:      branch_A
+  | |    user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     c_aG0
+  | |
+  | o  changeset:   7:99a2dc242c5d
+  | |  parent:      5:6ed1df20edb1
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     c_dF1
+  | |
+  | o    changeset:   5:6ed1df20edb1
+  | |\   parent:      4:9bf953aa81f6
+  | | |  parent:      2:286d02a6e2a2
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     c_dE0
+  | | |
+  | | o  changeset:   4:9bf953aa81f6
+  | | |  parent:      1:134bc3852ad2
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     c_dD0
+  | | |
+  o | |  changeset:   3:840af1c6bc88
+  | | |  branch:      branch_A
+  | | |  parent:      0:ea207398892e
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | | |  summary:     c_aC0
+  | | |
+  | o |  changeset:   2:286d02a6e2a2
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    summary:     c_dB0
+  | |
+  | o  changeset:   1:134bc3852ad2
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     c_dA0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+actual stripping
+
+  $ hg strip --config extensions.strip= --rev 'desc("c_dH0")'
+  saved backup bundle to $TESTTMP/client/.hg/strip-backup/fe47ea669cea-a41bf5a9-backup.hg (glob)
+
--- a/tests/test-ssh-bundle1.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-ssh-bundle1.t	Mon Dec 04 11:28:29 2017 -0500
@@ -468,7 +468,7 @@
   sending hello command
   sending between command
   remote: 372
-  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS$ unbundle=HG10GZ,HG10BZ,HG10UN
   remote: 1
   preparing listkeys for "bookmarks"
   sending listkeys command
--- a/tests/test-ssh.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-ssh.t	Mon Dec 04 11:28:29 2017 -0500
@@ -484,7 +484,7 @@
   sending hello command
   sending between command
   remote: 372
-  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS$ unbundle=HG10GZ,HG10BZ,HG10UN
   remote: 1
   query 1; heads
   sending batch command
@@ -578,3 +578,19 @@
   remote: abort: this is an exercise
   abort: pull failed on remote
   [255]
+
+abort with no error hint when there is a ssh problem when pulling
+
+  $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\""
+  pulling from ssh://brokenrepository/
+  abort: no suitable response from remote hg!
+  [255]
+
+abort with configured error hint when there is a ssh problem when pulling
+
+  $ hg pull ssh://brokenrepository -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" \
+  > --config ui.ssherrorhint="Please see http://company/internalwiki/ssh.html"
+  pulling from ssh://brokenrepository/
+  abort: no suitable response from remote hg!
+  (Please see http://company/internalwiki/ssh.html)
+  [255]
--- a/tests/test-status-color.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-status-color.t	Mon Dec 04 11:28:29 2017 -0500
@@ -29,6 +29,22 @@
   [status.unknown|? ][status.unknown|b/2/in_b_2]
   [status.unknown|? ][status.unknown|b/in_b]
   [status.unknown|? ][status.unknown|in_root]
+HGPLAIN disables color
+  $ HGPLAIN=1 hg status --color=debug
+  ? a/1/in_a_1 (glob)
+  ? a/in_a (glob)
+  ? b/1/in_b_1 (glob)
+  ? b/2/in_b_2 (glob)
+  ? b/in_b (glob)
+  ? in_root
+HGPLAINEXCEPT=color does not disable color
+  $ HGPLAINEXCEPT=color hg status --color=debug
+  [status.unknown|? ][status.unknown|a/1/in_a_1] (glob)
+  [status.unknown|? ][status.unknown|a/in_a] (glob)
+  [status.unknown|? ][status.unknown|b/1/in_b_1] (glob)
+  [status.unknown|? ][status.unknown|b/2/in_b_2] (glob)
+  [status.unknown|? ][status.unknown|b/in_b] (glob)
+  [status.unknown|? ][status.unknown|in_root]
 
 hg status with template
   $ hg status -T "{label('red', path)}\n" --color=debug
--- a/tests/test-status.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-status.t	Mon Dec 04 11:28:29 2017 -0500
@@ -572,6 +572,10 @@
   $ hg st --config ui.statuscopies=false
   M a
   R b
+  $ hg st --config ui.tweakdefaults=yes
+  M a
+    b
+  R b
 
 using log status template (issue5155)
   $ hg log -Tstatus -r 'wdir()' -C
--- a/tests/test-strip.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-strip.t	Mon Dec 04 11:28:29 2017 -0500
@@ -1307,13 +1307,13 @@
   o  0:426bada5c675 A b-B b-C b-I
   
   $ hg debugobsolete
-  1fc8102cda6204549f031015641606ccf5513ec3 1473d4b996d1d1b121de6b39fab6a04fbf9d873e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
-  64a8289d249234b9886244d379f15e6b650b28e3 d11b3456a873daec7c7bc53e5622e8df6d741bd2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
-  f585351a92f85104bff7c284233c338b10eb1df7 7c78f703e465d73102cc8780667ce269c5208a40 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
-  48b9aae0607f43ff110d84e6883c151942add5ab 0 {0000000000000000000000000000000000000000} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
-  112478962961147124edd43549aedd1a335e44bf 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
-  08ebfeb61bac6e3f12079de774d285a0d6689eba 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
-  26805aba1e600a82e93661149f2313866a221a7b 0 {112478962961147124edd43549aedd1a335e44bf} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
+  1fc8102cda6204549f031015641606ccf5513ec3 1473d4b996d1d1b121de6b39fab6a04fbf9d873e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'replace', 'user': 'test'}
+  64a8289d249234b9886244d379f15e6b650b28e3 d11b3456a873daec7c7bc53e5622e8df6d741bd2 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '13', 'operation': 'replace', 'user': 'test'}
+  f585351a92f85104bff7c284233c338b10eb1df7 7c78f703e465d73102cc8780667ce269c5208a40 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'operation': 'replace', 'user': 'test'}
+  48b9aae0607f43ff110d84e6883c151942add5ab 0 {0000000000000000000000000000000000000000} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'replace', 'user': 'test'}
+  112478962961147124edd43549aedd1a335e44bf 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'replace', 'user': 'test'}
+  08ebfeb61bac6e3f12079de774d285a0d6689eba 0 {426bada5c67598ca65036d57d9e4b64b0c1ce7a0} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'replace', 'user': 'test'}
+  26805aba1e600a82e93661149f2313866a221a7b 0 {112478962961147124edd43549aedd1a335e44bf} (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '0', 'operation': 'replace', 'user': 'test'}
   $ cd ..
 
 Test that obsmarkers are restored even when not using generaldelta
@@ -1328,11 +1328,11 @@
   $ hg ci -Aqm a
   $ hg ci --amend -m a2
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
   $ hg strip .
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/489bac576828-bef27e14-backup.hg (glob)
   $ hg unbundle -q .hg/strip-backup/*
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
   $ cd ..
--- a/tests/test-subrepo-git.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-subrepo-git.t	Mon Dec 04 11:28:29 2017 -0500
@@ -847,8 +847,8 @@
 the output contains a regex, because git 1.7.10 and 1.7.11
  change the amount of whitespace
   $ hg diff --subrepos --stat
-  \s*barfoo |\s*1 + (re)
-  \s*foobar |\s*2 +- (re)
+  \s*barfoo \|\s+1 \+ (re)
+  \s*foobar \|\s+2 \+- (re)
    2 files changed, 2 insertions\(\+\), 1 deletions?\(-\) (re)
 
 adding an include should ignore the other elements
--- a/tests/test-treediscovery.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-treediscovery.t	Mon Dec 04 11:28:29 2017 -0500
@@ -516,35 +516,35 @@
 #if zstd
   $ tstop show
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961+2c8d5d5ec612be65cdfdeac78b7662ab1696324a x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961+2c8d5d5ec612be65cdfdeac78b7662ab1696324a x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+1827a5bb63e602382eb89dd58f2ac9f3b007ad91* (glob)
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
 #else
   $ tstop show
   "GET /?cmd=capabilities HTTP/1.1" 200 -
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-unamend.t	Mon Dec 04 11:28:29 2017 -0500
@@ -0,0 +1,366 @@
+Test for command `hg unamend` which lives in uncommit extension
+===============================================================
+
+  $ cat >> $HGRCPATH << EOF
+  > [alias]
+  > glog = log -G -T '{rev}:{node|short}  {desc}'
+  > [experimental]
+  > evolution = createmarkers, allowunstable
+  > [extensions]
+  > rebase =
+  > amend =
+  > uncommit =
+  > EOF
+
+Repo Setup
+
+  $ hg init repo
+  $ cd repo
+  $ for ch in a b c d e f g h; do touch $ch; echo "foo" >> $ch; hg ci -Aqm "Added "$ch; done
+
+  $ hg glog
+  @  7:ec2426147f0e  Added h
+  |
+  o  6:87d6d6676308  Added g
+  |
+  o  5:825660c69f0c  Added f
+  |
+  o  4:aa98ab95a928  Added e
+  |
+  o  3:62615734edd5  Added d
+  |
+  o  2:28ad74487de9  Added c
+  |
+  o  1:29becc82797a  Added b
+  |
+  o  0:18d04c59bb5d  Added a
+  
+Trying to unamend when there was no amend done
+
+  $ hg unamend
+  abort: changeset must have one predecessor, found 0 predecessors
+  [255]
+
+Unamend on clean wdir and tip
+
+  $ echo "bar" >> h
+  $ hg amend
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID c9fa1a715c1b7661c0fafb362a9f30bd75878d7d
+  # Parent  87d6d66763085b629e6d7ed56778c79827273022
+  Added h
+  
+  diff -r 87d6d6676308 -r c9fa1a715c1b h
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/h	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,2 @@
+  +foo
+  +bar
+
+  $ hg glog --hidden
+  @  8:c9fa1a715c1b  Added h
+  |
+  | x  7:ec2426147f0e  Added h
+  |/
+  o  6:87d6d6676308  Added g
+  |
+  o  5:825660c69f0c  Added f
+  |
+  o  4:aa98ab95a928  Added e
+  |
+  o  3:62615734edd5  Added d
+  |
+  o  2:28ad74487de9  Added c
+  |
+  o  1:29becc82797a  Added b
+  |
+  o  0:18d04c59bb5d  Added a
+  
+  $ hg unamend
+  $ hg glog --hidden
+  @  9:46d02d47eec6  Added h
+  |
+  | x  8:c9fa1a715c1b  Added h
+  |/
+  | x  7:ec2426147f0e  Added h
+  |/
+  o  6:87d6d6676308  Added g
+  |
+  o  5:825660c69f0c  Added f
+  |
+  o  4:aa98ab95a928  Added e
+  |
+  o  3:62615734edd5  Added d
+  |
+  o  2:28ad74487de9  Added c
+  |
+  o  1:29becc82797a  Added b
+  |
+  o  0:18d04c59bb5d  Added a
+  
+  $ hg diff
+  diff -r 46d02d47eec6 h
+  --- a/h	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/h	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,2 @@
+   foo
+  +bar
+
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 46d02d47eec6ca096b8dcab3f8f5579c40c3dd9a
+  # Parent  87d6d66763085b629e6d7ed56778c79827273022
+  Added h
+  
+  diff -r 87d6d6676308 -r 46d02d47eec6 h
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/h	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +foo
+
+  $ hg status
+  M h
+
+  $ hg log -r . -T '{extras % "{extra}\n"}' --config alias.log=log
+  branch=default
+  unamend_source=c9fa1a715c1b7661c0fafb362a9f30bd75878d7d
+
+Using unamend to undo an unamed (intentional)
+
+  $ hg unamend
+  $ hg exp
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 850ddfc1bc662997ec6094ada958f01f0cc8070a
+  # Parent  87d6d66763085b629e6d7ed56778c79827273022
+  Added h
+  
+  diff -r 87d6d6676308 -r 850ddfc1bc66 h
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/h	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,2 @@
+  +foo
+  +bar
+  $ hg diff
+
+Unamend on a dirty working directory
+
+  $ echo "bar" >> a
+  $ hg amend
+  $ echo "foobar" >> a
+  $ echo "bar" >> b
+  $ hg status
+  M a
+  M b
+
+  $ hg unamend
+
+  $ hg status
+  M a
+  M b
+
+  $ hg diff
+  diff -r ec338db45d51 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,3 @@
+   foo
+  +bar
+  +foobar
+  diff -r ec338db45d51 b
+  --- a/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,2 @@
+   foo
+  +bar
+
+Unamending an added file
+
+  $ hg ci -m "Added things to a and b"
+  $ echo foo > bar
+  $ hg add bar
+  $ hg amend
+
+  $ hg unamend
+  $ hg status
+  A bar
+
+  $ hg revert --all
+  forgetting bar
+
+Unamending a removed file
+
+  $ hg remove a
+  $ hg amend
+
+  $ hg unamend
+  $ hg status
+  R a
+  ? bar
+
+  $ hg revert --all
+  undeleting a
+
+Unamending an added file with dirty wdir status
+
+  $ hg add bar
+  $ hg amend
+  $ echo bar >> bar
+  $ hg status
+  M bar
+
+  $ hg unamend
+  $ hg status
+  A bar
+  $ hg diff
+  diff -r 7f79409af972 bar
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/bar	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,2 @@
+  +foo
+  +bar
+
+  $ hg revert --all
+  forgetting bar
+
+Unamending in middle of a stack
+
+  $ hg glog
+  @  19:7f79409af972  Added things to a and b
+  |
+  o  12:ec338db45d51  Added h
+  |
+  o  6:87d6d6676308  Added g
+  |
+  o  5:825660c69f0c  Added f
+  |
+  o  4:aa98ab95a928  Added e
+  |
+  o  3:62615734edd5  Added d
+  |
+  o  2:28ad74487de9  Added c
+  |
+  o  1:29becc82797a  Added b
+  |
+  o  0:18d04c59bb5d  Added a
+  
+  $ hg up 5
+  2 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo bar >> f
+  $ hg amend
+  $ hg rebase -s 6 -d . -q
+
+  $ hg glog
+  o  23:03ddd6fc5af1  Added things to a and b
+  |
+  o  22:3e7b64ee157b  Added h
+  |
+  o  21:49635b68477e  Added g
+  |
+  @  20:93f0e8ffab32  Added f
+  |
+  o  4:aa98ab95a928  Added e
+  |
+  o  3:62615734edd5  Added d
+  |
+  o  2:28ad74487de9  Added c
+  |
+  o  1:29becc82797a  Added b
+  |
+  o  0:18d04c59bb5d  Added a
+  
+
+  $ hg unamend
+  abort: cannot unamend a changeset with children
+  [255]
+
+Trying to unamend a public changeset
+
+  $ hg up
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg phase -r . -p
+  $ hg unamend
+  abort: cannot unamend public changesets
+  [255]
+
+Testing whether unamend retains copies or not
+
+  $ hg status
+  ? bar
+
+  $ hg mv a foo
+
+  $ hg ci -m "Moved a to foo"
+  $ hg exp --git
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID cfef290346fbee5126313d7e1aab51d877679b09
+  # Parent  03ddd6fc5af19e028c44a2fd6d790dd22712f231
+  Moved a to foo
+  
+  diff --git a/a b/foo
+  rename from a
+  rename to foo
+
+  $ hg mv b foobar
+  $ hg diff --git
+  diff --git a/b b/foobar
+  rename from b
+  rename to foobar
+  $ hg amend
+
+  $ hg exp --git
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID eca050985275bb271ce3092b54e56ea5c85d29a3
+  # Parent  03ddd6fc5af19e028c44a2fd6d790dd22712f231
+  Moved a to foo
+  
+  diff --git a/a b/foo
+  rename from a
+  rename to foo
+  diff --git a/b b/foobar
+  rename from b
+  rename to foobar
+
+  $ hg mv c wat
+  $ hg unamend
+
+Retained copies in new prdecessor commit
+
+  $ hg exp --git
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 552e3af4f01f620f88ca27be1f898316235b736a
+  # Parent  03ddd6fc5af19e028c44a2fd6d790dd22712f231
+  Moved a to foo
+  
+  diff --git a/a b/foo
+  rename from a
+  rename to foo
+
+Retained copies in working directoy
+
+  $ hg diff --git
+  diff --git a/b b/foobar
+  rename from b
+  rename to foobar
+  diff --git a/c b/wat
+  rename from c
+  rename to wat
--- a/tests/test-wireproto.t	Fri Dec 01 23:27:08 2017 -0500
+++ b/tests/test-wireproto.t	Mon Dec 04 11:28:29 2017 -0500
@@ -94,23 +94,23 @@
   * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:1033* (glob)
   * - - [*] "POST /?cmd=debugwireargs HTTP/1.1" 200 - x-hgargs-post:1033* (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=quatre&one=un&three=trois&two=deux x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=qu++atre&one=+un&three=trois+&two=deux x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=vier&one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:one=eins&two=zwei x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs HTTP/1.1" 200 - x-hgarg-1:four=onethousandcharactersxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&one x-hgarg-2:=un&three=trois&two=deux x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
 
 HTTP without the httpheader capability:
 
@@ -133,17 +133,17 @@
   $ cat error2.log
   $ cat access2.log
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs&four=quatre&one=un&three=trois&two=deux HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs&four=vier&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
-  * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
+  $LOCALIP - - [$LOGDATE$] "GET /?cmd=debugwireargs&one=eins&two=zwei HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
 
 SSH (try to exercise the ssh functionality with a dummy script):