changeset 38739:7acec9408e1c stable

release: merge default into stable for 4.7 release freeze
author Augie Fackler <augie@google.com>
date Thu, 19 Jul 2018 13:55:54 -0400
parents 443029011990 (current diff) faea9b1980d9 (diff)
children c08ea1e219c0
files contrib/build-linux-wheels.sh contrib/builddeb contrib/buildrpm contrib/debian/cacerts.rc contrib/debian/changelog contrib/debian/compat contrib/debian/control contrib/debian/copyright contrib/debian/default-tools.rc contrib/debian/hgkpath.rc contrib/debian/rules contrib/docker/centos5 contrib/docker/centos6 contrib/docker/centos7 contrib/docker/debian.template contrib/docker/fedora20 contrib/docker/fedora21 contrib/docker/ubuntu.template contrib/dockerdeb contrib/dockerlib.sh contrib/dockerrpm contrib/fixpax.py contrib/linux-wheel-centos5-blacklist contrib/macosx/Readme.html contrib/macosx/Welcome.html contrib/macosx/distribution.xml contrib/mercurial.spec contrib/packagelib.sh hgext/narrow/narrowmerge.py mercurial/encoding.py tests/test-command-template.t tests/test-hybridencode.py.out tests/test-template-engine.t
diffstat 471 files changed, 23651 insertions(+), 12167 deletions(-) [+]
line wrap: on
line diff
--- a/.editorconfig	Sun Jul 01 23:36:53 2018 +0900
+++ b/.editorconfig	Thu Jul 19 13:55:54 2018 -0400
@@ -11,3 +11,8 @@
 indent_size = 8
 indent_style = tab
 trim_trailing_whitespace = true
+
+[*.t]
+indent_size = 2
+indent_style = space
+trim_trailing_whitespace = false
--- a/.hgignore	Sun Jul 01 23:36:53 2018 +0900
+++ b/.hgignore	Thu Jul 19 13:55:54 2018 -0400
@@ -31,8 +31,6 @@
 contrib/chg/chg
 contrib/hgsh/hgsh
 contrib/vagrant/.vagrant
-contrib/docker/debian-*
-contrib/docker/ubuntu-*
 dist
 packages
 doc/common.txt
--- a/Makefile	Sun Jul 01 23:36:53 2018 +0900
+++ b/Makefile	Thu Jul 19 13:55:54 2018 -0400
@@ -164,6 +164,39 @@
 
 # Packaging targets
 
+packaging_targets := \
+  centos5 \
+  centos6 \
+  centos7 \
+  deb \
+  docker-centos5 \
+  docker-centos6 \
+  docker-centos7 \
+  docker-debian-jessie \
+  docker-debian-stretch \
+  docker-fedora20 \
+  docker-fedora21 \
+  docker-fedora28 \
+  docker-ubuntu-trusty \
+  docker-ubuntu-trusty-ppa \
+  docker-ubuntu-xenial \
+  docker-ubuntu-xenial-ppa \
+  docker-ubuntu-artful \
+  docker-ubuntu-artful-ppa \
+  docker-ubuntu-bionic \
+  docker-ubuntu-bionic-ppa \
+  fedora20 \
+  fedora21 \
+  fedora28 \
+  linux-wheels \
+  linux-wheels-x86_64 \
+  linux-wheels-i686 \
+  ppa
+
+# Forward packaging targets for convenience.
+$(packaging_targets):
+	$(MAKE) -C contrib/packaging $@
+
 osx:
 	rm -rf build/mercurial
 	/usr/bin/python2.7 setup.py install --optimize=1 \
@@ -197,127 +230,14 @@
 	  --identifier org.mercurial-scm.mercurial \
 	  --version "$${HGVER}" \
 	  build/mercurial.pkg && \
-	productbuild --distribution contrib/macosx/distribution.xml \
+	productbuild --distribution contrib/packaging/macosx/distribution.xml \
 	  --package-path build/ \
 	  --version "$${HGVER}" \
-	  --resources contrib/macosx/ \
+	  --resources contrib/packaging/macosx/ \
 	  "$${OUTPUTDIR:-dist/}"/Mercurial-"$${HGVER}"-macosx"$${OSXVER}".pkg
 
-deb:
-	contrib/builddeb
-
-ppa:
-	contrib/builddeb --source-only
-
-contrib/docker/debian-%: contrib/docker/debian.template
-	sed "s/__CODENAME__/$*/" $< > $@
-
-docker-debian-jessie: contrib/docker/debian-jessie
-	contrib/dockerdeb debian jessie
-
-docker-debian-stretch: contrib/docker/debian-stretch
-	contrib/dockerdeb debian stretch
-
-contrib/docker/ubuntu-%: contrib/docker/ubuntu.template
-	sed "s/__CODENAME__/$*/" $< > $@
-
-docker-ubuntu-trusty: contrib/docker/ubuntu-trusty
-	contrib/dockerdeb ubuntu trusty
-
-docker-ubuntu-trusty-ppa: contrib/docker/ubuntu-trusty
-	contrib/dockerdeb ubuntu trusty --source-only
-
-docker-ubuntu-xenial: contrib/docker/ubuntu-xenial
-	contrib/dockerdeb ubuntu xenial
-
-docker-ubuntu-xenial-ppa: contrib/docker/ubuntu-xenial
-	contrib/dockerdeb ubuntu xenial --source-only
-
-docker-ubuntu-artful: contrib/docker/ubuntu-artful
-	contrib/dockerdeb ubuntu artful
-
-docker-ubuntu-artful-ppa: contrib/docker/ubuntu-artful
-	contrib/dockerdeb ubuntu artful --source-only
-
-docker-ubuntu-bionic: contrib/docker/ubuntu-bionic
-	contrib/dockerdeb ubuntu bionic
-
-docker-ubuntu-bionic-ppa: contrib/docker/ubuntu-bionic
-	contrib/dockerdeb ubuntu bionic --source-only
-
-fedora20:
-	mkdir -p packages/fedora20
-	contrib/buildrpm
-	cp rpmbuild/RPMS/*/* packages/fedora20
-	cp rpmbuild/SRPMS/* packages/fedora20
-	rm -rf rpmbuild
-
-docker-fedora20:
-	mkdir -p packages/fedora20
-	contrib/dockerrpm fedora20
-
-fedora21:
-	mkdir -p packages/fedora21
-	contrib/buildrpm
-	cp rpmbuild/RPMS/*/* packages/fedora21
-	cp rpmbuild/SRPMS/* packages/fedora21
-	rm -rf rpmbuild
-
-docker-fedora21:
-	mkdir -p packages/fedora21
-	contrib/dockerrpm fedora21
-
-centos5:
-	mkdir -p packages/centos5
-	contrib/buildrpm --withpython
-	cp rpmbuild/RPMS/*/* packages/centos5
-	cp rpmbuild/SRPMS/* packages/centos5
-
-docker-centos5:
-	mkdir -p packages/centos5
-	contrib/dockerrpm centos5 --withpython
-
-centos6:
-	mkdir -p packages/centos6
-	contrib/buildrpm --withpython
-	cp rpmbuild/RPMS/*/* packages/centos6
-	cp rpmbuild/SRPMS/* packages/centos6
-
-docker-centos6:
-	mkdir -p packages/centos6
-	contrib/dockerrpm centos6 --withpython
-
-centos7:
-	mkdir -p packages/centos7
-	contrib/buildrpm
-	cp rpmbuild/RPMS/*/* packages/centos7
-	cp rpmbuild/SRPMS/* packages/centos7
-
-docker-centos7:
-	mkdir -p packages/centos7
-	contrib/dockerrpm centos7
-
-linux-wheels: linux-wheels-x86_64 linux-wheels-i686
-
-linux-wheels-x86_64:
-	docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`:/src quay.io/pypa/manylinux1_x86_64 /src/contrib/build-linux-wheels.sh
-
-linux-wheels-i686:
-	docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`:/src quay.io/pypa/manylinux1_i686 linux32 /src/contrib/build-linux-wheels.sh
-
 .PHONY: help all local build doc cleanbutpackages clean install install-bin \
 	install-doc install-home install-home-bin install-home-doc \
 	dist dist-notests check tests check-code format-c update-pot \
-	osx deb ppa \
-	docker-debian-jessie \
-	docker-debian-stretch \
-	docker-ubuntu-trusty docker-ubuntu-trusty-ppa \
-	docker-ubuntu-xenial docker-ubuntu-xenial-ppa \
-	docker-ubuntu-artful docker-ubuntu-artful-ppa \
-	docker-ubuntu-bionic docker-ubuntu-bionic-ppa \
-	fedora20 docker-fedora20 \
-	fedora21 docker-fedora21 \
-	centos5 docker-centos5 \
-	centos6 docker-centos6 \
-	centos7 docker-centos7 \
-	linux-wheels
+	$(packaging_targets) \
+	osx
--- a/contrib/all-revsets.txt	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/all-revsets.txt	Thu Jul 19 13:55:54 2018 -0400
@@ -135,3 +135,7 @@
 # testing the mutable phases set
 draft()
 secret()
+
+# test finding common ancestors
+heads(commonancestors(last(head(), 2)))
+heads(commonancestors(head()))
--- a/contrib/base-revsets.txt	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/base-revsets.txt	Thu Jul 19 13:55:54 2018 -0400
@@ -46,3 +46,4 @@
 (20000::) - (20000)
 # The one below is used by rebase
 (children(ancestor(tip~5, tip)) and ::(tip~5))::
+heads(commonancestors(last(head(), 2)))
--- a/contrib/build-linux-wheels.sh	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-#!/bin/bash
-# This file is directly inspired by
-# https://github.com/pypa/python-manylinux-demo/blob/master/travis/build-wheels.sh
-set -e -x
-
-PYTHON_TARGETS=$(ls -d /opt/python/cp27*/bin)
-
-# Create an user for the tests
-useradd hgbuilder
-
-# Bypass uid/gid problems
-cp -R /src /io && chown -R hgbuilder:hgbuilder /io
-
-# Compile wheels for Python 2.X
-for PYBIN in $PYTHON_TARGETS; do
-    "${PYBIN}/pip" wheel /io/ -w wheelhouse/
-done
-
-# Bundle external shared libraries into the wheels with
-# auditwheel (https://github.com/pypa/auditwheel) repair.
-# It also fix the ABI tag on the wheel making it pip installable.
-for whl in wheelhouse/*.whl; do
-    auditwheel repair "$whl" -w /src/wheelhouse/
-done
-
-# Install packages and run the tests for all Python versions
-cd /io/tests/
-
-for PYBIN in $PYTHON_TARGETS; do
-    # Install mercurial wheel as root
-    "${PYBIN}/pip" install mercurial --no-index -f /src/wheelhouse
-    # But run tests as hgbuilder user (non-root)
-    su hgbuilder -c "\"${PYBIN}/python\" /io/tests/run-tests.py --with-hg=\"${PYBIN}/hg\" --blacklist=/io/contrib/linux-wheel-centos5-blacklist"
-done
--- a/contrib/builddeb	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-#!/bin/sh -e
-#
-# Build a Mercurial debian package from the current repo
-#
-# Tested on Jessie (stable as of original script authoring.)
-
-. $(dirname $0)/packagelib.sh
-
-BUILD=1
-CLEANUP=1
-DISTID=`(lsb_release -is 2> /dev/null | tr '[:upper:]' '[:lower:]') || echo debian`
-CODENAME=`lsb_release -cs 2> /dev/null || echo unknown`
-DEBFLAGS=-b
-while [ "$1" ]; do
-    case "$1" in
-    --distid )
-        shift
-        DISTID="$1"
-        shift
-        ;;
-    --codename )
-        shift
-        CODENAME="$1"
-        shift
-        ;;
-    --cleanup )
-        shift
-        BUILD=
-        ;;
-    --build )
-        shift
-        CLEANUP=
-        ;;
-    --source-only )
-        shift
-        DEBFLAGS=-S
-        ;;
-    * )
-        echo "Invalid parameter $1!" 1>&2
-        exit 1
-        ;;
-    esac
-done
-
-trap "if [ '$CLEANUP' ] ; then rm -r '$PWD/debian' ; fi" EXIT
-
-set -u
-
-if [ ! -d .hg ]; then
-    echo 'You are not inside a Mercurial repository!' 1>&2
-    exit 1
-fi
-
-gethgversion
-debver="$version"
-if [ -n "$type" ] ; then
-    debver="$debver~$type"
-fi
-if [ -n "$distance" ] ; then
-    debver="$debver+$distance-$CODENAME-$node"
-elif [ "$DEBFLAGS" = "-S" ] ; then
-    # for building a ppa (--source-only) for a release (distance == 0), we need
-    # to version the distroseries so that we can upload to launchpad
-    debver="$debver~${CODENAME}1"
-fi
-
-control=debian/control
-changelog=debian/changelog
-
-if [ "$BUILD" ]; then
-    if [ -d debian ] ; then
-        echo "Error! debian control directory already exists!"
-        exit 1
-    fi
-
-    cp -r "$PWD"/contrib/debian debian
-
-    sed -i.tmp "s/__VERSION__/$debver/" $changelog
-    sed -i.tmp "s/__DATE__/$(date --rfc-2822)/" $changelog
-    sed -i.tmp "s/__CODENAME__/$CODENAME/" $changelog
-    rm $changelog.tmp
-
-    # remove the node from the version string
-    SRCFILE="mercurial_$(echo $debver | sed "s,-$node,,").orig.tar.gz"
-    "$PWD/hg" archive $SRCFILE
-    mv $SRCFILE ..
-    debuild -us -uc -i -I $DEBFLAGS
-    if [ $? != 0 ]; then
-        echo 'debuild failed!'
-        exit 1
-    fi
-
-fi
-if [ "$CLEANUP" ] ; then
-    echo
-    OUTPUTDIR=${OUTPUTDIR:=packages/$DISTID-$CODENAME}
-    mkdir -p "$OUTPUTDIR"
-    find ../mercurial*.deb ../mercurial_*.build ../mercurial_*.changes \
-          ../mercurial*.dsc ../mercurial*.gz \
-          -type f -newer $control -print0 2>/dev/null | \
-      xargs -Inarf -0 mv narf "$OUTPUTDIR"
-    echo "Built packages for $debver:"
-    find "$OUTPUTDIR" -type f -newer $control -name '*.deb'
-fi
--- a/contrib/buildrpm	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-#!/bin/bash -e
-#
-# Build a Mercurial RPM from the current repo
-#
-# Tested on
-# - Fedora 20
-# - CentOS 5
-# - centOS 6
-
-. $(dirname $0)/packagelib.sh
-
-BUILD=1
-RPMBUILDDIR="$PWD/rpmbuild"
-
-while [ "$1" ]; do
-    case "$1" in
-    --prepare )
-        shift
-        BUILD=
-        ;;
-    --withpython | --with-python)
-        shift
-        PYTHONVER=2.7.14
-        PYTHONMD5=cee2e4b33ad3750da77b2e85f2f8b724
-        ;;
-    --rpmbuilddir )
-        shift
-        RPMBUILDDIR="$1"
-        shift
-        ;;
-    * )
-        echo "Invalid parameter $1!" 1>&2
-        exit 1
-        ;;
-    esac
-done
-
-cd "`dirname $0`/.."
-
-specfile=$PWD/contrib/mercurial.spec
-if [ ! -f $specfile ]; then
-    echo "Cannot find $specfile!" 1>&2
-    exit 1
-fi
-
-if [ ! -d .hg ]; then
-    echo 'You are not inside a Mercurial repository!' 1>&2
-    exit 1
-fi
-
-gethgversion
-
-# TODO: handle distance/node set, and type set
-
-if [ -z "$type" ] ; then
-   release=1
-else
-    release=0.9_$type
-fi
-
-if [ -n "$distance" ] ; then
-    release=$release+$distance_$node
-fi
-
-if [ "$PYTHONVER" ]; then
-    release=$release+$PYTHONVER
-    RPMPYTHONVER=$PYTHONVER
-else
-    RPMPYTHONVER=%{nil}
-fi
-
-mkdir -p $RPMBUILDDIR/{SOURCES,BUILD,SRPMS,RPMS}
-$HG archive -t tgz $RPMBUILDDIR/SOURCES/mercurial-$version-$release.tar.gz
-if [ "$PYTHONVER" ]; then
-(
-    mkdir -p build
-    cd build
-    PYTHON_SRCFILE=Python-$PYTHONVER.tgz
-    [ -f $PYTHON_SRCFILE ] || curl -Lo $PYTHON_SRCFILE http://www.python.org/ftp/python/$PYTHONVER/$PYTHON_SRCFILE
-    if [ "$PYTHONMD5" ]; then
-        echo "$PYTHONMD5 $PYTHON_SRCFILE" | md5sum -w -c
-    fi
-    ln -f $PYTHON_SRCFILE $RPMBUILDDIR/SOURCES/$PYTHON_SRCFILE
-
-    DOCUTILSVER=`sed -ne "s/^%global docutilsname docutils-//p" $specfile`
-    DOCUTILS_SRCFILE=docutils-$DOCUTILSVER.tar.gz
-    [ -f $DOCUTILS_SRCFILE ] || curl -Lo $DOCUTILS_SRCFILE http://downloads.sourceforge.net/project/docutils/docutils/$DOCUTILSVER/$DOCUTILS_SRCFILE
-    DOCUTILSMD5=`sed -ne "s/^%global docutilsmd5 //p" $specfile`
-    if [ "$DOCUTILSMD5" ]; then
-        echo "$DOCUTILSMD5 $DOCUTILS_SRCFILE" | md5sum -w -c
-    fi
-    ln -f $DOCUTILS_SRCFILE $RPMBUILDDIR/SOURCES/$DOCUTILS_SRCFILE
-)
-fi
-
-mkdir -p $RPMBUILDDIR/SPECS
-rpmspec=$RPMBUILDDIR/SPECS/mercurial.spec
-
-sed -e "s,^Version:.*,Version: $version," \
-    -e "s,^Release:.*,Release: $release," \
-    $specfile > $rpmspec
-
-echo >> $rpmspec
-echo "%changelog" >> $rpmspec
-
-if echo $version | grep '+' > /dev/null 2>&1; then
-    latesttag="`echo $version | sed -e 's/+.*//'`"
-    $HG log -r .:"$latesttag" -fM \
-        --template '{date|hgdate}\t{author}\t{desc|firstline}\n' | python -c '
-import sys, time
-
-def datestr(date, format):
-    return time.strftime(format, time.gmtime(float(date[0]) - date[1]))
-
-changelog = []
-for l in sys.stdin.readlines():
-    tok = l.split("\t")
-    hgdate = tuple(int(v) for v in tok[0].split())
-    changelog.append((datestr(hgdate, "%F"), tok[1], hgdate, tok[2]))
-prevtitle = ""
-for l in sorted(changelog, reverse=True):
-    title = "* %s %s" % (datestr(l[2], "%a %b %d %Y"), l[1])
-    if prevtitle != title:
-        prevtitle = title
-        print
-        print title
-    print "- %s" % l[3].strip()
-' >> $rpmspec
-
-else
-
-    $HG log \
-         --template '{date|hgdate}\t{author}\t{desc|firstline}\n' \
-         .hgtags | python -c '
-import sys, time
-
-def datestr(date, format):
-    return time.strftime(format, time.gmtime(float(date[0]) - date[1]))
-
-for l in sys.stdin.readlines():
-    tok = l.split("\t")
-    hgdate = tuple(int(v) for v in tok[0].split())
-    print "* %s %s\n- %s" % (datestr(hgdate, "%a %b %d %Y"), tok[1], tok[2])
-' >> $rpmspec
-
-fi
-
-sed -i \
-    -e "s/^%define withpython.*$/%define withpython $RPMPYTHONVER/" \
-    $rpmspec
-
-if [ "$BUILD" ]; then
-    rpmbuild --define "_topdir $RPMBUILDDIR" -ba $rpmspec --clean
-    if [ $? = 0 ]; then
-        echo
-        echo "Built packages for $version-$release:"
-        find $RPMBUILDDIR/*RPMS/ -type f -newer $rpmspec
-    fi
-else
-    echo "Prepared sources for $version-$release $rpmspec are in $RPMBUILDDIR/SOURCES/ - use like:"
-    echo "rpmbuild --define '_topdir $RPMBUILDDIR' -ba $rpmspec --clean"
-fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/byteify-strings.py	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,225 @@
+#!/usr/bin/env python3
+#
+# byteify-strings.py - transform string literals to be Python 3 safe
+#
+# Copyright 2015 Gregory Szorc <gregory.szorc@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import argparse
+import contextlib
+import errno
+import os
+import sys
+import tempfile
+import token
+import tokenize
+
+def adjusttokenpos(t, ofs):
+    """Adjust start/end column of the given token"""
+    return t._replace(start=(t.start[0], t.start[1] + ofs),
+                      end=(t.end[0], t.end[1] + ofs))
+
+def replacetokens(tokens, opts):
+    """Transform a stream of tokens from raw to Python 3.
+
+    Returns a generator of possibly rewritten tokens.
+
+    The input token list may be mutated as part of processing. However,
+    its changes do not necessarily match the output token stream.
+    """
+    sysstrtokens = set()
+
+    # The following utility functions access the tokens list and i index of
+    # the for i, t enumerate(tokens) loop below
+    def _isop(j, *o):
+        """Assert that tokens[j] is an OP with one of the given values"""
+        try:
+            return tokens[j].type == token.OP and tokens[j].string in o
+        except IndexError:
+            return False
+
+    def _findargnofcall(n):
+        """Find arg n of a call expression (start at 0)
+
+        Returns index of the first token of that argument, or None if
+        there is not that many arguments.
+
+        Assumes that token[i + 1] is '('.
+
+        """
+        nested = 0
+        for j in range(i + 2, len(tokens)):
+            if _isop(j, ')', ']', '}'):
+                # end of call, tuple, subscription or dict / set
+                nested -= 1
+                if nested < 0:
+                    return None
+            elif n == 0:
+                # this is the starting position of arg
+                return j
+            elif _isop(j, '(', '[', '{'):
+                nested += 1
+            elif _isop(j, ',') and nested == 0:
+                n -= 1
+
+        return None
+
+    def _ensuresysstr(j):
+        """Make sure the token at j is a system string
+
+        Remember the given token so the string transformer won't add
+        the byte prefix.
+
+        Ignores tokens that are not strings. Assumes bounds checking has
+        already been done.
+
+        """
+        st = tokens[j]
+        if st.type == token.STRING and st.string.startswith(("'", '"')):
+            sysstrtokens.add(st)
+
+    coldelta = 0  # column increment for new opening parens
+    coloffset = -1  # column offset for the current line (-1: TBD)
+    parens = [(0, 0, 0)]  # stack of (line, end-column, column-offset)
+    for i, t in enumerate(tokens):
+        # Compute the column offset for the current line, such that
+        # the current line will be aligned to the last opening paren
+        # as before.
+        if coloffset < 0:
+            if t.start[1] == parens[-1][1]:
+                coloffset = parens[-1][2]
+            elif t.start[1] + 1 == parens[-1][1]:
+                # fix misaligned indent of s/util.Abort/error.Abort/
+                coloffset = parens[-1][2] + (parens[-1][1] - t.start[1])
+            else:
+                coloffset = 0
+
+        # Reset per-line attributes at EOL.
+        if t.type in (token.NEWLINE, tokenize.NL):
+            yield adjusttokenpos(t, coloffset)
+            coldelta = 0
+            coloffset = -1
+            continue
+
+        # Remember the last paren position.
+        if _isop(i, '(', '[', '{'):
+            parens.append(t.end + (coloffset + coldelta,))
+        elif _isop(i, ')', ']', '}'):
+            parens.pop()
+
+        # Convert most string literals to byte literals. String literals
+        # in Python 2 are bytes. String literals in Python 3 are unicode.
+        # Most strings in Mercurial are bytes and unicode strings are rare.
+        # Rather than rewrite all string literals to use ``b''`` to indicate
+        # byte strings, we apply this token transformer to insert the ``b``
+        # prefix nearly everywhere.
+        if t.type == token.STRING and t not in sysstrtokens:
+            s = t.string
+
+            # Preserve docstrings as string literals. This is inconsistent
+            # with regular unprefixed strings. However, the
+            # "from __future__" parsing (which allows a module docstring to
+            # exist before it) doesn't properly handle the docstring if it
+            # is b''' prefixed, leading to a SyntaxError. We leave all
+            # docstrings as unprefixed to avoid this. This means Mercurial
+            # components touching docstrings need to handle unicode,
+            # unfortunately.
+            if s[0:3] in ("'''", '"""'):
+                yield adjusttokenpos(t, coloffset)
+                continue
+
+            # If the first character isn't a quote, it is likely a string
+            # prefixing character (such as 'b', 'u', or 'r'. Ignore.
+            if s[0] not in ("'", '"'):
+                yield adjusttokenpos(t, coloffset)
+                continue
+
+            # String literal. Prefix to make a b'' string.
+            yield adjusttokenpos(t._replace(string='b%s' % t.string),
+                                 coloffset)
+            coldelta += 1
+            continue
+
+        # This looks like a function call.
+        if t.type == token.NAME and _isop(i + 1, '('):
+            fn = t.string
+
+            # *attr() builtins don't accept byte strings to 2nd argument.
+            if (fn in ('getattr', 'setattr', 'hasattr', 'safehasattr') and
+                    not _isop(i - 1, '.')):
+                arg1idx = _findargnofcall(1)
+                if arg1idx is not None:
+                    _ensuresysstr(arg1idx)
+
+            # .encode() and .decode() on str/bytes/unicode don't accept
+            # byte strings on Python 3.
+            elif fn in ('encode', 'decode') and _isop(i - 1, '.'):
+                for argn in range(2):
+                    argidx = _findargnofcall(argn)
+                    if argidx is not None:
+                        _ensuresysstr(argidx)
+
+            # It changes iteritems/values to items/values as they are not
+            # present in Python 3 world.
+            elif opts['dictiter'] and fn in ('iteritems', 'itervalues'):
+                yield adjusttokenpos(t._replace(string=fn[4:]), coloffset)
+                continue
+
+        # Emit unmodified token.
+        yield adjusttokenpos(t, coloffset)
+
+def process(fin, fout, opts):
+    tokens = tokenize.tokenize(fin.readline)
+    tokens = replacetokens(list(tokens), opts)
+    fout.write(tokenize.untokenize(tokens))
+
+def tryunlink(fname):
+    try:
+        os.unlink(fname)
+    except OSError as err:
+        if err.errno != errno.ENOENT:
+            raise
+
+@contextlib.contextmanager
+def editinplace(fname):
+    n = os.path.basename(fname)
+    d = os.path.dirname(fname)
+    fp = tempfile.NamedTemporaryFile(prefix='.%s-' % n, suffix='~', dir=d,
+                                     delete=False)
+    try:
+        yield fp
+        fp.close()
+        if os.name == 'nt':
+            tryunlink(fname)
+        os.rename(fp.name, fname)
+    finally:
+        fp.close()
+        tryunlink(fp.name)
+
+def main():
+    ap = argparse.ArgumentParser()
+    ap.add_argument('-i', '--inplace', action='store_true', default=False,
+                    help='edit files in place')
+    ap.add_argument('--dictiter', action='store_true', default=False,
+                    help='rewrite iteritems() and itervalues()'),
+    ap.add_argument('files', metavar='FILE', nargs='+', help='source file')
+    args = ap.parse_args()
+    opts = {
+        'dictiter': args.dictiter,
+    }
+    for fname in args.files:
+        if args.inplace:
+            with editinplace(fname) as fout:
+                with open(fname, 'rb') as fin:
+                    process(fin, fout, opts)
+        else:
+            with open(fname, 'rb') as fin:
+                fout = sys.stdout.buffer
+                process(fin, fout, opts)
+
+if __name__ == '__main__':
+    main()
--- a/contrib/check-code.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/check-code.py	Thu Jul 19 13:55:54 2018 -0400
@@ -340,7 +340,8 @@
     (r'\butil\.Abort\b', "directly use error.Abort"),
     (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
     (r'^import atexit', "don't use atexit, use ui.atexit"),
-    (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
+    (r'^import Queue', "don't use Queue, use pycompat.queue.Queue + "
+                       "pycompat.queue.Empty"),
     (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
     (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
     (r'^import SocketServer', "don't use SockerServer, use util.socketserver"),
--- a/contrib/debian/cacerts.rc	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-# This config file points Mercurial at the system-wide certificate
-# store from the ca-certificates package.
-
-[web]
-cacerts = /etc/ssl/certs/ca-certificates.crt
--- a/contrib/debian/changelog	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-mercurial (__VERSION__) __CODENAME__; urgency=medium
-
-  * Automated build performed by upstream.
-
- -- Mercurial Devel <mercurial-devel@mercurial-scm.org>  __DATE__
--- a/contrib/debian/compat	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-9
--- a/contrib/debian/control	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-Source: mercurial
-Section: vcs
-Priority: optional
-Maintainer: Mercurial Developers <mercurial-devel@mercurial-scm.org>
-Build-Depends:
- debhelper (>= 9),
- dh-python,
- less,
- netbase,
- python-all,
- python-all-dev,
- python-docutils,
- unzip,
- zip
-Standards-Version: 3.9.4
-X-Python-Version: >= 2.7
-
-Package: mercurial
-Depends:
- python,
- ${shlibs:Depends},
- ${misc:Depends},
- ${python:Depends},
- mercurial-common (= ${source:Version})
-Architecture: any
-Description: fast, easy to use, distributed revision control tool.
- Mercurial is a fast, lightweight Source Control Management system designed
- for efficient handling of very large distributed projects.
- .
- Its features include:
-  * O(1) delta-compressed file storage and retrieval scheme
-  * Complete cross-indexing of files and changesets for efficient exploration
-    of project history
-  * Robust SHA1-based integrity checking and append-only storage model
-  * Decentralized development model with arbitrary merging between trees
-  * Easy-to-use command-line interface
-  * Integrated stand-alone web interface
-  * Small Python codebase
-
-Package: mercurial-common
-Architecture: all
-Depends:
- ${misc:Depends},
- ${python:Depends},
-Recommends: mercurial (= ${source:Version}), ca-certificates
-Suggests: wish
-Breaks: mercurial (<< ${source:Version})
-Replaces: mercurial (<< 2.6.3)
-Description: easy-to-use, scalable distributed version control system (common files)
- Mercurial is a fast, lightweight Source Control Management system designed
- for efficient handling of very large distributed projects.
- .
- This package contains the architecture independent components of Mercurial,
- and is generally useless without the mercurial package.
--- a/contrib/debian/copyright	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
-Upstream-Name: mercurial
-Source: https://www.mercurial-scm.org/
-
-Files: *
-Copyright: 2005-2018, Matt Mackall <mpm@selenic.com> and others.
-License: GPL-2+
- This program is free software; you can redistribute it
- and/or modify it under the terms of the GNU General Public
- License as published by the Free Software Foundation; either
- version 2 of the License, or (at your option) any later
- version.
- .
- This program is distributed in the hope that it will be
- useful, but WITHOUT ANY WARRANTY; without even the implied
- warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- PURPOSE.  See the GNU General Public License for more
- details.
- .
- You should have received a copy of the GNU General Public
- License along with this package; if not, write to the Free
- Software Foundation, Inc., 51 Franklin St, Fifth Floor,
- Boston, MA  02110-1301 USA
- .
- On Debian systems, the full text of the GNU General Public
- License version 2 can be found in the file
- `/usr/share/common-licenses/GPL-2'.
--- a/contrib/debian/default-tools.rc	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,5 +0,0 @@
-[ui]
-editor = sensible-editor
-
-[pager]
-pager = sensible-pager
--- a/contrib/debian/hgkpath.rc	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,2 +0,0 @@
-[hgk]
-path = /usr/share/mercurial/hgk
--- a/contrib/debian/rules	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-#!/usr/bin/make -f
-# Uncomment this to turn on verbose mode.
-# export DH_VERBOSE=1
-
-CPUS=$(shell cat /proc/cpuinfo | grep -E ^processor | wc -l)
-
-%:
-	dh $@ --with python2
-
-override_dh_auto_test:
-	http_proxy='' dh_auto_test -- TESTFLAGS="-j$(CPUS)"
-
-override_dh_python2:
-	dh_python2
-	find debian/mercurial/usr/share -type d -empty -delete
-
-override_dh_install:
-	python$(PYVERS) setup.py install --root "$(CURDIR)"/debian/mercurial --install-layout=deb
-	# chg
-	make -C contrib/chg \
-		DESTDIR="$(CURDIR)"/debian/mercurial \
-		PREFIX=/usr \
-		clean install
-	# remove arch-independent python stuff
-	find "$(CURDIR)"/debian/mercurial/usr/lib \
-		! -name '*.so' ! -type d -delete , \
-		-type d -empty -delete
-	python$(PYVERS) setup.py install --root "$(CURDIR)/debian/mercurial-common" --install-layout=deb
-	make install-doc PREFIX="$(CURDIR)"/debian/mercurial-common/usr
-	# remove arch-dependent python stuff
-	find "$(CURDIR)"/debian/mercurial-common/usr/lib \
-		-name '*.so' ! -type d -delete , \
-		-type d -empty -delete
-	cp contrib/hg-ssh "$(CURDIR)"/debian/mercurial-common/usr/bin
-	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/mercurial
-	cp contrib/hgk "$(CURDIR)"/debian/mercurial-common/usr/share/mercurial
-	mkdir -p "$(CURDIR)"/debian/mercurial-common/etc/mercurial/hgrc.d/
-	cp contrib/debian/*.rc "$(CURDIR)"/debian/mercurial-common/etc/mercurial/hgrc.d/
-	# completions
-	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/bash-completion/completions
-	cp contrib/bash_completion "$(CURDIR)"/debian/mercurial-common/usr/share/bash-completion/completions/hg
-	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/zsh/vendor-completions
-	cp contrib/zsh_completion "$(CURDIR)"/debian/mercurial-common/usr/share/zsh/vendor-completions/_hg
-	rm "$(CURDIR)"/debian/mercurial-common/usr/bin/hg
--- a/contrib/docker/centos5	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-FROM centos:centos5
-RUN \
-	sed -i 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/*.repo && \
-	sed -i 's/^#\(baseurl=\)http:\/\/mirror.centos.org\/centos/\1http:\/\/vault.centos.org/' /etc/yum.repos.d/*.repo && \
-	sed -i 's/\$releasever/5.11/' /etc/yum.repos.d/*.repo
-
-RUN yum install -y \
-	gcc \
-	gettext \
-	make \
-	python-devel \
-	python-docutils \
-	rpm-build \
-	tar
-
-# For creating repo meta data
-RUN yum install -y \
-	bzip2-devel \
-	createrepo \
-	ncurses-devel \
-	openssl-devel \
-	readline-devel \
-	zlib-devel
--- a/contrib/docker/centos6	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-FROM centos:centos6
-RUN yum install -y \
-	gcc \
-	gettext \
-	make \
-	python-devel \
-	python-docutils \
-	rpm-build \
-	tar
-
-# For creating repo meta data
-RUN yum install -y createrepo
-
-# For python
-RUN yum install -y \
-	bzip2-devel \
-	ncurses-devel \
-	openssl-devel \
-	readline-devel \
-	zlib-devel
--- a/contrib/docker/centos7	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-FROM centos:centos7
-RUN yum install -y \
-	gcc \
-	gettext \
-	make \
-	python-devel \
-	python-docutils \
-	rpm-build \
-	tar
-
-# For creating repo meta data
-RUN yum install -y createrepo
--- a/contrib/docker/debian.template	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-FROM debian:__CODENAME__
-RUN apt-get update && apt-get install -y \
-  build-essential \
-  debhelper \
-  devscripts \
-  dh-python \
-  less \
-  python \
-  python-all-dev \
-  python-docutils \
-  unzip \
-  zip
--- a/contrib/docker/fedora20	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-FROM fedora:20
-RUN yum install -y \
-	gcc \
-	gettext \
-	make \
-	python-devel \
-	python-docutils \
-	rpm-build
-
-# For creating repo meta data
-RUN yum install -y createrepo
--- a/contrib/docker/fedora21	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-FROM fedora:21
-RUN yum install -y \
-	gcc \
-	gettext \
-	make \
-	python-devel \
-	python-docutils \
-	rpm-build
-
-# For creating repo meta data
-RUN yum install -y createrepo
--- a/contrib/docker/ubuntu.template	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-FROM ubuntu:__CODENAME__
-RUN apt-get update && apt-get install -y \
-  build-essential \
-  debhelper \
-  devscripts \
-  dh-python \
-  less \
-  python \
-  python-all-dev \
-  python-docutils \
-  unzip \
-  zip
--- a/contrib/dockerdeb	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,35 +0,0 @@
-#!/bin/bash -eu
-
-. $(dirname $0)/dockerlib.sh
-. $(dirname $0)/packagelib.sh
-
-BUILDDIR=$(dirname $0)
-export ROOTDIR=$(cd $BUILDDIR/.. > /dev/null; pwd)
-
-checkdocker
-
-DISTID="$1"
-CODENAME="$2"
-PLATFORM="$1-$2"
-shift; shift # extra params are passed to build process
-
-OUTPUTDIR=${OUTPUTDIR:=$ROOTDIR/packages/$PLATFORM}
-
-initcontainer $PLATFORM
-
-# debuild only appears to be able to save built debs etc to .., so we
-# have to share the .. of the current directory with the docker
-# container and hope it's writable. Whee.
-dn=$(basename $PWD)
-
-if [ $(uname) = "Darwin" ] ; then
-    $DOCKER run -u $DBUILDUSER --rm -v $PWD/..:/mnt $CONTAINER \
-            sh -c "cd /mnt/$dn && make clean && make local"
-fi
-$DOCKER run -u $DBUILDUSER --rm -v $PWD/..:/mnt $CONTAINER \
-  sh -c "cd /mnt/$dn && DEB_BUILD_OPTIONS='${DEB_BUILD_OPTIONS:=}' contrib/builddeb --build --distid $DISTID --codename $CODENAME $@"
-contrib/builddeb --cleanup --distid $DISTID --codename $CODENAME
-if [ $(uname) = "Darwin" ] ; then
-    $DOCKER run -u $DBUILDUSER --rm -v $PWD/..:/mnt $CONTAINER \
-            sh -c "cd /mnt/$dn && make clean"
-fi
--- a/contrib/dockerlib.sh	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,42 +0,0 @@
-#!/bin/sh -eu
-
-# This function exists to set up the DOCKER variable and verify that
-# it's the binary we expect. It also verifies that the docker service
-# is running on the system and we can talk to it.
-function checkdocker() {
-  if which docker.io >> /dev/null 2>&1 ; then
-    DOCKER=docker.io
-  elif which docker >> /dev/null 2>&1 ; then
-    DOCKER=docker
-  else
-    echo "Error: docker must be installed"
-    exit 1
-  fi
-
-  $DOCKER -h 2> /dev/null | grep -q Jansens && { echo "Error: $DOCKER is the Docking System Tray - install docker.io instead"; exit 1; }
-  $DOCKER version | grep -Eq "^Client( version)?:" || { echo "Error: unexpected output from \"$DOCKER version\""; exit 1; }
-  $DOCKER version | grep -Eq "^Server( version)?:" || { echo "Error: could not get docker server version - check it is running and your permissions"; exit 1; }
-}
-
-# Construct a container and leave its name in $CONTAINER for future use.
-function initcontainer() {
-  [ "$1" ] || { echo "Error: platform name must be specified"; exit 1; }
-
-  DFILE="$ROOTDIR/contrib/docker/$1"
-  [ -f "$DFILE" ] || { echo "Error: docker file $DFILE not found"; exit 1; }
-
-  CONTAINER="hg-dockerrpm-$1"
-  DBUILDUSER=build
-  (
-    cat $DFILE
-    if [ $(uname) = "Darwin" ] ; then
-        # The builder is using boot2docker on OS X, so we're going to
-        # *guess* the uid of the user inside the VM that is actually
-        # running docker. This is *very likely* to fail at some point.
-        echo RUN useradd $DBUILDUSER -u 1000
-    else
-        echo RUN groupadd $DBUILDUSER -g `id -g` -o
-        echo RUN useradd $DBUILDUSER -u `id -u` -g $DBUILDUSER -o
-    fi
-  ) | $DOCKER build --build-arg http_proxy --build-arg https_proxy --tag $CONTAINER -
-}
--- a/contrib/dockerrpm	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-#!/bin/bash -e
-
-. $(dirname $0)/dockerlib.sh
-
-BUILDDIR=$(dirname $0)
-export ROOTDIR=$(cd $BUILDDIR/..; pwd)
-
-checkdocker
-
-PLATFORM="$1"
-shift # extra params are passed to buildrpm
-
-initcontainer $PLATFORM
-
-RPMBUILDDIR=$ROOTDIR/packages/$PLATFORM
-contrib/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $*
-
-DSHARED=/mnt/shared
-$DOCKER run -e http_proxy -e https_proxy -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
-    rpmbuild --define "_topdir $DSHARED" -ba $DSHARED/SPECS/mercurial.spec --clean
-
-$DOCKER run -e http_proxy -e https_proxy -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
-    createrepo $DSHARED
-
-cat << EOF > $RPMBUILDDIR/mercurial.repo
-# Place this file in /etc/yum.repos.d/mercurial.repo
-[mercurial]
-name=Mercurial packages for $PLATFORM
-# baseurl=file://$RPMBUILDDIR/
-baseurl=http://hg.example.com/build/$PLATFORM/
-skip_if_unavailable=True
-gpgcheck=0
-enabled=1
-EOF
-
-echo
-echo "Build complete - results can be found in $RPMBUILDDIR"
--- a/contrib/fixpax.py	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-#!/usr/bin/env python
-# fixpax - fix ownership in bdist_mpkg output
-#
-# Copyright 2015 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# MIT license (http://opensource.org/licenses/MIT)
-
-"""Set file ownership to 0 in an Archive.pax.gz.
-Suitable for fixing files bdist_mpkg output:
-*.mpkg/Contents/Packages/*.pkg/Contents/Archive.pax.gz
-"""
-
-from __future__ import absolute_import, print_function
-import gzip
-import os
-import sys
-
-def fixpax(iname, oname):
-    i = gzip.GzipFile(iname)
-    o = gzip.GzipFile(oname, "w")
-
-    while True:
-        magic = i.read(6)
-        dev = i.read(6)
-        ino = i.read(6)
-        mode = i.read(6)
-        i.read(6) # uid
-        i.read(6) # gid
-        nlink = i.read(6)
-        rdev = i.read(6)
-        mtime = i.read(11)
-        namesize = i.read(6)
-        filesize = i.read(11)
-        name = i.read(int(namesize, 8))
-        data = i.read(int(filesize, 8))
-
-        o.write(magic)
-        o.write(dev)
-        o.write(ino)
-        o.write(mode)
-        o.write("000000")
-        o.write("000000")
-        o.write(nlink)
-        o.write(rdev)
-        o.write(mtime)
-        o.write(namesize)
-        o.write(filesize)
-        o.write(name)
-        o.write(data)
-
-        if name.startswith("TRAILER!!!"):
-            o.write(i.read())
-            break
-
-    o.close()
-    i.close()
-
-if __name__ == '__main__':
-    for iname in sys.argv[1:]:
-        print('fixing file ownership in %s' % iname)
-        oname = sys.argv[1] + '.tmp'
-        fixpax(iname, oname)
-        os.rename(oname, iname)
--- a/contrib/fuzz/Makefile	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/fuzz/Makefile	Thu Jul 19 13:55:54 2018 -0400
@@ -1,40 +1,81 @@
+CC = clang
+CXX = clang++
+
+all: bdiff mpatch xdiff
+
+fuzzutil.o: fuzzutil.cc fuzzutil.h
+	$(CXX) $(CXXFLAGS) -g -O1 -fsanitize=fuzzer-no-link,address \
+	  -std=c++17 \
+	  -I../../mercurial -c -o fuzzutil.o fuzzutil.cc
+
+fuzzutil-oss-fuzz.o: fuzzutil.cc fuzzutil.h
+	$(CXX) $(CXXFLAGS) -std=c++17 \
+	  -I../../mercurial -c -o fuzzutil-oss-fuzz.o fuzzutil.cc
+
 bdiff.o: ../../mercurial/bdiff.c
-	clang -g -O1 -fsanitize=fuzzer-no-link,address -c -o bdiff.o \
+	$(CC) $(CFLAGS) -fsanitize=fuzzer-no-link,address -c -o bdiff.o \
 	  ../../mercurial/bdiff.c
 
-bdiff: bdiff.cc bdiff.o
-	clang -DHG_FUZZER_INCLUDE_MAIN=1 -g -O1 -fsanitize=fuzzer-no-link,address \
-	  -I../../mercurial bdiff.cc bdiff.o -o bdiff
+bdiff: bdiff.cc bdiff.o fuzzutil.o
+	$(CXX) $(CXXFLAGS) -DHG_FUZZER_INCLUDE_MAIN=1 -g -O1 -fsanitize=fuzzer-no-link,address \
+	  -std=c++17 \
+	  -I../../mercurial bdiff.cc bdiff.o fuzzutil.o -o bdiff
 
 bdiff-oss-fuzz.o: ../../mercurial/bdiff.c
-	$$CC $$CFLAGS -c -o bdiff-oss-fuzz.o ../../mercurial/bdiff.c
+	$(CC) $(CFLAGS) -c -o bdiff-oss-fuzz.o ../../mercurial/bdiff.c
+
+bdiff_fuzzer: bdiff.cc bdiff-oss-fuzz.o fuzzutil-oss-fuzz.o
+	$(CXX) $(CXXFLAGS) -std=c++17 -I../../mercurial bdiff.cc \
+	  bdiff-oss-fuzz.o fuzzutil-oss-fuzz.o -lFuzzingEngine -o \
+	  $$OUT/bdiff_fuzzer
+
+mpatch.o: ../../mercurial/mpatch.c
+	$(CC) -g -O1 -fsanitize=fuzzer-no-link,address -c -o mpatch.o \
+	  ../../mercurial/mpatch.c
 
-bdiff_fuzzer: bdiff.cc bdiff-oss-fuzz.o
-	$$CXX $$CXXFLAGS -std=c++11 -I../../mercurial bdiff.cc \
-	  bdiff-oss-fuzz.o -lFuzzingEngine -o $$OUT/bdiff_fuzzer
+mpatch: CXXFLAGS += -std=c++17
+mpatch: mpatch.cc mpatch.o fuzzutil.o
+	$(CXX) $(CXXFLAGS) -DHG_FUZZER_INCLUDE_MAIN=1 -g -O1 -fsanitize=fuzzer-no-link,address \
+	  -I../../mercurial mpatch.cc mpatch.o fuzzutil.o -o mpatch
+
+mpatch-oss-fuzz.o: ../../mercurial/mpatch.c
+	$(CC) $(CFLAGS) -c -o mpatch-oss-fuzz.o ../../mercurial/mpatch.c
+
+mpatch_fuzzer: mpatch.cc mpatch-oss-fuzz.o fuzzutil-oss-fuzz.o
+	$(CXX) $(CXXFLAGS) -std=c++17 -I../../mercurial mpatch.cc \
+	  mpatch-oss-fuzz.o fuzzutil-oss-fuzz.o -lFuzzingEngine -o \
+	  $$OUT/mpatch_fuzzer
+
+mpatch_corpus.zip:
+	python mpatch_corpus.py $$OUT/mpatch_fuzzer_seed_corpus.zip
 
 x%.o: ../../mercurial/thirdparty/xdiff/x%.c ../../mercurial/thirdparty/xdiff/*.h
-	clang -g -O1 -fsanitize=fuzzer-no-link,address -c \
+	$(CC) -g -O1 -fsanitize=fuzzer-no-link,address -c \
 	  -o $@ \
 	  $<
 
-xdiff: xdiff.cc xdiffi.o xprepare.o  xutils.o
-	clang -DHG_FUZZER_INCLUDE_MAIN=1 -g -O1 -fsanitize=fuzzer-no-link,address \
+xdiff: CXXFLAGS += -std=c++17
+xdiff: xdiff.cc xdiffi.o xprepare.o xutils.o fuzzutil.o
+	$(CXX) $(CXXFLAGS) -DHG_FUZZER_INCLUDE_MAIN=1 -g -O1 -fsanitize=fuzzer-no-link,address \
 	  -I../../mercurial xdiff.cc \
-	  xdiffi.o xprepare.o xutils.o -o xdiff
+	  xdiffi.o xprepare.o xutils.o fuzzutil.o -o xdiff
 
 fuzz-x%.o: ../../mercurial/thirdparty/xdiff/x%.c ../../mercurial/thirdparty/xdiff/*.h
-	$$CC $$CFLAGS -c \
+	$(CC) $(CFLAGS) -c \
 	  -o $@ \
 	  $<
 
-xdiff_fuzzer: xdiff.cc fuzz-xdiffi.o fuzz-xprepare.o  fuzz-xutils.o
-	$$CXX $$CXXFLAGS -std=c++11 -I../../mercurial xdiff.cc \
-	  fuzz-xdiffi.o fuzz-xprepare.o fuzz-xutils.o \
+xdiff_fuzzer: xdiff.cc fuzz-xdiffi.o fuzz-xprepare.o fuzz-xutils.o fuzzutil-oss-fuzz.o
+	$(CXX) $(CXXFLAGS) -std=c++17 -I../../mercurial xdiff.cc \
+	  fuzz-xdiffi.o fuzz-xprepare.o fuzz-xutils.o fuzzutil-oss-fuzz.o \
 	  -lFuzzingEngine -o $$OUT/xdiff_fuzzer
 
-all: bdiff xdiff
+clean:
+	$(RM) *.o *_fuzzer \
+	  bdiff \
+	  mpatch \
+	  xdiff
 
-oss-fuzz: bdiff_fuzzer xdiff_fuzzer
+oss-fuzz: bdiff_fuzzer mpatch_fuzzer mpatch_corpus.zip xdiff_fuzzer
 
-.PHONY: all oss-fuzz
+.PHONY: all clean oss-fuzz
--- a/contrib/fuzz/bdiff.cc	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/fuzz/bdiff.cc	Thu Jul 19 13:55:54 2018 -0400
@@ -6,30 +6,25 @@
  * This software may be used and distributed according to the terms of
  * the GNU General Public License, incorporated herein by reference.
  */
+#include <memory>
 #include <stdlib.h>
 
+#include "fuzzutil.h"
+
 extern "C" {
 #include "bdiff.h"
 
 int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
 {
-	if (!Size) {
+	auto maybe_inputs = SplitInputs(Data, Size);
+	if (!maybe_inputs) {
 		return 0;
 	}
-	// figure out a random point in [0, Size] to split our input.
-	size_t split = Data[0] / 255.0 * Size;
-
-	// left input to diff is data[1:split]
-	const uint8_t *left = Data + 1;
-	// which has len split-1
-	size_t left_size = split - 1;
-	// right starts at the next byte after left ends
-	const uint8_t *right = left + left_size;
-	size_t right_size = Size - split;
+	auto inputs = std::move(maybe_inputs.value());
 
 	struct bdiff_line *a, *b;
-	int an = bdiff_splitlines((const char *)left, split - 1, &a);
-	int bn = bdiff_splitlines((const char *)right, right_size, &b);
+	int an = bdiff_splitlines(inputs.left.get(), inputs.left_size, &a);
+	int bn = bdiff_splitlines(inputs.right.get(), inputs.right_size, &b);
 	struct bdiff_hunk l;
 	bdiff_diff(a, an, b, bn, &l);
 	free(a);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/fuzz/fuzzutil.cc	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,27 @@
+#include "fuzzutil.h"
+
+#include <cstring>
+#include <utility>
+
+contrib::optional<two_inputs> SplitInputs(const uint8_t *Data, size_t Size)
+{
+	if (!Size) {
+		return contrib::nullopt;
+	}
+	// figure out a random point in [0, Size] to split our input.
+	size_t left_size = (Data[0] / 255.0) * (Size - 1);
+
+	// Copy inputs to new allocations so if bdiff over-reads
+	// AddressSanitizer can detect it.
+	std::unique_ptr<char[]> left(new char[left_size]);
+	std::memcpy(left.get(), Data + 1, left_size);
+	// right starts at the next byte after left ends
+	size_t right_size = Size - (left_size + 1);
+	std::unique_ptr<char[]> right(new char[right_size]);
+	std::memcpy(right.get(), Data + 1 + left_size, right_size);
+	LOG(2) << "inputs are  " << left_size << " and " << right_size
+	       << " bytes" << std::endl;
+	two_inputs result = {std::move(right), right_size, std::move(left),
+	                     left_size};
+	return result;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/fuzz/fuzzutil.h	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,47 @@
+#ifndef CONTRIB_FUZZ_FUZZUTIL_H
+#define CONTRIB_FUZZ_FUZZUTIL_H
+#include <iostream>
+#include <memory>
+#include <stdint.h>
+
+/* Try and use std::optional, but failing that assume we'll have a
+ * workable https://abseil.io/ install on the include path to get
+ * their backport of std::optional. */
+#ifdef __has_include
+#if __has_include(<optional>) && __cplusplus >= 201703L
+#include <optional>
+#define CONTRIB_FUZZ_HAVE_STD_OPTIONAL
+#endif
+#endif
+#ifdef CONTRIB_FUZZ_HAVE_STD_OPTIONAL
+namespace contrib
+{
+using std::nullopt;
+using std::optional;
+} /* namespace contrib */
+#else
+#include "third_party/absl/types/optional.h"
+namespace contrib
+{
+using absl::nullopt;
+using absl::optional;
+} /* namespace contrib */
+#endif
+
+/* set DEBUG to 1 for a few debugging prints, or 2 for a lot */
+#define DEBUG 0
+#define LOG(level)                                                             \
+	if (level <= DEBUG)                                                    \
+	std::cout
+
+struct two_inputs {
+	std::unique_ptr<char[]> right;
+	size_t right_size;
+	std::unique_ptr<char[]> left;
+	size_t left_size;
+};
+
+/* Split a non-zero-length input into two inputs. */
+contrib::optional<two_inputs> SplitInputs(const uint8_t *Data, size_t Size);
+
+#endif /* CONTRIB_FUZZ_FUZZUTIL_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/fuzz/mpatch.cc	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,122 @@
+/*
+ * mpatch.cc - fuzzer harness for mpatch.c
+ *
+ * Copyright 2018, Google Inc.
+ *
+ * This software may be used and distributed according to the terms of
+ * the GNU General Public License, incorporated herein by reference.
+ */
+#include <iostream>
+#include <memory>
+#include <stdint.h>
+#include <stdlib.h>
+#include <vector>
+
+#include "fuzzutil.h"
+
+// To avoid having too many OOMs from the fuzzer infrastructure, we'll
+// skip patch application if the resulting fulltext would be bigger
+// than 10MiB.
+#define MAX_OUTPUT_SIZE 10485760
+
+extern "C" {
+#include "bitmanipulation.h"
+#include "mpatch.h"
+
+struct mpatchbin {
+	std::unique_ptr<char[]> data;
+	size_t len;
+};
+
+static mpatch_flist *getitem(void *vbins, ssize_t pos)
+{
+	std::vector<mpatchbin> *bins = (std::vector<mpatchbin> *)vbins;
+	const mpatchbin &bin = bins->at(pos + 1);
+	struct mpatch_flist *res;
+	LOG(2) << "mpatch_decode " << bin.len << std::endl;
+	if (mpatch_decode(bin.data.get(), bin.len, &res) < 0)
+		return NULL;
+	return res;
+}
+
+// input format:
+// u8 number of inputs
+// one u16 for each input, its length
+// the inputs
+int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
+{
+	if (!Size) {
+		return 0;
+	}
+	// First byte of data is how many texts we expect, first text
+	// being the base the rest being the deltas.
+	ssize_t numtexts = Data[0];
+	if (numtexts < 2) {
+		// No point if we don't have at least a base text and a delta...
+		return 0;
+	}
+	// Each text will be described by a byte for how long it
+	// should be, so give up if we don't have enough.
+	if ((Size - 1) < (numtexts * 2)) {
+		return 0;
+	}
+	size_t consumed = 1 + (numtexts * 2);
+	LOG(2) << "input contains " << Size << std::endl;
+	LOG(2) << numtexts << " texts, consuming " << consumed << std::endl;
+	std::vector<mpatchbin> bins;
+	bins.reserve(numtexts);
+	for (int i = 0; i < numtexts; ++i) {
+		mpatchbin bin;
+		size_t nthsize = getbeuint16((char *)Data + 1 + (2 * i));
+		LOG(2) << "text " << i << " is " << nthsize << std::endl;
+		char *start = (char *)Data + consumed;
+		consumed += nthsize;
+		if (consumed > Size) {
+			LOG(2) << "ran out of data, consumed " << consumed
+			       << " of " << Size << std::endl;
+			return 0;
+		}
+		bin.len = nthsize;
+		bin.data.reset(new char[nthsize]);
+		memcpy(bin.data.get(), start, nthsize);
+		bins.push_back(std::move(bin));
+	}
+	LOG(2) << "mpatch_flist" << std::endl;
+	struct mpatch_flist *patch =
+	    mpatch_fold(&bins, getitem, 0, numtexts - 1);
+	if (!patch) {
+		return 0;
+	}
+	LOG(2) << "mpatch_calcsize" << std::endl;
+	ssize_t outlen = mpatch_calcsize(bins[0].len, patch);
+	LOG(2) << "outlen " << outlen << std::endl;
+	if (outlen < 0 || outlen > MAX_OUTPUT_SIZE) {
+		goto cleanup;
+	}
+	{
+		char *dest = (char *)malloc(outlen);
+		LOG(2) << "expecting " << outlen << " total bytes at "
+		       << (void *)dest << std::endl;
+		mpatch_apply(dest, bins[0].data.get(), bins[0].len, patch);
+		free(dest);
+		LOG(1) << "applied a complete patch" << std::endl;
+	}
+cleanup:
+	mpatch_lfree(patch);
+	return 0;
+}
+
+#ifdef HG_FUZZER_INCLUDE_MAIN
+int main(int argc, char **argv)
+{
+	// One text, one patch.
+	const char data[] = "\x02\x00\0x1\x00\x0d"
+	                    // base text
+	                    "a"
+	                    // binary delta that will append a single b
+	                    "\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01b";
+	return LLVMFuzzerTestOneInput((const uint8_t *)data, 19);
+}
+#endif
+
+} // extern "C"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/fuzz/mpatch_corpus.py	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,345 @@
+from __future__ import absolute_import, print_function
+
+import argparse
+import struct
+import zipfile
+
+from mercurial import (
+    hg,
+    ui as uimod,
+)
+
+ap = argparse.ArgumentParser()
+ap.add_argument("out", metavar="some.zip", type=str, nargs=1)
+args = ap.parse_args()
+
+class deltafrag(object):
+    def __init__(self, start, end, data):
+        self.start = start
+        self.end = end
+        self.data = data
+
+    def __str__(self):
+        return struct.pack(
+            ">lll", self.start, self.end, len(self.data)) + self.data
+
+class delta(object):
+    def __init__(self, frags):
+        self.frags = frags
+
+    def __str__(self):
+        return ''.join(str(f) for f in self.frags)
+
+class corpus(object):
+
+    def __init__(self, base, deltas):
+        self.base = base
+        self.deltas = deltas
+
+    def __str__(self):
+        deltas = [str(d) for d in self.deltas]
+        parts = (
+            [
+                struct.pack(">B", len(deltas) + 1),
+                struct.pack(">H", len(self.base)),
+            ]
+            + [struct.pack(">H", len(d)) for d in deltas]
+            + [self.base]
+            + deltas
+        )
+        return "".join(parts)
+
+with zipfile.ZipFile(args.out[0], "w", zipfile.ZIP_STORED) as zf:
+    # Manually constructed entries
+    zf.writestr(
+        "one_delta_applies",
+        str(corpus('a', [delta([deltafrag(0, 1, 'b')])]))
+    )
+    zf.writestr(
+        "one_delta_starts_late",
+        str(corpus('a', [delta([deltafrag(3, 1, 'b')])]))
+    )
+    zf.writestr(
+        "one_delta_ends_late",
+        str(corpus('a', [delta([deltafrag(0, 20, 'b')])]))
+    )
+
+    try:
+        # Generated from repo data
+        r = hg.repository(uimod.ui(), '../..')
+        fl = r.file('mercurial/manifest.py')
+        rl = getattr(fl, '_revlog', fl)
+        bins = rl._chunks(rl._deltachain(10)[0])
+        zf.writestr('manifest_py_rev_10',
+                    str(corpus(bins[0], bins[1:])))
+    except: # skip this, so no re-raises
+        print('skipping seed file from repo data')
+    # Automatically discovered by running the fuzzer
+    zf.writestr(
+        "mpatch_decode_old_overread", "\x02\x00\x00\x00\x02\x00\x00\x00"
+    )
+    # https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=8876
+    zf.writestr(
+        "mpatch_ossfuzz_getbe32_ubsan",
+        "\x02\x00\x00\x00\x0c    \xff\xff\xff\xff    ")
+    zf.writestr(
+        "mpatch_apply_over_memcpy",
+        '\x13\x01\x00\x05\xd0\x00\x00\x00\x00\x00\x00\x00\x00\n \x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8c\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x00A\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x94\x18'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x94\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfa\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x13\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00]\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00se\x00\x00'
+        '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+        '\x00\x00\x00\x00')
--- a/contrib/fuzz/xdiff.cc	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/fuzz/xdiff.cc	Thu Jul 19 13:55:54 2018 -0400
@@ -10,6 +10,8 @@
 #include <inttypes.h>
 #include <stdlib.h>
 
+#include "fuzzutil.h"
+
 extern "C" {
 
 int hunk_consumer(long a1, long a2, long b1, long b2, void *priv)
@@ -20,21 +22,17 @@
 
 int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
 {
-	if (!Size) {
+	auto maybe_inputs = SplitInputs(Data, Size);
+	if (!maybe_inputs) {
 		return 0;
 	}
-	// figure out a random point in [0, Size] to split our input.
-	size_t split = Data[0] / 255.0 * Size;
-
+	auto inputs = std::move(maybe_inputs.value());
 	mmfile_t a, b;
 
-	// `a` input to diff is data[1:split]
-	a.ptr = (char *)Data + 1;
-	// which has len split-1
-	a.size = split - 1;
-	// `b` starts at the next byte after `a` ends
-	b.ptr = a.ptr + a.size;
-	b.size = Size - split;
+	a.ptr = inputs.left.get();
+	a.size = inputs.left_size;
+	b.ptr = inputs.right.get();
+	b.size = inputs.right_size;
 	xpparam_t xpp = {
 	    XDF_INDENT_HEURISTIC, /* flags */
 	};
--- a/contrib/genosxversion.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/genosxversion.py	Thu Jul 19 13:55:54 2018 -0400
@@ -117,9 +117,9 @@
         return
     with open(opts.versionfile) as f:
         for l in f:
-            if l.startswith('version = '):
+            if l.startswith('version = b'):
                 # version number is entire line minus the quotes
-                ver = l[len('version = ') + 1:-2]
+                ver = l[len('version = b') + 1:-2]
                 break
     if opts.paranoid:
         print(paranoidver(ver))
--- a/contrib/hg-ssh	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/hg-ssh	Thu Jul 19 13:55:54 2018 -0400
@@ -39,10 +39,14 @@
 
 from mercurial import (
     dispatch,
+    pycompat,
     ui as uimod,
 )
 
 def main():
+    # Prevent insertion/deletion of CRs
+    dispatch.initstdio()
+
     cwd = os.getcwd()
     readonly = False
     args = sys.argv[1:]
@@ -66,15 +70,15 @@
         path = cmdargv[2]
         repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path)))
         if repo in allowed_paths:
-            cmd = ['-R', repo, 'serve', '--stdio']
+            cmd = [b'-R', pycompat.fsencode(repo), b'serve', b'--stdio']
             req = dispatch.request(cmd)
             if readonly:
                 if not req.ui:
                     req.ui = uimod.ui.load()
-                req.ui.setconfig('hooks', 'pretxnopen.hg-ssh',
-                                 'python:__main__.rejectpush', 'hg-ssh')
-                req.ui.setconfig('hooks', 'prepushkey.hg-ssh',
-                                 'python:__main__.rejectpush', 'hg-ssh')
+                req.ui.setconfig(b'hooks', b'pretxnopen.hg-ssh',
+                                 b'python:__main__.rejectpush', b'hg-ssh')
+                req.ui.setconfig(b'hooks', b'prepushkey.hg-ssh',
+                                 b'python:__main__.rejectpush', b'hg-ssh')
             dispatch.dispatch(req)
         else:
             sys.stderr.write('Illegal repository "%s"\n' % repo)
@@ -84,7 +88,7 @@
         sys.exit(255)
 
 def rejectpush(ui, **kwargs):
-    ui.warn(("Permission denied\n"))
+    ui.warn((b"Permission denied\n"))
     # mercurial hooks use unix process conventions for hook return values
     # so a truthy return means failure
     return True
--- a/contrib/linux-wheel-centos5-blacklist	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,3 +0,0 @@
-test-convert-git.t
-test-subrepo-git.t
-test-patchbomb-tls.t
--- a/contrib/macosx/Readme.html	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,37 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<!-- This is the second screen displayed during the install. -->
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-  <meta http-equiv="Content-Style-Type" content="text/css">
-  <title>Read Me - Important Information</title>
-  <style type="text/css">
-    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
-    p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
-    p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
-    p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #000fed}
-    span.s1 {text-decoration: underline}
-    span.s2 {font: 12.0px Courier}
-  </style>
-</head>
-<body>
-<p class="p1"><b>Before you install</b></p>
-<p class="p2"><br></p>
-<p class="p3">This is an OS X version of Mercurial that depends on the default Python installation.</p>
-<p class="p2"><br></p>
-<p class="p1"><b>After you install</b></p>
-<p class="p2"><br></p>
-<p class="p3">This package installs the <span class="s2">hg</span> executable as <span class="s2">/usr/local/bin/hg</span>. See <span class="s2">hg debuginstall</span> for more info on file locations.</p>
-<p class="p2"><br></p>
-<p class="p1"><b>Documentation</b></p>
-<p class="p2"><br></p>
-<p class="p3">Visit the <a href="https://mercurial-scm.org/">Mercurial web site and wiki</a></p>
-<p class="p2"><br></p>
-<p class="p3">There's also a free book, <a href="https://book.mercurial-scm.org/">Distributed revision control with Mercurial</a></p>
-<p class="p2"><br></p>
-<p class="p1"><b>Reporting problems</b></p>
-<p class="p2"><br></p>
-<p class="p3">If you run into any problems, please file a bug online:</p>
-<p class="p3"><a href="https://bz.mercurial-scm.org/">https://bz.mercurial-scm.org/</a></p>
-</body>
-</html>
--- a/contrib/macosx/Welcome.html	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
-<!-- This is the first screen displayed during the install. -->
-<html>
-<head>
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
-  <meta http-equiv="Content-Style-Type" content="text/css">
-  <title></title>
-  <style type="text/css">
-    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
-    p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
-  </style>
-</head>
-<body>
-<p class="p1">This is a prepackaged release of <a href="https://mercurial-scm.org/">Mercurial</a> for Mac OS X.</p>
-<p class="p2"><br></p>
-<br>
-<p>
-Please be sure to read the latest <a href="https://mercurial-scm.org/wiki/WhatsNew">release notes</a>.</p>
-</body>
-</html>
--- a/contrib/macosx/distribution.xml	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8" standalone="no"?>
-<installer-gui-script minSpecVersion="1">
-  <title>Mercurial SCM</title>
-  <organization>org.mercurial-scm</organization>
-  <options customize="never" require-scripts="false" rootVolumeOnly="true" />
-  <welcome file="Welcome.html" mime-type="text/html" />
-  <license file="../../COPYING" mime-type="text/plain" />
-  <readme file="Readme.html" mime-type="text/html" />
-  <pkg-ref id="org.mercurial-scm.mercurial"
-           version="0"
-           auth="root"
-           onConclusion="none">mercurial.pkg</pkg-ref>
-  <choices-outline>
-    <line choice="org.mercurial-scm.mercurial"/>
-  </choices-outline>
-  <choice id="org.mercurial-scm.mercurial" visible="false">
-    <pkg-ref id="org.mercurial-scm.mercurial"/>
-  </choice>
-</installer-gui-script>
--- a/contrib/mercurial.spec	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-%global emacs_lispdir %{_datadir}/emacs/site-lisp
-
-%define withpython %{nil}
-
-%if "%{?withpython}"
-
-%global pythonver %{withpython}
-%global pythonname Python-%{withpython}
-%global docutilsname docutils-0.14
-%global docutilsmd5 c53768d63db3873b7d452833553469de
-%global pythonhg python-hg
-%global hgpyprefix /opt/%{pythonhg}
-# byte compilation will fail on some some Python /test/ files
-%global _python_bytecompile_errors_terminate_build 0
-
-%else
-
-%global pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))')
-
-%endif
-
-Summary: A fast, lightweight Source Control Management system
-Name: mercurial
-Version: snapshot
-Release: 0
-License: GPLv2+
-Group: Development/Tools
-URL: https://mercurial-scm.org/
-Source0: %{name}-%{version}-%{release}.tar.gz
-%if "%{?withpython}"
-Source1: %{pythonname}.tgz
-Source2: %{docutilsname}.tar.gz
-%endif
-BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
-
-BuildRequires: make, gcc, gettext
-%if "%{?withpython}"
-BuildRequires: readline-devel, openssl-devel, ncurses-devel, zlib-devel, bzip2-devel
-%else
-BuildRequires: python >= 2.7, python-devel, python-docutils >= 0.5
-Requires: python >= 2.7
-%endif
-# The hgk extension uses the wish tcl interpreter, but we don't enforce it
-#Requires: tk
-
-%description
-Mercurial is a fast, lightweight source control management system designed
-for efficient handling of very large distributed projects.
-
-%prep
-
-%if "%{?withpython}"
-%setup -q -n mercurial-%{version}-%{release} -a1 -a2
-# despite the comments in cgi.py, we do this to prevent rpmdeps from picking /usr/local/bin/python up
-sed -i '1c#! /usr/bin/env python' %{pythonname}/Lib/cgi.py
-%else
-%setup -q -n mercurial-%{version}-%{release}
-%endif
-
-%build
-
-%if "%{?withpython}"
-
-PYPATH=$PWD/%{pythonname}
-cd $PYPATH
-./configure --prefix=%{hgpyprefix}
-make all %{?_smp_mflags}
-cd -
-
-cd %{docutilsname}
-LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py build
-cd -
-
-# verify Python environment
-LD_LIBRARY_PATH=$PYPATH PYTHONPATH=$PWD/%{docutilsname} $PYPATH/python -c 'import sys, zlib, bz2, ssl, curses, readline'
-
-# set environment for make
-export PATH=$PYPATH:$PATH
-export LD_LIBRARY_PATH=$PYPATH
-export CFLAGS="-L $PYPATH"
-export PYTHONPATH=$PWD/%{docutilsname}
-
-%endif
-
-make all
-make -C contrib/chg
-
-%install
-rm -rf $RPM_BUILD_ROOT
-
-%if "%{?withpython}"
-
-PYPATH=$PWD/%{pythonname}
-cd $PYPATH
-make install DESTDIR=$RPM_BUILD_ROOT
-# these .a are not necessary and they are readonly and strip fails - kill them!
-rm -f %{buildroot}%{hgpyprefix}/lib/{,python2.*/config}/libpython2.*.a
-cd -
-
-cd %{docutilsname}
-LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py install --root="$RPM_BUILD_ROOT"
-cd -
-
-PATH=$PYPATH:$PATH LD_LIBRARY_PATH=$PYPATH make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{hgpyprefix} MANDIR=%{_mandir}
-mkdir -p $RPM_BUILD_ROOT%{_bindir}
-( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/hg . )
-( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/python2.? %{pythonhg} )
-
-%else
-
-make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir}
-
-%endif
-
-install -m 755 contrib/chg/chg $RPM_BUILD_ROOT%{_bindir}/
-install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/
-install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/
-
-bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
-mkdir -p $bash_completion_dir
-install -m 644 contrib/bash_completion $bash_completion_dir/mercurial.sh
-
-zsh_completion_dir=$RPM_BUILD_ROOT%{_datadir}/zsh/site-functions
-mkdir -p $zsh_completion_dir
-install -m 644 contrib/zsh_completion $zsh_completion_dir/_mercurial
-
-mkdir -p $RPM_BUILD_ROOT%{emacs_lispdir}
-install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}/
-install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}/
-
-mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/mercurial/hgrc.d
-
-%clean
-rm -rf $RPM_BUILD_ROOT
-
-%files
-%defattr(-,root,root,-)
-%doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi
-%doc %attr(644,root,root) %{_mandir}/man?/hg*
-%doc %attr(644,root,root) contrib/*.svg
-%dir %{_datadir}/zsh/
-%dir %{_datadir}/zsh/site-functions/
-%{_datadir}/zsh/site-functions/_mercurial
-%dir %{_datadir}/emacs/site-lisp/
-%{_datadir}/emacs/site-lisp/mercurial.el
-%{_datadir}/emacs/site-lisp/mq.el
-%{_bindir}/hg
-%{_bindir}/chg
-%{_bindir}/hgk
-%{_bindir}/hg-ssh
-%dir %{_sysconfdir}/bash_completion.d/
-%config(noreplace) %{_sysconfdir}/bash_completion.d/mercurial.sh
-%dir %{_sysconfdir}/mercurial
-%dir %{_sysconfdir}/mercurial/hgrc.d
-%if "%{?withpython}"
-%{_bindir}/%{pythonhg}
-%{hgpyprefix}
-%else
-%{_libdir}/python%{pythonver}/site-packages/%{name}-*-py%{pythonver}.egg-info
-%{_libdir}/python%{pythonver}/site-packages/%{name}
-%{_libdir}/python%{pythonver}/site-packages/hgext
-%{_libdir}/python%{pythonver}/site-packages/hgext3rd
-%{_libdir}/python%{pythonver}/site-packages/hgdemandimport
-%endif
--- a/contrib/packagelib.sh	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,38 +0,0 @@
-# Extract version number into 4 parts, some of which may be empty:
-#
-# version: the numeric part of the most recent tag. Will always look like 1.3.
-#
-# type: if an rc build, "rc", otherwise empty
-#
-# distance: the distance from the nearest tag, or empty if built from a tag
-#
-# node: the node|short hg was built from, or empty if built from a tag
-gethgversion() {
-    export HGRCPATH=
-    export HGPLAIN=
-
-    make cleanbutpackages
-    make local PURE=--pure
-    HG="$PWD/hg"
-
-    "$HG" version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
-
-    hgversion=`LANGUAGE=C "$HG" version | sed -ne 's/.*(version \(.*\))$/\1/p'`
-
-    if echo $hgversion | grep + > /dev/null 2>&1 ; then
-        tmp=`echo $hgversion | cut -d+ -f 2`
-        hgversion=`echo $hgversion | cut -d+ -f 1`
-        distance=`echo $tmp | cut -d- -f 1`
-        node=`echo $tmp | cut -d- -f 2`
-    else
-        distance=''
-        node=''
-    fi
-    if echo $hgversion | grep -- '-' > /dev/null 2>&1; then
-        version=`echo $hgversion | cut -d- -f1`
-        type=`echo $hgversion | cut -d- -f2`
-    else
-        version=$hgversion
-        type=''
-    fi
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/Makefile	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,144 @@
+$(eval HGROOT := $(shell cd ../..; pwd))
+
+DEBIAN_CODENAMES := \
+  jessie \
+  stretch \
+  buster
+
+UBUNTU_CODENAMES := \
+  trusty \
+  xenial \
+  artful \
+  bionic \
+
+FEDORA_RELEASES := \
+  20 \
+  21 \
+  28
+
+CENTOS_RELEASES := \
+  5 \
+  6 \
+  7
+
+# Build a Python for these CentOS releases.
+CENTOS_WITH_PYTHON_RELEASES := 5 6
+
+help:
+	@echo 'Packaging Make Targets'
+	@echo ''
+	@echo 'docker-centos{$(strip $(CENTOS_RELEASES))}'
+	@echo '   Build an RPM for a specific CentOS version using Docker.'
+	@echo ''
+	@echo 'docker-debian-{$(strip $(DEBIAN_CODENAMES))}'
+	@echo '   Build Debian packages specific to a Debian distro using Docker.'
+	@echo ''
+	@echo 'docker-fedora{$(strip $(FEDORA_RELEASES))}'
+	@echo '   Build an RPM for a specific Fedora version using Docker.'
+	@echo ''
+	@echo 'docker-ubuntu-{$(strip $(UBUNTU_CODENAMES))}'
+	@echo '   Build Debian package specific to an Ubuntu distro using Docker.'
+	@echo ''
+	@echo 'docker-ubuntu-{$(strip $(UBUNTU_CODENAMES))}-ppa'
+	@echo '   Build a source-only Debian package specific to an Ubuntu distro'
+	@echo '   using Docker.'
+	@echo ''
+	@echo 'linux-wheels'
+	@echo '   Build Linux manylinux wheels using Docker.'
+	@echo ''
+	@echo 'linux-wheels-{x86_64, i686}'
+	@echo '   Build Linux manylinux wheels for a specific architecture using Docker'
+	@echo ''
+	@echo 'deb'
+	@echo '   Build a Debian package locally targeting the current system'
+	@echo ''
+	@echo 'ppa'
+	@echo '   Build a Debian source package locally targeting the current system'
+	@echo ''
+	@echo 'centos{$(strip $(CENTOS_RELEASES))}'
+	@echo '   Build an RPM for a specific CentOS version locally'
+	@echo ''
+	@echo 'fedora{$(strip $(FEDORA_RELEASES))}'
+	@echo '   Build an RPM for a specific Fedora version locally'
+
+.PHONY: help
+
+.PHONY: deb
+deb:
+	./builddeb
+
+.PHONY: ppa
+ppa:
+	./builddeb --source-only
+
+# Debian targets.
+define debian_targets =
+.PHONY: docker-debian-$(1)
+docker-debian-$(1):
+	./dockerdeb debian $(1)
+
+endef
+
+$(foreach codename,$(DEBIAN_CODENAMES),$(eval $(call debian_targets,$(codename))))
+
+# Ubuntu targets.
+define ubuntu_targets =
+.PHONY: docker-ubuntu-$(1)
+docker-ubuntu-$(1):
+	./dockerdeb ubuntu $(1)
+
+.PHONY: docker-ubuntu-$(1)-ppa
+docker-ubuntu-$(1)-ppa:
+	./dockerdeb ubuntu $(1) --source-only
+
+endef
+
+$(foreach codename,$(UBUNTU_CODENAMES),$(eval $(call ubuntu_targets,$(codename))))
+
+# Fedora targets.
+define fedora_targets
+.PHONY: fedora$(1)
+fedora$(1):
+	mkdir -p $$(HGROOT)/packages/fedora$(1)
+	./buildrpm
+	cp $$(HGROOT)/contrib/packaging/rpmbuild/RPMS/*/* $$(HGROOT)/packages/fedora$(1)
+	cp $$(HGROOT)/contrib/packaging/rpmbuild/SRPMS/* $$(HGROOT)/packages/fedora$(1)
+	rm -rf $(HGROOT)/rpmbuild
+
+.PHONY: docker-fedora$(1)
+docker-fedora$(1):
+	mkdir -p $$(HGROOT)/packages/fedora$(1)
+	./dockerrpm fedora$(1)
+
+endef
+
+$(foreach release,$(FEDORA_RELEASES),$(eval $(call fedora_targets,$(release))))
+
+# CentOS targets.
+define centos_targets
+.PHONY: centos$(1)
+centos$(1):
+	mkdir -p $$(HGROOT)/packages/centos$(1)
+	./buildrpm $$(if $$(filter $(1),$$(CENTOS_WITH_PYTHON_RELEASES)),--withpython)
+	cp $$(HGROOT)/rpmbuild/RPMS/*/* $$(HGROOT)/packages/centos$(1)
+	cp $$(HGROOT)/rpmbuild/SRPMS/* $$(HGROOT)/packages/centos$(1)
+
+.PHONY: docker-centos$(1)
+docker-centos$(1):
+	mkdir -p $$(HGROOT)/packages/centos$(1)
+	./dockerrpm centos$(1) $$(if $$(filter $(1),$$(CENTOS_WITH_PYTHON_RELEASES)),--withpython)
+
+endef
+
+$(foreach release,$(CENTOS_RELEASES),$(eval $(call centos_targets,$(release))))
+
+.PHONY: linux-wheels
+linux-wheels: linux-wheels-x86_64 linux-wheels-i686
+
+.PHONY: linux-wheels-x86_64
+linux-wheels-x86_64:
+	docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`/../..:/src quay.io/pypa/manylinux1_x86_64 /src/contrib/packaging/build-linux-wheels.sh
+
+.PHONY: linux-wheels-i686
+linux-wheels-i686:
+	docker run -e "HGTEST_JOBS=$(shell nproc)" --rm -ti -v `pwd`/../..:/src quay.io/pypa/manylinux1_i686 linux32 /src/contrib/packaging/build-linux-wheels.sh
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/build-linux-wheels.sh	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,34 @@
+#!/bin/bash
+# This file is directly inspired by
+# https://github.com/pypa/python-manylinux-demo/blob/master/travis/build-wheels.sh
+set -e -x
+
+PYTHON_TARGETS=$(ls -d /opt/python/cp27*/bin)
+
+# Create an user for the tests
+useradd hgbuilder
+
+# Bypass uid/gid problems
+cp -R /src /io && chown -R hgbuilder:hgbuilder /io
+
+# Compile wheels for Python 2.X
+for PYBIN in $PYTHON_TARGETS; do
+    "${PYBIN}/pip" wheel /io/ -w wheelhouse/
+done
+
+# Bundle external shared libraries into the wheels with
+# auditwheel (https://github.com/pypa/auditwheel) repair.
+# It also fix the ABI tag on the wheel making it pip installable.
+for whl in wheelhouse/*.whl; do
+    auditwheel repair "$whl" -w /src/wheelhouse/
+done
+
+# Install packages and run the tests for all Python versions
+cd /io/tests/
+
+for PYBIN in $PYTHON_TARGETS; do
+    # Install mercurial wheel as root
+    "${PYBIN}/pip" install mercurial --no-index -f /src/wheelhouse
+    # But run tests as hgbuilder user (non-root)
+    su hgbuilder -c "\"${PYBIN}/python\" /io/tests/run-tests.py --with-hg=\"${PYBIN}/hg\" --blacklist=/io/contrib/packaging/linux-wheel-centos5-blacklist"
+done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/builddeb	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,106 @@
+#!/bin/sh -e
+#
+# Build a Mercurial debian package from the current repo
+#
+# Tested on Jessie (stable as of original script authoring.)
+
+. $(dirname $0)/packagelib.sh
+
+ROOTDIR=$(cd $(dirname $0)/../.. > /dev/null; pwd)
+
+BUILD=1
+CLEANUP=1
+DISTID=`(lsb_release -is 2> /dev/null | tr '[:upper:]' '[:lower:]') || echo debian`
+CODENAME=`lsb_release -cs 2> /dev/null || echo unknown`
+DEBFLAGS=-b
+while [ "$1" ]; do
+    case "$1" in
+    --distid )
+        shift
+        DISTID="$1"
+        shift
+        ;;
+    --codename )
+        shift
+        CODENAME="$1"
+        shift
+        ;;
+    --cleanup )
+        shift
+        BUILD=
+        ;;
+    --build )
+        shift
+        CLEANUP=
+        ;;
+    --source-only )
+        shift
+        DEBFLAGS=-S
+        ;;
+    * )
+        echo "Invalid parameter $1!" 1>&2
+        exit 1
+        ;;
+    esac
+done
+
+trap "if [ '$CLEANUP' ] ; then rm -r '$PWD/debian' ; fi" EXIT
+
+set -u
+
+if [ ! -d .hg ]; then
+    echo 'You are not inside a Mercurial repository!' 1>&2
+    exit 1
+fi
+
+gethgversion
+debver="$version"
+if [ -n "$type" ] ; then
+    debver="$debver~$type"
+fi
+if [ -n "$distance" ] ; then
+    debver="$debver+$distance-$CODENAME-$node"
+elif [ "$DEBFLAGS" = "-S" ] ; then
+    # for building a ppa (--source-only) for a release (distance == 0), we need
+    # to version the distroseries so that we can upload to launchpad
+    debver="$debver~${CODENAME}1"
+fi
+
+control=debian/control
+changelog=debian/changelog
+
+if [ "$BUILD" ]; then
+    if [ -d debian ] ; then
+        echo "Error! debian control directory already exists!"
+        exit 1
+    fi
+
+    cp -r "$ROOTDIR"/contrib/packaging/debian debian
+
+    sed -i.tmp "s/__VERSION__/$debver/" $changelog
+    sed -i.tmp "s/__DATE__/$(date --rfc-2822)/" $changelog
+    sed -i.tmp "s/__CODENAME__/$CODENAME/" $changelog
+    rm $changelog.tmp
+
+    # remove the node from the version string
+    SRCFILE="mercurial_$(echo $debver | sed "s,-$node,,").orig.tar.gz"
+    "$ROOTDIR/hg" archive $SRCFILE
+    mv $SRCFILE ..
+    debuild -us -uc -i -I $DEBFLAGS
+    if [ $? != 0 ]; then
+        echo 'debuild failed!'
+        exit 1
+    fi
+
+fi
+if [ "$CLEANUP" ] ; then
+    echo
+    OUTPUTDIR=${OUTPUTDIR:=packages/$DISTID-$CODENAME}
+    mkdir -p "$OUTPUTDIR"
+    find ../mercurial*.deb ../mercurial_*.build ../mercurial_*.changes \
+          ../mercurial*.dsc ../mercurial*.gz \
+          -type f -newer $control -print0 2>/dev/null | \
+      xargs -Inarf -0 mv narf "$OUTPUTDIR"
+    echo "Built packages for $debver:"
+    find "$OUTPUTDIR" -type f -newer $control -name '*.deb'
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/buildrpm	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,162 @@
+#!/bin/bash -e
+#
+# Build a Mercurial RPM from the current repo
+#
+# Tested on
+# - Fedora 20
+# - CentOS 5
+# - centOS 6
+
+. $(dirname $0)/packagelib.sh
+
+BUILD=1
+RPMBUILDDIR="$PWD/rpmbuild"
+
+while [ "$1" ]; do
+    case "$1" in
+    --prepare )
+        shift
+        BUILD=
+        ;;
+    --withpython | --with-python)
+        shift
+        PYTHONVER=2.7.14
+        PYTHONMD5=cee2e4b33ad3750da77b2e85f2f8b724
+        ;;
+    --rpmbuilddir )
+        shift
+        RPMBUILDDIR="$1"
+        shift
+        ;;
+    * )
+        echo "Invalid parameter $1!" 1>&2
+        exit 1
+        ;;
+    esac
+done
+
+cd "`dirname $0`/../.."
+
+specfile=$PWD/contrib/packaging/mercurial.spec
+if [ ! -f $specfile ]; then
+    echo "Cannot find $specfile!" 1>&2
+    exit 1
+fi
+
+if [ ! -d .hg ]; then
+    echo 'You are not inside a Mercurial repository!' 1>&2
+    exit 1
+fi
+
+gethgversion
+
+# TODO: handle distance/node set, and type set
+
+if [ -z "$type" ] ; then
+   release=1
+else
+    release=0.9_$type
+fi
+
+if [ -n "$distance" ] ; then
+    release=$release+$distance_$node
+fi
+
+if [ "$PYTHONVER" ]; then
+    release=$release+$PYTHONVER
+    RPMPYTHONVER=$PYTHONVER
+else
+    RPMPYTHONVER=%{nil}
+fi
+
+mkdir -p $RPMBUILDDIR/{SOURCES,BUILD,SRPMS,RPMS}
+$HG archive -t tgz $RPMBUILDDIR/SOURCES/mercurial-$version-$release.tar.gz
+if [ "$PYTHONVER" ]; then
+(
+    mkdir -p build
+    cd build
+    PYTHON_SRCFILE=Python-$PYTHONVER.tgz
+    [ -f $PYTHON_SRCFILE ] || curl -Lo $PYTHON_SRCFILE http://www.python.org/ftp/python/$PYTHONVER/$PYTHON_SRCFILE
+    if [ "$PYTHONMD5" ]; then
+        echo "$PYTHONMD5 $PYTHON_SRCFILE" | md5sum -w -c
+    fi
+    ln -f $PYTHON_SRCFILE $RPMBUILDDIR/SOURCES/$PYTHON_SRCFILE
+
+    DOCUTILSVER=`sed -ne "s/^%global docutilsname docutils-//p" $specfile`
+    DOCUTILS_SRCFILE=docutils-$DOCUTILSVER.tar.gz
+    [ -f $DOCUTILS_SRCFILE ] || curl -Lo $DOCUTILS_SRCFILE http://downloads.sourceforge.net/project/docutils/docutils/$DOCUTILSVER/$DOCUTILS_SRCFILE
+    DOCUTILSMD5=`sed -ne "s/^%global docutilsmd5 //p" $specfile`
+    if [ "$DOCUTILSMD5" ]; then
+        echo "$DOCUTILSMD5 $DOCUTILS_SRCFILE" | md5sum -w -c
+    fi
+    ln -f $DOCUTILS_SRCFILE $RPMBUILDDIR/SOURCES/$DOCUTILS_SRCFILE
+)
+fi
+
+mkdir -p $RPMBUILDDIR/SPECS
+rpmspec=$RPMBUILDDIR/SPECS/mercurial.spec
+
+sed -e "s,^Version:.*,Version: $version," \
+    -e "s,^Release:.*,Release: $release," \
+    $specfile > $rpmspec
+
+echo >> $rpmspec
+echo "%changelog" >> $rpmspec
+
+if echo $version | grep '+' > /dev/null 2>&1; then
+    latesttag="`echo $version | sed -e 's/+.*//'`"
+    $HG log -r .:"$latesttag" -fM \
+        --template '{date|hgdate}\t{author}\t{desc|firstline}\n' | python -c '
+import sys, time
+
+def datestr(date, format):
+    return time.strftime(format, time.gmtime(float(date[0]) - date[1]))
+
+changelog = []
+for l in sys.stdin.readlines():
+    tok = l.split("\t")
+    hgdate = tuple(int(v) for v in tok[0].split())
+    changelog.append((datestr(hgdate, "%F"), tok[1], hgdate, tok[2]))
+prevtitle = ""
+for l in sorted(changelog, reverse=True):
+    title = "* %s %s" % (datestr(l[2], "%a %b %d %Y"), l[1])
+    if prevtitle != title:
+        prevtitle = title
+        print
+        print title
+    print "- %s" % l[3].strip()
+' >> $rpmspec
+
+else
+
+    $HG log \
+         --template '{date|hgdate}\t{author}\t{desc|firstline}\n' \
+         .hgtags | python -c '
+import sys, time
+
+def datestr(date, format):
+    return time.strftime(format, time.gmtime(float(date[0]) - date[1]))
+
+for l in sys.stdin.readlines():
+    tok = l.split("\t")
+    hgdate = tuple(int(v) for v in tok[0].split())
+    print "* %s %s\n- %s" % (datestr(hgdate, "%a %b %d %Y"), tok[1], tok[2])
+' >> $rpmspec
+
+fi
+
+sed -i \
+    -e "s/^%define withpython.*$/%define withpython $RPMPYTHONVER/" \
+    $rpmspec
+
+if [ "$BUILD" ]; then
+    rpmbuild --define "_topdir $RPMBUILDDIR" -ba $rpmspec --clean
+    if [ $? = 0 ]; then
+        echo
+        echo "Built packages for $version-$release:"
+        find $RPMBUILDDIR/*RPMS/ -type f -newer $rpmspec
+    fi
+else
+    echo "Prepared sources for $version-$release $rpmspec are in $RPMBUILDDIR/SOURCES/ - use like:"
+    echo "rpmbuild --define '_topdir $RPMBUILDDIR' -ba $rpmspec --clean"
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/debian/cacerts.rc	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,5 @@
+# This config file points Mercurial at the system-wide certificate
+# store from the ca-certificates package.
+
+[web]
+cacerts = /etc/ssl/certs/ca-certificates.crt
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/debian/changelog	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,5 @@
+mercurial (__VERSION__) __CODENAME__; urgency=medium
+
+  * Automated build performed by upstream.
+
+ -- Mercurial Devel <mercurial-devel@mercurial-scm.org>  __DATE__
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/debian/compat	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,1 @@
+9
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/debian/control	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,54 @@
+Source: mercurial
+Section: vcs
+Priority: optional
+Maintainer: Mercurial Developers <mercurial-devel@mercurial-scm.org>
+Build-Depends:
+ debhelper (>= 9),
+ dh-python,
+ less,
+ netbase,
+ python-all,
+ python-all-dev,
+ python-docutils,
+ unzip,
+ zip
+Standards-Version: 3.9.4
+X-Python-Version: >= 2.7
+
+Package: mercurial
+Depends:
+ python,
+ ${shlibs:Depends},
+ ${misc:Depends},
+ ${python:Depends},
+ mercurial-common (= ${source:Version})
+Architecture: any
+Description: fast, easy to use, distributed revision control tool.
+ Mercurial is a fast, lightweight Source Control Management system designed
+ for efficient handling of very large distributed projects.
+ .
+ Its features include:
+  * O(1) delta-compressed file storage and retrieval scheme
+  * Complete cross-indexing of files and changesets for efficient exploration
+    of project history
+  * Robust SHA1-based integrity checking and append-only storage model
+  * Decentralized development model with arbitrary merging between trees
+  * Easy-to-use command-line interface
+  * Integrated stand-alone web interface
+  * Small Python codebase
+
+Package: mercurial-common
+Architecture: all
+Depends:
+ ${misc:Depends},
+ ${python:Depends},
+Recommends: mercurial (= ${source:Version}), ca-certificates
+Suggests: wish
+Breaks: mercurial (<< ${source:Version})
+Replaces: mercurial (<< 2.6.3)
+Description: easy-to-use, scalable distributed version control system (common files)
+ Mercurial is a fast, lightweight Source Control Management system designed
+ for efficient handling of very large distributed projects.
+ .
+ This package contains the architecture independent components of Mercurial,
+ and is generally useless without the mercurial package.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/debian/copyright	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,27 @@
+Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: mercurial
+Source: https://www.mercurial-scm.org/
+
+Files: *
+Copyright: 2005-2018, Matt Mackall <mpm@selenic.com> and others.
+License: GPL-2+
+ This program is free software; you can redistribute it
+ and/or modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later
+ version.
+ .
+ This program is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ PURPOSE.  See the GNU General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU General Public
+ License along with this package; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ Boston, MA  02110-1301 USA
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 2 can be found in the file
+ `/usr/share/common-licenses/GPL-2'.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/debian/default-tools.rc	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,5 @@
+[ui]
+editor = sensible-editor
+
+[pager]
+pager = sensible-pager
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/debian/hgkpath.rc	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,2 @@
+[hgk]
+path = /usr/share/mercurial/hgk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/debian/rules	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,44 @@
+#!/usr/bin/make -f
+# Uncomment this to turn on verbose mode.
+# export DH_VERBOSE=1
+
+CPUS=$(shell cat /proc/cpuinfo | grep -E ^processor | wc -l)
+
+%:
+	dh $@ --with python2
+
+override_dh_auto_test:
+	http_proxy='' dh_auto_test -- TESTFLAGS="-j$(CPUS)"
+
+override_dh_python2:
+	dh_python2
+	find debian/mercurial/usr/share -type d -empty -delete
+
+override_dh_install:
+	python$(PYVERS) setup.py install --root "$(CURDIR)"/debian/mercurial --install-layout=deb
+	# chg
+	make -C contrib/chg \
+		DESTDIR="$(CURDIR)"/debian/mercurial \
+		PREFIX=/usr \
+		clean install
+	# remove arch-independent python stuff
+	find "$(CURDIR)"/debian/mercurial/usr/lib \
+		! -name '*.so' ! -type d -delete , \
+		-type d -empty -delete
+	python$(PYVERS) setup.py install --root "$(CURDIR)/debian/mercurial-common" --install-layout=deb
+	make install-doc PREFIX="$(CURDIR)"/debian/mercurial-common/usr
+	# remove arch-dependent python stuff
+	find "$(CURDIR)"/debian/mercurial-common/usr/lib \
+		-name '*.so' ! -type d -delete , \
+		-type d -empty -delete
+	cp contrib/hg-ssh "$(CURDIR)"/debian/mercurial-common/usr/bin
+	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/mercurial
+	cp contrib/hgk "$(CURDIR)"/debian/mercurial-common/usr/share/mercurial
+	mkdir -p "$(CURDIR)"/debian/mercurial-common/etc/mercurial/hgrc.d/
+	cp contrib/packaging/debian/*.rc "$(CURDIR)"/debian/mercurial-common/etc/mercurial/hgrc.d/
+	# completions
+	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/bash-completion/completions
+	cp contrib/bash_completion "$(CURDIR)"/debian/mercurial-common/usr/share/bash-completion/completions/hg
+	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/zsh/vendor-completions
+	cp contrib/zsh_completion "$(CURDIR)"/debian/mercurial-common/usr/share/zsh/vendor-completions/_hg
+	rm "$(CURDIR)"/debian/mercurial-common/usr/bin/hg
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/docker/centos5	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,27 @@
+FROM centos:centos5
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build
+
+RUN \
+	sed -i 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/*.repo && \
+	sed -i 's/^#\(baseurl=\)http:\/\/mirror.centos.org\/centos/\1http:\/\/vault.centos.org/' /etc/yum.repos.d/*.repo && \
+	sed -i 's/\$releasever/5.11/' /etc/yum.repos.d/*.repo
+
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build \
+	tar
+
+# For creating repo meta data
+RUN yum install -y \
+	bzip2-devel \
+	createrepo \
+	ncurses-devel \
+	openssl-devel \
+	readline-devel \
+	zlib-devel
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/docker/centos6	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,24 @@
+FROM centos:centos6
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build
+
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build \
+	tar
+
+# For creating repo meta data
+RUN yum install -y createrepo
+
+# For python
+RUN yum install -y \
+	bzip2-devel \
+	ncurses-devel \
+	openssl-devel \
+	readline-devel \
+	zlib-devel
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/docker/centos7	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,16 @@
+FROM centos:centos7
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build
+
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build \
+	tar
+
+# For creating repo meta data
+RUN yum install -y createrepo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/docker/debian.template	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,16 @@
+FROM debian:%CODENAME%
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build
+
+RUN apt-get update && apt-get install -y \
+  build-essential \
+  debhelper \
+  devscripts \
+  dh-python \
+  less \
+  python \
+  python-all-dev \
+  python-docutils \
+  unzip \
+  zip
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/docker/fedora20	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,15 @@
+FROM fedora:20
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build
+
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
+# For creating repo meta data
+RUN yum install -y createrepo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/docker/fedora21	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,15 @@
+FROM fedora:21
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build
+
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
+# For creating repo meta data
+RUN yum install -y createrepo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/docker/fedora28	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,15 @@
+FROM fedora:28
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build
+
+RUN dnf install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
+# For creating repo meta data
+RUN dnf install -y createrepo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/docker/ubuntu.template	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,16 @@
+FROM ubuntu:%CODENAME%
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -s /bin/bash -d /build -m build
+
+RUN apt-get update && apt-get install -y \
+  build-essential \
+  debhelper \
+  devscripts \
+  dh-python \
+  less \
+  python \
+  python-all-dev \
+  python-docutils \
+  unzip \
+  zip
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/dockerdeb	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,40 @@
+#!/bin/bash -eu
+
+. $(dirname $0)/packagelib.sh
+
+BUILDDIR=$(dirname $0)
+export ROOTDIR=$(cd $BUILDDIR/../.. > /dev/null; pwd)
+
+DISTID="$1"
+CODENAME="$2"
+PLATFORM="$1-$2"
+shift; shift # extra params are passed to build process
+
+OUTPUTDIR=${OUTPUTDIR:=$ROOTDIR/packages/$PLATFORM}
+CONTAINER=hg-docker-$PLATFORM
+
+DOCKER=$($BUILDDIR/hg-docker docker-path)
+
+$BUILDDIR/hg-docker build \
+    --build-arg CODENAME=$CODENAME \
+    $BUILDDIR/docker/$DISTID.template \
+    $CONTAINER
+
+# debuild only appears to be able to save built debs etc to .., so we
+# have to share the .. of the current directory with the docker
+# container and hope it's writable. Whee.
+dn=$(basename $ROOTDIR)
+
+DBUILDUSER=build
+
+if [ $(uname) = "Darwin" ] ; then
+    $DOCKER run -u $DBUILDUSER --rm -v $PWD/..:/mnt $CONTAINER \
+            sh -c "cd /mnt/$dn && make clean && make local"
+fi
+$DOCKER run -u $DBUILDUSER --rm -v $ROOTDIR/..:/mnt $CONTAINER \
+  sh -c "cd /mnt/$dn && DEB_BUILD_OPTIONS='${DEB_BUILD_OPTIONS:=}' contrib/packaging/builddeb --build --distid $DISTID --codename $CODENAME $@"
+contrib/packaging/builddeb --cleanup --distid $DISTID --codename $CODENAME
+if [ $(uname) = "Darwin" ] ; then
+    $DOCKER run -u $DBUILDUSER --rm -v $PWD/..:/mnt $CONTAINER \
+            sh -c "cd /mnt/$dn && make clean"
+fi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/dockerrpm	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,39 @@
+#!/bin/bash -e
+
+BUILDDIR=$(dirname $0)
+export ROOTDIR=$(cd $BUILDDIR/../..; pwd)
+
+PLATFORM="$1"
+shift # extra params are passed to buildrpm
+
+DOCKER=$($BUILDDIR/hg-docker docker-path)
+
+CONTAINER=hg-docker-$PLATFORM
+
+$BUILDDIR/hg-docker build $BUILDDIR/docker/$PLATFORM $CONTAINER
+
+RPMBUILDDIR=$ROOTDIR/packages/$PLATFORM
+$ROOTDIR/contrib/packaging/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $*
+
+DSHARED=/mnt/shared
+DBUILDUSER=build
+
+$DOCKER run -e http_proxy -e https_proxy -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
+    rpmbuild --define "_topdir $DSHARED" -ba $DSHARED/SPECS/mercurial.spec --clean
+
+$DOCKER run -e http_proxy -e https_proxy -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
+    createrepo $DSHARED
+
+cat << EOF > $RPMBUILDDIR/mercurial.repo
+# Place this file in /etc/yum.repos.d/mercurial.repo
+[mercurial]
+name=Mercurial packages for $PLATFORM
+# baseurl=file://$RPMBUILDDIR/
+baseurl=http://hg.example.com/build/$PLATFORM/
+skip_if_unavailable=True
+gpgcheck=0
+enabled=1
+EOF
+
+echo
+echo "Build complete - results can be found in $RPMBUILDDIR"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/hg-docker	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,111 @@
+#!/usr/bin/env python3
+#
+# Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import argparse
+import pathlib
+import shutil
+import subprocess
+import sys
+
+def get_docker() -> str:
+    docker = shutil.which('docker.io') or shutil.which('docker')
+    if not docker:
+        print('could not find docker executable')
+        return 1
+
+    try:
+        out = subprocess.check_output([docker, '-h'], stderr=subprocess.STDOUT)
+
+        if b'Jansens' in out:
+            print('%s is the Docking System Tray; try installing docker.io' %
+                  docker)
+            sys.exit(1)
+    except subprocess.CalledProcessError as e:
+        print('error calling `%s -h`: %s' % (docker, e.output))
+        sys.exit(1)
+
+    out = subprocess.check_output([docker, 'version'],
+                                  stderr=subprocess.STDOUT)
+
+    lines = out.splitlines()
+    if not any(l.startswith((b'Client:', b'Client version:')) for l in lines):
+        print('`%s version` does not look like Docker' % docker)
+        sys.exit(1)
+
+    if not any(l.startswith((b'Server:', b'Server version:')) for l in lines):
+        print('`%s version` does not look like Docker' % docker)
+        sys.exit(1)
+
+    return docker
+
+def get_dockerfile(path: pathlib.Path, args: list) -> bytes:
+    with path.open('rb') as fh:
+        df = fh.read()
+
+    for k, v in args:
+        df = df.replace(b'%%%s%%' % k, v)
+
+    return df
+
+def build_docker_image(dockerfile: pathlib.Path, params: list, tag: str):
+    """Build a Docker image from a templatized Dockerfile."""
+    docker = get_docker()
+
+    dockerfile_path = pathlib.Path(dockerfile)
+
+    dockerfile = get_dockerfile(dockerfile_path, params)
+
+    print('building Dockerfile:')
+    print(dockerfile.decode('utf-8', 'replace'))
+
+    args = [
+        docker,
+        'build',
+        '--build-arg', 'http_proxy',
+        '--build-arg', 'https_proxy',
+        '--tag', tag,
+        '-',
+    ]
+
+    print('executing: %r' % args)
+    subprocess.run(args, input=dockerfile, check=True)
+
+def command_build(args):
+    build_args = []
+    for arg in args.build_arg:
+        k, v = arg.split('=', 1)
+        build_args.append((k.encode('utf-8'), v.encode('utf-8')))
+
+    build_docker_image(pathlib.Path(args.dockerfile),
+                       build_args,
+                       args.tag)
+
+def command_docker(args):
+    print(get_docker())
+
+def main() -> int:
+    parser = argparse.ArgumentParser()
+
+    subparsers = parser.add_subparsers(title='subcommands')
+
+    build = subparsers.add_parser('build', help='Build a Docker image')
+    build.set_defaults(func=command_build)
+    build.add_argument('--build-arg', action='append', default=[],
+                        help='Substitution to perform in Dockerfile; '
+                             'format: key=value')
+    build.add_argument('dockerfile', help='path to Dockerfile to use')
+    build.add_argument('tag', help='Tag to apply to created image')
+
+    docker = subparsers.add_parser('docker-path', help='Resolve path to Docker')
+    docker.set_defaults(func=command_docker)
+
+    args = parser.parse_args()
+
+    return args.func(args)
+
+if __name__ == '__main__':
+    sys.exit(main())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/linux-wheel-centos5-blacklist	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,3 @@
+test-convert-git.t
+test-subrepo-git.t
+test-patchbomb-tls.t
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/macosx/Readme.html	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,37 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!-- This is the second screen displayed during the install. -->
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta http-equiv="Content-Style-Type" content="text/css">
+  <title>Read Me - Important Information</title>
+  <style type="text/css">
+    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
+    p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
+    p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
+    p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #000fed}
+    span.s1 {text-decoration: underline}
+    span.s2 {font: 12.0px Courier}
+  </style>
+</head>
+<body>
+<p class="p1"><b>Before you install</b></p>
+<p class="p2"><br></p>
+<p class="p3">This is an OS X version of Mercurial that depends on the default Python installation.</p>
+<p class="p2"><br></p>
+<p class="p1"><b>After you install</b></p>
+<p class="p2"><br></p>
+<p class="p3">This package installs the <span class="s2">hg</span> executable as <span class="s2">/usr/local/bin/hg</span>. See <span class="s2">hg debuginstall</span> for more info on file locations.</p>
+<p class="p2"><br></p>
+<p class="p1"><b>Documentation</b></p>
+<p class="p2"><br></p>
+<p class="p3">Visit the <a href="https://mercurial-scm.org/">Mercurial web site and wiki</a></p>
+<p class="p2"><br></p>
+<p class="p3">There's also a free book, <a href="https://book.mercurial-scm.org/">Distributed revision control with Mercurial</a></p>
+<p class="p2"><br></p>
+<p class="p1"><b>Reporting problems</b></p>
+<p class="p2"><br></p>
+<p class="p3">If you run into any problems, please file a bug online:</p>
+<p class="p3"><a href="https://bz.mercurial-scm.org/">https://bz.mercurial-scm.org/</a></p>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/macosx/Welcome.html	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,20 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<!-- This is the first screen displayed during the install. -->
+<html>
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta http-equiv="Content-Style-Type" content="text/css">
+  <title></title>
+  <style type="text/css">
+    p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px Helvetica}
+    p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
+  </style>
+</head>
+<body>
+<p class="p1">This is a prepackaged release of <a href="https://mercurial-scm.org/">Mercurial</a> for Mac OS X.</p>
+<p class="p2"><br></p>
+<br>
+<p>
+Please be sure to read the latest <a href="https://mercurial-scm.org/wiki/WhatsNew">release notes</a>.</p>
+</body>
+</html>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/macosx/distribution.xml	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<installer-gui-script minSpecVersion="1">
+  <title>Mercurial SCM</title>
+  <organization>org.mercurial-scm</organization>
+  <options customize="never" require-scripts="false" rootVolumeOnly="true" />
+  <welcome file="Welcome.html" mime-type="text/html" />
+  <license file="../../../COPYING" mime-type="text/plain" />
+  <readme file="Readme.html" mime-type="text/html" />
+  <pkg-ref id="org.mercurial-scm.mercurial"
+           version="0"
+           auth="root"
+           onConclusion="none">mercurial.pkg</pkg-ref>
+  <choices-outline>
+    <line choice="org.mercurial-scm.mercurial"/>
+  </choices-outline>
+  <choice id="org.mercurial-scm.mercurial" visible="false">
+    <pkg-ref id="org.mercurial-scm.mercurial"/>
+  </choice>
+</installer-gui-script>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/mercurial.spec	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,164 @@
+%global emacs_lispdir %{_datadir}/emacs/site-lisp
+
+%define withpython %{nil}
+
+%if "%{?withpython}"
+
+%global pythonver %{withpython}
+%global pythonname Python-%{withpython}
+%global docutilsname docutils-0.14
+%global docutilsmd5 c53768d63db3873b7d452833553469de
+%global pythonhg python-hg
+%global hgpyprefix /opt/%{pythonhg}
+# byte compilation will fail on some some Python /test/ files
+%global _python_bytecompile_errors_terminate_build 0
+
+%else
+
+%global pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))')
+
+%endif
+
+Summary: A fast, lightweight Source Control Management system
+Name: mercurial
+Version: snapshot
+Release: 0
+License: GPLv2+
+Group: Development/Tools
+URL: https://mercurial-scm.org/
+Source0: %{name}-%{version}-%{release}.tar.gz
+%if "%{?withpython}"
+Source1: %{pythonname}.tgz
+Source2: %{docutilsname}.tar.gz
+%endif
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
+
+BuildRequires: make, gcc, gettext
+%if "%{?withpython}"
+BuildRequires: readline-devel, openssl-devel, ncurses-devel, zlib-devel, bzip2-devel
+%else
+BuildRequires: python >= 2.7, python-devel, python-docutils >= 0.5
+Requires: python >= 2.7
+%endif
+# The hgk extension uses the wish tcl interpreter, but we don't enforce it
+#Requires: tk
+
+%description
+Mercurial is a fast, lightweight source control management system designed
+for efficient handling of very large distributed projects.
+
+%prep
+
+%if "%{?withpython}"
+%setup -q -n mercurial-%{version}-%{release} -a1 -a2
+# despite the comments in cgi.py, we do this to prevent rpmdeps from picking /usr/local/bin/python up
+sed -i '1c#! /usr/bin/env python' %{pythonname}/Lib/cgi.py
+%else
+%setup -q -n mercurial-%{version}-%{release}
+%endif
+
+%build
+
+%if "%{?withpython}"
+
+PYPATH=$PWD/%{pythonname}
+cd $PYPATH
+./configure --prefix=%{hgpyprefix}
+make all %{?_smp_mflags}
+cd -
+
+cd %{docutilsname}
+LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py build
+cd -
+
+# verify Python environment
+LD_LIBRARY_PATH=$PYPATH PYTHONPATH=$PWD/%{docutilsname} $PYPATH/python -c 'import sys, zlib, bz2, ssl, curses, readline'
+
+# set environment for make
+export PATH=$PYPATH:$PATH
+export LD_LIBRARY_PATH=$PYPATH
+export CFLAGS="-L $PYPATH"
+export PYTHONPATH=$PWD/%{docutilsname}
+
+%endif
+
+make all
+make -C contrib/chg
+
+%install
+rm -rf $RPM_BUILD_ROOT
+
+%if "%{?withpython}"
+
+PYPATH=$PWD/%{pythonname}
+cd $PYPATH
+make install DESTDIR=$RPM_BUILD_ROOT
+# these .a are not necessary and they are readonly and strip fails - kill them!
+rm -f %{buildroot}%{hgpyprefix}/lib/{,python2.*/config}/libpython2.*.a
+cd -
+
+cd %{docutilsname}
+LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py install --root="$RPM_BUILD_ROOT"
+cd -
+
+PATH=$PYPATH:$PATH LD_LIBRARY_PATH=$PYPATH make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{hgpyprefix} MANDIR=%{_mandir}
+mkdir -p $RPM_BUILD_ROOT%{_bindir}
+( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/hg . )
+( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/python2.? %{pythonhg} )
+
+%else
+
+make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir}
+
+%endif
+
+install -m 755 contrib/chg/chg $RPM_BUILD_ROOT%{_bindir}/
+install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/
+install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/
+
+bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
+mkdir -p $bash_completion_dir
+install -m 644 contrib/bash_completion $bash_completion_dir/mercurial.sh
+
+zsh_completion_dir=$RPM_BUILD_ROOT%{_datadir}/zsh/site-functions
+mkdir -p $zsh_completion_dir
+install -m 644 contrib/zsh_completion $zsh_completion_dir/_mercurial
+
+mkdir -p $RPM_BUILD_ROOT%{emacs_lispdir}
+install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}/
+install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}/
+
+mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/mercurial/hgrc.d
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root,-)
+%doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi
+%doc %attr(644,root,root) %{_mandir}/man?/hg*
+%doc %attr(644,root,root) contrib/*.svg
+%dir %{_datadir}/zsh/
+%dir %{_datadir}/zsh/site-functions/
+%{_datadir}/zsh/site-functions/_mercurial
+%dir %{_datadir}/emacs/site-lisp/
+%{_datadir}/emacs/site-lisp/mercurial.el
+%{_datadir}/emacs/site-lisp/mq.el
+%{_bindir}/hg
+%{_bindir}/chg
+%{_bindir}/hgk
+%{_bindir}/hg-ssh
+%dir %{_sysconfdir}/bash_completion.d/
+%config(noreplace) %{_sysconfdir}/bash_completion.d/mercurial.sh
+%dir %{_sysconfdir}/mercurial
+%dir %{_sysconfdir}/mercurial/hgrc.d
+%if "%{?withpython}"
+%{_bindir}/%{pythonhg}
+%{hgpyprefix}
+%else
+%{_libdir}/python%{pythonver}/site-packages/%{name}-*-py%{pythonver}.egg-info
+%{_libdir}/python%{pythonver}/site-packages/%{name}
+%{_libdir}/python%{pythonver}/site-packages/hgext
+%{_libdir}/python%{pythonver}/site-packages/hgext3rd
+%{_libdir}/python%{pythonver}/site-packages/hgdemandimport
+%endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/packaging/packagelib.sh	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,38 @@
+# Extract version number into 4 parts, some of which may be empty:
+#
+# version: the numeric part of the most recent tag. Will always look like 1.3.
+#
+# type: if an rc build, "rc", otherwise empty
+#
+# distance: the distance from the nearest tag, or empty if built from a tag
+#
+# node: the node|short hg was built from, or empty if built from a tag
+gethgversion() {
+    export HGRCPATH=
+    export HGPLAIN=
+
+    make cleanbutpackages
+    make local PURE=--pure
+    HG="$PWD/hg"
+
+    "$HG" version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
+
+    hgversion=`LANGUAGE=C "$HG" version | sed -ne 's/.*(version \(.*\))$/\1/p'`
+
+    if echo $hgversion | grep + > /dev/null 2>&1 ; then
+        tmp=`echo $hgversion | cut -d+ -f 2`
+        hgversion=`echo $hgversion | cut -d+ -f 1`
+        distance=`echo $tmp | cut -d- -f 1`
+        node=`echo $tmp | cut -d- -f 2`
+    else
+        distance=''
+        node=''
+    fi
+    if echo $hgversion | grep -- '-' > /dev/null 2>&1; then
+        version=`echo $hgversion | cut -d- -f1`
+        type=`echo $hgversion | cut -d- -f2`
+    else
+        version=$hgversion
+        type=''
+    fi
+}
--- a/contrib/perf.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/perf.py	Thu Jul 19 13:55:54 2018 -0400
@@ -71,6 +71,25 @@
     import inspect
     getargspec = inspect.getargspec
 
+try:
+    # 4.7+
+    queue = pycompat.queue.Queue
+except (AttributeError, ImportError):
+    # <4.7.
+    try:
+        queue = pycompat.queue
+    except (AttributeError, ImportError):
+        queue = util.queue
+
+try:
+    from mercurial import logcmdutil
+    makelogtemplater = logcmdutil.maketemplater
+except (AttributeError, ImportError):
+    try:
+        makelogtemplater = cmdutil.makelogtemplater
+    except (AttributeError, ImportError):
+        makelogtemplater = None
+
 # for "historical portability":
 # define util.safehasattr forcibly, because util.safehasattr has been
 # available since 1.9.3 (or 94b200a11cf7)
@@ -159,6 +178,9 @@
     configitem('perf', 'parentscount',
         default=mercurial.configitems.dynamicdefault,
     )
+    configitem('perf', 'all-timing',
+        default=mercurial.configitems.dynamicdefault,
+    )
 except (ImportError, AttributeError):
     pass
 
@@ -228,12 +250,15 @@
     # experimental config: perf.stub
     if ui.configbool("perf", "stub", False):
         return functools.partial(stub_timer, fm), fm
-    return functools.partial(_timer, fm), fm
+
+    # experimental config: perf.all-timing
+    displayall = ui.configbool("perf", "all-timing", False)
+    return functools.partial(_timer, fm, displayall=displayall), fm
 
 def stub_timer(fm, func, title=None):
     func()
 
-def _timer(fm, func, title=None):
+def _timer(fm, func, title=None, displayall=False):
     gc.collect()
     results = []
     begin = util.timer()
@@ -258,14 +283,27 @@
         fm.write('title', '! %s\n', title)
     if r:
         fm.write('result', '! result: %s\n', r)
-    m = min(results)
-    fm.plain('!')
-    fm.write('wall', ' wall %f', m[0])
-    fm.write('comb', ' comb %f', m[1] + m[2])
-    fm.write('user', ' user %f', m[1])
-    fm.write('sys',  ' sys %f', m[2])
-    fm.write('count',  ' (best of %d)', count)
-    fm.plain('\n')
+    def display(role, entry):
+        prefix = ''
+        if role != 'best':
+            prefix = '%s.' % role
+        fm.plain('!')
+        fm.write(prefix + 'wall', ' wall %f', entry[0])
+        fm.write(prefix + 'comb', ' comb %f', entry[1] + entry[2])
+        fm.write(prefix + 'user', ' user %f', entry[1])
+        fm.write(prefix + 'sys',  ' sys %f', entry[2])
+        fm.write(prefix + 'count',  ' (%s of %d)', role, count)
+        fm.plain('\n')
+    results.sort()
+    min_val = results[0]
+    display('best', min_val)
+    if displayall:
+        max_val = results[-1]
+        display('max', max_val)
+        avg = tuple([sum(x) / count for x in zip(*results)])
+        display('avg', avg)
+        median = results[len(results) // 2]
+        display('median', median)
 
 # utilities for historical portability
 
@@ -755,6 +793,10 @@
 
 @command('perfmanifest', [], 'REV')
 def perfmanifest(ui, repo, rev, **opts):
+    """benchmark the time to read a manifest from disk and return a usable
+    dict-like object
+
+    Manifest caches are cleared before retrieval."""
     timer, fm = gettimer(ui, opts)
     ctx = scmutil.revsingle(repo, rev, rev)
     t = ctx.manifestnode()
@@ -887,16 +929,36 @@
     timer(moonwalk)
     fm.end()
 
-@command('perftemplating', formatteropts)
-def perftemplating(ui, repo, rev=None, **opts):
-    if rev is None:
-        rev=[]
+@command('perftemplating',
+         [('r', 'rev', [], 'revisions to run the template on'),
+         ] + formatteropts)
+def perftemplating(ui, repo, testedtemplate=None, **opts):
+    """test the rendering time of a given template"""
+    if makelogtemplater is None:
+        raise error.Abort(("perftemplating not available with this Mercurial"),
+                          hint="use 4.3 or later")
+
+    nullui = ui.copy()
+    nullui.fout = open(os.devnull, 'wb')
+    nullui.disablepager()
+    revs = opts.get('rev')
+    if not revs:
+        revs = ['all()']
+    revs = list(scmutil.revrange(repo, revs))
+
+    defaulttemplate = ('{date|shortdate} [{rev}:{node|short}]'
+                       ' {author|person}: {desc|firstline}\n')
+    if testedtemplate is None:
+        testedtemplate = defaulttemplate
+    displayer = makelogtemplater(nullui, repo, testedtemplate)
+    def format():
+        for r in revs:
+            ctx = repo[r]
+            displayer.show(ctx)
+            displayer.flush(ctx)
+
     timer, fm = gettimer(ui, opts)
-    ui.pushbuffer()
-    timer(lambda: commands.log(ui, repo, rev=rev, date='', user='',
-                               template='{date|shortdate} [{rev}:{node|short}]'
-                               ' {author|person}: {desc|firstline}\n'))
-    ui.popbuffer()
+    timer(format)
     fm.end()
 
 @command('perfcca', formatteropts)
@@ -918,9 +980,10 @@
 def perffncachewrite(ui, repo, **opts):
     timer, fm = gettimer(ui, opts)
     s = repo.store
+    lock = repo.lock()
     s.fncache._load()
-    lock = repo.lock()
     tr = repo.transaction('perffncachewrite')
+    tr.addbackup('fncache')
     def d():
         s.fncache._dirty = True
         s.fncache.write(tr)
@@ -1029,7 +1092,7 @@
                 else:
                     mdiff.textdiff(*pair)
     else:
-        q = util.queue()
+        q = queue()
         for i in xrange(threads):
             q.put(None)
         ready = threading.Condition()
--- a/contrib/phabricator.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/phabricator.py	Thu Jul 19 13:55:54 2018 -0400
@@ -21,10 +21,6 @@
     # Phabricator URL
     url = https://phab.example.com/
 
-    # API token. Get it from https://$HOST/conduit/login/
-    # Deprecated: see [phabricator.auth] below
-    #token = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
-
     # Repo callsign. If a repo has a URL https://$HOST/diffusion/FOO, then its
     # callsign is "FOO".
     callsign = FOO
@@ -35,10 +31,12 @@
     # the internal library.
     curlcmd = curl --connect-timeout 2 --retry 3 --silent
 
-    [phabricator.auth]
-    example.url = https://phab.example.com/
+    [auth]
+    example.schemes = https
+    example.prefix = phab.example.com
+
     # API token. Get it from https://$HOST/conduit/login/
-    example.token = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
+    example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
 """
 
 from __future__ import absolute_import
@@ -55,6 +53,7 @@
     context,
     encoding,
     error,
+    httpconnection as httpconnectionmod,
     mdiff,
     obsutil,
     parser,
@@ -74,13 +73,37 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+# developer config: phabricator.batchsize
+configitem(b'phabricator', b'batchsize',
+    default=12,
+)
+configitem(b'phabricator', b'callsign',
+    default=None,
+)
+configitem(b'phabricator', b'curlcmd',
+    default=None,
+)
+# developer config: phabricator.repophid
+configitem(b'phabricator', b'repophid',
+    default=None,
+)
+configitem(b'phabricator', b'url',
+    default=None,
+)
+configitem(b'phabsend', b'confirm',
+    default=False,
+)
+
 colortable = {
-    'phabricator.action.created': 'green',
-    'phabricator.action.skipped': 'magenta',
-    'phabricator.action.updated': 'magenta',
-    'phabricator.desc': '',
-    'phabricator.drev': 'bold',
-    'phabricator.node': '',
+    b'phabricator.action.created': b'green',
+    b'phabricator.action.skipped': b'magenta',
+    b'phabricator.action.updated': b'magenta',
+    b'phabricator.desc': b'',
+    b'phabricator.drev': b'bold',
+    b'phabricator.node': b'',
 }
 
 def urlencodenested(params):
@@ -98,59 +121,69 @@
         else:
             for k, v in items(obj):
                 if prefix:
-                    process('%s[%s]' % (prefix, k), v)
+                    process(b'%s[%s]' % (prefix, k), v)
                 else:
                     process(k, v)
-    process('', params)
+    process(b'', params)
     return util.urlreq.urlencode(flatparams)
 
 printed_token_warning = False
 
-def readlegacytoken(repo):
+def readlegacytoken(repo, url):
     """Transitional support for old phabricator tokens.
 
-    Remove before the 4.6 release.
+    Remove before the 4.7 release.
     """
+    groups = {}
+    for key, val in repo.ui.configitems(b'phabricator.auth'):
+        if b'.' not in key:
+            repo.ui.warn(_(b"ignoring invalid [phabricator.auth] key '%s'\n")
+                         % key)
+            continue
+        group, setting = key.rsplit(b'.', 1)
+        groups.setdefault(group, {})[setting] = val
+
+    token = None
+    for group, auth in groups.iteritems():
+        if url != auth.get(b'url'):
+            continue
+        token = auth.get(b'token')
+        if token:
+            break
+
     global printed_token_warning
-    token = repo.ui.config('phabricator', 'token')
+
     if token and not printed_token_warning:
         printed_token_warning = True
-        repo.ui.warn(_('phabricator.token is deprecated - please '
-                       'migrate to the phabricator.auth section.\n'))
+        repo.ui.warn(_(b'phabricator.auth.token is deprecated - please '
+                       b'migrate to auth.phabtoken.\n'))
     return token
 
 def readurltoken(repo):
     """return conduit url, token and make sure they exist
 
-    Currently read from [phabricator] config section. In the future, it might
+    Currently read from [auth] config section. In the future, it might
     make sense to read from .arcconfig and .arcrc as well.
     """
-    url = repo.ui.config('phabricator', 'url')
+    url = repo.ui.config(b'phabricator', b'url')
     if not url:
-        raise error.Abort(_('config %s.%s is required')
-                          % ('phabricator', 'url'))
+        raise error.Abort(_(b'config %s.%s is required')
+                          % (b'phabricator', b'url'))
 
-    groups = {}
-    for key, val in repo.ui.configitems('phabricator.auth'):
-        if '.' not in key:
-            repo.ui.warn(_("ignoring invalid [phabricator.auth] key '%s'\n")
-                         % key)
-            continue
-        group, setting = key.rsplit('.', 1)
-        groups.setdefault(group, {})[setting] = val
+    res = httpconnectionmod.readauthforuri(repo.ui, url, util.url(url).user)
+    token = None
 
-    token = None
-    for group, auth in groups.iteritems():
-        if url != auth.get('url'):
-            continue
-        token = auth.get('token')
-        if token:
-            break
+    if res:
+        group, auth = res
+
+        repo.ui.debug(b"using auth.%s.* for authentication\n" % group)
+
+        token = auth.get(b'phabtoken')
 
     if not token:
-        token = readlegacytoken(repo)
+        token = readlegacytoken(repo, url)
         if not token:
-            raise error.Abort(_('Can\'t find conduit token associated to %s')
+            raise error.Abort(_(b'Can\'t find conduit token associated to %s')
                               % (url,))
 
     return url, token
@@ -158,14 +191,14 @@
 def callconduit(repo, name, params):
     """call Conduit API, params is a dict. return json.loads result, or None"""
     host, token = readurltoken(repo)
-    url, authinfo = util.url('/'.join([host, 'api', name])).authinfo()
-    repo.ui.debug('Conduit Call: %s %s\n' % (url, params))
+    url, authinfo = util.url(b'/'.join([host, b'api', name])).authinfo()
+    repo.ui.debug(b'Conduit Call: %s %s\n' % (url, params))
     params = params.copy()
-    params['api.token'] = token
+    params[b'api.token'] = token
     data = urlencodenested(params)
-    curlcmd = repo.ui.config('phabricator', 'curlcmd')
+    curlcmd = repo.ui.config(b'phabricator', b'curlcmd')
     if curlcmd:
-        sin, sout = procutil.popen2('%s -d @- %s'
+        sin, sout = procutil.popen2(b'%s -d @- %s'
                                     % (curlcmd, procutil.shellquote(url)))
         sin.write(data)
         sin.close()
@@ -174,15 +207,15 @@
         urlopener = urlmod.opener(repo.ui, authinfo)
         request = util.urlreq.request(url, data=data)
         body = urlopener.open(request).read()
-    repo.ui.debug('Conduit Response: %s\n' % body)
+    repo.ui.debug(b'Conduit Response: %s\n' % body)
     parsed = json.loads(body)
     if parsed.get(r'error_code'):
-        msg = (_('Conduit Error (%s): %s')
+        msg = (_(b'Conduit Error (%s): %s')
                % (parsed[r'error_code'], parsed[r'error_info']))
         raise error.Abort(msg)
     return parsed[r'result']
 
-@command('debugcallconduit', [], _('METHOD'))
+@command(b'debugcallconduit', [], _(b'METHOD'))
 def debugcallconduit(ui, repo, name):
     """call Conduit API
 
@@ -191,29 +224,29 @@
     """
     params = json.loads(ui.fin.read())
     result = callconduit(repo, name, params)
-    s = json.dumps(result, sort_keys=True, indent=2, separators=(',', ': '))
-    ui.write('%s\n' % s)
+    s = json.dumps(result, sort_keys=True, indent=2, separators=(b',', b': '))
+    ui.write(b'%s\n' % s)
 
 def getrepophid(repo):
     """given callsign, return repository PHID or None"""
     # developer config: phabricator.repophid
-    repophid = repo.ui.config('phabricator', 'repophid')
+    repophid = repo.ui.config(b'phabricator', b'repophid')
     if repophid:
         return repophid
-    callsign = repo.ui.config('phabricator', 'callsign')
+    callsign = repo.ui.config(b'phabricator', b'callsign')
     if not callsign:
         return None
-    query = callconduit(repo, 'diffusion.repository.search',
-                        {'constraints': {'callsigns': [callsign]}})
+    query = callconduit(repo, b'diffusion.repository.search',
+                        {b'constraints': {b'callsigns': [callsign]}})
     if len(query[r'data']) == 0:
         return None
     repophid = encoding.strtolocal(query[r'data'][0][r'phid'])
-    repo.ui.setconfig('phabricator', 'repophid', repophid)
+    repo.ui.setconfig(b'phabricator', b'repophid', repophid)
     return repophid
 
-_differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z')
+_differentialrevisiontagre = re.compile(b'\AD([1-9][0-9]*)\Z')
 _differentialrevisiondescre = re.compile(
-    '^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M)
+    b'^Differential Revision:\s*(?P<url>(?:.*)D(?P<id>[1-9][0-9]*))$', re.M)
 
 def getoldnodedrevmap(repo, nodelist):
     """find previous nodes that has been sent to Phabricator
@@ -254,16 +287,16 @@
         # Check commit message
         m = _differentialrevisiondescre.search(ctx.description())
         if m:
-            toconfirm[node] = (1, set(precnodes), int(m.group('id')))
+            toconfirm[node] = (1, set(precnodes), int(m.group(b'id')))
 
     # Double check if tags are genuine by collecting all old nodes from
     # Phabricator, and expect precursors overlap with it.
     if toconfirm:
         drevs = [drev for force, precs, drev in toconfirm.values()]
-        alldiffs = callconduit(unfi, 'differential.querydiffs',
-                               {'revisionIDs': drevs})
+        alldiffs = callconduit(unfi, b'differential.querydiffs',
+                               {b'revisionIDs': drevs})
         getnode = lambda d: bin(encoding.unitolocal(
-            getdiffmeta(d).get(r'node', ''))) or None
+            getdiffmeta(d).get(r'node', b''))) or None
         for newnode, (force, precset, drev) in toconfirm.items():
             diffs = [d for d in alldiffs.values()
                      if int(d[r'revisionID']) == drev]
@@ -274,11 +307,11 @@
             # Ignore if precursors (Phabricator and local repo) do not overlap,
             # and force is not set (when commit message says nothing)
             if not force and not bool(phprecset & precset):
-                tagname = 'D%d' % drev
+                tagname = b'D%d' % drev
                 tags.tag(repo, tagname, nullid, message=None, user=None,
                          date=None, local=True)
-                unfi.ui.warn(_('D%s: local tag removed - does not match '
-                               'Differential history\n') % drev)
+                unfi.ui.warn(_(b'D%s: local tag removed - does not match '
+                               b'Differential history\n') % drev)
                 continue
 
             # Find the last node using Phabricator metadata, and make sure it
@@ -307,40 +340,40 @@
     repo = ctx.repo()
     repophid = getrepophid(repo)
     # Create a "Differential Diff" via "differential.createrawdiff" API
-    params = {'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
+    params = {b'diff': getdiff(ctx, mdiff.diffopts(git=True, context=32767))}
     if repophid:
-        params['repositoryPHID'] = repophid
-    diff = callconduit(repo, 'differential.createrawdiff', params)
+        params[b'repositoryPHID'] = repophid
+    diff = callconduit(repo, b'differential.createrawdiff', params)
     if not diff:
-        raise error.Abort(_('cannot create diff for %s') % ctx)
+        raise error.Abort(_(b'cannot create diff for %s') % ctx)
     return diff
 
 def writediffproperties(ctx, diff):
     """write metadata to diff so patches could be applied losslessly"""
     params = {
-        'diff_id': diff[r'id'],
-        'name': 'hg:meta',
-        'data': json.dumps({
-            'user': ctx.user(),
-            'date': '%d %d' % ctx.date(),
-            'node': ctx.hex(),
-            'parent': ctx.p1().hex(),
+        b'diff_id': diff[r'id'],
+        b'name': b'hg:meta',
+        b'data': json.dumps({
+            b'user': ctx.user(),
+            b'date': b'%d %d' % ctx.date(),
+            b'node': ctx.hex(),
+            b'parent': ctx.p1().hex(),
         }),
     }
-    callconduit(ctx.repo(), 'differential.setdiffproperty', params)
+    callconduit(ctx.repo(), b'differential.setdiffproperty', params)
 
     params = {
-        'diff_id': diff[r'id'],
-        'name': 'local:commits',
-        'data': json.dumps({
+        b'diff_id': diff[r'id'],
+        b'name': b'local:commits',
+        b'data': json.dumps({
             ctx.hex(): {
-                'author': stringutil.person(ctx.user()),
-                'authorEmail': stringutil.email(ctx.user()),
-                'time': ctx.date()[0],
+                b'author': stringutil.person(ctx.user()),
+                b'authorEmail': stringutil.email(ctx.user()),
+                b'time': ctx.date()[0],
             },
         }),
     }
-    callconduit(ctx.repo(), 'differential.setdiffproperty', params)
+    callconduit(ctx.repo(), b'differential.setdiffproperty', params)
 
 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
                                olddiff=None, actions=None):
@@ -365,7 +398,7 @@
     transactions = []
     if neednewdiff:
         diff = creatediff(ctx)
-        transactions.append({'type': 'update', 'value': diff[r'phid']})
+        transactions.append({b'type': b'update', b'value': diff[r'phid']})
     else:
         # Even if we don't need to upload a new diff because the patch content
         # does not change. We might still need to update its metadata so
@@ -379,52 +412,52 @@
     # existing revision (revid is not None) since that introduces visible
     # churns (someone edited "Summary" twice) on the web page.
     if parentrevid and revid is None:
-        summary = 'Depends on D%s' % parentrevid
-        transactions += [{'type': 'summary', 'value': summary},
-                         {'type': 'summary', 'value': ' '}]
+        summary = b'Depends on D%s' % parentrevid
+        transactions += [{b'type': b'summary', b'value': summary},
+                         {b'type': b'summary', b'value': b' '}]
 
     if actions:
         transactions += actions
 
     # Parse commit message and update related fields.
     desc = ctx.description()
-    info = callconduit(repo, 'differential.parsecommitmessage',
-                       {'corpus': desc})
+    info = callconduit(repo, b'differential.parsecommitmessage',
+                       {b'corpus': desc})
     for k, v in info[r'fields'].items():
-        if k in ['title', 'summary', 'testPlan']:
-            transactions.append({'type': k, 'value': v})
+        if k in [b'title', b'summary', b'testPlan']:
+            transactions.append({b'type': k, b'value': v})
 
-    params = {'transactions': transactions}
+    params = {b'transactions': transactions}
     if revid is not None:
         # Update an existing Differential Revision
-        params['objectIdentifier'] = revid
+        params[b'objectIdentifier'] = revid
 
-    revision = callconduit(repo, 'differential.revision.edit', params)
+    revision = callconduit(repo, b'differential.revision.edit', params)
     if not revision:
-        raise error.Abort(_('cannot create revision for %s') % ctx)
+        raise error.Abort(_(b'cannot create revision for %s') % ctx)
 
     return revision, diff
 
 def userphids(repo, names):
     """convert user names to PHIDs"""
-    query = {'constraints': {'usernames': names}}
-    result = callconduit(repo, 'user.search', query)
+    query = {b'constraints': {b'usernames': names}}
+    result = callconduit(repo, b'user.search', query)
     # username not found is not an error of the API. So check if we have missed
     # some names here.
     data = result[r'data']
     resolved = set(entry[r'fields'][r'username'] for entry in data)
     unresolved = set(names) - resolved
     if unresolved:
-        raise error.Abort(_('unknown username: %s')
-                          % ' '.join(sorted(unresolved)))
+        raise error.Abort(_(b'unknown username: %s')
+                          % b' '.join(sorted(unresolved)))
     return [entry[r'phid'] for entry in data]
 
-@command('phabsend',
-         [('r', 'rev', [], _('revisions to send'), _('REV')),
-          ('', 'amend', True, _('update commit messages')),
-          ('', 'reviewer', [], _('specify reviewers')),
-          ('', 'confirm', None, _('ask for confirmation before sending'))],
-         _('REV [OPTIONS]'))
+@command(b'phabsend',
+         [(b'r', b'rev', [], _(b'revisions to send'), _(b'REV')),
+          (b'', b'amend', True, _(b'update commit messages')),
+          (b'', b'reviewer', [], _(b'specify reviewers')),
+          (b'', b'confirm', None, _(b'ask for confirmation before sending'))],
+         _(b'REV [OPTIONS]'))
 def phabsend(ui, repo, *revs, **opts):
     """upload changesets to Phabricator
 
@@ -452,29 +485,29 @@
     phabsend will check obsstore and the above association to decide whether to
     update an existing Differential Revision, or create a new one.
     """
-    revs = list(revs) + opts.get('rev', [])
+    revs = list(revs) + opts.get(b'rev', [])
     revs = scmutil.revrange(repo, revs)
 
     if not revs:
-        raise error.Abort(_('phabsend requires at least one changeset'))
-    if opts.get('amend'):
+        raise error.Abort(_(b'phabsend requires at least one changeset'))
+    if opts.get(b'amend'):
         cmdutil.checkunfinished(repo)
 
     # {newnode: (oldnode, olddiff, olddrev}
     oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
 
-    confirm = ui.configbool('phabsend', 'confirm')
-    confirm |= bool(opts.get('confirm'))
+    confirm = ui.configbool(b'phabsend', b'confirm')
+    confirm |= bool(opts.get(b'confirm'))
     if confirm:
         confirmed = _confirmbeforesend(repo, revs, oldmap)
         if not confirmed:
-            raise error.Abort(_('phabsend cancelled'))
+            raise error.Abort(_(b'phabsend cancelled'))
 
     actions = []
-    reviewers = opts.get('reviewer', [])
+    reviewers = opts.get(b'reviewer', [])
     if reviewers:
         phids = userphids(repo, reviewers)
-        actions.append({'type': 'reviewers.add', 'value': phids})
+        actions.append({b'type': b'reviewers.add', b'value': phids})
 
     drevids = [] # [int]
     diffmap = {} # {newnode: diff}
@@ -483,54 +516,54 @@
     # can provide dependency relationship
     lastrevid = None
     for rev in revs:
-        ui.debug('sending rev %d\n' % rev)
+        ui.debug(b'sending rev %d\n' % rev)
         ctx = repo[rev]
 
         # Get Differential Revision ID
         oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
-        if oldnode != ctx.node() or opts.get('amend'):
+        if oldnode != ctx.node() or opts.get(b'amend'):
             # Create or update Differential Revision
             revision, diff = createdifferentialrevision(
                 ctx, revid, lastrevid, oldnode, olddiff, actions)
             diffmap[ctx.node()] = diff
             newrevid = int(revision[r'object'][r'id'])
             if revid:
-                action = 'updated'
+                action = b'updated'
             else:
-                action = 'created'
+                action = b'created'
 
             # Create a local tag to note the association, if commit message
             # does not have it already
             m = _differentialrevisiondescre.search(ctx.description())
-            if not m or int(m.group('id')) != newrevid:
-                tagname = 'D%d' % newrevid
+            if not m or int(m.group(b'id')) != newrevid:
+                tagname = b'D%d' % newrevid
                 tags.tag(repo, tagname, ctx.node(), message=None, user=None,
                          date=None, local=True)
         else:
             # Nothing changed. But still set "newrevid" so the next revision
             # could depend on this one.
             newrevid = revid
-            action = 'skipped'
+            action = b'skipped'
 
         actiondesc = ui.label(
-            {'created': _('created'),
-             'skipped': _('skipped'),
-             'updated': _('updated')}[action],
-            'phabricator.action.%s' % action)
-        drevdesc = ui.label('D%s' % newrevid, 'phabricator.drev')
-        nodedesc = ui.label(bytes(ctx), 'phabricator.node')
-        desc = ui.label(ctx.description().split('\n')[0], 'phabricator.desc')
-        ui.write(_('%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
-                                            desc))
+            {b'created': _(b'created'),
+             b'skipped': _(b'skipped'),
+             b'updated': _(b'updated')}[action],
+            b'phabricator.action.%s' % action)
+        drevdesc = ui.label(b'D%s' % newrevid, b'phabricator.drev')
+        nodedesc = ui.label(bytes(ctx), b'phabricator.node')
+        desc = ui.label(ctx.description().split(b'\n')[0], b'phabricator.desc')
+        ui.write(_(b'%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
+                                             desc))
         drevids.append(newrevid)
         lastrevid = newrevid
 
     # Update commit messages and remove tags
-    if opts.get('amend'):
+    if opts.get(b'amend'):
         unfi = repo.unfiltered()
-        drevs = callconduit(repo, 'differential.query', {'ids': drevids})
-        with repo.wlock(), repo.lock(), repo.transaction('phabsend'):
-            wnode = unfi['.'].node()
+        drevs = callconduit(repo, b'differential.query', {b'ids': drevids})
+        with repo.wlock(), repo.lock(), repo.transaction(b'phabsend'):
+            wnode = unfi[b'.'].node()
             mapping = {} # {oldnode: [newnode]}
             for i, rev in enumerate(revs):
                 old = unfi[rev]
@@ -546,23 +579,25 @@
                     new = context.metadataonlyctx(
                         repo, old, parents=parents, text=newdesc,
                         user=old.user(), date=old.date(), extra=old.extra())
+
                     newnode = new.commit()
+
                     mapping[old.node()] = [newnode]
                     # Update diff property
                     writediffproperties(unfi[newnode], diffmap[old.node()])
                 # Remove local tags since it's no longer necessary
-                tagname = 'D%d' % drevid
+                tagname = b'D%d' % drevid
                 if tagname in repo.tags():
                     tags.tag(repo, tagname, nullid, message=None, user=None,
                              date=None, local=True)
-            scmutil.cleanupnodes(repo, mapping, 'phabsend')
+            scmutil.cleanupnodes(repo, mapping, b'phabsend', fixphase=True)
             if wnode in mapping:
                 unfi.setparents(mapping[wnode][0])
 
 # Map from "hg:meta" keys to header understood by "hg import". The order is
 # consistent with "hg export" output.
-_metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'),
-                              (r'node', 'Node ID'), (r'parent', 'Parent ')])
+_metanamemap = util.sortdict([(r'user', b'User'), (r'date', b'Date'),
+                              (r'node', b'Node ID'), (r'parent', b'Parent ')])
 
 def _confirmbeforesend(repo, revs, oldmap):
     url, token = readurltoken(repo)
@@ -572,68 +607,69 @@
         desc = ctx.description().splitlines()[0]
         oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
         if drevid:
-            drevdesc = ui.label('D%s' % drevid, 'phabricator.drev')
+            drevdesc = ui.label(b'D%s' % drevid, b'phabricator.drev')
         else:
-            drevdesc = ui.label(_('NEW'), 'phabricator.drev')
+            drevdesc = ui.label(_(b'NEW'), b'phabricator.drev')
 
-        ui.write(_('%s - %s: %s\n') % (drevdesc,
-                                       ui.label(bytes(ctx), 'phabricator.node'),
-                                       ui.label(desc, 'phabricator.desc')))
+        ui.write(_(b'%s - %s: %s\n')
+                 % (drevdesc,
+                    ui.label(bytes(ctx), b'phabricator.node'),
+                    ui.label(desc, b'phabricator.desc')))
 
-    if ui.promptchoice(_('Send the above changes to %s (yn)?'
-                         '$$ &Yes $$ &No') % url):
+    if ui.promptchoice(_(b'Send the above changes to %s (yn)?'
+                         b'$$ &Yes $$ &No') % url):
         return False
 
     return True
 
-_knownstatusnames = {'accepted', 'needsreview', 'needsrevision', 'closed',
-                     'abandoned'}
+_knownstatusnames = {b'accepted', b'needsreview', b'needsrevision', b'closed',
+                     b'abandoned'}
 
 def _getstatusname(drev):
     """get normalized status name from a Differential Revision"""
-    return drev[r'statusName'].replace(' ', '').lower()
+    return drev[r'statusName'].replace(b' ', b'').lower()
 
 # Small language to specify differential revisions. Support symbols: (), :X,
 # +, and -.
 
 _elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
-    '(':      (12, None, ('group', 1, ')'), None, None),
-    ':':      (8, None, ('ancestors', 8), None, None),
-    '&':      (5,  None, None, ('and_', 5), None),
-    '+':      (4,  None, None, ('add', 4), None),
-    '-':      (4,  None, None, ('sub', 4), None),
-    ')':      (0,  None, None, None, None),
-    'symbol': (0, 'symbol', None, None, None),
-    'end':    (0, None, None, None, None),
+    b'(':      (12, None, (b'group', 1, b')'), None, None),
+    b':':      (8, None, (b'ancestors', 8), None, None),
+    b'&':      (5,  None, None, (b'and_', 5), None),
+    b'+':      (4,  None, None, (b'add', 4), None),
+    b'-':      (4,  None, None, (b'sub', 4), None),
+    b')':      (0,  None, None, None, None),
+    b'symbol': (0, b'symbol', None, None, None),
+    b'end':    (0, None, None, None, None),
 }
 
 def _tokenize(text):
     view = memoryview(text) # zero-copy slice
-    special = '():+-& '
+    special = b'():+-& '
     pos = 0
     length = len(text)
     while pos < length:
-        symbol = ''.join(itertools.takewhile(lambda ch: ch not in special,
-                                             view[pos:]))
+        symbol = b''.join(itertools.takewhile(lambda ch: ch not in special,
+                                              view[pos:]))
         if symbol:
-            yield ('symbol', symbol, pos)
+            yield (b'symbol', symbol, pos)
             pos += len(symbol)
         else: # special char, ignore space
-            if text[pos] != ' ':
+            if text[pos] != b' ':
                 yield (text[pos], None, pos)
             pos += 1
-    yield ('end', None, pos)
+    yield (b'end', None, pos)
 
 def _parse(text):
     tree, pos = parser.parser(_elements).parse(_tokenize(text))
     if pos != len(text):
-        raise error.ParseError('invalid token', pos)
+        raise error.ParseError(b'invalid token', pos)
     return tree
 
 def _parsedrev(symbol):
     """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
-    if symbol.startswith('D') and symbol[1:].isdigit():
+    if symbol.startswith(b'D') and symbol[1:].isdigit():
         return int(symbol[1:])
     if symbol.isdigit():
         return int(symbol)
@@ -643,11 +679,11 @@
     drevs = set()
     ancestordrevs = set()
     op = tree[0]
-    if op == 'symbol':
+    if op == b'symbol':
         r = _parsedrev(tree[1])
         if r:
             drevs.add(r)
-    elif op == 'ancestors':
+    elif op == b'ancestors':
         r, a = _prefetchdrevs(tree[1])
         drevs.update(r)
         ancestordrevs.update(r)
@@ -706,13 +742,14 @@
         key = (params.get(r'ids') or params.get(r'phids') or [None])[0]
         if key in prefetched:
             return prefetched[key]
-        drevs = callconduit(repo, 'differential.query', params)
+        drevs = callconduit(repo, b'differential.query', params)
         # Fill prefetched with the result
         for drev in drevs:
             prefetched[drev[r'phid']] = drev
             prefetched[int(drev[r'id'])] = drev
         if key not in prefetched:
-            raise error.Abort(_('cannot get Differential Revision %r') % params)
+            raise error.Abort(_(b'cannot get Differential Revision %r')
+                              % params)
         return prefetched[key]
 
     def getstack(topdrevids):
@@ -730,7 +767,7 @@
             auxiliary = drev.get(r'auxiliary', {})
             depends = auxiliary.get(r'phabricator:depends-on', [])
             for phid in depends:
-                queue.append({'phids': [phid]})
+                queue.append({b'phids': [phid]})
         result.reverse()
         return smartset.baseset(result)
 
@@ -741,7 +778,7 @@
     drevs, ancestordrevs = _prefetchdrevs(tree)
 
     # developer config: phabricator.batchsize
-    batchsize = repo.ui.configint('phabricator', 'batchsize', 12)
+    batchsize = repo.ui.configint(b'phabricator', b'batchsize')
 
     # Prefetch Differential Revisions in batch
     tofetch = set(drevs)
@@ -754,7 +791,7 @@
     # Walk through the tree, return smartsets
     def walk(tree):
         op = tree[0]
-        if op == 'symbol':
+        if op == b'symbol':
             drev = _parsedrev(tree[1])
             if drev:
                 return smartset.baseset([drev])
@@ -763,16 +800,16 @@
                          if _getstatusname(prefetched[r]) == tree[1]]
                 return smartset.baseset(drevs)
             else:
-                raise error.Abort(_('unknown symbol: %s') % tree[1])
-        elif op in {'and_', 'add', 'sub'}:
+                raise error.Abort(_(b'unknown symbol: %s') % tree[1])
+        elif op in {b'and_', b'add', b'sub'}:
             assert len(tree) == 3
             return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
-        elif op == 'group':
+        elif op == b'group':
             return walk(tree[1])
-        elif op == 'ancestors':
+        elif op == b'ancestors':
             return getstack(walk(tree[1]))
         else:
-            raise error.ProgrammingError('illegal tree: %r' % tree)
+            raise error.ProgrammingError(b'illegal tree: %r' % tree)
 
     return [prefetched[r] for r in walk(tree)]
 
@@ -786,9 +823,9 @@
     summary = drev[r'summary'].rstrip()
     testplan = drev[r'testPlan'].rstrip()
     if testplan:
-        testplan = 'Test Plan:\n%s' % testplan
-    uri = 'Differential Revision: %s' % drev[r'uri']
-    return '\n\n'.join(filter(None, [title, summary, testplan, uri]))
+        testplan = b'Test Plan:\n%s' % testplan
+    uri = b'Differential Revision: %s' % drev[r'uri']
+    return b'\n\n'.join(filter(None, [title, summary, testplan, uri]))
 
 def getdiffmeta(diff):
     """get commit metadata (date, node, user, p1) from a diff object
@@ -848,16 +885,17 @@
     """
     # Prefetch hg:meta property for all diffs
     diffids = sorted(set(max(int(v) for v in drev[r'diffs']) for drev in drevs))
-    diffs = callconduit(repo, 'differential.querydiffs', {'ids': diffids})
+    diffs = callconduit(repo, b'differential.querydiffs', {b'ids': diffids})
 
     # Generate patch for each drev
     for drev in drevs:
-        repo.ui.note(_('reading D%s\n') % drev[r'id'])
+        repo.ui.note(_(b'reading D%s\n') % drev[r'id'])
 
         diffid = max(int(v) for v in drev[r'diffs'])
-        body = callconduit(repo, 'differential.getrawdiff', {'diffID': diffid})
+        body = callconduit(repo, b'differential.getrawdiff',
+                           {b'diffID': diffid})
         desc = getdescfromdrev(drev)
-        header = '# HG changeset patch\n'
+        header = b'# HG changeset patch\n'
 
         # Try to preserve metadata from hg:meta property. Write hg patch
         # headers that can be read by the "import" command. See patchheadermap
@@ -865,14 +903,14 @@
         meta = getdiffmeta(diffs[str(diffid)])
         for k in _metanamemap.keys():
             if k in meta:
-                header += '# %s %s\n' % (_metanamemap[k], meta[k])
+                header += b'# %s %s\n' % (_metanamemap[k], meta[k])
 
-        content = '%s%s\n%s' % (header, desc, body)
+        content = b'%s%s\n%s' % (header, desc, body)
         write(encoding.unitolocal(content))
 
-@command('phabread',
-         [('', 'stack', False, _('read dependencies'))],
-         _('DREVSPEC [OPTIONS]'))
+@command(b'phabread',
+         [(b'', b'stack', False, _(b'read dependencies'))],
+         _(b'DREVSPEC [OPTIONS]'))
 def phabread(ui, repo, spec, **opts):
     """print patches from Phabricator suitable for importing
 
@@ -892,51 +930,51 @@
     If --stack is given, follow dependencies information and read all patches.
     It is equivalent to the ``:`` operator.
     """
-    if opts.get('stack'):
-        spec = ':(%s)' % spec
+    if opts.get(b'stack'):
+        spec = b':(%s)' % spec
     drevs = querydrev(repo, spec)
     readpatch(repo, drevs, ui.write)
 
-@command('phabupdate',
-         [('', 'accept', False, _('accept revisions')),
-          ('', 'reject', False, _('reject revisions')),
-          ('', 'abandon', False, _('abandon revisions')),
-          ('', 'reclaim', False, _('reclaim revisions')),
-          ('m', 'comment', '', _('comment on the last revision')),
-         ], _('DREVSPEC [OPTIONS]'))
+@command(b'phabupdate',
+         [(b'', b'accept', False, _(b'accept revisions')),
+          (b'', b'reject', False, _(b'reject revisions')),
+          (b'', b'abandon', False, _(b'abandon revisions')),
+          (b'', b'reclaim', False, _(b'reclaim revisions')),
+          (b'm', b'comment', b'', _(b'comment on the last revision')),
+          ], _(b'DREVSPEC [OPTIONS]'))
 def phabupdate(ui, repo, spec, **opts):
     """update Differential Revision in batch
 
     DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
     """
-    flags = [n for n in 'accept reject abandon reclaim'.split() if opts.get(n)]
+    flags = [n for n in b'accept reject abandon reclaim'.split() if opts.get(n)]
     if len(flags) > 1:
-        raise error.Abort(_('%s cannot be used together') % ', '.join(flags))
+        raise error.Abort(_(b'%s cannot be used together') % b', '.join(flags))
 
     actions = []
     for f in flags:
-        actions.append({'type': f, 'value': 'true'})
+        actions.append({b'type': f, b'value': b'true'})
 
     drevs = querydrev(repo, spec)
     for i, drev in enumerate(drevs):
-        if i + 1 == len(drevs) and opts.get('comment'):
-            actions.append({'type': 'comment', 'value': opts['comment']})
+        if i + 1 == len(drevs) and opts.get(b'comment'):
+            actions.append({b'type': b'comment', b'value': opts[b'comment']})
         if actions:
-            params = {'objectIdentifier': drev[r'phid'],
-                      'transactions': actions}
-            callconduit(repo, 'differential.revision.edit', params)
+            params = {b'objectIdentifier': drev[r'phid'],
+                      b'transactions': actions}
+            callconduit(repo, b'differential.revision.edit', params)
 
 templatekeyword = registrar.templatekeyword()
 
-@templatekeyword('phabreview', requires={'ctx'})
+@templatekeyword(b'phabreview', requires={b'ctx'})
 def template_review(context, mapping):
     """:phabreview: Object describing the review for this changeset.
     Has attributes `url` and `id`.
     """
-    ctx = context.resource(mapping, 'ctx')
+    ctx = context.resource(mapping, b'ctx')
     m = _differentialrevisiondescre.search(ctx.description())
     if m:
         return {
-            'url': m.group('url'),
-            'id': "D{}".format(m.group('id')),
+            b'url': m.group(b'url'),
+            b'id': b"D{}".format(m.group(b'id')),
         }
--- a/contrib/python3-whitelist	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/python3-whitelist	Thu Jul 19 13:55:54 2018 -0400
@@ -2,6 +2,7 @@
 test-add.t
 test-addremove-similar.t
 test-addremove.t
+test-alias.t
 test-amend-subrepo.t
 test-amend.t
 test-ancestor.py
@@ -14,6 +15,7 @@
 test-automv.t
 test-backout.t
 test-backwards-remove.t
+test-bad-pull.t
 test-basic.t
 test-bheads.t
 test-bisect.t
@@ -22,6 +24,7 @@
 test-blackbox.t
 test-bookmarks-current.t
 test-bookmarks-merge.t
+test-bookmarks-pushpull.t
 test-bookmarks-rebase.t
 test-bookmarks-strip.t
 test-bookmarks.t
@@ -30,16 +33,24 @@
 test-branch-tag-confict.t
 test-branches.t
 test-bundle-phases.t
+test-bundle-r.t
 test-bundle-type.t
 test-bundle-vs-outgoing.t
+test-bundle.t
+test-bundle2-exchange.t
+test-bundle2-format.t
 test-bundle2-multiple-changegroups.t
+test-bundle2-pushback.t
+test-bundle2-remote-changegroup.t
 test-cappedreader.py
 test-casecollision.t
 test-cat.t
+test-cbor.py
 test-censor.t
 test-changelog-exec.t
 test-check-commit.t
 test-check-execute.t
+test-check-interfaces.py
 test-check-module-imports.t
 test-check-pyflakes.t
 test-check-pylint.t
@@ -49,7 +60,7 @@
 test-clone-pull-corruption.t
 test-clone-r.t
 test-clone-update-order.t
-test-command-template.t
+test-clonebundles.t
 test-commit-amend.t
 test-commit-interactive.t
 test-commit-multiple.t
@@ -61,10 +72,16 @@
 test-config.t
 test-conflict.t
 test-confused-revert.t
+test-context.py
 test-contrib-check-code.t
 test-contrib-check-commit.t
 test-convert-authormap.t
 test-convert-clonebranches.t
+test-convert-cvs-branch.t
+test-convert-cvs-detectmerge.t
+test-convert-cvs-synthetic.t
+test-convert-cvs.t
+test-convert-cvsnt-mergepoints.t
 test-convert-datesort.t
 test-convert-filemap.t
 test-convert-hg-sink.t
@@ -81,6 +98,7 @@
 test-debugindexdot.t
 test-debugrename.t
 test-default-push.t
+test-diff-antipatience.t
 test-diff-binary-file.t
 test-diff-change.t
 test-diff-copy-depth.t
@@ -99,6 +117,7 @@
 test-dirstate-backup.t
 test-dirstate-nonnormalset.t
 test-dirstate.t
+test-dispatch.py
 test-doctest.py
 test-double-merge.t
 test-drawdag.t
@@ -114,8 +133,11 @@
 test-eol-add.t
 test-eol-clone.t
 test-eol-hook.t
+test-eol-patch.t
 test-eol-tag.t
 test-eol-update.t
+test-eol.t
+test-eolfilename.t
 test-excessive-merge.t
 test-exchange-obsmarkers-case-A1.t
 test-exchange-obsmarkers-case-A2.t
@@ -143,9 +165,15 @@
 test-export.t
 test-extdata.t
 test-extdiff.t
+test-extensions-afterloaded.t
+test-extensions-wrapfunction.py
 test-extra-filelog-entry.t
+test-fetch.t
 test-filebranch.t
+test-filecache.py
+test-filelog.py
 test-fileset-generated.t
+test-fileset.t
 test-fix-topology.t
 test-flags.t
 test-generaldelta.t
@@ -158,10 +186,12 @@
 test-hghave.t
 test-hgignore.t
 test-hgk.t
+test-hgrc.t
 test-hgweb-bundle.t
 test-hgweb-descend-empties.t
 test-hgweb-empty.t
 test-hgweb-removed.t
+test-hgwebdir-paths.py
 test-hgwebdirsym.t
 test-histedit-arguments.t
 test-histedit-base.t
@@ -171,6 +201,7 @@
 test-histedit-edit.t
 test-histedit-fold-non-commute.t
 test-histedit-fold.t
+test-histedit-no-backup.t
 test-histedit-no-change.t
 test-histedit-non-commute-abort.t
 test-histedit-non-commute.t
@@ -181,12 +212,18 @@
 test-http-bundle1.t
 test-http-clone-r.t
 test-http.t
+test-hybridencode.py
 test-identify.t
+test-impexp-branch.t
+test-import-bypass.t
+test-import-eol.t
+test-import-merge.t
 test-import-unknown.t
 test-import.t
 test-imports-checker.t
 test-incoming-outgoing.t
 test-inherit-mode.t
+test-init.t
 test-issue1089.t
 test-issue1102.t
 test-issue1175.t
@@ -209,12 +246,14 @@
 test-journal-exists.t
 test-journal-share.t
 test-journal.t
+test-known.t
 test-largefiles-cache.t
 test-largefiles-misc.t
 test-largefiles-small-disk.t
 test-largefiles-update.t
 test-largefiles.t
 test-lfs-largefiles.t
+test-lfs-pointer.py
 test-linerange.py
 test-locate.t
 test-lock-badness.t
@@ -254,6 +293,8 @@
 test-merge7.t
 test-merge8.t
 test-merge9.t
+test-minifileset.py
+test-minirst.py
 test-mq-git.t
 test-mq-header-date.t
 test-mq-header-from.t
@@ -298,8 +339,11 @@
 test-narrow-shallow.t
 test-narrow-strip.t
 test-narrow-update.t
+test-narrow-widen.t
+test-narrow.t
 test-nested-repo.t
 test-newbranch.t
+test-nointerrupt.t
 test-obshistory.t
 test-obsmarker-template.t
 test-obsmarkers-effectflag.t
@@ -307,10 +351,16 @@
 test-obsolete-changeset-exchange.t
 test-obsolete-checkheads.t
 test-obsolete-distributed.t
+test-obsolete-divergent.t
 test-obsolete-tag-cache.t
+test-pager.t
 test-parents.t
+test-parseindex2.py
+test-patch-offset.t
+test-patch.t
 test-pathconflicts-merge.t
 test-pathconflicts-update.t
+test-pathencode.py
 test-pending.t
 test-permissions.t
 test-phases.t
@@ -320,6 +370,7 @@
 test-pull-pull-corruption.t
 test-pull-r.t
 test-pull-update.t
+test-pull.t
 test-purge.t
 test-push-checkheads-partial-C1.t
 test-push-checkheads-partial-C2.t
@@ -350,7 +401,9 @@
 test-push-checkheads-unpushed-D7.t
 test-push-http.t
 test-push-warn.t
+test-push.t
 test-pushvars.t
+test-qrecord.t
 test-rebase-abort.t
 test-rebase-base-flag.t
 test-rebase-bookmarks.t
@@ -378,9 +431,11 @@
 test-rebase-scenario-global.t
 test-rebase-templates.t
 test-rebase-transaction.t
+test-rebuildstate.t
 test-record.t
 test-relink.t
 test-remove.t
+test-removeemptydirs.t
 test-rename-after-merge.t
 test-rename-dir-merge.t
 test-rename-merge1.t
@@ -389,11 +444,14 @@
 test-repo-compengines.t
 test-resolve.t
 test-revert-flags.t
+test-revert-interactive.t
 test-revert-unknown.t
 test-revlog-ancestry.py
 test-revlog-group-emptyiter.t
 test-revlog-mmapindex.t
 test-revlog-packentry.t
+test-revlog-raw.py
+test-revlog-v2.t
 test-revset-dirstate-parents.t
 test-revset-legacy-lookup.t
 test-revset-outgoing.t
@@ -409,34 +467,56 @@
 test-show-work.t
 test-show.t
 test-simple-update.t
+test-simplekeyvaluefile.py
+test-simplemerge.py
 test-single-head.t
 test-sparse-clear.t
+test-sparse-clone.t
 test-sparse-import.t
 test-sparse-merges.t
 test-sparse-profiles.t
 test-sparse-requirement.t
 test-sparse-verbose-json.t
+test-sparse.t
+test-split.t
+test-ssh-bundle1.t
 test-ssh-clone-r.t
+test-ssh-proto-unbundle.t
 test-ssh-proto.t
+test-ssh.t
 test-sshserver.py
 test-stack.t
+test-status-inprocess.py
 test-status-rev.t
 test-status-terse.t
+test-strict.t
 test-strip-cross.t
 test-strip.t
 test-subrepo-deep-nested-change.t
 test-subrepo-missing.t
+test-subrepo-paths.t
 test-subrepo-recursion.t
 test-subrepo-relative-path.t
 test-subrepo.t
+test-symlink-os-yes-fs-no.py
+test-symlink-placeholder.t
 test-symlinks.t
 test-tag.t
 test-tags.t
-test-template-engine.t
+test-template-basic.t
+test-template-functions.t
+test-template-keywords.t
+test-template-map.t
+test-transplant.t
 test-treemanifest.t
+test-ui-color.py
+test-ui-config.py
+test-ui-verbosity.py
 test-unamend.t
+test-unbundlehash.t
 test-uncommit.t
 test-unified-test.t
+test-unionrepo.t
 test-unrelated-pull.t
 test-up-local-change.t
 test-update-branches.t
@@ -447,11 +527,16 @@
 test-upgrade-repo.t
 test-url-download.t
 test-url-rev.t
+test-url.py
 test-username-newline.t
 test-verify.t
+test-walk.t
+test-walkrepo.py
 test-websub.t
 test-win32text.t
 test-wireproto-clientreactor.py
 test-wireproto-framing.py
 test-wireproto-serverreactor.py
+test-wireproto.py
+test-wsgirequest.py
 test-xdg.t
--- a/contrib/synthrepo.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/synthrepo.py	Thu Jul 19 13:55:54 2018 -0400
@@ -54,13 +54,16 @@
 )
 from mercurial import (
     context,
+    diffutil,
     error,
     hg,
     patch,
     registrar,
     scmutil,
 )
-from mercurial.utils import dateutil
+from mercurial.utils import (
+    dateutil,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -172,13 +175,10 @@
         revs = scmutil.revrange(repo, revs)
         revs.sort()
 
-        progress = ui.progress
-        _analyzing = _('analyzing')
-        _changesets = _('changesets')
-        _total = len(revs)
-
+        progress = ui.makeprogress(_('analyzing'), unit=_('changesets'),
+                                   total=len(revs))
         for i, rev in enumerate(revs):
-            progress(_analyzing, i, unit=_changesets, total=_total)
+            progress.update(i)
             ctx = repo[rev]
             pl = ctx.parents()
             pctx = pl[0]
@@ -196,7 +196,9 @@
             if lastctx.rev() != nullrev:
                 timedelta = ctx.date()[0] - lastctx.date()[0]
                 interarrival[roundto(timedelta, 300)] += 1
-            diff = sum((d.splitlines() for d in ctx.diff(pctx, git=True)), [])
+            diffopts = diffutil.diffallopts(ui, {'git': True})
+            diff = sum((d.splitlines()
+                       for d in ctx.diff(pctx, opts=diffopts)), [])
             fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0
             for filename, mar, lineadd, lineremove, isbin in parsegitdiff(diff):
                 if isbin:
@@ -222,6 +224,7 @@
             filesadded[fileadds] += 1
             dirsadded[diradds] += 1
             filesremoved[fileremoves] += 1
+        progress.complete()
 
     invchildren = zerodict()
 
@@ -338,7 +341,6 @@
 
     nevertouch = {'.hgsub', '.hgignore', '.hgtags'}
 
-    progress = ui.progress
     _synthesizing = _('synthesizing')
     _files = _('initial files')
     _changesets = _('changesets')
@@ -362,8 +364,9 @@
                 path = os.path.dirname(path)
             return True
 
+        progress = ui.makeprogress(_synthesizing, unit=_files, total=initcount)
         for i in xrange(0, initcount):
-            ui.progress(_synthesizing, i, unit=_files, total=initcount)
+            progress.update(i)
 
             path = pickpath()
             while not validpath(path):
@@ -378,7 +381,7 @@
         def filectxfn(repo, memctx, path):
             return context.memfilectx(repo, memctx, path, files[path])
 
-        ui.progress(_synthesizing, None)
+        progress.complete()
         message = 'synthesized wide repo with %d files' % (len(files),)
         mc = context.memctx(repo, [pctx.node(), nullid], message,
                             files, filectxfn, ui.username(),
@@ -394,8 +397,9 @@
     # Synthesize incremental revisions to the repository, adding repo depth.
     count = int(opts['count'])
     heads = set(map(repo.changelog.rev, repo.heads()))
+    progress = ui.makeprogress(_synthesizing, unit=_changesets, total=count)
     for i in xrange(count):
-        progress(_synthesizing, i, unit=_changesets, total=count)
+        progress.update(i)
 
         node = repo.changelog.node
         revs = len(repo)
@@ -485,6 +489,7 @@
         heads.add(repo.changelog.rev(newnode))
         heads.discard(r1)
         heads.discard(r2)
+    progress.complete()
 
     lock.release()
     wlock.release()
--- a/contrib/wix/help.wxs	Sun Jul 01 23:36:53 2018 +0900
+++ b/contrib/wix/help.wxs	Thu Jul 19 13:55:54 2018 -0400
@@ -19,6 +19,7 @@
           <File Name="color.txt" />
           <File Name="config.txt" KeyPath="yes" />
           <File Name="dates.txt" />
+          <File Name="deprecated.txt" />
           <File Name="diffs.txt" />
           <File Name="environment.txt" />
           <File Name="extensions.txt" />
--- a/hgdemandimport/__init__.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgdemandimport/__init__.py	Thu Jul 19 13:55:54 2018 -0400
@@ -21,8 +21,9 @@
 else:
     from . import demandimportpy2 as demandimport
 
-# Extensions can add to this list if necessary.
-ignore = [
+# Full module names which can't be lazy imported.
+# Extensions can add to this set.
+IGNORES = {
     '__future__',
     '_hashlib',
     # ImportError during pkg_resources/__init__.py:fixup_namespace_package
@@ -55,17 +56,15 @@
     '__builtin__',
     'builtins',
     'urwid.command_map', # for pudb
-    ]
+}
 
 _pypy = '__pypy__' in sys.builtin_module_names
 
 if _pypy:
-    ignore.extend([
-        # _ctypes.pointer is shadowed by "from ... import pointer" (PyPy 5)
-        '_ctypes.pointer',
-    ])
+    # _ctypes.pointer is shadowed by "from ... import pointer" (PyPy 5)
+    IGNORES.add('_ctypes.pointer')
 
-demandimport.init(ignore)
+demandimport.init(IGNORES)
 
 # Re-export.
 isenabled = demandimport.isenabled
--- a/hgdemandimport/demandimportpy2.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgdemandimport/demandimportpy2.py	Thu Jul 19 13:55:54 2018 -0400
@@ -162,7 +162,7 @@
 _pypy = '__pypy__' in sys.builtin_module_names
 
 def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
-    if locals is None or name in ignore or fromlist == ('*',):
+    if locals is None or name in ignores or fromlist == ('*',):
         # these cases we can't really delay
         return _hgextimport(_origimport, name, globals, locals, fromlist, level)
     elif not fromlist:
@@ -209,7 +209,7 @@
                     # while processing the import statement.
                     return
                 mn = '%s.%s' % (mod.__name__, attr)
-                if mn in ignore:
+                if mn in ignores:
                     importfunc = _origimport
                 else:
                     importfunc = _demandmod
@@ -273,11 +273,11 @@
 
         return mod
 
-ignore = []
+ignores = set()
 
-def init(ignorelist):
-    global ignore
-    ignore = ignorelist
+def init(ignoreset):
+    global ignores
+    ignores = ignoreset
 
 def isenabled():
     return builtins.__import__ == _demandimport
--- a/hgdemandimport/demandimportpy3.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgdemandimport/demandimportpy3.py	Thu Jul 19 13:55:54 2018 -0400
@@ -40,7 +40,7 @@
     """
     def exec_module(self, module):
         """Make the module load lazily."""
-        if _deactivated or module.__name__ in ignore:
+        if _deactivated or module.__name__ in ignores:
             self.loader.exec_module(module)
         else:
             super().exec_module(module)
@@ -62,11 +62,11 @@
         (_bytecode_loader, importlib.machinery.BYTECODE_SUFFIXES),
     )
 
-ignore = []
+ignores = set()
 
-def init(ignorelist):
-    global ignore
-    ignore = ignorelist
+def init(ignoreset):
+    global ignores
+    ignores = ignoreset
 
 def isenabled():
     return _makefinder in sys.path_hooks and not _deactivated
--- a/hgext/acl.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/acl.py	Thu Jul 19 13:55:54 2018 -0400
@@ -57,6 +57,28 @@
 a glob syntax by default). The corresponding values follow the same
 syntax as the other sections above.
 
+Bookmark-based Access Control
+-----------------------------
+Use the ``acl.deny.bookmarks`` and ``acl.allow.bookmarks`` sections to
+have bookmark-based access control. Keys in these sections can be
+either:
+
+- a bookmark name, or
+- an asterisk, to match any bookmark;
+
+The corresponding values can be either:
+
+- a comma-separated list containing users and groups, or
+- an asterisk, to match anyone;
+
+You can add the "!" prefix to a user or group name to invert the sense
+of the match.
+
+Note: for interactions between clients and servers using Mercurial 3.6+
+a rejection will generally reject the entire push, for interactions
+involving older clients, the commit transactions will already be accepted,
+and only the bookmark movement will be rejected.
+
 Groups
 ------
 
@@ -326,9 +348,10 @@
 
     ensureenabled(ui)
 
-    if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
-        raise error.Abort(_('config error - hook type "%s" cannot stop '
-                           'incoming changesets nor commits') % hooktype)
+    if hooktype not in ['pretxnchangegroup', 'pretxncommit', 'prepushkey']:
+        raise error.Abort(
+            _('config error - hook type "%s" cannot stop '
+              'incoming changesets, commits, nor bookmarks') % hooktype)
     if (hooktype == 'pretxnchangegroup' and
         source not in ui.configlist('acl', 'sources')):
         ui.debug('acl: changes have source "%s" - skipping\n' % source)
@@ -345,6 +368,30 @@
 
     ui.debug('acl: checking access for user "%s"\n' % user)
 
+    if hooktype == 'prepushkey':
+        _pkhook(ui, repo, hooktype, node, source, user, **kwargs)
+    else:
+        _txnhook(ui, repo, hooktype, node, source, user, **kwargs)
+
+def _pkhook(ui, repo, hooktype, node, source, user, **kwargs):
+    if kwargs['namespace'] == 'bookmarks':
+        bookmark = kwargs['key']
+        ctx = kwargs['new']
+        allowbookmarks = buildmatch(ui, None, user, 'acl.allow.bookmarks')
+        denybookmarks = buildmatch(ui, None, user, 'acl.deny.bookmarks')
+
+        if denybookmarks and denybookmarks(bookmark):
+            raise error.Abort(_('acl: user "%s" denied on bookmark "%s"'
+                               ' (changeset "%s")')
+                               % (user, bookmark, ctx))
+        if allowbookmarks and not allowbookmarks(bookmark):
+            raise error.Abort(_('acl: user "%s" not allowed on bookmark "%s"'
+                               ' (changeset "%s")')
+                               % (user, bookmark, ctx))
+        ui.debug('acl: bookmark access granted: "%s" on bookmark "%s"\n'
+                 % (ctx, bookmark))
+
+def _txnhook(ui, repo, hooktype, node, source, user, **kwargs):
     # deprecated config: acl.config
     cfg = ui.config('acl', 'config')
     if cfg:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/beautifygraph.py	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,93 @@
+# -*- coding: UTF-8 -*-
+# beautifygraph.py - improve graph output by using Unicode characters
+#
+# Copyright 2018 John Stiles <johnstiles@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+'''beautify log -G output by using Unicode characters (EXPERIMENTAL)
+
+   A terminal with UTF-8 support and monospace narrow text are required.
+'''
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+from mercurial import (
+    encoding,
+    extensions,
+    graphmod,
+    templatekw,
+)
+
+# 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'
+
+def prettyedge(before, edge, after):
+    if edge == '~':
+        return '\xE2\x95\xA7' # U+2567 ╧
+    if edge == 'X':
+        return '\xE2\x95\xB3' # U+2573 ╳
+    if edge == '/':
+        return '\xE2\x95\xB1' # U+2571 ╱
+    if edge == '-':
+        return '\xE2\x94\x80' # U+2500 ─
+    if edge == '|':
+        return '\xE2\x94\x82' # U+2502 │
+    if edge == ':':
+        return '\xE2\x94\x86' # U+2506 ┆
+    if edge == '\\':
+        return '\xE2\x95\xB2' # U+2572 ╲
+    if edge == '+':
+        if before == ' ' and not after  == ' ':
+            return '\xE2\x94\x9C' # U+251C ├
+        if after  == ' ' and not before == ' ':
+            return '\xE2\x94\xA4' # U+2524 ┤
+        return '\xE2\x94\xBC' # U+253C ┼
+    return edge
+
+def convertedges(line):
+    line = ' %s ' % line
+    pretty = []
+    for idx in xrange(len(line) - 2):
+        pretty.append(prettyedge(line[idx], line[idx + 1], line[idx + 2]))
+    return ''.join(pretty)
+
+def getprettygraphnode(orig, *args, **kwargs):
+    node = orig(*args, **kwargs)
+    if node == 'o':
+        return '\xE2\x97\x8B' # U+25CB ○
+    if node == '@':
+        return '\xE2\x97\x8D' # U+25CD ◍
+    if node == '*':
+        return '\xE2\x88\x97' # U+2217 ∗
+    if node == 'x':
+        return '\xE2\x97\x8C' # U+25CC ◌
+    if node == '_':
+        return '\xE2\x95\xA4' # U+2564 ╤
+    return node
+
+def outputprettygraph(orig, ui, graph, *args, **kwargs):
+    (edges, text) = zip(*graph)
+    graph = zip([convertedges(e) for e in edges], text)
+    return orig(ui, graph, *args, **kwargs)
+
+def extsetup(ui):
+    if encoding.encoding != 'UTF-8':
+        ui.warn(_('beautifygraph: unsupported encoding, UTF-8 required\n'))
+        return
+
+    if 'A' in encoding._wide:
+        ui.warn(_('beautifygraph: unsupported terminal settings, '
+                  'monospace narrow text required\n'))
+        return
+
+    if ui.plain('graph'):
+        return
+
+    extensions.wrapfunction(graphmod, 'outputgraph', outputprettygraph)
+    extensions.wrapfunction(templatekw, 'getgraphnode', getprettygraphnode)
--- a/hgext/censor.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/censor.py	Thu Jul 19 13:55:54 2018 -0400
@@ -32,7 +32,6 @@
 
 from mercurial import (
     error,
-    lock as lockmod,
     registrar,
     revlog,
     scmutil,
@@ -52,13 +51,8 @@
      ('t', 'tombstone', '', _('replacement tombstone data'), _('TEXT'))],
     _('-r REV [-t TEXT] [FILE]'))
 def censor(ui, repo, path, rev='', tombstone='', **opts):
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
+    with repo.wlock(), repo.lock():
         return _docensor(ui, repo, path, rev, tombstone, **opts)
-    finally:
-        lockmod.release(lock, wlock)
 
 def _docensor(ui, repo, path, rev='', tombstone='', **opts):
     if not path:
--- a/hgext/churn.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/churn.py	Thu Jul 19 13:55:54 2018 -0400
@@ -52,7 +52,7 @@
         def getkey(ctx):
             t, tz = ctx.date()
             date = datetime.datetime(*time.gmtime(float(t) - tz)[:6])
-            return date.strftime(opts['dateformat'])
+            return date.strftime(encoding.strfromlocal(opts['dateformat']))
     else:
         tmpl = opts.get('oldtemplate') or opts.get('template')
         tmpl = logcmdutil.maketemplater(ui, repo, tmpl)
@@ -61,7 +61,8 @@
             tmpl.show(ctx)
             return ui.popbuffer()
 
-    state = {'count': 0}
+    progress = ui.makeprogress(_('analyzing'), unit=_('revisions'),
+                               total=len(repo))
     rate = {}
     df = False
     if opts.get('date'):
@@ -87,14 +88,12 @@
             lines = changedlines(ui, repo, ctx1, ctx, fns)
             rate[key] = [r + l for r, l in zip(rate.get(key, (0, 0)), lines)]
 
-        state['count'] += 1
-        ui.progress(_('analyzing'), state['count'], total=len(repo),
-                    unit=_('revisions'))
+        progress.increment()
 
     for ctx in cmdutil.walkchangerevs(repo, m, opts, prep):
         continue
 
-    ui.progress(_('analyzing'), None)
+    progress.complete()
 
     return rate
 
@@ -161,7 +160,7 @@
     if not aliases and os.path.exists(repo.wjoin('.hgchurn')):
         aliases = repo.wjoin('.hgchurn')
     if aliases:
-        for l in open(aliases, "r"):
+        for l in open(aliases, "rb"):
             try:
                 alias, actual = l.rsplit('=' in l and '=' or None, 1)
                 amap[alias.strip()] = actual.strip()
--- a/hgext/convert/__init__.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/convert/__init__.py	Thu Jul 19 13:55:54 2018 -0400
@@ -204,6 +204,14 @@
 
     :convert.hg.revs: revset specifying the source revisions to convert.
 
+    Bazaar Source
+    #############
+
+    The following options can be used with ``--config``:
+
+    :convert.bzr.saverev: whether to store the original Bazaar commit ID in
+        the metadata of the destination commit. The default is True.
+
     CVS Source
     ##########
 
--- a/hgext/convert/bzr.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/convert/bzr.py	Thu Jul 19 13:55:54 2018 -0400
@@ -19,7 +19,7 @@
 from . import common
 
 # these do not work with demandimport, blacklist
-demandimport.ignore.extend([
+demandimport.IGNORES.update([
         'bzrlib.transactions',
         'bzrlib.urlutils',
         'ElementPath',
@@ -65,6 +65,7 @@
             raise common.NoRepo(_('%s does not look like a Bazaar repository')
                               % path)
         self._parentids = {}
+        self._saverev = ui.configbool('convert', 'bzr.saverev')
 
     def _checkrepotype(self, path):
         # Lightweight checkouts detection is informational but probably
@@ -175,7 +176,8 @@
                 author=self.recode(rev.committer),
                 desc=self.recode(rev.message),
                 branch=branch,
-                rev=version)
+                rev=version,
+                saverev=self._saverev)
 
     def gettags(self):
         bytetags = {}
--- a/hgext/convert/common.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/convert/common.py	Thu Jul 19 13:55:54 2018 -0400
@@ -214,7 +214,7 @@
         if not encoding:
             encoding = self.encoding or 'utf-8'
 
-        if isinstance(s, unicode):
+        if isinstance(s, pycompat.unicode):
             return s.encode("utf-8")
         try:
             return s.decode(pycompat.sysstr(encoding)).encode("utf-8")
--- a/hgext/convert/convcmd.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/convert/convcmd.py	Thu Jul 19 13:55:54 2018 -0400
@@ -55,7 +55,7 @@
 orig_encoding = 'ascii'
 
 def recode(s):
-    if isinstance(s, unicode):
+    if isinstance(s, pycompat.unicode):
         return s.encode(pycompat.sysstr(orig_encoding), 'replace')
     else:
         return s.decode('utf-8').encode(
@@ -123,7 +123,7 @@
             exceptions.append(inst)
     if not ui.quiet:
         for inst in exceptions:
-            ui.write("%s\n" % inst)
+            ui.write("%s\n" % pycompat.bytestr(inst))
     raise error.Abort(_('%s: missing or unsupported repository') % path)
 
 def convertsink(ui, path, type):
@@ -143,13 +143,11 @@
     def __init__(self, ui, source, filecount):
         self.ui = ui
         self.source = source
-        self.filecount = filecount
-        self.retrieved = 0
+        self.progress = ui.makeprogress(_('getting files'), unit=_('files'),
+                                        total=filecount)
 
     def getfile(self, file, rev):
-        self.retrieved += 1
-        self.ui.progress(_('getting files'), self.retrieved,
-                         item=file, total=self.filecount, unit=_('files'))
+        self.progress.increment(item=file)
         return self.source.getfile(file, rev)
 
     def targetfilebelongstosource(self, targetfilename):
@@ -159,7 +157,7 @@
         return self.source.lookuprev(rev)
 
     def close(self):
-        self.ui.progress(_('getting files'), None)
+        self.progress.complete()
 
 class converter(object):
     def __init__(self, ui, source, dest, revmapfile, opts):
@@ -234,10 +232,12 @@
     def walktree(self, heads):
         '''Return a mapping that identifies the uncommitted parents of every
         uncommitted changeset.'''
-        visit = heads
+        visit = list(heads)
         known = set()
         parents = {}
         numcommits = self.source.numcommits()
+        progress = self.ui.makeprogress(_('scanning'), unit=_('revisions'),
+                                        total=numcommits)
         while visit:
             n = visit.pop(0)
             if n in known:
@@ -247,14 +247,13 @@
                 if m == SKIPREV or self.dest.hascommitfrommap(m):
                     continue
             known.add(n)
-            self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
-                             total=numcommits)
+            progress.update(len(known))
             commit = self.cachecommit(n)
             parents[n] = []
             for p in commit.parents:
                 parents[n].append(p)
                 visit.append(p)
-        self.ui.progress(_('scanning'), None)
+        progress.complete()
 
         return parents
 
@@ -510,6 +509,8 @@
             c = None
 
             self.ui.status(_("converting...\n"))
+            progress = self.ui.makeprogress(_('converting'),
+                                            unit=_('revisions'), total=len(t))
             for i, c in enumerate(t):
                 num -= 1
                 desc = self.commitcache[c].desc
@@ -520,10 +521,9 @@
                 # uses is 'utf-8'
                 self.ui.status("%d %s\n" % (num, recode(desc)))
                 self.ui.note(_("source: %s\n") % recode(c))
-                self.ui.progress(_('converting'), i, unit=_('revisions'),
-                                 total=len(t))
+                progress.update(i)
                 self.copy(c)
-            self.ui.progress(_('converting'), None)
+            progress.complete()
 
             if not self.ui.configbool('convert', 'skiptags'):
                 tags = self.source.gettags()
--- a/hgext/convert/cvsps.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/convert/cvsps.py	Thu Jul 19 13:55:54 2018 -0400
@@ -6,6 +6,7 @@
 # GNU General Public License version 2 or any later version.
 from __future__ import absolute_import
 
+import functools
 import os
 import re
 
@@ -50,8 +51,8 @@
         self.__dict__.update(entries)
 
     def __repr__(self):
-        items = ("%s=%r"%(k, self.__dict__[k]) for k in sorted(self.__dict__))
-        return "%s(%s)"%(type(self).__name__, ", ".join(items))
+        items = (r"%s=%r"%(k, self.__dict__[k]) for k in sorted(self.__dict__))
+        return r"%s(%s)"%(type(self).__name__, r", ".join(items))
 
 class logerror(Exception):
     pass
@@ -110,25 +111,25 @@
     log = []      # list of logentry objects containing the CVS state
 
     # patterns to match in CVS (r)log output, by state of use
-    re_00 = re.compile('RCS file: (.+)$')
-    re_01 = re.compile('cvs \\[r?log aborted\\]: (.+)$')
-    re_02 = re.compile('cvs (r?log|server): (.+)\n$')
-    re_03 = re.compile("(Cannot access.+CVSROOT)|"
-                       "(can't create temporary directory.+)$")
-    re_10 = re.compile('Working file: (.+)$')
-    re_20 = re.compile('symbolic names:')
-    re_30 = re.compile('\t(.+): ([\\d.]+)$')
-    re_31 = re.compile('----------------------------$')
-    re_32 = re.compile('======================================='
-                       '======================================$')
-    re_50 = re.compile('revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
-    re_60 = re.compile(r'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
-                       r'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
-                       r'(\s+commitid:\s+([^;]+);)?'
-                       r'(.*mergepoint:\s+([^;]+);)?')
-    re_70 = re.compile('branches: (.+);$')
+    re_00 = re.compile(b'RCS file: (.+)$')
+    re_01 = re.compile(b'cvs \\[r?log aborted\\]: (.+)$')
+    re_02 = re.compile(b'cvs (r?log|server): (.+)\n$')
+    re_03 = re.compile(b"(Cannot access.+CVSROOT)|"
+                       b"(can't create temporary directory.+)$")
+    re_10 = re.compile(b'Working file: (.+)$')
+    re_20 = re.compile(b'symbolic names:')
+    re_30 = re.compile(b'\t(.+): ([\\d.]+)$')
+    re_31 = re.compile(b'----------------------------$')
+    re_32 = re.compile(b'======================================='
+                       b'======================================$')
+    re_50 = re.compile(b'revision ([\\d.]+)(\s+locked by:\s+.+;)?$')
+    re_60 = re.compile(br'date:\s+(.+);\s+author:\s+(.+);\s+state:\s+(.+?);'
+                       br'(\s+lines:\s+(\+\d+)?\s+(-\d+)?;)?'
+                       br'(\s+commitid:\s+([^;]+);)?'
+                       br'(.*mergepoint:\s+([^;]+);)?')
+    re_70 = re.compile(b'branches: (.+);$')
 
-    file_added_re = re.compile(r'file [^/]+ was (initially )?added on branch')
+    file_added_re = re.compile(br'file [^/]+ was (initially )?added on branch')
 
     prefix = ''   # leading path to strip of what we get from CVS
 
@@ -509,7 +510,8 @@
             comment = entry.comment
             for e in encodings:
                 try:
-                    entry.comment = comment.decode(e).encode('utf-8')
+                    entry.comment = comment.decode(
+                        pycompat.sysstr(e)).encode('utf-8')
                     if ui.debugflag:
                         ui.debug("transcoding by %s: %s of %s\n" %
                                  (e, revstr(entry.revision), entry.file))
@@ -565,11 +567,15 @@
     mindate = {}
     for e in log:
         if e.commitid:
-            mindate[e.commitid] = min(e.date, mindate.get(e.commitid))
+            if e.commitid not in mindate:
+                mindate[e.commitid] = e.date
+            else:
+                mindate[e.commitid] = min(e.date, mindate[e.commitid])
 
     # Merge changesets
-    log.sort(key=lambda x: (mindate.get(x.commitid), x.commitid, x.comment,
-                            x.author, x.branch, x.date, x.branchpoints))
+    log.sort(key=lambda x: (mindate.get(x.commitid, (-1, 0)),
+                            x.commitid or '', x.comment,
+                            x.author, x.branch or '', x.date, x.branchpoints))
 
     changesets = []
     files = set()
@@ -653,7 +659,7 @@
         return 0
 
     for c in changesets:
-        c.entries.sort(entitycompare)
+        c.entries.sort(key=functools.cmp_to_key(entitycompare))
 
     # Sort changesets by date
 
@@ -706,7 +712,7 @@
             d = c(len(l.branchpoints), len(r.branchpoints))
         return d
 
-    changesets.sort(cscmp)
+    changesets.sort(key=functools.cmp_to_key(cscmp))
 
     # Collect tags
 
@@ -729,12 +735,12 @@
     # {{mergefrombranch BRANCHNAME}} by setting two parents.
 
     if mergeto is None:
-        mergeto = r'{{mergetobranch ([-\w]+)}}'
+        mergeto = br'{{mergetobranch ([-\w]+)}}'
     if mergeto:
         mergeto = re.compile(mergeto)
 
     if mergefrom is None:
-        mergefrom = r'{{mergefrombranch ([-\w]+)}}'
+        mergefrom = br'{{mergefrombranch ([-\w]+)}}'
     if mergefrom:
         mergefrom = re.compile(mergefrom)
 
@@ -797,7 +803,7 @@
                 except KeyError:
                     ui.warn(_("warning: CVS commit message references "
                               "non-existent branch %r:\n%s\n")
-                            % (m, c.comment))
+                            % (pycompat.bytestr(m), c.comment))
                 if m in branches and c.branch != m and not candidate.synthetic:
                     c.parents.append(candidate)
 
@@ -940,7 +946,8 @@
                 if fn.startswith(opts["prefix"]):
                     fn = fn[len(opts["prefix"]):]
                 ui.write('\t%s:%s->%s%s \n' % (
-                        fn, '.'.join([str(x) for x in f.parent]) or 'INITIAL',
+                        fn,
+                        '.'.join([b"%d" % x for x in f.parent]) or 'INITIAL',
                         '.'.join([(b"%d" % x) for x in f.revision]),
                         ['', '(DEAD)'][f.dead]))
             ui.write('\n')
--- a/hgext/convert/darcs.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/convert/darcs.py	Thu Jul 19 13:55:54 2018 -0400
@@ -10,10 +10,11 @@
 import os
 import re
 import shutil
-import tempfile
+
 from mercurial.i18n import _
 from mercurial import (
     error,
+    pycompat,
     util,
 )
 from mercurial.utils import dateutil
@@ -76,7 +77,7 @@
             self.ui.warn(_('failed to detect repository format!'))
 
     def before(self):
-        self.tmppath = tempfile.mkdtemp(
+        self.tmppath = pycompat.mkdtemp(
             prefix='convert-' + os.path.basename(self.path) + '-')
         output, status = self.run('init', repodir=self.tmppath)
         self.checkexit(status)
@@ -103,7 +104,7 @@
         shutil.rmtree(self.tmppath, ignore_errors=True)
 
     def recode(self, s, encoding=None):
-        if isinstance(s, unicode):
+        if isinstance(s, pycompat.unicode):
             # XMLParser returns unicode objects for anything it can't
             # encode into ASCII. We convert them back to str to get
             # recode's normal conversion behavior.
@@ -125,8 +126,7 @@
         return etree.getroot()
 
     def format(self):
-        output, status = self.run('show', 'repo', no_files=True,
-                                  repodir=self.path)
+        output, status = self.run('show', 'repo', repodir=self.path)
         self.checkexit(status)
         m = re.search(r'^\s*Format:\s*(.*)$', output, re.MULTILINE)
         if not m:
--- a/hgext/convert/subversion.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/convert/subversion.py	Thu Jul 19 13:55:54 2018 -0400
@@ -5,7 +5,6 @@
 
 import os
 import re
-import tempfile
 import xml.dom.minidom
 
 from mercurial.i18n import _
@@ -751,9 +750,10 @@
             self.module = new_module
             self.reparent(self.module)
 
+        progress = self.ui.makeprogress(_('scanning paths'), unit=_('paths'),
+                                        total=len(paths))
         for i, (path, ent) in enumerate(paths):
-            self.ui.progress(_('scanning paths'), i, item=path,
-                             total=len(paths), unit=_('paths'))
+            progress.update(i, item=path)
             entrypath = self.getrelpath(path)
 
             kind = self._checkpath(entrypath, revnum)
@@ -839,7 +839,7 @@
                     copytopath = self.getrelpath(copytopath)
                     copies[self.recode(copytopath)] = self.recode(childpath)
 
-        self.ui.progress(_('scanning paths'), None)
+        progress.complete()
         changed.update(removed)
         return (list(changed), removed, copies)
 
@@ -1081,7 +1081,7 @@
                                ' hg executable is in PATH'))
         return logstream(stdout)
 
-pre_revprop_change = '''#!/bin/sh
+pre_revprop_change = b'''#!/bin/sh
 
 REPOS="$1"
 REV="$2"
@@ -1098,8 +1098,8 @@
 '''
 
 class svn_sink(converter_sink, commandline):
-    commit_re = re.compile(r'Committed revision (\d+).', re.M)
-    uuid_re = re.compile(r'Repository UUID:\s*(\S+)', re.M)
+    commit_re = re.compile(br'Committed revision (\d+).', re.M)
+    uuid_re = re.compile(br'Repository UUID:\s*(\S+)', re.M)
 
     def prerun(self):
         if self.wc:
@@ -1225,7 +1225,7 @@
         wdest = self.wjoin(dest)
         exists = os.path.lexists(wdest)
         if exists:
-            fd, tempname = tempfile.mkstemp(
+            fd, tempname = pycompat.mkstemp(
                 prefix='hg-copy-', dir=os.path.dirname(wdest))
             os.close(fd)
             os.unlink(tempname)
@@ -1313,7 +1313,7 @@
             self.xargs(self.setexec, 'propset', 'svn:executable', '*')
             self.setexec = []
 
-        fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
+        fd, messagefile = pycompat.mkstemp(prefix='hg-convert-')
         fp = os.fdopen(fd, r'wb')
         fp.write(util.tonativeeol(commit.desc))
         fp.close()
--- a/hgext/eol.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/eol.py	Thu Jul 19 13:55:54 2018 -0400
@@ -142,7 +142,7 @@
     if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
         return s
     if (ui.configbool('eol', 'fix-trailing-newline')
-        and s and s[-1] != '\n'):
+        and s and not s.endswith('\n')):
         s = s + '\n'
     return util.tolf(s)
 
@@ -153,7 +153,7 @@
     if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
         return s
     if (ui.configbool('eol', 'fix-trailing-newline')
-        and s and s[-1] != '\n'):
+        and s and not s.endswith('\n')):
         s = s + '\n'
     return util.tocrlf(s)
 
--- a/hgext/extdiff.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/extdiff.py	Thu Jul 19 13:55:54 2018 -0400
@@ -71,7 +71,7 @@
 import re
 import shutil
 import stat
-import tempfile
+
 from mercurial.i18n import _
 from mercurial.node import (
     nullid,
@@ -210,7 +210,7 @@
         if not common:
             return 0
 
-    tmproot = tempfile.mkdtemp(prefix='extdiff.')
+    tmproot = pycompat.mkdtemp(prefix='extdiff.')
     try:
         if not opts.get('patch'):
             # Always make a copy of node1a (and node1b, if applicable)
--- a/hgext/fix.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/fix.py	Thu Jul 19 13:55:54 2018 -0400
@@ -70,6 +70,7 @@
     registrar,
     scmutil,
     util,
+    worker,
 )
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -133,32 +134,56 @@
             raise error.Abort(_('cannot specify both "--rev" and "--all"'))
         opts['rev'] = ['not public() and not obsolete()']
         opts['working_dir'] = True
-    with repo.wlock(), repo.lock():
+    with repo.wlock(), repo.lock(), repo.transaction('fix'):
         revstofix = getrevstofix(ui, repo, opts)
         basectxs = getbasectxs(repo, opts, revstofix)
         workqueue, numitems = getworkqueue(ui, repo, pats, opts, revstofix,
                                            basectxs)
+        fixers = getfixers(ui)
+
+        # There are no data dependencies between the workers fixing each file
+        # revision, so we can use all available parallelism.
+        def getfixes(items):
+            for rev, path in items:
+                ctx = repo[rev]
+                olddata = ctx[path].data()
+                newdata = fixfile(ui, opts, fixers, ctx, path, basectxs[rev])
+                # Don't waste memory/time passing unchanged content back, but
+                # produce one result per item either way.
+                yield (rev, path, newdata if newdata != olddata else None)
+        results = worker.worker(ui, 1.0, getfixes, tuple(), workqueue)
+
+        # We have to hold on to the data for each successor revision in memory
+        # until all its parents are committed. We ensure this by committing and
+        # freeing memory for the revisions in some topological order. This
+        # leaves a little bit of memory efficiency on the table, but also makes
+        # the tests deterministic. It might also be considered a feature since
+        # it makes the results more easily reproducible.
         filedata = collections.defaultdict(dict)
         replacements = {}
-        fixers = getfixers(ui)
-        # Some day this loop can become a worker pool, but for now it's easier
-        # to fix everything serially in topological order.
-        for rev, path in sorted(workqueue):
-            ctx = repo[rev]
-            olddata = ctx[path].data()
-            newdata = fixfile(ui, opts, fixers, ctx, path, basectxs[rev])
-            if newdata != olddata:
-                filedata[rev][path] = newdata
-            numitems[rev] -= 1
-            if not numitems[rev]:
-                if rev == wdirrev:
-                    writeworkingdir(repo, ctx, filedata[rev], replacements)
-                else:
-                    replacerev(ui, repo, ctx, filedata[rev], replacements)
-                del filedata[rev]
+        commitorder = sorted(revstofix, reverse=True)
+        with ui.makeprogress(topic=_('fixing'), unit=_('files'),
+                             total=sum(numitems.values())) as progress:
+            for rev, path, newdata in results:
+                progress.increment(item=path)
+                if newdata is not None:
+                    filedata[rev][path] = newdata
+                numitems[rev] -= 1
+                # Apply the fixes for this and any other revisions that are
+                # ready and sitting at the front of the queue. Using a loop here
+                # prevents the queue from being blocked by the first revision to
+                # be ready out of order.
+                while commitorder and not numitems[commitorder[-1]]:
+                    rev = commitorder.pop()
+                    ctx = repo[rev]
+                    if rev == wdirrev:
+                        writeworkingdir(repo, ctx, filedata[rev], replacements)
+                    else:
+                        replacerev(ui, repo, ctx, filedata[rev], replacements)
+                    del filedata[rev]
 
         replacements = {prec: [succ] for prec, succ in replacements.iteritems()}
-        scmutil.cleanupnodes(repo, replacements, 'fix')
+        scmutil.cleanupnodes(repo, replacements, 'fix', fixphase=True)
 
 def getworkqueue(ui, repo, pats, opts, revstofix, basectxs):
     """"Constructs the list of files to be fixed at specific revisions
@@ -168,11 +193,19 @@
     topological order. Each work item represents a file in the working copy or
     in some revision that should be fixed and written back to the working copy
     or into a replacement revision.
+
+    Work items for the same revision are grouped together, so that a worker
+    pool starting with the first N items in parallel is likely to finish the
+    first revision's work before other revisions. This can allow us to write
+    the result to disk and reduce memory footprint. At time of writing, the
+    partition strategy in worker.py seems favorable to this. We also sort the
+    items by ascending revision number to match the order in which we commit
+    the fixes later.
     """
     workqueue = []
     numitems = collections.defaultdict(int)
     maxfilesize = ui.configbytes('fix', 'maxfilesize')
-    for rev in revstofix:
+    for rev in sorted(revstofix):
         fixctx = repo[rev]
         match = scmutil.match(fixctx, pats, opts)
         for path in pathstofix(ui, repo, pats, opts, match, basectxs[rev],
@@ -352,7 +385,9 @@
     """Returns a map of the base contexts for each revision
 
     The base contexts determine which lines are considered modified when we
-    attempt to fix just the modified lines in a file.
+    attempt to fix just the modified lines in a file. It also determines which
+    files we attempt to fix, so it is important to compute this even when
+    --whole is used.
     """
     # The --base flag overrides the usual logic, and we give every revision
     # exactly the set of baserevs that the user specified.
@@ -484,25 +519,23 @@
             isexec=fctx.isexec(),
             copied=copied)
 
-    overrides = {('phases', 'new-commit'): ctx.phase()}
-    with ui.configoverride(overrides, source='fix'):
-        memctx = context.memctx(
-            repo,
-            parents=(newp1node, newp2node),
-            text=ctx.description(),
-            files=set(ctx.files()) | set(filedata.keys()),
-            filectxfn=filectxfn,
-            user=ctx.user(),
-            date=ctx.date(),
-            extra=ctx.extra(),
-            branch=ctx.branch(),
-            editor=None)
-        sucnode = memctx.commit()
-        prenode = ctx.node()
-        if prenode == sucnode:
-            ui.debug('node %s already existed\n' % (ctx.hex()))
-        else:
-            replacements[ctx.node()] = sucnode
+    memctx = context.memctx(
+        repo,
+        parents=(newp1node, newp2node),
+        text=ctx.description(),
+        files=set(ctx.files()) | set(filedata.keys()),
+        filectxfn=filectxfn,
+        user=ctx.user(),
+        date=ctx.date(),
+        extra=ctx.extra(),
+        branch=ctx.branch(),
+        editor=None)
+    sucnode = memctx.commit()
+    prenode = ctx.node()
+    if prenode == sucnode:
+        ui.debug('node %s already existed\n' % (ctx.hex()))
+    else:
+        replacements[ctx.node()] = sucnode
 
 def getfixers(ui):
     """Returns a map of configured fixer tools indexed by their names
--- a/hgext/githelp.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/githelp.py	Thu Jul 19 13:55:54 2018 -0400
@@ -67,7 +67,7 @@
 
     cmd = args[0]
     if not cmd in gitcommands:
-        raise error.Abort("error: unknown git command %s" % (cmd))
+        raise error.Abort(_("error: unknown git command %s") % (cmd))
 
     ui.pager('githelp')
     args = args[1:]
@@ -90,14 +90,13 @@
             elif ('-' + ex.opt) in ex.msg:
                 flag = '-' + ex.opt
             else:
-                raise error.Abort("unknown option %s" % ex.opt)
+                raise error.Abort(_("unknown option %s") % ex.opt)
             try:
                 args.remove(flag)
             except Exception:
-                raise error.Abort(
-                    "unknown option {0} packed with other options\n"
-                    "Please try passing the option as it's own flag: -{0}" \
-                    .format(ex.opt))
+                msg = _("unknown option '%s' packed with other options")
+                hint = _("please try passing the option as its own flag: -%s")
+                raise error.Abort(msg % ex.opt, hint=hint % ex.opt)
 
             ui.warn(_("ignoring unknown option %s\n") % flag)
 
@@ -171,7 +170,7 @@
             cmd.extend(args)
         else:
             ui.status(_("note: use hg addremove to remove files that have "
-                        "been deleted.\n\n"))
+                        "been deleted\n\n"))
 
     ui.status((bytes(cmd)), "\n")
 
@@ -196,7 +195,7 @@
     ui.status((bytes(cmd)), "\n")
 
 def bisect(ui, repo, *args, **kwargs):
-    ui.status(_("See 'hg help bisect' for how to use bisect.\n\n"))
+    ui.status(_("see 'hg help bisect' for how to use bisect\n\n"))
 
 def blame(ui, repo, *args, **kwargs):
     cmdoptions = [
@@ -236,6 +235,8 @@
                 # shell command to output the active bookmark for the active
                 # revision
                 old = '`hg log -T"{activebookmark}" -r .`'
+        else:
+            raise error.Abort(_('missing newbranch argument'))
         new = args[0]
         cmd['-m'] = old
         cmd.append(new)
@@ -334,7 +335,7 @@
         cmd = Command('revert')
         cmd['--all'] = None
     else:
-        raise error.Abort("a commit must be specified")
+        raise error.Abort(_("a commit must be specified"))
 
     ui.status((bytes(cmd)), "\n")
 
@@ -353,7 +354,7 @@
     if opts.get('continue'):
         cmd['--continue'] = None
     elif opts.get('abort'):
-        ui.status(_("note: hg graft does not have --abort.\n\n"))
+        ui.status(_("note: hg graft does not have --abort\n\n"))
         return
     else:
         cmd.extend(args)
@@ -384,7 +385,7 @@
     args, opts = parseoptions(ui, cmdoptions, args)
 
     if len(args) == 0:
-        raise error.Abort("a repository to clone must be specified")
+        raise error.Abort(_("a repository to clone must be specified"))
 
     cmd = Command('clone')
     cmd.append(args[0])
@@ -393,8 +394,8 @@
 
     if opts.get('bare'):
         cmd['-U'] = None
-        ui.status(_("note: Mercurial does not have bare clones. " +
-            "-U will clone the repo without checking out a commit\n\n"))
+        ui.status(_("note: Mercurial does not have bare clones. "
+                    "-U will clone the repo without checking out a commit\n\n"))
     elif opts.get('no_checkout'):
         cmd['-U'] = None
 
@@ -436,9 +437,9 @@
         cmd['-m'] = "'%s'" % (opts.get('message'),)
 
     if opts.get('all'):
-        ui.status(_("note: Mercurial doesn't have a staging area, " +
-            "so there is no --all. -A will add and remove files " +
-            "for you though.\n\n"))
+        ui.status(_("note: Mercurial doesn't have a staging area, "
+                    "so there is no --all. -A will add and remove files "
+                    "for you though.\n\n"))
 
     if opts.get('file'):
         cmd['-l'] = opts.get('file')
@@ -454,8 +455,8 @@
     ui.status((bytes(cmd)), "\n")
 
 def deprecated(ui, repo, *args, **kwargs):
-    ui.warn(_('This command has been deprecated in the git project, ' +
-        'thus isn\'t supported by this tool.\n\n'))
+    ui.warn(_('this command has been deprecated in the git project, '
+              'thus isn\'t supported by this tool\n\n'))
 
 def diff(ui, repo, *args, **kwargs):
     cmdoptions = [
@@ -468,8 +469,8 @@
     cmd = Command('diff')
 
     if opts.get('cached'):
-        ui.status(_('note: Mercurial has no concept of a staging area, ' +
-            'so --cached does nothing.\n\n'))
+        ui.status(_('note: Mercurial has no concept of a staging area, '
+                    'so --cached does nothing\n\n'))
 
     if opts.get('reverse'):
         cmd['--reverse'] = None
@@ -505,10 +506,10 @@
     if len(args) > 0:
         cmd.append(args[0])
         if len(args) > 1:
-            ui.status(_("note: Mercurial doesn't have refspecs. " +
-                "-r can be used to specify which commits you want to pull. " +
-                "-B can be used to specify which bookmark you want to pull." +
-                "\n\n"))
+            ui.status(_("note: Mercurial doesn't have refspecs. "
+                        "-r can be used to specify which commits you want to "
+                        "pull. -B can be used to specify which bookmark you "
+                        "want to pull.\n\n"))
             for v in args[1:]:
                 if v in repo._bookmarks:
                     cmd['-B'] = v
@@ -556,10 +557,10 @@
         ('p', 'patch', None, ''),
     ]
     args, opts = parseoptions(ui, cmdoptions, args)
-    ui.status(_('note: -v prints the entire commit message like Git does. To ' +
-              'print just the first line, drop the -v.\n\n'))
-    ui.status(_("note: see hg help revset for information on how to filter " +
-        "log output.\n\n"))
+    ui.status(_('note: -v prints the entire commit message like Git does. To '
+                'print just the first line, drop the -v.\n\n'))
+    ui.status(_("note: see hg help revset for information on how to filter "
+                "log output\n\n"))
 
     cmd = Command('log')
     cmd['-v'] = None
@@ -578,13 +579,13 @@
     if opts.get('pretty') or opts.get('format') or opts.get('oneline'):
         format = opts.get('format', '')
         if 'format:' in format:
-            ui.status(_("note: --format format:??? equates to Mercurial's " +
-                "--template. See hg help templates for more info.\n\n"))
+            ui.status(_("note: --format format:??? equates to Mercurial's "
+                        "--template. See hg help templates for more info.\n\n"))
             cmd['--template'] = '???'
         else:
-            ui.status(_("note: --pretty/format/oneline equate to Mercurial's " +
-                "--style or --template. See hg help templates for more info." +
-                "\n\n"))
+            ui.status(_("note: --pretty/format/oneline equate to Mercurial's "
+                        "--style or --template. See hg help templates for "
+                        "more info.\n\n"))
             cmd['--style'] = '???'
 
     if len(args) > 0:
@@ -654,8 +655,8 @@
     cmd = Command("log -T '{node}\\n' -r 'ancestor(%s,%s)'"
                   % (args[0], args[1]))
 
-    ui.status(_('NOTE: ancestors() is part of the revset language.\n'),
-              _("Learn more about revsets with 'hg help revsets'\n\n"))
+    ui.status(_('note: ancestors() is part of the revset language\n'),
+              _("(learn more about revsets with 'hg help revsets')\n\n"))
     ui.status((bytes(cmd)), "\n")
 
 def mergetool(ui, repo, *args, **kwargs):
@@ -697,10 +698,10 @@
     if len(args) > 0:
         cmd.append(args[0])
         if len(args) > 1:
-            ui.status(_("note: Mercurial doesn't have refspecs. " +
-                "-r can be used to specify which commits you want to pull. " +
-                "-B can be used to specify which bookmark you want to pull." +
-                "\n\n"))
+            ui.status(_("note: Mercurial doesn't have refspecs. "
+                        "-r can be used to specify which commits you want to "
+                        "pull. -B can be used to specify which bookmark you "
+                        "want to pull.\n\n"))
             for v in args[1:]:
                 if v in repo._bookmarks:
                     cmd['-B'] = v
@@ -721,10 +722,10 @@
     if len(args) > 0:
         cmd.append(args[0])
         if len(args) > 1:
-            ui.status(_("note: Mercurial doesn't have refspecs. " +
-                "-r can be used to specify which commits you want to push. " +
-                "-B can be used to specify which bookmark you want to push." +
-                "\n\n"))
+            ui.status(_("note: Mercurial doesn't have refspecs. "
+                        "-r can be used to specify which commits you want "
+                        "to push. -B can be used to specify which bookmark "
+                        "you want to push.\n\n"))
             for v in args[1:]:
                 if v in repo._bookmarks:
                     cmd['-B'] = v
@@ -748,12 +749,12 @@
     args, opts = parseoptions(ui, cmdoptions, args)
 
     if opts.get('interactive'):
-        ui.status(_("note: hg histedit does not perform a rebase. " +
-            "It just edits history.\n\n"))
+        ui.status(_("note: hg histedit does not perform a rebase. "
+                    "It just edits history.\n\n"))
         cmd = Command('histedit')
         if len(args) > 0:
             ui.status(_("also note: 'hg histedit' will automatically detect"
-                      " your stack, so no second argument is necessary.\n\n"))
+                      " your stack, so no second argument is necessary\n\n"))
         ui.status((bytes(cmd)), "\n")
         return
 
@@ -769,12 +770,12 @@
         cmd['--abort'] = None
 
     if opts.get('onto'):
-        ui.status(_("note: if you're trying to lift a commit off one branch, " +
-            "try hg rebase -d <destination commit> -s <commit to be lifted>" +
-            "\n\n"))
+        ui.status(_("note: if you're trying to lift a commit off one branch, "
+                    "try hg rebase -d <destination commit> -s <commit to be "
+                    "lifted>\n\n"))
         cmd['-d'] = convert(opts.get('onto'))
         if len(args) < 2:
-            raise error.Abort("Expected format: git rebase --onto X Y Z")
+            raise error.Abort(_("expected format: git rebase --onto X Y Z"))
         cmd['-s'] = "'::%s - ::%s'" % (convert(args[1]), convert(args[0]))
     else:
         if len(args) == 1:
@@ -799,7 +800,7 @@
 
     ui.status(bytes(cmd), "\n\n")
     ui.status(_("note: in hg commits can be deleted from repo but we always"
-              " have backups.\n"))
+              " have backups\n"))
 
 def reset(ui, repo, *args, **kwargs):
     cmdoptions = [
@@ -813,10 +814,10 @@
     hard = opts.get('hard')
 
     if opts.get('mixed'):
-        ui.status(_('NOTE: --mixed has no meaning since Mercurial has no '
+        ui.status(_('note: --mixed has no meaning since Mercurial has no '
                     'staging area\n\n'))
     if opts.get('soft'):
-        ui.status(_('NOTE: --soft has no meaning since Mercurial has no '
+        ui.status(_('note: --soft has no meaning since Mercurial has no '
                     'staging area\n\n'))
 
     cmd = Command('update')
@@ -833,7 +834,7 @@
     args, opts = parseoptions(ui, cmdoptions, args)
 
     if len(args) > 1:
-        ui.status(_("note: hg backout doesn't support multiple commits at " +
+        ui.status(_("note: hg backout doesn't support multiple commits at "
                     "once\n\n"))
 
     cmd = Command('backout')
@@ -930,8 +931,8 @@
             cmd['--keep'] = None
     elif (action == 'branch' or action == 'show' or action == 'clear'
         or action == 'create'):
-        ui.status(_("note: Mercurial doesn't have equivalents to the " +
-            "git stash branch, show, clear, or create actions.\n\n"))
+        ui.status(_("note: Mercurial doesn't have equivalents to the "
+                    "git stash branch, show, clear, or create actions\n\n"))
         return
     else:
         if len(args) > 0:
@@ -957,9 +958,11 @@
     ui.status((bytes(cmd)), "\n")
 
 def svn(ui, repo, *args, **kwargs):
+    if not args:
+        raise error.Abort(_('missing svn command'))
     svncmd = args[0]
-    if not svncmd in gitsvncommands:
-        ui.warn(_("error: unknown git svn command %s\n") % (svncmd))
+    if svncmd not in gitsvncommands:
+        raise error.Abort(_('unknown git svn command "%s"') % (svncmd))
 
     args = args[1:]
     return gitsvncommands[svncmd](ui, repo, *args, **kwargs)
@@ -988,6 +991,9 @@
     ]
     args, opts = parseoptions(ui, cmdoptions, args)
 
+    if not args:
+        raise error.Abort(_('missing find-rev argument'))
+
     cmd = Command('log')
     cmd['-r'] = args[0]
 
@@ -1020,6 +1026,10 @@
         cmd = Command('tags')
     else:
         cmd = Command('tag')
+
+        if not args:
+            raise error.Abort(_('missing tag argument'))
+
         cmd.append(args[0])
         if len(args) > 1:
             cmd['-r'] = args[1]
--- a/hgext/gpg.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/gpg.py	Thu Jul 19 13:55:54 2018 -0400
@@ -9,7 +9,6 @@
 
 import binascii
 import os
-import tempfile
 
 from mercurial.i18n import _
 from mercurial import (
@@ -61,11 +60,11 @@
         sigfile = datafile = None
         try:
             # create temporary files
-            fd, sigfile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".sig")
+            fd, sigfile = pycompat.mkstemp(prefix="hg-gpg-", suffix=".sig")
             fp = os.fdopen(fd, r'wb')
             fp.write(sig)
             fp.close()
-            fd, datafile = tempfile.mkstemp(prefix="hg-gpg-", suffix=".txt")
+            fd, datafile = pycompat.mkstemp(prefix="hg-gpg-", suffix=".txt")
             fp = os.fdopen(fd, r'wb')
             fp.write(data)
             fp.close()
--- a/hgext/highlight/__init__.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/highlight/__init__.py	Thu Jul 19 13:55:54 2018 -0400
@@ -36,7 +36,6 @@
 
 from mercurial import (
     extensions,
-    fileset,
 )
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -51,9 +50,8 @@
     filenameonly = web.configbool('web', 'highlightonlymatchfilename', False)
 
     ctx = fctx.changectx()
-    tree = fileset.parse(expr)
-    mctx = fileset.matchctx(ctx, subset=[fctx.path()], status=None)
-    if fctx.path() in fileset.getset(mctx, tree):
+    m = ctx.matchfileset(expr)
+    if m(fctx.path()):
         highlight.pygmentize(field, fctx, style, tmpl,
                 guessfilenameonly=filenameonly)
 
--- a/hgext/highlight/highlight.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/highlight/highlight.py	Thu Jul 19 13:55:54 2018 -0400
@@ -11,7 +11,7 @@
 from __future__ import absolute_import
 
 from mercurial import demandimport
-demandimport.ignore.extend(['pkgutil', 'pkg_resources', '__main__'])
+demandimport.IGNORES.update(['pkgutil', 'pkg_resources', '__main__'])
 
 from mercurial import (
     encoding,
@@ -44,7 +44,8 @@
 def pygmentize(field, fctx, style, tmpl, guessfilenameonly=False):
 
     # append a <link ...> to the syntax highlighting css
-    old_header = tmpl.load('header')
+    tmpl.load('header')
+    old_header = tmpl.cache['header']
     if SYNTAX_CSS not in old_header:
         new_header = old_header + SYNTAX_CSS
         tmpl.cache['header'] = new_header
@@ -89,7 +90,7 @@
     coloriter = (s.encode(encoding.encoding, 'replace')
                  for s in colorized.splitlines())
 
-    tmpl.filters['colorize'] = lambda x: next(coloriter)
+    tmpl._filters['colorize'] = lambda x: next(coloriter)
 
     oldl = tmpl.cache[field]
     newl = oldl.replace('line|escape', 'line|colorize')
--- a/hgext/histedit.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/histedit.py	Thu Jul 19 13:55:54 2018 -0400
@@ -183,7 +183,6 @@
 
 from __future__ import absolute_import
 
-import errno
 import os
 
 from mercurial.i18n import _
@@ -207,6 +206,7 @@
     registrar,
     repair,
     scmutil,
+    state as statemod,
     util,
 )
 from mercurial.utils import (
@@ -304,6 +304,7 @@
         self.lock = lock
         self.wlock = wlock
         self.backupfile = None
+        self.stateobj = statemod.cmdstate(repo, 'histedit-state')
         if replacements is None:
             self.replacements = []
         else:
@@ -311,29 +312,33 @@
 
     def read(self):
         """Load histedit state from disk and set fields appropriately."""
-        try:
-            state = self.repo.vfs.read('histedit-state')
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
+        if not self.stateobj.exists():
             cmdutil.wrongtooltocontinue(self.repo, _('histedit'))
 
-        if state.startswith('v1\n'):
+        data = self._read()
+
+        self.parentctxnode = data['parentctxnode']
+        actions = parserules(data['rules'], self)
+        self.actions = actions
+        self.keep = data['keep']
+        self.topmost = data['topmost']
+        self.replacements = data['replacements']
+        self.backupfile = data['backupfile']
+
+    def _read(self):
+        fp = self.repo.vfs.read('histedit-state')
+        if fp.startswith('v1\n'):
             data = self._load()
             parentctxnode, rules, keep, topmost, replacements, backupfile = data
         else:
-            data = pickle.loads(state)
+            data = pickle.loads(fp)
             parentctxnode, rules, keep, topmost, replacements = data
             backupfile = None
+        rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
 
-        self.parentctxnode = parentctxnode
-        rules = "\n".join(["%s %s" % (verb, rest) for [verb, rest] in rules])
-        actions = parserules(rules, self)
-        self.actions = actions
-        self.keep = keep
-        self.topmost = topmost
-        self.replacements = replacements
-        self.backupfile = backupfile
+        return {'parentctxnode': parentctxnode, "rules": rules, "keep": keep,
+                "topmost": topmost, "replacements": replacements,
+                "backupfile": backupfile}
 
     def write(self, tr=None):
         if tr:
@@ -779,9 +784,7 @@
 
     def finishfold(self, ui, repo, ctx, oldctx, newnode, internalchanges):
         parent = ctx.parents()[0].node()
-        repo.ui.pushbuffer()
-        hg.update(repo, parent)
-        repo.ui.popbuffer()
+        hg.updaterepo(repo, parent, overwrite=False)
         ### prepare new commit data
         commitopts = {}
         commitopts['user'] = ctx.user()
@@ -812,9 +815,7 @@
                          skipprompt=self.skipprompt())
         if n is None:
             return ctx, []
-        repo.ui.pushbuffer()
-        hg.update(repo, n)
-        repo.ui.popbuffer()
+        hg.updaterepo(repo, n, overwrite=False)
         replacements = [(oldctx.node(), (newnode,)),
                         (ctx.node(), (n,)),
                         (newnode, (n,)),
@@ -1109,6 +1110,8 @@
     fm.startitem()
     goal = _getgoal(opts)
     revs = opts.get('rev', [])
+    # experimental config: ui.history-editing-backup
+    nobackup = not ui.configbool('ui', 'history-editing-backup')
     rules = opts.get('commands', '')
     state.keep = opts.get('keep', False)
 
@@ -1122,7 +1125,7 @@
         _edithisteditplan(ui, repo, state, rules)
         return
     elif goal == goalabort:
-        _aborthistedit(ui, repo, state)
+        _aborthistedit(ui, repo, state, nobackup=nobackup)
         return
     else:
         # goal == goalnew
@@ -1149,8 +1152,6 @@
     # even if there's an exception before the first transaction serialize.
     state.write()
 
-    total = len(state.actions)
-    pos = 0
     tr = None
     # Don't use singletransaction by default since it rolls the entire
     # transaction back if an unexpected exception happens (like a
@@ -1160,13 +1161,13 @@
         # and reopen a transaction. For example, if the action executes an
         # external process it may choose to commit the transaction first.
         tr = repo.transaction('histedit')
-    with util.acceptintervention(tr):
+    progress = ui.makeprogress(_("editing"), unit=_('changes'),
+                               total=len(state.actions))
+    with progress, util.acceptintervention(tr):
         while state.actions:
             state.write(tr=tr)
             actobj = state.actions[0]
-            pos += 1
-            ui.progress(_("editing"), pos, actobj.torule(),
-                        _('changes'), total)
+            progress.increment(item=actobj.torule())
             ui.debug('histedit: processing %s %s\n' % (actobj.verb,\
                                                        actobj.torule()))
             parentctx, replacement_ = actobj.run()
@@ -1175,13 +1176,10 @@
             state.actions.pop(0)
 
     state.write()
-    ui.progress(_("editing"), None)
 
 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)
-    repo.ui.popbuffer()
+    hg.updaterepo(repo, state.parentctxnode, overwrite=False)
 
     mapping, tmpnodes, created, ntm = processreplacement(state)
     if mapping:
@@ -1225,7 +1223,7 @@
     if repo.vfs.exists('histedit-last-edit.txt'):
         repo.vfs.unlink('histedit-last-edit.txt')
 
-def _aborthistedit(ui, repo, state):
+def _aborthistedit(ui, repo, state, nobackup=False):
     try:
         state.read()
         __, leafs, tmpnodes, __ = processreplacement(state)
@@ -1247,8 +1245,8 @@
         if repo.unfiltered().revs('parents() and (%n  or %ln::)',
                                 state.parentctxnode, leafs | tmpnodes):
             hg.clean(repo, state.topmost, show_stats=True, quietempty=True)
-        cleanupnode(ui, repo, tmpnodes)
-        cleanupnode(ui, repo, leafs)
+        cleanupnode(ui, repo, tmpnodes, nobackup=nobackup)
+        cleanupnode(ui, repo, leafs, nobackup=nobackup)
     except Exception:
         if state.inprogress():
             ui.warn(_('warning: encountered an exception during histedit '
@@ -1605,7 +1603,7 @@
                 changes.append((name, newtopmost))
             marks.applychanges(repo, tr, changes)
 
-def cleanupnode(ui, repo, nodes):
+def cleanupnode(ui, repo, nodes, nobackup=False):
     """strip a group of nodes from the repository
 
     The set of node to strip may contains unknown nodes."""
@@ -1620,7 +1618,8 @@
         nodes = sorted(n for n in nodes if n in nm)
         roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
         if roots:
-            repair.strip(ui, repo, roots)
+            backup = not nobackup
+            repair.strip(ui, repo, roots, backup=backup)
 
 def stripwrapper(orig, ui, repo, nodelist, *args, **kwargs):
     if isinstance(nodelist, str):
--- a/hgext/infinitepush/__init__.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/infinitepush/__init__.py	Thu Jul 19 13:55:54 2018 -0400
@@ -94,7 +94,6 @@
 import re
 import socket
 import subprocess
-import tempfile
 import time
 
 from mercurial.node import (
@@ -565,19 +564,19 @@
         if isinstance(localkey, str) and _scratchbranchmatcher(localkey):
             scratchnode = repo.bundlestore.index.getnode(localkey)
             if scratchnode:
-                return "%s %s\n" % (1, scratchnode)
+                return "%d %s\n" % (1, scratchnode)
             else:
-                return "%s %s\n" % (0, 'scratch branch %s not found' % localkey)
+                return "%d %s\n" % (0, 'scratch branch %s not found' % localkey)
         else:
             try:
                 r = hex(repo.lookup(localkey))
-                return "%s %s\n" % (1, r)
+                return "%d %s\n" % (1, r)
             except Exception as inst:
                 if repo.bundlestore.index.getbundle(localkey):
-                    return "%s %s\n" % (1, localkey)
+                    return "%d %s\n" % (1, localkey)
                 else:
-                    r = str(inst)
-                    return "%s %s\n" % (0, r)
+                    r = stringutil.forcebytestr(inst)
+                    return "%d %s\n" % (0, r)
     return _lookup
 
 def _pull(orig, ui, repo, source="default", **opts):
@@ -912,7 +911,7 @@
 
     # storing the bundle in the bundlestore
     buf = util.chunkbuffer(bundler.getchunks())
-    fd, bundlefile = tempfile.mkstemp()
+    fd, bundlefile = pycompat.mkstemp()
     try:
         try:
             fp = os.fdopen(fd, r'wb')
@@ -998,7 +997,7 @@
     # If commits were sent, store them
     if cgparams:
         buf = util.chunkbuffer(bundler.getchunks())
-        fd, bundlefile = tempfile.mkstemp()
+        fd, bundlefile = pycompat.mkstemp()
         try:
             try:
                 fp = os.fdopen(fd, r'wb')
@@ -1110,7 +1109,7 @@
     bundler.addpart(cgpart)
     buf = util.chunkbuffer(bundler.getchunks())
 
-    fd, bundlefile = tempfile.mkstemp()
+    fd, bundlefile = pycompat.mkstemp()
     try:
         try:
             fp = os.fdopen(fd, r'wb')
--- a/hgext/infinitepush/common.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/infinitepush/common.py	Thu Jul 19 13:55:54 2018 -0400
@@ -6,13 +6,13 @@
 from __future__ import absolute_import
 
 import os
-import tempfile
 
 from mercurial.node import hex
 
 from mercurial import (
     error,
     extensions,
+    pycompat,
 )
 
 def isremotebooksenabled(ui):
@@ -30,7 +30,7 @@
 
 def _makebundlefromraw(data):
     fp = None
-    fd, bundlefile = tempfile.mkstemp()
+    fd, bundlefile = pycompat.mkstemp()
     try:  # guards bundlefile
         try:  # guards fp
             fp = os.fdopen(fd, 'wb')
--- a/hgext/infinitepush/store.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/infinitepush/store.py	Thu Jul 19 13:55:54 2018 -0400
@@ -120,6 +120,8 @@
     def write(self, data):
         # Won't work on windows because you can't open file second time without
         # closing it
+        # TODO: rewrite without str.format() and replace NamedTemporaryFile()
+        # with pycompat.namedtempfile()
         with NamedTemporaryFile() as temp:
             temp.write(data)
             temp.flush()
@@ -142,6 +144,8 @@
     def read(self, handle):
         # Won't work on windows because you can't open file second time without
         # closing it
+        # TODO: rewrite without str.format() and replace NamedTemporaryFile()
+        # with pycompat.namedtempfile()
         with NamedTemporaryFile() as temp:
             formatted_args = [arg.format(filename=temp.name, handle=handle)
                               for arg in self.get_args]
--- a/hgext/keyword.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/keyword.py	Thu Jul 19 13:55:54 2018 -0400
@@ -87,7 +87,6 @@
 
 import os
 import re
-import tempfile
 import weakref
 
 from mercurial.i18n import _
@@ -246,7 +245,7 @@
     @util.propertycache
     def escape(self):
         '''Returns bar-separated and escaped keywords.'''
-        return '|'.join(map(re.escape, self.templates.keys()))
+        return '|'.join(map(stringutil.reescape, self.templates.keys()))
 
     @util.propertycache
     def rekw(self):
@@ -434,7 +433,7 @@
             ui.write('%s = %s\n' % (k, v))
 
     fn = 'demo.txt'
-    tmpdir = tempfile.mkdtemp('', 'kwdemo.')
+    tmpdir = pycompat.mkdtemp('', 'kwdemo.')
     ui.note(_('creating temporary repository at %s\n') % tmpdir)
     if repo is None:
         baseui = ui
--- a/hgext/largefiles/basestore.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/largefiles/basestore.py	Thu Jul 19 13:55:54 2018 -0400
@@ -62,9 +62,10 @@
 
         at = 0
         available = self.exists(set(hash for (_filename, hash) in files))
+        progress = ui.makeprogress(_('getting largefiles'), unit=_('files'),
+                                   total=len(files))
         for filename, hash in files:
-            ui.progress(_('getting largefiles'), at, unit=_('files'),
-                total=len(files))
+            progress.update(at)
             at += 1
             ui.note(_('getting %s:%s\n') % (filename, hash))
 
@@ -79,7 +80,7 @@
             else:
                 missing.append(filename)
 
-        ui.progress(_('getting largefiles'), None)
+        progress.complete()
         return (success, missing)
 
     def _gethash(self, filename, hash):
--- a/hgext/largefiles/lfcommands.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/largefiles/lfcommands.py	Thu Jul 19 13:55:54 2018 -0400
@@ -118,12 +118,14 @@
                 matcher = None
 
             lfiletohash = {}
+            progress = ui.makeprogress(_('converting revisions'),
+                                       unit=_('revisions'),
+                                       total=rsrc['tip'].rev())
             for ctx in ctxs:
-                ui.progress(_('converting revisions'), ctx.rev(),
-                    unit=_('revisions'), total=rsrc['tip'].rev())
+                progress.update(ctx.rev())
                 _lfconvert_addchangeset(rsrc, rdst, ctx, revmap,
                     lfiles, normalfiles, matcher, size, lfiletohash)
-            ui.progress(_('converting revisions'), None)
+            progress.complete()
 
             if rdst.wvfs.exists(lfutil.shortname):
                 rdst.wvfs.rmtree(lfutil.shortname)
@@ -368,9 +370,10 @@
     files = [h for h in files if not retval[h]]
     ui.debug("%d largefiles need to be uploaded\n" % len(files))
 
+    progress = ui.makeprogress(_('uploading largefiles'), unit=_('files'),
+                               total=len(files))
     for hash in files:
-        ui.progress(_('uploading largefiles'), at, unit=_('files'),
-                    total=len(files))
+        progress.update(at)
         source = lfutil.findfile(rsrc, hash)
         if not source:
             raise error.Abort(_('largefile %s missing from store'
@@ -378,7 +381,7 @@
         # XXX check for errors here
         store.put(source, hash)
         at += 1
-    ui.progress(_('uploading largefiles'), None)
+    progress.complete()
 
 def verifylfiles(ui, repo, all=False, contents=False):
     '''Verify that every largefile revision in the current changeset
--- a/hgext/largefiles/lfutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/largefiles/lfutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -501,9 +501,10 @@
     return filelist
 
 def getlfilestoupload(repo, missing, addfunc):
+    progress = repo.ui.makeprogress(_('finding outgoing largefiles'),
+                                    unit=_('revisions'), total=len(missing))
     for i, n in enumerate(missing):
-        repo.ui.progress(_('finding outgoing largefiles'), i,
-            unit=_('revisions'), total=len(missing))
+        progress.update(i)
         parents = [p for p in repo[n].parents() if p != node.nullid]
 
         oldlfstatus = repo.lfstatus
@@ -530,7 +531,7 @@
         for fn in files:
             if isstandin(fn) and fn in ctx:
                 addfunc(fn, readasstandin(ctx[fn]))
-    repo.ui.progress(_('finding outgoing largefiles'), None)
+    progress.complete()
 
 def updatestandinsbymatch(repo, match):
     '''Update standins in the working directory according to specified match
--- a/hgext/lfs/__init__.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/lfs/__init__.py	Thu Jul 19 13:55:54 2018 -0400
@@ -362,8 +362,10 @@
     """File that uses LFS storage."""
     # i18n: "lfs" is a keyword
     fileset.getargs(x, 0, 0, _("lfs takes no arguments"))
-    return [f for f in mctx.subset
-            if wrapper.pointerfromctx(mctx.ctx, f, removed=True) is not None]
+    ctx = mctx.ctx
+    def lfsfilep(f):
+        return wrapper.pointerfromctx(ctx, f, removed=True) is not None
+    return mctx.predicate(lfsfilep, predrepr='<lfs>')
 
 @templatekeyword('lfs_files', requires={'ctx'})
 def lfsfiles(context, mapping):
--- a/hgext/lfs/blobstore.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/lfs/blobstore.py	Thu Jul 19 13:55:54 2018 -0400
@@ -405,7 +405,8 @@
         if len(objects) > 1:
             self.ui.note(_('lfs: need to transfer %d objects (%s)\n')
                          % (len(objects), util.bytecount(total)))
-        self.ui.progress(topic, 0, total=total)
+        progress = self.ui.makeprogress(topic, total=total)
+        progress.update(0)
         def transfer(chunk):
             for obj in chunk:
                 objsize = obj.get('size', 0)
@@ -443,9 +444,9 @@
         for _one, oid in oids:
             processed += sizes[oid]
             blobs += 1
-            self.ui.progress(topic, processed, total=total)
+            progress.update(processed)
             self.ui.note(_('lfs: processed: %s\n') % oid)
-        self.ui.progress(topic, pos=None, total=total)
+        progress.complete()
 
         if blobs > 0:
             if action == 'upload':
--- a/hgext/lfs/pointer.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/lfs/pointer.py	Thu Jul 19 13:55:54 2018 -0400
@@ -15,6 +15,9 @@
     error,
     pycompat,
 )
+from mercurial.utils import (
+    stringutil,
+)
 
 class InvalidPointer(error.RevlogError):
     pass
@@ -32,7 +35,8 @@
         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)
+            raise InvalidPointer(_('cannot parse git-lfs text: %s')
+                                 % stringutil.pprint(text))
 
     def serialize(self):
         sortkeyfunc = lambda x: (x[0] != 'version', x)
@@ -52,7 +56,7 @@
     _requiredre = {
         'size': re.compile(br'\A[0-9]+\Z'),
         'oid': re.compile(br'\Asha256:[0-9a-f]{64}\Z'),
-        'version': re.compile(br'\A%s\Z' % re.escape(VERSION)),
+        'version': re.compile(br'\A%s\Z' % stringutil.reescape(VERSION)),
     }
 
     def validate(self):
@@ -61,15 +65,19 @@
         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))
+                    raise InvalidPointer(
+                        _('unexpected lfs pointer value: %s=%s')
+                        % (k, stringutil.pprint(v)))
                 requiredcount += 1
             elif not self._keyre.match(k):
-                raise InvalidPointer(_('unexpected key: %s') % k)
+                raise InvalidPointer(_('unexpected lfs pointer key: %s') % k)
             if not self._valuere.match(v):
-                raise InvalidPointer(_('unexpected value: %s=%r') % (k, v))
+                raise InvalidPointer(_('unexpected lfs pointer value: %s=%s')
+                                     % (k, stringutil.pprint(v)))
         if len(self._requiredre) != requiredcount:
             miss = sorted(set(self._requiredre.keys()).difference(self.keys()))
-            raise InvalidPointer(_('missed keys: %s') % ', '.join(miss))
+            raise InvalidPointer(_('missing lfs pointer keys: %s')
+                                 % ', '.join(miss))
         return self
 
 deserialize = gitlfspointer.deserialize
--- a/hgext/mq.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/mq.py	Thu Jul 19 13:55:54 2018 -0400
@@ -492,7 +492,8 @@
                     n, name = entry
                     yield statusentry(bin(n), name)
                 elif l.strip():
-                    self.ui.warn(_('malformated mq status line: %s\n') % entry)
+                    self.ui.warn(_('malformated mq status line: %s\n') %
+                                 stringutil.pprint(entry))
                 # else we ignore empty lines
         try:
             lines = self.opener.read(self.statuspath).splitlines()
@@ -2872,7 +2873,7 @@
     patch = None
     args = list(args)
     if opts.get(r'list'):
-        if args or opts.get('none'):
+        if args or opts.get(r'none'):
             raise error.Abort(_('cannot mix -l/--list with options or '
                                'arguments'))
         for i in xrange(len(q.series)):
@@ -2886,7 +2887,7 @@
         patch = args.pop(0)
     if patch is None:
         raise error.Abort(_('no patch to work with'))
-    if args or opts.get('none'):
+    if args or opts.get(r'none'):
         idx = q.findseries(patch)
         if idx is None:
             raise error.Abort(_('no patch named %s') % patch)
--- a/hgext/narrow/__init__.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/narrow/__init__.py	Thu Jul 19 13:55:54 2018 -0400
@@ -28,8 +28,6 @@
     narrowchangegroup,
     narrowcommands,
     narrowcopies,
-    narrowdirstate,
-    narrowmerge,
     narrowpatch,
     narrowrepo,
     narrowrevlog,
@@ -64,7 +62,6 @@
     localrepo.featuresetupfuncs.add(featuresetup)
     narrowrevlog.setup()
     narrowbundle2.setup()
-    narrowmerge.setup()
     narrowcommands.setup()
     narrowchangegroup.setup()
     narrowwirepeer.uisetup()
@@ -74,10 +71,9 @@
     if not repo.local():
         return
 
-    narrowrepo.wraprepo(repo)
     if changegroup.NARROW_REQUIREMENT in repo.requirements:
+        narrowrepo.wraprepo(repo)
         narrowcopies.setup(repo)
-        narrowdirstate.setup(repo)
         narrowpatch.setup(repo)
         narrowwirepeer.reposetup(repo)
 
--- a/hgext/narrow/narrowbundle2.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/narrow/narrowbundle2.py	Thu Jul 19 13:55:54 2018 -0400
@@ -408,6 +408,8 @@
                                  topic='widen')
         repo._bookmarks = bmstore
         if chgrpfile:
+            op._widen_uninterr = repo.ui.uninterruptable()
+            op._widen_uninterr.__enter__()
             # presence of _widen_bundle attribute activates widen handler later
             op._widen_bundle = chgrpfile
     # Set the new narrowspec if we're widening. The setnewnarrowpats() method
@@ -455,6 +457,7 @@
                         (undovfs.join(undofile), stringutil.forcebytestr(e)))
 
     # Remove partial backup only if there were no exceptions
+    op._widen_uninterr.__exit__(None, None, None)
     vfs.unlink(chgrpfile)
 
 def setup():
--- a/hgext/narrow/narrowcommands.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/narrow/narrowcommands.py	Thu Jul 19 13:55:54 2018 -0400
@@ -203,50 +203,51 @@
                               hint=_('use --force-delete-local-changes to '
                                      'ignore'))
 
-    if revstostrip:
-        tostrip = [unfi.changelog.node(r) for r in revstostrip]
-        if repo['.'].node() in tostrip:
-            # stripping working copy, so move to a different commit first
-            urev = max(repo.revs('(::%n) - %ln + null',
-                                 repo['.'].node(), visibletostrip))
-            hg.clean(repo, urev)
-        repair.strip(ui, unfi, tostrip, topic='narrow')
+    with ui.uninterruptable():
+        if revstostrip:
+            tostrip = [unfi.changelog.node(r) for r in revstostrip]
+            if repo['.'].node() in tostrip:
+                # stripping working copy, so move to a different commit first
+                urev = max(repo.revs('(::%n) - %ln + null',
+                                     repo['.'].node(), visibletostrip))
+                hg.clean(repo, urev)
+            repair.strip(ui, unfi, tostrip, topic='narrow')
 
-    todelete = []
-    for f, f2, size in repo.store.datafiles():
-        if f.startswith('data/'):
-            file = f[5:-2]
-            if not newmatch(file):
-                todelete.append(f)
-        elif f.startswith('meta/'):
-            dir = f[5:-13]
-            dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
-            include = True
-            for d in dirs:
-                visit = newmatch.visitdir(d)
-                if not visit:
-                    include = False
-                    break
-                if visit == 'all':
-                    break
-            if not include:
-                todelete.append(f)
+        todelete = []
+        for f, f2, size in repo.store.datafiles():
+            if f.startswith('data/'):
+                file = f[5:-2]
+                if not newmatch(file):
+                    todelete.append(f)
+            elif f.startswith('meta/'):
+                dir = f[5:-13]
+                dirs = ['.'] + sorted(util.dirs({dir})) + [dir]
+                include = True
+                for d in dirs:
+                    visit = newmatch.visitdir(d)
+                    if not visit:
+                        include = False
+                        break
+                    if visit == 'all':
+                        break
+                if not include:
+                    todelete.append(f)
 
-    repo.destroying()
+        repo.destroying()
 
-    with repo.transaction("narrowing"):
-        for f in todelete:
-            ui.status(_('deleting %s\n') % f)
-            util.unlinkpath(repo.svfs.join(f))
-            repo.store.markremoved(f)
+        with repo.transaction("narrowing"):
+            for f in todelete:
+                ui.status(_('deleting %s\n') % f)
+                util.unlinkpath(repo.svfs.join(f))
+                repo.store.markremoved(f)
 
-        for f in repo.dirstate:
-            if not newmatch(f):
-                repo.dirstate.drop(f)
-                repo.wvfs.unlinkpath(f)
-        repo.setnarrowpats(newincludes, newexcludes)
+            for f in repo.dirstate:
+                if not newmatch(f):
+                    repo.dirstate.drop(f)
+                    repo.wvfs.unlinkpath(f)
+            repo.setnarrowpats(newincludes, newexcludes)
 
-    repo.destroyed()
+        repo.destroyed()
 
 def _widen(ui, repo, remote, commoninc, newincludes, newexcludes):
     newmatch = narrowspec.match(repo.root, newincludes, newexcludes)
@@ -269,28 +270,29 @@
         repo.setnarrowpats(newincludes, newexcludes)
     repo.setnewnarrowpats = setnewnarrowpats
 
-    ds = repo.dirstate
-    p1, p2 = ds.p1(), ds.p2()
-    with ds.parentchange():
-        ds.setparents(node.nullid, node.nullid)
-    common = commoninc[0]
-    with wrappedextraprepare:
-        exchange.pull(repo, remote, heads=common)
-    with ds.parentchange():
-        ds.setparents(p1, p2)
+    with ui.uninterruptable():
+        ds = repo.dirstate
+        p1, p2 = ds.p1(), ds.p2()
+        with ds.parentchange():
+            ds.setparents(node.nullid, node.nullid)
+        common = commoninc[0]
+        with wrappedextraprepare:
+            exchange.pull(repo, remote, heads=common)
+        with ds.parentchange():
+            ds.setparents(p1, p2)
 
-    actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
-    addgaction = actions['g'].append
+        actions = {k: [] for k in 'a am f g cd dc r dm dg m e k p pr'.split()}
+        addgaction = actions['g'].append
 
-    mf = repo['.'].manifest().matches(newmatch)
-    for f, fn in mf.iteritems():
-        if f not in repo.dirstate:
-            addgaction((f, (mf.flags(f), False),
-                        "add from widened narrow clone"))
+        mf = repo['.'].manifest().matches(newmatch)
+        for f, fn in mf.iteritems():
+            if f not in repo.dirstate:
+                addgaction((f, (mf.flags(f), False),
+                            "add from widened narrow clone"))
 
-    merge.applyupdates(repo, actions, wctx=repo[None],
-                       mctx=repo['.'], overwrite=False)
-    merge.recordupdates(repo, actions, branchmerge=False)
+        merge.applyupdates(repo, actions, wctx=repo[None],
+                           mctx=repo['.'], overwrite=False)
+        merge.recordupdates(repo, actions, branchmerge=False)
 
 # TODO(rdamazio): Make new matcher format and update description
 @command('tracked',
--- a/hgext/narrow/narrowdirstate.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/narrow/narrowdirstate.py	Thu Jul 19 13:55:54 2018 -0400
@@ -9,74 +9,91 @@
 
 from mercurial.i18n import _
 from mercurial import (
-    dirstate,
     error,
-    extensions,
     match as matchmod,
     narrowspec,
     util as hgutil,
 )
 
-def setup(repo):
+def wrapdirstate(repo, dirstate):
     """Add narrow spec dirstate ignore, block changes outside narrow spec."""
 
-    def walk(orig, self, match, subrepos, unknown, ignored, full=True,
-             narrowonly=True):
-        if narrowonly:
-            # hack to not exclude explicitly-specified paths so that they can
-            # be warned later on e.g. dirstate.add()
-            em = matchmod.exact(match._root, match._cwd, match.files())
-            nm = matchmod.unionmatcher([repo.narrowmatch(), em])
-            match = matchmod.intersectmatchers(match, nm)
-        return orig(self, match, subrepos, unknown, ignored, full)
-
-    extensions.wrapfunction(dirstate.dirstate, 'walk', walk)
-
-    # Prevent adding files that are outside the sparse checkout
-    editfuncs = ['normal', 'add', 'normallookup', 'copy', 'remove', 'merge']
-    for func in editfuncs:
-        def _wrapper(orig, self, *args):
+    def _editfunc(fn):
+        def _wrapper(self, *args):
             dirstate = repo.dirstate
             narrowmatch = repo.narrowmatch()
             for f in args:
                 if f is not None and not narrowmatch(f) and f not in dirstate:
                     raise error.Abort(_("cannot track '%s' - it is outside " +
                         "the narrow clone") % f)
-            return orig(self, *args)
-        extensions.wrapfunction(dirstate.dirstate, func, _wrapper)
-
-    def filterrebuild(orig, self, parent, allfiles, changedfiles=None):
-        if changedfiles is None:
-            # Rebuilding entire dirstate, let's filter allfiles to match the
-            # narrowspec.
-            allfiles = [f for f in allfiles if repo.narrowmatch()(f)]
-        orig(self, parent, allfiles, changedfiles)
-
-    extensions.wrapfunction(dirstate.dirstate, 'rebuild', filterrebuild)
+            return fn(self, *args)
+        return _wrapper
 
     def _narrowbackupname(backupname):
         assert 'dirstate' in backupname
         return backupname.replace('dirstate', narrowspec.FILENAME)
 
-    def restorebackup(orig, self, tr, backupname):
-        self._opener.rename(_narrowbackupname(backupname), narrowspec.FILENAME,
-                            checkambig=True)
-        orig(self, tr, backupname)
+    class narrowdirstate(dirstate.__class__):
+        def walk(self, match, subrepos, unknown, ignored, full=True,
+                 narrowonly=True):
+            if narrowonly:
+                # hack to not exclude explicitly-specified paths so that they
+                # can be warned later on e.g. dirstate.add()
+                em = matchmod.exact(match._root, match._cwd, match.files())
+                nm = matchmod.unionmatcher([repo.narrowmatch(), em])
+                match = matchmod.intersectmatchers(match, nm)
+            return super(narrowdirstate, self).walk(match, subrepos, unknown,
+                                                    ignored, full)
 
-    extensions.wrapfunction(dirstate.dirstate, 'restorebackup', restorebackup)
+        # Prevent adding/editing/copying/deleting files that are outside the
+        # sparse checkout
+        @_editfunc
+        def normal(self, *args):
+            return super(narrowdirstate, self).normal(*args)
 
-    def savebackup(orig, self, tr, backupname):
-        orig(self, tr, backupname)
+        @_editfunc
+        def add(self, *args):
+            return super(narrowdirstate, self).add(*args)
+
+        @_editfunc
+        def normallookup(self, *args):
+            return super(narrowdirstate, self).normallookup(*args)
+
+        @_editfunc
+        def copy(self, *args):
+            return super(narrowdirstate, self).copy(*args)
 
-        narrowbackupname = _narrowbackupname(backupname)
-        self._opener.tryunlink(narrowbackupname)
-        hgutil.copyfile(self._opener.join(narrowspec.FILENAME),
-                        self._opener.join(narrowbackupname), hardlink=True)
+        @_editfunc
+        def remove(self, *args):
+            return super(narrowdirstate, self).remove(*args)
+
+        @_editfunc
+        def merge(self, *args):
+            return super(narrowdirstate, self).merge(*args)
+
+        def rebuild(self, parent, allfiles, changedfiles=None):
+            if changedfiles is None:
+                # Rebuilding entire dirstate, let's filter allfiles to match the
+                # narrowspec.
+                allfiles = [f for f in allfiles if repo.narrowmatch()(f)]
+            super(narrowdirstate, self).rebuild(parent, allfiles, changedfiles)
 
-    extensions.wrapfunction(dirstate.dirstate, 'savebackup', savebackup)
+        def restorebackup(self, tr, backupname):
+            self._opener.rename(_narrowbackupname(backupname),
+                                narrowspec.FILENAME, checkambig=True)
+            super(narrowdirstate, self).restorebackup(tr, backupname)
+
+        def savebackup(self, tr, backupname):
+            super(narrowdirstate, self).savebackup(tr, backupname)
 
-    def clearbackup(orig, self, tr, backupname):
-        orig(self, tr, backupname)
-        self._opener.unlink(_narrowbackupname(backupname))
+            narrowbackupname = _narrowbackupname(backupname)
+            self._opener.tryunlink(narrowbackupname)
+            hgutil.copyfile(self._opener.join(narrowspec.FILENAME),
+                            self._opener.join(narrowbackupname), hardlink=True)
 
-    extensions.wrapfunction(dirstate.dirstate, 'clearbackup', clearbackup)
+        def clearbackup(self, tr, backupname):
+            super(narrowdirstate, self).clearbackup(tr, backupname)
+            self._opener.unlink(_narrowbackupname(backupname))
+
+    dirstate.__class__ = narrowdirstate
+    return dirstate
--- a/hgext/narrow/narrowmerge.py	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-# narrowmerge.py - extensions to mercurial merge module to support narrow clones
-#
-# Copyright 2017 Google, Inc.
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from mercurial.i18n import _
-from mercurial import (
-    copies,
-    error,
-    extensions,
-    merge,
-)
-
-def setup():
-    def _manifestmerge(orig, repo, wctx, p2, pa, branchmerge, *args, **kwargs):
-        """Filter updates to only lay out files that match the narrow spec."""
-        actions, diverge, renamedelete = orig(
-            repo, wctx, p2, pa, branchmerge, *args, **kwargs)
-
-        narrowmatch = repo.narrowmatch()
-        if narrowmatch.always():
-            return actions, diverge, renamedelete
-
-        nooptypes = set(['k']) # TODO: handle with nonconflicttypes
-        nonconflicttypes = set('a am c cm f g r e'.split())
-        # We mutate the items in the dict during iteration, so iterate
-        # over a copy.
-        for f, action in list(actions.items()):
-            if narrowmatch(f):
-                pass
-            elif not branchmerge:
-                del actions[f] # just updating, ignore changes outside clone
-            elif action[0] in nooptypes:
-                del actions[f] # merge does not affect file
-            elif action[0] in nonconflicttypes:
-                raise error.Abort(_('merge affects file \'%s\' outside narrow, '
-                                    'which is not yet supported') % f,
-                                  hint=_('merging in the other direction '
-                                         'may work'))
-            else:
-                raise error.Abort(_('conflict in file \'%s\' is outside '
-                                    'narrow clone') % f)
-
-        return actions, diverge, renamedelete
-
-    extensions.wrapfunction(merge, 'manifestmerge', _manifestmerge)
-
-    def _checkcollision(orig, repo, wmf, actions):
-        narrowmatch = repo.narrowmatch()
-        if not narrowmatch.always():
-            wmf = wmf.matches(narrowmatch)
-            if actions:
-                narrowactions = {}
-                for m, actionsfortype in actions.iteritems():
-                    narrowactions[m] = []
-                    for (f, args, msg) in actionsfortype:
-                        if narrowmatch(f):
-                            narrowactions[m].append((f, args, msg))
-                actions = narrowactions
-        return orig(repo, wmf, actions)
-
-    extensions.wrapfunction(merge, '_checkcollision', _checkcollision)
-
-    def _computenonoverlap(orig, repo, *args, **kwargs):
-        u1, u2 = orig(repo, *args, **kwargs)
-        narrowmatch = repo.narrowmatch()
-        if narrowmatch.always():
-            return u1, u2
-
-        u1 = [f for f in u1 if narrowmatch(f)]
-        u2 = [f for f in u2 if narrowmatch(f)]
-        return u1, u2
-    extensions.wrapfunction(copies, '_computenonoverlap', _computenonoverlap)
--- a/hgext/narrow/narrowrepo.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/narrow/narrowrepo.py	Thu Jul 19 13:55:54 2018 -0400
@@ -15,6 +15,7 @@
 )
 
 from . import (
+    narrowdirstate,
     narrowrevlog,
 )
 
@@ -62,4 +63,8 @@
             return scmutil.status(modified, added, removed, deleted, unknown,
                                   ignored, clean)
 
+        def _makedirstate(self):
+            dirstate = super(narrowrepository, self)._makedirstate()
+            return narrowdirstate.wrapdirstate(self, dirstate)
+
     repo.__class__ = narrowrepository
--- a/hgext/notify.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/notify.py	Thu Jul 19 13:55:54 2018 -0400
@@ -113,6 +113,9 @@
 notify.diffstat
   Set to True to include a diffstat before diff content. Default: True.
 
+notify.showfunc
+  If set, override ``diff.showfunc`` for the diff content. Default: None.
+
 notify.merge
   If True, send notifications for merge changesets. Default: True.
 
@@ -206,6 +209,9 @@
 configitem('notify', 'sources',
     default='serve',
 )
+configitem('notify', 'showfunc',
+    default=None,
+)
 configitem('notify', 'strip',
     default=0,
 )
@@ -260,6 +266,9 @@
         self.charsets = mail._charsets(self.ui)
         self.subs = self.subscribers()
         self.merge = self.ui.configbool('notify', 'merge')
+        self.showfunc = self.ui.configbool('notify', 'showfunc')
+        if self.showfunc is None:
+            self.showfunc = self.ui.configbool('diff', 'showfunc')
 
         mapfile = None
         template = (self.ui.config('notify', hooktype) or
@@ -420,8 +429,9 @@
             ref = ref.node()
         else:
             ref = ctx.node()
-        chunks = patch.diff(self.repo, prev, ref,
-                            opts=patch.diffallopts(self.ui))
+        diffopts = patch.diffallopts(self.ui)
+        diffopts.showfunc = self.showfunc
+        chunks = patch.diff(self.repo, prev, ref, opts=diffopts)
         difflines = ''.join(chunks).splitlines()
 
         if self.ui.configbool('notify', 'diffstat'):
--- a/hgext/patchbomb.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/patchbomb.py	Thu Jul 19 13:55:54 2018 -0400
@@ -75,11 +75,12 @@
 
 import email as emailmod
 import email.generator as emailgen
+import email.mime.base as emimebase
+import email.mime.multipart as emimemultipart
 import email.utils as eutil
 import errno
 import os
 import socket
-import tempfile
 
 from mercurial.i18n import _
 from mercurial import (
@@ -94,7 +95,6 @@
     patch,
     pycompat,
     registrar,
-    repair,
     scmutil,
     templater,
     util,
@@ -256,7 +256,7 @@
         body += '\n'.join(patchlines)
 
     if addattachment:
-        msg = emailmod.MIMEMultipart.MIMEMultipart()
+        msg = emimemultipart.MIMEMultipart()
         if body:
             msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
         p = mail.mimetextpatch('\n'.join(patchlines), 'x-patch',
@@ -318,7 +318,7 @@
     The bundle is a returned as a single in-memory binary blob.
     """
     ui = repo.ui
-    tmpdir = tempfile.mkdtemp(prefix='hg-email-bundle-')
+    tmpdir = pycompat.mkdtemp(prefix='hg-email-bundle-')
     tmpfn = os.path.join(tmpdir, 'bundle')
     btype = ui.config('patchbomb', 'bundletype')
     if btype:
@@ -367,10 +367,10 @@
             or prompt(ui, 'Subject:', 'A bundle for your repository'))
 
     body = _getdescription(repo, '', sender, **opts)
-    msg = emailmod.MIMEMultipart.MIMEMultipart()
+    msg = emimemultipart.MIMEMultipart()
     if body:
         msg.attach(mail.mimeencode(ui, body, _charsets, opts.get(r'test')))
-    datapart = emailmod.MIMEBase.MIMEBase('application', 'x-mercurial-bundle')
+    datapart = emimebase.MIMEBase('application', 'x-mercurial-bundle')
     datapart.set_payload(bundle)
     bundlename = '%s.hg' % opts.get(r'bundlename', 'bundle')
     datapart.add_header('Content-Disposition', 'attachment',
@@ -624,7 +624,7 @@
     elif bookmark:
         if bookmark not in repo._bookmarks:
             raise error.Abort(_("bookmark '%s' not found") % bookmark)
-        revs = repair.stripbmrevset(repo, bookmark)
+        revs = scmutil.bookmarkrevs(repo, bookmark)
 
     revs = scmutil.revrange(repo, revs)
     if outgoing:
@@ -753,6 +753,7 @@
     sender = mail.addressencode(ui, sender, _charsets, opts.get('test'))
     sendmail = None
     firstpatch = None
+    progress = ui.makeprogress(_('sending'), unit=_('emails'), total=len(msgs))
     for i, (m, subj, ds) in enumerate(msgs):
         try:
             m['Message-Id'] = genmsgid(m['X-Mercurial-Node'])
@@ -793,8 +794,7 @@
             if not sendmail:
                 sendmail = mail.connect(ui, mbox=mbox)
             ui.status(_('sending '), subj, ' ...\n')
-            ui.progress(_('sending'), i, item=subj, total=len(msgs),
-                        unit=_('emails'))
+            progress.update(i, item=subj)
             if not mbox:
                 # Exim does not remove the Bcc field
                 del m['Bcc']
@@ -803,5 +803,4 @@
             generator.flatten(m, 0)
             sendmail(sender_addr, to + bcc + cc, fp.getvalue())
 
-    ui.progress(_('writing'), None)
-    ui.progress(_('sending'), None)
+    progress.complete()
--- a/hgext/rebase.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/rebase.py	Thu Jul 19 13:55:54 2018 -0400
@@ -34,7 +34,6 @@
     error,
     extensions,
     hg,
-    lock,
     merge as mergemod,
     mergeutil,
     obsolete,
@@ -48,11 +47,10 @@
     revsetlang,
     scmutil,
     smartset,
+    state as statemod,
     util,
 )
 
-release = lock.release
-
 # The following constants are used throughout the rebase module. The ordering of
 # their values must be maintained.
 
@@ -184,6 +182,7 @@
         self.obsoletenotrebased = {}
         self.obsoletewithoutsuccessorindestination = set()
         self.inmemory = inmemory
+        self.stateobj = statemod.cmdstate(repo, 'rebasestate')
 
     @property
     def repo(self):
@@ -225,40 +224,55 @@
 
     def restorestatus(self):
         """Restore a previously stored status"""
+        if not self.stateobj.exists():
+            cmdutil.wrongtooltocontinue(self.repo, _('rebase'))
+
+        data = self._read()
+        self.repo.ui.debug('rebase status resumed\n')
+
+        self.originalwd = data['originalwd']
+        self.destmap = data['destmap']
+        self.state = data['state']
+        self.skipped = data['skipped']
+        self.collapsef = data['collapse']
+        self.keepf = data['keep']
+        self.keepbranchesf = data['keepbranches']
+        self.external = data['external']
+        self.activebookmark = data['activebookmark']
+
+    def _read(self):
         self.prepared = True
         repo = self.repo
         assert repo.filtername is None
-        keepbranches = None
+        data = {'keepbranches': None, 'collapse': None, 'activebookmark': None,
+                'external': nullrev, 'keep': None, 'originalwd': None}
         legacydest = None
-        collapse = False
-        external = nullrev
-        activebookmark = None
         state = {}
         destmap = {}
 
-        try:
+        if True:
             f = repo.vfs("rebasestate")
             for i, l in enumerate(f.read().splitlines()):
                 if i == 0:
-                    originalwd = repo[l].rev()
+                    data['originalwd'] = repo[l].rev()
                 elif i == 1:
                     # this line should be empty in newer version. but legacy
                     # clients may still use it
                     if l:
                         legacydest = repo[l].rev()
                 elif i == 2:
-                    external = repo[l].rev()
+                    data['external'] = repo[l].rev()
                 elif i == 3:
-                    collapse = bool(int(l))
+                    data['collapse'] = bool(int(l))
                 elif i == 4:
-                    keep = bool(int(l))
+                    data['keep'] = bool(int(l))
                 elif i == 5:
-                    keepbranches = bool(int(l))
+                    data['keepbranches'] = bool(int(l))
                 elif i == 6 and not (len(l) == 81 and ':' in l):
                     # line 6 is a recent addition, so for backwards
                     # compatibility check that the line doesn't look like the
                     # oldrev:newrev lines
-                    activebookmark = l
+                    data['activebookmark'] = l
                 else:
                     args = l.split(':')
                     oldrev = repo[args[0]].rev()
@@ -276,35 +290,24 @@
                     else:
                         state[oldrev] = repo[newrev].rev()
 
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-            cmdutil.wrongtooltocontinue(repo, _('rebase'))
-
-        if keepbranches is None:
+        if data['keepbranches'] is None:
             raise error.Abort(_('.hg/rebasestate is incomplete'))
 
+        data['destmap'] = destmap
+        data['state'] = state
         skipped = set()
         # recompute the set of skipped revs
-        if not collapse:
+        if not data['collapse']:
             seen = set(destmap.values())
             for old, new in sorted(state.items()):
                 if new != revtodo and new in seen:
                     skipped.add(old)
                 seen.add(new)
+        data['skipped'] = skipped
         repo.ui.debug('computed skipped revs: %s\n' %
                         (' '.join('%d' % r for r in sorted(skipped)) or ''))
-        repo.ui.debug('rebase status resumed\n')
 
-        self.originalwd = originalwd
-        self.destmap = destmap
-        self.state = state
-        self.skipped = skipped
-        self.collapsef = collapse
-        self.keepf = keep
-        self.keepbranchesf = keepbranches
-        self.external = external
-        self.activebookmark = activebookmark
+        return data
 
     def _handleskippingobsolete(self, obsoleterevs, destmap):
         """Compute structures necessary for skipping obsolete revisions
@@ -325,7 +328,7 @@
         skippedset.update(obsoleteextinctsuccessors)
         _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
 
-    def _prepareabortorcontinue(self, isabort):
+    def _prepareabortorcontinue(self, isabort, backup=True, suppwarns=False):
         try:
             self.restorestatus()
             self.collapsemsg = restorecollapsemsg(self.repo, isabort)
@@ -341,8 +344,9 @@
                 hint = _('use "hg rebase --abort" to clear broken state')
                 raise error.Abort(msg, hint=hint)
         if isabort:
-            return abort(self.repo, self.originalwd, self.destmap,
-                         self.state, activebookmark=self.activebookmark)
+            return abort(self.repo, self.originalwd, self.destmap, self.state,
+                         activebookmark=self.activebookmark, backup=backup,
+                         suppwarns=suppwarns)
 
     def _preparenewrebase(self, destmap):
         if not destmap:
@@ -433,13 +437,10 @@
             self.storestatus(tr)
 
         cands = [k for k, v in self.state.iteritems() if v == revtodo]
-        total = len(cands)
-        posholder = [0]
+        p = repo.ui.makeprogress(_("rebasing"), unit=_('changesets'),
+                                 total=len(cands))
         def progress(ctx):
-            posholder[0] += 1
-            self.repo.ui.progress(_("rebasing"), posholder[0],
-                                  ("%d:%s" % (ctx.rev(), ctx)),
-                                  _('changesets'), total)
+            p.increment(item=("%d:%s" % (ctx.rev(), ctx)))
         allowdivergence = self.ui.configbool(
             'experimental', 'evolution.allowdivergence')
         for subset in sortsource(self.destmap):
@@ -452,7 +453,7 @@
                 )
             for rev in sortedrevs:
                 self._rebasenode(tr, rev, allowdivergence, progress)
-        ui.progress(_('rebasing'), None)
+        p.complete()
         ui.note(_('rebase merging completed\n'))
 
     def _concludenode(self, rev, p1, p2, editor, commitmsg=None):
@@ -625,7 +626,7 @@
             newwd = self.originalwd
         if newwd not in [c.rev() for c in repo[None].parents()]:
             ui.note(_("update back to initial working directory parent\n"))
-            hg.updaterepo(repo, newwd, False)
+            hg.updaterepo(repo, newwd, overwrite=False)
 
         collapsedas = None
         if self.collapsef and not self.keepf:
@@ -673,8 +674,7 @@
     ('a', 'abort', False, _('abort an interrupted rebase')),
     ('', 'auto-orphans', '', _('automatically rebase orphan revisions '
                                'in the specified revset (EXPERIMENTAL)')),
-     ] +
-    cmdutil.formatteropts,
+     ] + cmdutil.dryrunopts + cmdutil.formatteropts + cmdutil.confirmopts,
     _('[-s REV | -b REV] [-d REV] [OPTION]'))
 def rebase(ui, repo, **opts):
     """move changeset (and descendants) to a different branch
@@ -797,7 +797,23 @@
     unresolved conflicts.
 
     """
+    opts = pycompat.byteskwargs(opts)
     inmemory = ui.configbool('rebase', 'experimental.inmemory')
+    dryrun = opts.get('dry_run')
+    if dryrun:
+        if opts.get('abort'):
+            raise error.Abort(_('cannot specify both --dry-run and --abort'))
+        if opts.get('continue'):
+            raise error.Abort(_('cannot specify both --dry-run and --continue'))
+    if opts.get('confirm'):
+        dryrun = True
+        if opts.get('dry_run'):
+            raise error.Abort(_('cannot specify both --confirm and --dry-run'))
+        if opts.get('abort'):
+            raise error.Abort(_('cannot specify both --confirm and --abort'))
+        if opts.get('continue'):
+            raise error.Abort(_('cannot specify both --confirm and --continue'))
+
     if (opts.get('continue') or opts.get('abort') or
         repo.currenttransaction() is not None):
         # in-memory rebase is not compatible with resuming rebases.
@@ -814,25 +830,67 @@
         opts['rev'] = [revsetlang.formatspec('%ld and orphan()', userrevs)]
         opts['dest'] = '_destautoorphanrebase(SRC)'
 
-    if inmemory:
+    if dryrun:
+        return _dryrunrebase(ui, repo, opts)
+    elif inmemory:
         try:
             # in-memory merge doesn't support conflicts, so if we hit any, abort
             # and re-run as an on-disk merge.
             overrides = {('rebase', 'singletransaction'): True}
             with ui.configoverride(overrides, 'rebase'):
-                return _origrebase(ui, repo, inmemory=inmemory, **opts)
+                return _dorebase(ui, repo, opts, inmemory=inmemory)
         except error.InMemoryMergeConflictsError:
             ui.warn(_('hit merge conflicts; re-running rebase without in-memory'
                       ' merge\n'))
-            _origrebase(ui, repo, **{'abort': True})
-            return _origrebase(ui, repo, inmemory=False, **opts)
+            _dorebase(ui, repo, {'abort': True})
+            return _dorebase(ui, repo, opts, inmemory=False)
     else:
-        return _origrebase(ui, repo, **opts)
+        return _dorebase(ui, repo, opts)
 
-def _origrebase(ui, repo, inmemory=False, **opts):
-    opts = pycompat.byteskwargs(opts)
+def _dryrunrebase(ui, repo, opts):
+    rbsrt = rebaseruntime(repo, ui, inmemory=True, opts=opts)
+    confirm = opts.get('confirm')
+    if confirm:
+        ui.status(_('starting in-memory rebase\n'))
+    else:
+        ui.status(_('starting dry-run rebase; repository will not be '
+                    'changed\n'))
+    with repo.wlock(), repo.lock():
+        needsabort = True
+        try:
+            overrides = {('rebase', 'singletransaction'): True}
+            with ui.configoverride(overrides, 'rebase'):
+                _origrebase(ui, repo, opts, rbsrt, inmemory=True,
+                            leaveunfinished=True)
+        except error.InMemoryMergeConflictsError:
+            ui.status(_('hit a merge conflict\n'))
+            return 1
+        else:
+            if confirm:
+                ui.status(_('rebase completed successfully\n'))
+                if not ui.promptchoice(_(b'apply changes (yn)?'
+                                         b'$$ &Yes $$ &No')):
+                    # finish unfinished rebase
+                    rbsrt._finishrebase()
+                else:
+                    rbsrt._prepareabortorcontinue(isabort=True, backup=False,
+                                                  suppwarns=True)
+                needsabort = False
+            else:
+                ui.status(_('dry-run rebase completed successfully; run without'
+                            ' -n/--dry-run to perform this rebase\n'))
+            return 0
+        finally:
+            if needsabort:
+                # no need to store backup in case of dryrun
+                rbsrt._prepareabortorcontinue(isabort=True, backup=False,
+                                              suppwarns=True)
+
+def _dorebase(ui, repo, opts, inmemory=False):
     rbsrt = rebaseruntime(repo, ui, inmemory, opts)
+    return _origrebase(ui, repo, opts, rbsrt, inmemory=inmemory)
 
+def _origrebase(ui, repo, opts, rbsrt, inmemory=False, leaveunfinished=False):
     with repo.wlock(), repo.lock():
         # Validate input and define rebasing points
         destf = opts.get('dest', None)
@@ -902,7 +960,8 @@
                 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
             with util.acceptintervention(dsguard):
                 rbsrt._performrebase(tr)
-                rbsrt._finishrebase()
+                if not leaveunfinished:
+                    rbsrt._finishrebase()
 
 def _definedestmap(ui, repo, inmemory, destf=None, srcf=None, basef=None,
                    revf=None, destspace=None):
@@ -1255,13 +1314,7 @@
     # use unfiltered changelog since successorrevs may return filtered nodes
     assert repo.filtername is None
     cl = repo.changelog
-    def isancestor(a, b):
-        # take revision numbers instead of nodes
-        if a == b:
-            return True
-        elif a > b:
-            return False
-        return cl.isancestor(cl.node(a), cl.node(b))
+    isancestor = cl.isancestorrev
 
     dest = destmap[rev]
     oldps = repo.changelog.parentrevs(rev) # old parents
@@ -1527,7 +1580,8 @@
 
     return False
 
-def abort(repo, originalwd, destmap, state, activebookmark=None):
+def abort(repo, originalwd, destmap, state, activebookmark=None, backup=True,
+          suppwarns=False):
     '''Restore the repository to its original state.  Additional args:
 
     activebookmark: the name of the bookmark that should be active after the
@@ -1572,8 +1626,7 @@
 
             # Strip from the first rebased revision
             if rebased:
-                # no backup of rebased cset versions needed
-                repair.strip(repo.ui, repo, strippoints)
+                repair.strip(repo.ui, repo, strippoints, backup=backup)
 
         if activebookmark and activebookmark in repo._bookmarks:
             bookmarks.activate(repo, activebookmark)
@@ -1581,7 +1634,8 @@
     finally:
         clearstatus(repo)
         clearcollapsemsg(repo)
-        repo.ui.warn(_('rebase aborted\n'))
+        if not suppwarns:
+            repo.ui.warn(_('rebase aborted\n'))
     return 0
 
 def sortsource(destmap):
@@ -1790,33 +1844,31 @@
     assert repo.filtername is None
     cl = repo.changelog
     nodemap = cl.nodemap
-    extinctnodes = set(cl.node(r) for r in repo.revs('extinct()'))
+    extinctrevs = set(repo.revs('extinct()'))
     for srcrev in rebaseobsrevs:
         srcnode = cl.node(srcrev)
-        destnode = cl.node(destmap[srcrev])
         # XXX: more advanced APIs are required to handle split correctly
         successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
         # obsutil.allsuccessors includes node itself
         successors.remove(srcnode)
-        if successors.issubset(extinctnodes):
+        succrevs = {nodemap[s] for s in successors if s in nodemap}
+        if succrevs.issubset(extinctrevs):
             # all successors are extinct
             obsoleteextinctsuccessors.add(srcrev)
         if not successors:
             # no successor
             obsoletenotrebased[srcrev] = None
         else:
-            for succnode in successors:
-                if succnode not in nodemap:
-                    continue
-                if cl.isancestor(succnode, destnode):
-                    obsoletenotrebased[srcrev] = nodemap[succnode]
+            dstrev = destmap[srcrev]
+            for succrev in succrevs:
+                if cl.isancestorrev(succrev, dstrev):
+                    obsoletenotrebased[srcrev] = succrev
                     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 in nodemap):
+                if any(s in destmap for s in succrevs):
                     obsoletewithoutsuccessorindestination.add(srcrev)
 
     return (
--- a/hgext/relink.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/relink.py	Thu Jul 19 13:55:54 2018 -0400
@@ -70,17 +70,10 @@
         # No point in continuing
         raise error.Abort(_('source and destination are on different devices'))
 
-    locallock = repo.lock()
-    try:
-        remotelock = src.lock()
-        try:
-            candidates = sorted(collect(src, ui))
-            targets = prune(candidates, src.store.path, repo.store.path, ui)
-            do_relink(src.store.path, repo.store.path, targets, ui)
-        finally:
-            remotelock.release()
-    finally:
-        locallock.release()
+    with repo.lock(), src.lock():
+        candidates = sorted(collect(src, ui))
+        targets = prune(candidates, src.store.path, repo.store.path, ui)
+        do_relink(src.store.path, repo.store.path, targets, ui)
 
 def collect(src, ui):
     seplen = len(os.path.sep)
@@ -94,6 +87,7 @@
     # mozilla-central as of 2010-06-10 had a ratio of just over 7:5.
     total = live * 3 // 2
     src = src.store.path
+    progress = ui.makeprogress(_('collecting'), unit=_('files'), total=total)
     pos = 0
     ui.status(_("tip has %d files, estimated total number of files: %d\n")
               % (live, total))
@@ -108,9 +102,9 @@
                 continue
             pos += 1
             candidates.append((os.path.join(relpath, filename), st))
-            ui.progress(_('collecting'), pos, filename, _('files'), total)
+            progress.update(pos, item=filename)
 
-    ui.progress(_('collecting'), None)
+    progress.complete()
     ui.status(_('collected %d candidate storage files\n') % len(candidates))
     return candidates
 
@@ -132,7 +126,8 @@
         return st
 
     targets = []
-    total = len(candidates)
+    progress = ui.makeprogress(_('pruning'), unit=_('files'),
+                               total=len(candidates))
     pos = 0
     for fn, st in candidates:
         pos += 1
@@ -143,9 +138,9 @@
             ui.debug('not linkable: %s\n' % fn)
             continue
         targets.append((fn, ts.st_size))
-        ui.progress(_('pruning'), pos, fn, _('files'), total)
+        progress.update(pos, item=fn)
 
-    ui.progress(_('pruning'), None)
+    progress.complete()
     ui.status(_('pruned down to %d probably relinkable files\n') % len(targets))
     return targets
 
@@ -164,8 +159,9 @@
     relinked = 0
     savedbytes = 0
 
+    progress = ui.makeprogress(_('relinking'), unit=_('files'),
+                               total=len(files))
     pos = 0
-    total = len(files)
     for f, sz in files:
         pos += 1
         source = os.path.join(src, f)
@@ -186,13 +182,13 @@
             continue
         try:
             relinkfile(source, tgt)
-            ui.progress(_('relinking'), pos, f, _('files'), total)
+            progress.update(pos, item=f)
             relinked += 1
             savedbytes += sz
         except OSError as inst:
             ui.warn('%s: %s\n' % (tgt, stringutil.forcebytestr(inst)))
 
-    ui.progress(_('relinking'), None)
+    progress.complete()
 
     ui.status(_('relinked %d files (%s reclaimed)\n') %
               (relinked, util.bytecount(savedbytes)))
--- a/hgext/remotenames.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/remotenames.py	Thu Jul 19 13:55:54 2018 -0400
@@ -249,6 +249,10 @@
     extensions.wrapfunction(bookmarks, '_printbookmarks', wrapprintbookmarks)
 
 def reposetup(ui, repo):
+
+    # set the config option to store remotenames
+    repo.ui.setconfig('experimental', 'remotenames', True, 'remotenames-ext')
+
     if not repo.local():
         return
 
--- a/hgext/schemes.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/schemes.py	Thu Jul 19 13:55:54 2018 -0400
@@ -114,7 +114,7 @@
 
 def extsetup(ui):
     schemes.update(dict(ui.configitems('schemes')))
-    t = templater.engine(lambda x: x)
+    t = templater.engine(templater.parse)
     for scheme, url in schemes.items():
         if (pycompat.iswindows and len(scheme) == 1 and scheme.isalpha()
             and os.path.exists('%s:\\' % scheme)):
--- a/hgext/shelve.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/shelve.py	Thu Jul 19 13:55:54 2018 -0400
@@ -594,10 +594,15 @@
                 for chunk, label in patch.diffstatui(difflines, width=width):
                     ui.write(chunk, label=label)
 
-def patchcmds(ui, repo, pats, opts, subcommand):
+def patchcmds(ui, repo, pats, opts):
     """subcommand that displays shelves"""
     if len(pats) == 0:
-        raise error.Abort(_("--%s expects at least one shelf") % subcommand)
+        shelves = listshelves(repo)
+        if not shelves:
+            raise error.Abort(_("there are no shelves to show"))
+        mtime, name = shelves[0]
+        sname = util.split(name)[1]
+        pats = [sname]
 
     for shelfname in pats:
         if not shelvedfile(repo, shelfname, patchextension).exists():
@@ -621,14 +626,14 @@
         try:
             checkparents(repo, state)
 
-            repo.vfs.rename('unshelverebasestate', 'rebasestate')
-            try:
-                rebase.rebase(ui, repo, **{
-                    r'abort' : True
-                })
-            except Exception:
-                repo.vfs.rename('rebasestate', 'unshelverebasestate')
-                raise
+            merge.update(repo, state.pendingctx, False, True)
+            if (state.activebookmark
+                    and state.activebookmark in repo._bookmarks):
+                bookmarks.activate(repo, state.activebookmark)
+
+            if repo.vfs.exists('unshelverebasestate'):
+                repo.vfs.rename('unshelverebasestate', 'rebasestate')
+                rebase.clearstatus(repo)
 
             mergefiles(ui, repo, state.wctx, state.pendingctx)
             repair.strip(ui, repo, state.nodestoremove, backup=False,
@@ -683,22 +688,41 @@
                 _("unresolved conflicts, can't continue"),
                 hint=_("see 'hg resolve', then 'hg unshelve --continue'"))
 
-        repo.vfs.rename('unshelverebasestate', 'rebasestate')
-        try:
-            rebase.rebase(ui, repo, **{
-                r'continue' : True
-            })
-        except Exception:
-            repo.vfs.rename('rebasestate', 'unshelverebasestate')
-            raise
+        shelvectx = repo[state.parents[1]]
+        pendingctx = state.pendingctx
+
+        with repo.dirstate.parentchange():
+            repo.setparents(state.pendingctx.node(), nodemod.nullid)
+            repo.dirstate.write(repo.currenttransaction())
+
+        overrides = {('phases', 'new-commit'): phases.secret}
+        with repo.ui.configoverride(overrides, 'unshelve'):
+            with repo.dirstate.parentchange():
+                repo.setparents(state.parents[0], nodemod.nullid)
+                newnode = repo.commit(text=shelvectx.description(),
+                                      extra=shelvectx.extra(),
+                                      user=shelvectx.user(),
+                                      date=shelvectx.date())
 
-        shelvectx = repo['tip']
-        if state.pendingctx not in shelvectx.parents():
-            # rebase was a no-op, so it produced no child commit
+        if newnode is None:
+            # If it ended up being a no-op commit, then the normal
+            # merge state clean-up path doesn't happen, so do it
+            # here. Fix issue5494
+            merge.mergestate.clean(repo)
             shelvectx = state.pendingctx
+            msg = _('note: unshelved changes already existed '
+                    'in the working copy\n')
+            ui.status(msg)
         else:
-            # only strip the shelvectx if the rebase produced it
-            state.nodestoremove.append(shelvectx.node())
+            # only strip the shelvectx if we produced one
+            state.nodestoremove.append(newnode)
+            shelvectx = repo[newnode]
+
+        hg.updaterepo(repo, pendingctx.node(), overwrite=False)
+
+        if repo.vfs.exists('unshelverebasestate'):
+            repo.vfs.rename('unshelverebasestate', 'rebasestate')
+            rebase.clearstatus(repo)
 
         mergefiles(ui, repo, state.wctx, shelvectx)
         restorebranch(ui, repo, state.branchtorestore)
@@ -746,33 +770,46 @@
     if tmpwctx.node() == shelvectx.parents()[0].node():
         return shelvectx
 
-    ui.status(_('rebasing shelved changes\n'))
-    try:
-        rebase.rebase(ui, repo, **{
-            r'rev': [shelvectx.rev()],
-            r'dest': "%d" % tmpwctx.rev(),
-            r'keep': True,
-            r'tool': opts.get('tool', ''),
-        })
-    except error.InterventionRequired:
-        tr.close()
+    overrides = {
+        ('ui', 'forcemerge'): opts.get('tool', ''),
+        ('phases', 'new-commit'): phases.secret,
+    }
+    with repo.ui.configoverride(overrides, 'unshelve'):
+        ui.status(_('rebasing shelved changes\n'))
+        stats = merge.graft(repo, shelvectx, shelvectx.p1(),
+                           labels=['shelve', 'working-copy'],
+                           keepconflictparent=True)
+        if stats.unresolvedcount:
+            tr.close()
+
+            nodestoremove = [repo.changelog.node(rev)
+                             for rev in xrange(oldtiprev, len(repo))]
+            shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
+                              branchtorestore, opts.get('keep'), activebookmark)
+            raise error.InterventionRequired(
+                _("unresolved conflicts (see 'hg resolve', then "
+                  "'hg unshelve --continue')"))
 
-        nodestoremove = [repo.changelog.node(rev)
-                         for rev in xrange(oldtiprev, len(repo))]
-        shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
-                          branchtorestore, opts.get('keep'), activebookmark)
+        with repo.dirstate.parentchange():
+            repo.setparents(tmpwctx.node(), nodemod.nullid)
+            newnode = repo.commit(text=shelvectx.description(),
+                                  extra=shelvectx.extra(),
+                                  user=shelvectx.user(),
+                                  date=shelvectx.date())
 
-        repo.vfs.rename('rebasestate', 'unshelverebasestate')
-        raise error.InterventionRequired(
-            _("unresolved conflicts (see 'hg resolve', then "
-              "'hg unshelve --continue')"))
+        if newnode is None:
+            # If it ended up being a no-op commit, then the normal
+            # merge state clean-up path doesn't happen, so do it
+            # here. Fix issue5494
+            merge.mergestate.clean(repo)
+            shelvectx = tmpwctx
+            msg = _('note: unshelved changes already existed '
+                    'in the working copy\n')
+            ui.status(msg)
+        else:
+            shelvectx = repo[newnode]
+            hg.updaterepo(repo, tmpwctx.node(), False)
 
-    # refresh ctx after rebase completes
-    shelvectx = repo['tip']
-
-    if tmpwctx not in shelvectx.parents():
-        # rebase was a no-op, so it produced no child commit
-        shelvectx = tmpwctx
     return shelvectx
 
 def _forgetunknownfiles(repo, shelvectx, addedbefore):
@@ -933,27 +970,27 @@
         # to the original pctx.
 
         activebookmark = _backupactivebookmark(repo)
+        tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
+                                                         tmpwctx)
+        repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
+        _checkunshelveuntrackedproblems(ui, repo, shelvectx)
+        branchtorestore = ''
+        if shelvectx.branch() != shelvectx.p1().branch():
+            branchtorestore = shelvectx.branch()
+
+        shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
+                                          basename, pctx, tmpwctx,
+                                          shelvectx, branchtorestore,
+                                          activebookmark)
         overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
         with ui.configoverride(overrides, 'unshelve'):
-            tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
-                                                             tmpwctx)
-            repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
-            _checkunshelveuntrackedproblems(ui, repo, shelvectx)
-            branchtorestore = ''
-            if shelvectx.branch() != shelvectx.p1().branch():
-                branchtorestore = shelvectx.branch()
+            mergefiles(ui, repo, pctx, shelvectx)
+        restorebranch(ui, repo, branchtorestore)
+        _forgetunknownfiles(repo, shelvectx, addedbefore)
 
-            shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
-                                              basename, pctx, tmpwctx,
-                                              shelvectx, branchtorestore,
-                                              activebookmark)
-            mergefiles(ui, repo, pctx, shelvectx)
-            restorebranch(ui, repo, branchtorestore)
-            _forgetunknownfiles(repo, shelvectx, addedbefore)
-
-            shelvedstate.clear(repo)
-            _finishunshelve(repo, oldtiprev, tr, activebookmark)
-            unshelvecleanup(ui, repo, basename, opts)
+        shelvedstate.clear(repo)
+        _finishunshelve(repo, oldtiprev, tr, activebookmark)
+        unshelvecleanup(ui, repo, basename, opts)
     finally:
         if tr:
             tr.release()
@@ -979,11 +1016,14 @@
           ('n', 'name', '',
            _('use the given name for the shelved commit'), _('NAME')),
           ('p', 'patch', None,
-           _('show patch')),
+           _('output patches for changes (provide the names of the shelved '
+             'changes as positional arguments)')),
           ('i', 'interactive', None,
            _('interactive mode, only works while creating a shelve')),
           ('', 'stat', None,
-           _('output diffstat-style summary of changes'))] + cmdutil.walkopts,
+           _('output diffstat-style summary of changes (provide the names of '
+             'the shelved changes as positional arguments)')
+           )] + cmdutil.walkopts,
          _('hg shelve [OPTION]... [FILE]...'))
 def shelvecmd(ui, repo, *pats, **opts):
     '''save and set aside changes from the working directory
@@ -1047,10 +1087,8 @@
         return deletecmd(ui, repo, pats)
     elif checkopt('list'):
         return listcmd(ui, repo, pats, opts)
-    elif checkopt('patch'):
-        return patchcmds(ui, repo, pats, opts, subcommand='patch')
-    elif checkopt('stat'):
-        return patchcmds(ui, repo, pats, opts, subcommand='stat')
+    elif checkopt('patch') or checkopt('stat'):
+        return patchcmds(ui, repo, pats, opts)
     else:
         return createcmd(ui, repo, pats, opts)
 
--- a/hgext/sparse.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/sparse.py	Thu Jul 19 13:55:54 2018 -0400
@@ -138,9 +138,9 @@
     extensions.wrapfunction(logcmdutil, '_initialrevs', _initialrevs)
 
 def _clonesparsecmd(orig, ui, repo, *args, **opts):
-    include_pat = opts.get('include')
-    exclude_pat = opts.get('exclude')
-    enableprofile_pat = opts.get('enable_profile')
+    include_pat = opts.get(r'include')
+    exclude_pat = opts.get(r'exclude')
+    enableprofile_pat = opts.get(r'enable_profile')
     include = exclude = enableprofile = False
     if include_pat:
         pat = include_pat
@@ -178,7 +178,7 @@
                     'also include directories of added files in sparse config'))
 
     def _add(orig, ui, repo, *pats, **opts):
-        if opts.get('sparse'):
+        if opts.get(r'sparse'):
             dirs = set()
             for pat in pats:
                 dirname, basename = util.split(pat)
--- a/hgext/split.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/split.py	Thu Jul 19 13:55:54 2018 -0400
@@ -60,6 +60,7 @@
     By default, rebase connected non-obsoleted descendants onto the new
     changeset. Use --no-rebase to avoid the rebase.
     """
+    opts = pycompat.byteskwargs(opts)
     revlist = []
     if opts.get('rev'):
         revlist.append(opts.get('rev'))
@@ -169,7 +170,7 @@
         raise error.Abort(_('cannot split an empty revision'))
 
     scmutil.cleanupnodes(repo, {ctx.node(): [c.node() for c in committed]},
-                         operation='split')
+                         operation='split', fixphase=True)
 
     return committed[-1]
 
--- a/hgext/strip.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/strip.py	Thu Jul 19 13:55:54 2018 -0400
@@ -103,8 +103,9 @@
                                'option)'), _('REV')),
           ('f', 'force', None, _('force removal of changesets, discard '
                                  'uncommitted changes (no backup)')),
-          ('', 'no-backup', None, _('no backups')),
-          ('', 'nobackup', None, _('no backups (DEPRECATED)')),
+          ('', 'no-backup', None, _('do not save backup bundle')),
+          ('', 'nobackup', None, _('do not save backup bundle '
+                                   '(DEPRECATED)')),
           ('n', '', None, _('ignored  (DEPRECATED)')),
           ('k', 'keep', None, _("do not modify working directory during "
                                 "strip")),
@@ -165,7 +166,7 @@
                 nodetobookmarks.setdefault(node, []).append(mark)
             for marks in nodetobookmarks.values():
                 if bookmarks.issuperset(marks):
-                    rsrevs = repair.stripbmrevset(repo, marks[0])
+                    rsrevs = scmutil.bookmarkrevs(repo, marks[0])
                     revs.update(set(rsrevs))
             if not revs:
                 with repo.lock(), repo.transaction('bookmark') as tr:
--- a/hgext/transplant.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/transplant.py	Thu Jul 19 13:55:54 2018 -0400
@@ -16,7 +16,7 @@
 from __future__ import absolute_import
 
 import os
-import tempfile
+
 from mercurial.i18n import _
 from mercurial import (
     bundlerepo,
@@ -215,7 +215,7 @@
                 if skipmerge:
                     patchfile = None
                 else:
-                    fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
+                    fd, patchfile = pycompat.mkstemp(prefix='hg-transplant-')
                     fp = os.fdopen(fd, r'wb')
                     gen = patch.diff(source, parent, node, opts=diffopts)
                     for chunk in gen:
@@ -263,7 +263,7 @@
 
         self.ui.status(_('filtering %s\n') % patchfile)
         user, date, msg = (changelog[1], changelog[2], changelog[4])
-        fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
+        fd, headerfile = pycompat.mkstemp(prefix='hg-transplant-')
         fp = os.fdopen(fd, r'wb')
         fp.write("# HG changeset patch\n")
         fp.write("# User %s\n" % user)
@@ -523,7 +523,8 @@
         displayer.show(repo[node])
         action = None
         while not action:
-            action = 'ynmpcq?'[ui.promptchoice(prompt)]
+            choice = ui.promptchoice(prompt)
+            action = 'ynmpcq?'[choice:choice + 1]
             if action == '?':
                 for c, t in ui.extractchoices(prompt)[1]:
                     ui.write('%s: %s\n' % (c, t))
@@ -682,7 +683,7 @@
     sourcerepo = opts.get('source')
     if sourcerepo:
         peer = hg.peer(repo, opts, ui.expandpath(sourcerepo))
-        heads = map(peer.lookup, opts.get('branch', ()))
+        heads = pycompat.maplist(peer.lookup, opts.get('branch', ()))
         target = set(heads)
         for r in revs:
             try:
@@ -693,7 +694,7 @@
                                     onlyheads=sorted(target), force=True)
     else:
         source = repo
-        heads = map(source.lookup, opts.get('branch', ()))
+        heads = pycompat.maplist(source.lookup, opts.get('branch', ()))
         cleanupfn = None
 
     try:
@@ -708,7 +709,7 @@
             matchfn = lambda x: tf(x) and x not in prune
         else:
             matchfn = tf
-        merges = map(source.lookup, opts.get('merge', ()))
+        merges = pycompat.maplist(source.lookup, opts.get('merge', ()))
         revmap = {}
         if revs:
             for r in scmutil.revrange(source, revs):
--- a/hgext/uncommit.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/uncommit.py	Thu Jul 19 13:55:54 2018 -0400
@@ -91,12 +91,7 @@
                          user=ctx.user(),
                          date=ctx.date(),
                          extra=ctx.extra())
-    # phase handling
-    commitphase = ctx.phase()
-    overrides = {('phases', 'new-commit'): commitphase}
-    with repo.ui.configoverride(overrides, 'uncommit'):
-        newid = repo.commitctx(new)
-    return newid
+    return repo.commitctx(new)
 
 def _fixdirstate(repo, oldctx, newctx, status):
     """ fix the dirstate after switching the working directory from oldctx to
@@ -183,7 +178,7 @@
                 # Fully removed the old commit
                 mapping[old.node()] = ()
 
-            scmutil.cleanupnodes(repo, mapping, 'uncommit')
+            scmutil.cleanupnodes(repo, mapping, 'uncommit', fixphase=True)
 
             with repo.dirstate.parentchange():
                 repo.dirstate.setparents(newid, node.nullid)
@@ -242,12 +237,7 @@
                                 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)
-
+        newprednode = repo.commitctx(newctx)
         newpredctx = repo[newprednode]
         dirstate = repo.dirstate
 
@@ -257,4 +247,4 @@
             _fixdirstate(repo, curctx, newpredctx, s)
 
         mapping = {curctx.node(): (newprednode,)}
-        scmutil.cleanupnodes(repo, mapping, 'unamend')
+        scmutil.cleanupnodes(repo, mapping, 'unamend', fixphase=True)
--- a/hgext/win32mbcs.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/hgext/win32mbcs.py	Thu Jul 19 13:55:54 2018 -0400
@@ -90,7 +90,7 @@
     return arg
 
 def encode(arg):
-    if isinstance(arg, unicode):
+    if isinstance(arg, pycompat.unicode):
         return arg.encode(_encoding)
     elif isinstance(arg, tuple):
         return tuple(map(encode, arg))
@@ -127,7 +127,7 @@
                          " %s encoding\n") % (_encoding))
 
 def wrapper(func, args, kwds):
-    return basewrapper(func, unicode, encode, decode, args, kwds)
+    return basewrapper(func, pycompat.unicode, encode, decode, args, kwds)
 
 
 def reversewrapper(func, args, kwds):
--- a/i18n/da.po	Sun Jul 01 23:36:53 2018 +0900
+++ b/i18n/da.po	Thu Jul 19 13:55:54 2018 -0400
@@ -13696,7 +13696,7 @@
 msgstr ""
 
 msgid ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    List of archive format (bz2, gz, zip) allowed for downloading.\n"
 "    Default is empty."
 msgstr ""
--- a/i18n/de.po	Sun Jul 01 23:36:53 2018 +0900
+++ b/i18n/de.po	Thu Jul 19 13:55:54 2018 -0400
@@ -17347,7 +17347,7 @@
 msgstr ""
 
 msgid ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    List of archive format (bz2, gz, zip) allowed for downloading.\n"
 "    Default is empty."
 msgstr ""
--- a/i18n/ja.po	Sun Jul 01 23:36:53 2018 +0900
+++ b/i18n/ja.po	Thu Jul 19 13:55:54 2018 -0400
@@ -27712,11 +27712,11 @@
 "    サーバの待ちうけアドレス。 (デフォルト値: ホストの持つ全アドレス)"
 
 msgid ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    List of archive format (bz2, gz, zip) allowed for downloading.\n"
 "    (default: empty)"
 msgstr ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    利用可能なダウンロード向けのアーカイブ形式 (bz2, gz, zip) 一覧。\n"
 "    (デフォルト値: 空 = ダウンロード不可)"
 
--- a/i18n/pt_BR.po	Sun Jul 01 23:36:53 2018 +0900
+++ b/i18n/pt_BR.po	Thu Jul 19 13:55:54 2018 -0400
@@ -28663,11 +28663,11 @@
 "    (padrão: usa todos os endereços)"
 
 msgid ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    List of archive format (bz2, gz, zip) allowed for downloading.\n"
 "    (default: empty)"
 msgstr ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    Lista de formatos de pacote (bz2, gz, zip) permitidos para download.\n"
 "    (padrão: lista vazia)"
 
--- a/i18n/ro.po	Sun Jul 01 23:36:53 2018 +0900
+++ b/i18n/ro.po	Thu Jul 19 13:55:54 2018 -0400
@@ -12099,7 +12099,7 @@
 msgstr ""
 
 msgid ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    List of archive format (bz2, gz, zip) allowed for downloading.\n"
 "    Default is empty."
 msgstr ""
--- a/i18n/ru.po	Sun Jul 01 23:36:53 2018 +0900
+++ b/i18n/ru.po	Thu Jul 19 13:55:54 2018 -0400
@@ -19776,11 +19776,11 @@
 "    Адрес прослушиваемого интерфейса. По умолчанию все интерфейсы."
 
 msgid ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    List of archive format (bz2, gz, zip) allowed for downloading.\n"
 "    Default is empty."
 msgstr ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    Список форматов архивов (bz2, gz, zip), которые можно скачивать.\n"
 "    По умолчанию пуст."
 
--- a/i18n/sv.po	Sun Jul 01 23:36:53 2018 +0900
+++ b/i18n/sv.po	Thu Jul 19 13:55:54 2018 -0400
@@ -15034,7 +15034,7 @@
 msgstr ""
 
 msgid ""
-"``allow_archive``\n"
+"``allow-archive``\n"
 "    List of archive format (bz2, gz, zip) allowed for downloading.\n"
 "    Default is empty."
 msgstr ""
--- a/mercurial/ancestor.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/ancestor.py	Thu Jul 19 13:55:54 2018 -0400
@@ -339,6 +339,10 @@
         seen = self._containsseen
         if target in seen:
             return True
+        # Only integer target is valid, but some callers expect 'None in self'
+        # to be False. So we explicitly allow it.
+        if target is None:
+            return False
 
         parentrevs = self._parentrevs
         visit = self._containsvisit
--- a/mercurial/archival.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/archival.py	Thu Jul 19 13:55:54 2018 -0400
@@ -322,13 +322,14 @@
         files.sort()
         scmutil.prefetchfiles(repo, [ctx.rev()],
                               scmutil.matchfiles(repo, files))
-        repo.ui.progress(_('archiving'), 0, unit=_('files'), total=total)
-        for i, f in enumerate(files):
+        progress = scmutil.progress(repo.ui, _('archiving'), unit=_('files'),
+                                    total=total)
+        progress.update(0)
+        for f in files:
             ff = ctx.flags(f)
             write(f, 'x' in ff and 0o755 or 0o644, 'l' in ff, ctx[f].data)
-            repo.ui.progress(_('archiving'), i + 1, item=f,
-                             unit=_('files'), total=total)
-        repo.ui.progress(_('archiving'), None)
+            progress.increment(item=f)
+        progress.complete()
 
     if subrepos:
         for subpath in sorted(ctx.substate):
--- a/mercurial/bdiff.c	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/bdiff.c	Thu Jul 19 13:55:54 2018 -0400
@@ -310,6 +310,7 @@
 	return count;
 }
 
+/* deallocate list of hunks; l may be NULL */
 void bdiff_freehunks(struct bdiff_hunk *l)
 {
 	struct bdiff_hunk *n;
--- a/mercurial/bitmanipulation.h	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/bitmanipulation.h	Thu Jul 19 13:55:54 2018 -0400
@@ -9,7 +9,8 @@
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 24) | (d[1] << 16) | (d[2] << 8) | (d[3]));
+	return ((((uint32_t)d[0]) << 24) | (((uint32_t)d[1]) << 16) |
+	        (((uint32_t)d[2]) << 8) | (d[3]));
 }
 
 static inline int16_t getbeint16(const char *c)
--- a/mercurial/bookmarks.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/bookmarks.py	Thu Jul 19 13:55:54 2018 -0400
@@ -43,7 +43,7 @@
     fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
     return fp
 
-class bmstore(dict):
+class bmstore(object):
     """Storage for bookmarks.
 
     This object should do all bookmark-related reads and writes, so
@@ -58,13 +58,13 @@
     """
 
     def __init__(self, repo):
-        dict.__init__(self)
         self._repo = repo
+        self._refmap = refmap = {}  # refspec: node
+        self._nodemap = nodemap = {}  # node: sorted([refspec, ...])
         self._clean = True
         self._aclean = True
         nm = repo.changelog.nodemap
         tonode = bin # force local lookup
-        setitem = dict.__setitem__
         try:
             with _getbkfile(repo) as bkfile:
                 for line in bkfile:
@@ -76,7 +76,15 @@
                         node = tonode(sha)
                         if node in nm:
                             refspec = encoding.tolocal(refspec)
-                            setitem(self, refspec, node)
+                            refmap[refspec] = node
+                            nrefs = nodemap.get(node)
+                            if nrefs is None:
+                                nodemap[node] = [refspec]
+                            else:
+                                nrefs.append(refspec)
+                                if nrefs[-2] > refspec:
+                                    # bookmarks weren't sorted before 4.5
+                                    nrefs.sort()
                     except (TypeError, ValueError):
                         # TypeError:
                         # - bin(...)
@@ -96,38 +104,78 @@
 
     @active.setter
     def active(self, mark):
-        if mark is not None and mark not in self:
+        if mark is not None and mark not in self._refmap:
             raise AssertionError('bookmark %s does not exist!' % mark)
 
         self._active = mark
         self._aclean = False
 
-    def __setitem__(self, *args, **kwargs):
-        raise error.ProgrammingError("use 'bookmarks.applychanges' instead")
+    def __len__(self):
+        return len(self._refmap)
+
+    def __iter__(self):
+        return iter(self._refmap)
+
+    def iteritems(self):
+        return self._refmap.iteritems()
+
+    def items(self):
+        return self._refmap.items()
 
-    def _set(self, key, value):
-        self._clean = False
-        return dict.__setitem__(self, key, value)
+    # TODO: maybe rename to allnames()?
+    def keys(self):
+        return self._refmap.keys()
+
+    # TODO: maybe rename to allnodes()? but nodes would have to be deduplicated
+    # could be self._nodemap.keys()
+    def values(self):
+        return self._refmap.values()
+
+    def __contains__(self, mark):
+        return mark in self._refmap
+
+    def __getitem__(self, mark):
+        return self._refmap[mark]
 
-    def __delitem__(self, key):
-        raise error.ProgrammingError("use 'bookmarks.applychanges' instead")
+    def get(self, mark, default=None):
+        return self._refmap.get(mark, default)
 
-    def _del(self, key):
+    def _set(self, mark, node):
         self._clean = False
-        return dict.__delitem__(self, key)
+        if mark in self._refmap:
+            self._del(mark)
+        self._refmap[mark] = node
+        nrefs = self._nodemap.get(node)
+        if nrefs is None:
+            self._nodemap[node] = [mark]
+        else:
+            nrefs.append(mark)
+            nrefs.sort()
 
-    def update(self, *others):
-        raise error.ProgrammingError("use 'bookmarks.applychanges' instead")
+    def _del(self, mark):
+        self._clean = False
+        node = self._refmap.pop(mark)
+        nrefs = self._nodemap[node]
+        if len(nrefs) == 1:
+            assert nrefs[0] == mark
+            del self._nodemap[node]
+        else:
+            nrefs.remove(mark)
+
+    def names(self, node):
+        """Return a sorted list of bookmarks pointing to the specified node"""
+        return self._nodemap.get(node, [])
 
     def changectx(self, mark):
-        return self._repo[self[mark]]
+        node = self._refmap[mark]
+        return self._repo[node]
 
     def applychanges(self, repo, tr, changes):
         """Apply a list of changes to bookmarks
         """
         bmchanges = tr.changes.get('bookmarks')
         for name, node in changes:
-            old = self.get(name)
+            old = self._refmap.get(name)
             if node is None:
                 self._del(name)
             else:
@@ -151,7 +199,7 @@
     def _writerepo(self, repo):
         """Factored out for extensibility"""
         rbm = repo._bookmarks
-        if rbm.active not in self:
+        if rbm.active not in self._refmap:
             rbm.active = None
             rbm._writeactive()
 
@@ -182,7 +230,7 @@
         self._aclean = True
 
     def _write(self, fp):
-        for name, node in sorted(self.iteritems()):
+        for name, node in sorted(self._refmap.iteritems()):
             fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
         self._clean = True
         self._repo.invalidatevolatilesets()
@@ -208,15 +256,15 @@
         If divergent bookmark are to be deleted, they will be returned as list.
         """
         cur = self._repo['.'].node()
-        if mark in self and not force:
+        if mark in self._refmap and not force:
             if target:
-                if self[mark] == target and target == cur:
+                if self._refmap[mark] == target and target == cur:
                     # re-activating a bookmark
                     return []
                 rev = self._repo[target].rev()
                 anc = self._repo.changelog.ancestors([rev])
                 bmctx = self.changectx(mark)
-                divs = [self[b] for b in self
+                divs = [self._refmap[b] for b in self._refmap
                         if b.split('@', 1)[0] == mark.split('@', 1)[0]]
 
                 # allow resolving a single divergent bookmark even if moving
@@ -765,7 +813,7 @@
         return new.node() in obsutil.foreground(repo, [old.node()])
     else:
         # still an independent clause as it is lazier (and therefore faster)
-        return old.descendant(new)
+        return old.isancestorof(new)
 
 def checkformat(repo, mark):
     """return a valid version of a potential bookmark name
@@ -875,11 +923,14 @@
     """
     opts = pycompat.byteskwargs(opts)
     fm = ui.formatter('bookmarks', opts)
+    contexthint = fm.contexthint('bookmark rev node active')
     hexfn = fm.hexfunc
     if len(bmarks) == 0 and fm.isplain():
         ui.status(_("no bookmarks set\n"))
     for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
         fm.startitem()
+        if 'ctx' in contexthint:
+            fm.context(ctx=repo[n])
         if not ui.quiet:
             fm.plain(' %s ' % prefix, label=label)
         fm.write('bookmark', '%s', bmark, label=label)
--- a/mercurial/bundle2.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/bundle2.py	Thu Jul 19 13:55:54 2018 -0400
@@ -628,9 +628,10 @@
     def addparam(self, name, value=None):
         """add a stream level parameter"""
         if not name:
-            raise ValueError(r'empty parameter name')
+            raise error.ProgrammingError(b'empty parameter name')
         if name[0:1] not in pycompat.bytestr(string.ascii_letters):
-            raise ValueError(r'non letter first character: %s' % name)
+            raise error.ProgrammingError(b'non letter first character: %s'
+                                         % name)
         self._params.append((name, value))
 
     def addpart(self, part):
@@ -1877,7 +1878,7 @@
         real_part.validate()
     except error.Abort as e:
         raise error.Abort(_('bundle at %s is corrupted:\n%s') %
-            (util.hidepassword(raw_url), str(e)))
+                          (util.hidepassword(raw_url), bytes(e)))
     assert not inpart.read()
 
 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
--- a/mercurial/bundlerepo.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/bundlerepo.py	Thu Jul 19 13:55:54 2018 -0400
@@ -15,7 +15,6 @@
 
 import os
 import shutil
-import tempfile
 
 from .i18n import _
 from .node import nullid
@@ -270,7 +269,7 @@
         try:
             localrepo.localrepository.__init__(self, ui, repopath)
         except error.RepoError:
-            self._tempparent = tempfile.mkdtemp()
+            self._tempparent = pycompat.mkdtemp()
             localrepo.instance(ui, self._tempparent, 1)
             localrepo.localrepository.__init__(self, ui, self._tempparent)
         self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
--- a/mercurial/cext/bdiff.c	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/cext/bdiff.c	Thu Jul 19 13:55:54 2018 -0400
@@ -157,9 +157,7 @@
 	PyBuffer_Release(&bb);
 	free(al);
 	free(bl);
-	if (l.next) {
-		bdiff_freehunks(l.next);
-	}
+	bdiff_freehunks(l.next);
 	return result;
 }
 
--- a/mercurial/cext/parsers.c	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/cext/parsers.c	Thu Jul 19 13:55:54 2018 -0400
@@ -713,7 +713,7 @@
 void manifest_module_init(PyObject *mod);
 void revlog_module_init(PyObject *mod);
 
-static const int version = 4;
+static const int version = 5;
 
 static void module_init(PyObject *mod)
 {
--- a/mercurial/cext/pathencode.c	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/cext/pathencode.c	Thu Jul 19 13:55:54 2018 -0400
@@ -474,7 +474,10 @@
 	static const uint32_t twobytes[8] = {0, 0, 0x87fffffe};
 
 	static const uint32_t onebyte[8] = {
-	    1, 0x2bff3bfa, 0x68000001, 0x2fffffff,
+	    1,
+	    0x2bff3bfa,
+	    0x68000001,
+	    0x2fffffff,
 	};
 
 	Py_ssize_t destlen = 0;
@@ -655,16 +658,10 @@
 	PyObject *shaobj, *hashobj;
 
 	if (shafunc == NULL) {
-		PyObject *hashlib, *name = PyBytes_FromString("hashlib");
-
-		if (name == NULL)
-			return -1;
-
-		hashlib = PyImport_Import(name);
-		Py_DECREF(name);
-
+		PyObject *hashlib = PyImport_ImportModule("hashlib");
 		if (hashlib == NULL) {
-			PyErr_SetString(PyExc_ImportError, "hashlib");
+			PyErr_SetString(PyExc_ImportError,
+			                "pathencode failed to find hashlib");
 			return -1;
 		}
 		shafunc = PyObject_GetAttrString(hashlib, "sha1");
@@ -673,12 +670,12 @@
 		if (shafunc == NULL) {
 			PyErr_SetString(PyExc_AttributeError,
 			                "module 'hashlib' has no "
-			                "attribute 'sha1'");
+			                "attribute 'sha1' in pathencode");
 			return -1;
 		}
 	}
 
-	shaobj = PyObject_CallFunction(shafunc, "s#", str, len);
+	shaobj = PyObject_CallFunction(shafunc, PY23("s#", "y#"), str, len);
 
 	if (shaobj == NULL)
 		return -1;
--- a/mercurial/cext/revlog.c	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/cext/revlog.c	Thu Jul 19 13:55:54 2018 -0400
@@ -248,6 +248,20 @@
 	return data ? data + 32 : NULL;
 }
 
+/*
+ * Return the 20-byte SHA of the node corresponding to the given rev. The
+ * rev is assumed to be existing. If not, an exception is set.
+ */
+static const char *index_node_existing(indexObject *self, Py_ssize_t pos)
+{
+	const char *node = index_node(self, pos);
+	if (node == NULL) {
+		PyErr_Format(PyExc_IndexError, "could not access rev %d",
+		             (int)pos);
+	}
+	return node;
+}
+
 static int nt_insert(indexObject *self, const char *node, int rev);
 
 static int node_check(PyObject *obj, char **node, Py_ssize_t *nodelen)
@@ -1052,10 +1066,12 @@
 			return 0;
 		}
 		if (v < 0) {
-			const char *oldnode = index_node(self, -(v + 1));
+			const char *oldnode = index_node_existing(self, -(v + 1));
 			int noff;
 
-			if (!oldnode || !memcmp(oldnode, node, 20)) {
+			if (oldnode == NULL)
+				return -1;
+			if (!memcmp(oldnode, node, 20)) {
 				n->children[k] = -rev - 1;
 				return 0;
 			}
@@ -1135,9 +1151,9 @@
 	 */
 	if (self->ntmisses++ < 4) {
 		for (rev = self->ntrev - 1; rev >= 0; rev--) {
-			const char *n = index_node(self, rev);
+			const char *n = index_node_existing(self, rev);
 			if (n == NULL)
-				return -2;
+				return -3;
 			if (memcmp(node, n, nodelen > 20 ? 20 : nodelen) == 0) {
 				if (nt_insert(self, n, rev) == -1)
 					return -3;
@@ -1146,11 +1162,9 @@
 		}
 	} else {
 		for (rev = self->ntrev - 1; rev >= 0; rev--) {
-			const char *n = index_node(self, rev);
-			if (n == NULL) {
-				self->ntrev = rev + 1;
-				return -2;
-			}
+			const char *n = index_node_existing(self, rev);
+			if (n == NULL)
+				return -3;
 			if (nt_insert(self, n, rev) == -1) {
 				self->ntrev = rev + 1;
 				return -3;
@@ -1216,27 +1230,84 @@
 	return NULL;
 }
 
+/*
+ * Fully populate the radix tree.
+ */
+static int nt_populate(indexObject *self) {
+	int rev;
+	if (self->ntrev > 0) {
+		for (rev = self->ntrev - 1; rev >= 0; rev--) {
+			const char *n = index_node_existing(self, rev);
+			if (n == NULL)
+				return -1;
+			if (nt_insert(self, n, rev) == -1)
+				return -1;
+		}
+		self->ntrev = -1;
+	}
+	return 0;
+}
+
 static int nt_partialmatch(indexObject *self, const char *node,
 			   Py_ssize_t nodelen)
 {
-	int rev;
+	if (nt_init(self) == -1)
+		return -3;
+	if (nt_populate(self) == -1)
+		return -3;
+
+	return nt_find(self, node, nodelen, 1);
+}
+
+/*
+ * Find the length of the shortest unique prefix of node.
+ *
+ * Return values:
+ *
+ *   -3: error (exception set)
+ *   -2: not found (no exception set)
+ * rest: length of shortest prefix
+ */
+static int nt_shortest(indexObject *self, const char *node)
+{
+	int level, off;
 
 	if (nt_init(self) == -1)
 		return -3;
+	if (nt_populate(self) == -1)
+		return -3;
 
-	if (self->ntrev > 0) {
-		/* ensure that the radix tree is fully populated */
-		for (rev = self->ntrev - 1; rev >= 0; rev--) {
-			const char *n = index_node(self, rev);
+	for (level = off = 0; level < 40; level++) {
+		int k, v;
+		nodetree *n = &self->nt[off];
+		k = nt_level(node, level);
+		v = n->children[k];
+		if (v < 0) {
+			const char *n;
+			v = -(v + 1);
+			n = index_node_existing(self, v);
 			if (n == NULL)
+				return -3;
+			if (memcmp(node, n, 20) != 0)
+				/*
+				 * Found a unique prefix, but it wasn't for the
+				 * requested node (i.e the requested node does
+				 * not exist).
+				 */
 				return -2;
-			if (nt_insert(self, n, rev) == -1)
-				return -3;
+			return level + 1;
 		}
-		self->ntrev = rev;
+		if (v == 0)
+			return -2;
+		off = v;
 	}
-
-	return nt_find(self, node, nodelen, 1);
+	/*
+	 * The node was still not unique after 40 hex digits, so this won't
+	 * happen. Also, if we get here, then there's a programming error in
+	 * this file that made us insert a node longer than 40 hex digits.
+	 */
+	PyErr_SetString(PyExc_Exception, "broken node tree");
+	return -3;
 }
 
 static PyObject *index_partialmatch(indexObject *self, PyObject *args)
@@ -1249,7 +1320,7 @@
 	if (!PyArg_ParseTuple(args, PY23("s#", "y#"), &node, &nodelen))
 		return NULL;
 
-	if (nodelen < 4) {
+	if (nodelen < 1) {
 		PyErr_SetString(PyExc_ValueError, "key too short");
 		return NULL;
 	}
@@ -1280,15 +1351,36 @@
 		return PyBytes_FromStringAndSize(nullid, 20);
 	}
 
-	fullnode = index_node(self, rev);
+	fullnode = index_node_existing(self, rev);
 	if (fullnode == NULL) {
-		PyErr_Format(PyExc_IndexError,
-			     "could not access rev %d", rev);
 		return NULL;
 	}
 	return PyBytes_FromStringAndSize(fullnode, 20);
 }
 
+static PyObject *index_shortest(indexObject *self, PyObject *args)
+{
+	Py_ssize_t nodelen;
+	PyObject *val;
+	char *node;
+	int length;
+
+	if (!PyArg_ParseTuple(args, "O", &val))
+		return NULL;
+	if (node_check(val, &node, &nodelen) == -1)
+		return NULL;
+
+	self->ntlookups++;
+	length = nt_shortest(self, node);
+	if (length == -3)
+		return NULL;
+	if (length == -2) {
+		raise_revlog_error();
+		return NULL;
+	}
+	return PyInt_FromLong(length);
+}
+
 static PyObject *index_m_get(indexObject *self, PyObject *args)
 {
 	Py_ssize_t nodelen;
@@ -1758,10 +1850,11 @@
 			Py_ssize_t i;
 
 			for (i = start + 1; i < self->length - 1; i++) {
-				const char *node = index_node(self, i);
+				const char *node = index_node_existing(self, i);
+				if (node == NULL)
+					return -1;
 
-				if (node)
-					nt_insert(self, node, -1);
+				nt_insert(self, node, -1);
 			}
 			if (self->added)
 				nt_invalidate_added(self, 0);
@@ -1977,6 +2070,8 @@
 	 "insert an index entry"},
 	{"partialmatch", (PyCFunction)index_partialmatch, METH_VARARGS,
 	 "match a potentially ambiguous node ID"},
+	{"shortest", (PyCFunction)index_shortest, METH_VARARGS,
+	 "find length of shortest hex nodeid of a binary ID"},
 	{"stats", (PyCFunction)index_stats, METH_NOARGS,
 	 "stats for the index"},
 	{NULL} /* Sentinel */
--- a/mercurial/changegroup.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/changegroup.py	Thu Jul 19 13:55:54 2018 -0400
@@ -9,7 +9,6 @@
 
 import os
 import struct
-import tempfile
 import weakref
 
 from .i18n import _
@@ -80,7 +79,7 @@
                 # small (4k is common on Linux).
                 fh = open(filename, "wb", 131072)
         else:
-            fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
+            fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
             fh = os.fdopen(fd, r"wb")
         cleanup = filename
         for c in chunks:
@@ -238,18 +237,16 @@
                     pos = next
             yield closechunk()
 
-    def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
-        # We know that we'll never have more manifests than we had
-        # changesets.
-        self.callback = prog(_('manifests'), numchanges)
+    def _unpackmanifests(self, repo, revmap, trp, prog):
+        self.callback = prog.increment
         # no need to check for empty manifest group here:
         # if the result of the merge of 1 and 2 is the same in 3 and 4,
         # no new manifest will be created and the manifest group will
         # be empty during the pull
         self.manifestheader()
         deltas = self.deltaiter()
-        repo.manifestlog._revlog.addgroup(deltas, revmap, trp)
-        repo.ui.progress(_('manifests'), None)
+        repo.manifestlog.addgroup(deltas, revmap, trp)
+        prog.complete()
         self.callback = None
 
     def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
@@ -294,16 +291,9 @@
             # pull off the changeset group
             repo.ui.status(_("adding changesets\n"))
             clstart = len(cl)
-            class prog(object):
-                def __init__(self, step, total):
-                    self._step = step
-                    self._total = total
-                    self._count = 1
-                def __call__(self):
-                    repo.ui.progress(self._step, self._count, unit=_('chunks'),
-                                     total=self._total)
-                    self._count += 1
-            self.callback = prog(_('changesets'), expectedtotal)
+            progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
+                                            total=expectedtotal)
+            self.callback = progress.increment
 
             efiles = set()
             def onchangelog(cl, node):
@@ -319,12 +309,16 @@
                                   config='warn-empty-changegroup')
             clend = len(cl)
             changesets = clend - clstart
-            repo.ui.progress(_('changesets'), None)
+            progress.complete()
             self.callback = None
 
             # pull off the manifest group
             repo.ui.status(_("adding manifests\n"))
-            self._unpackmanifests(repo, revmap, trp, prog, changesets)
+            # We know that we'll never have more manifests than we had
+            # changesets.
+            progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
+                                            total=changesets)
+            self._unpackmanifests(repo, revmap, trp, progress)
 
             needfiles = {}
             if repo.ui.configbool('server', 'validate'):
@@ -476,9 +470,8 @@
         node, p1, p2, deltabase, cs, flags = headertuple
         return node, p1, p2, deltabase, cs, flags
 
-    def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
-        super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
-                                                  numchanges)
+    def _unpackmanifests(self, repo, revmap, trp, prog):
+        super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
         for chunkdata in iter(self.filelogheader, {}):
             # If we get here, there are directory manifests in the changegroup
             d = chunkdata["filename"]
@@ -523,7 +516,6 @@
             reorder = stringutil.parsebool(reorder)
         self._repo = repo
         self._reorder = reorder
-        self._progress = repo.ui.progress
         if self._repo.ui.verbose and not self._repo.ui.debugflag:
             self._verbosenote = self._repo.ui.note
         else:
@@ -572,18 +564,20 @@
         revs.insert(0, p)
 
         # build deltas
-        total = len(revs) - 1
-        msgbundling = _('bundling')
+        progress = None
+        if units is not None:
+            progress = self._repo.ui.makeprogress(_('bundling'), unit=units,
+                                                  total=(len(revs) - 1))
         for r in xrange(len(revs) - 1):
-            if units is not None:
-                self._progress(msgbundling, r + 1, unit=units, total=total)
+            if progress:
+                progress.update(r + 1)
             prev, curr = revs[r], revs[r + 1]
             linknode = lookup(revlog.node(curr))
             for c in self.revchunk(revlog, curr, prev, linknode):
                 yield c
 
-        if units is not None:
-            self._progress(msgbundling, None)
+        if progress:
+            progress.complete()
         yield self.close()
 
     # filter any nodes that claim to be part of the known set
@@ -749,12 +743,8 @@
     # The 'source' parameter is useful for extensions
     def generatefiles(self, changedfiles, linknodes, commonrevs, source):
         repo = self._repo
-        progress = self._progress
-        msgbundling = _('bundling')
-
-        total = len(changedfiles)
-        # for progress output
-        msgfiles = _('files')
+        progress = repo.ui.makeprogress(_('bundling'), unit=_('files'),
+                                        total=len(changedfiles))
         for i, fname in enumerate(sorted(changedfiles)):
             filerevlog = repo.file(fname)
             if not filerevlog:
@@ -769,8 +759,7 @@
 
             filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
             if filenodes:
-                progress(msgbundling, i + 1, item=fname, unit=msgfiles,
-                         total=total)
+                progress.update(i + 1, item=fname)
                 h = self.fileheader(fname)
                 size = len(h)
                 yield h
@@ -778,7 +767,7 @@
                     size += len(chunk)
                     yield chunk
                 self._verbosenote(_('%8.i  %s\n') % (size, fname))
-        progress(msgbundling, None)
+        progress.complete()
 
     def deltaparent(self, revlog, rev, p1, p2, prev):
         if not revlog.candelta(prev, rev):
@@ -982,12 +971,13 @@
 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
     revisions = 0
     files = 0
+    progress = repo.ui.makeprogress(_('files'), unit=_('files'),
+                                    total=expectedfiles)
     for chunkdata in iter(source.filelogheader, {}):
         files += 1
         f = chunkdata["filename"]
         repo.ui.debug("adding %s revisions\n" % f)
-        repo.ui.progress(_('files'), files, unit=_('files'),
-                         total=expectedfiles)
+        progress.increment()
         fl = repo.file(f)
         o = len(fl)
         try:
@@ -1008,7 +998,7 @@
                         _("received spurious file revlog entry"))
             if not needs:
                 del needfiles[f]
-    repo.ui.progress(_('files'), None)
+    progress.complete()
 
     for f, needs in needfiles.iteritems():
         fl = repo.file(f)
--- a/mercurial/cmdutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/cmdutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -10,7 +10,6 @@
 import errno
 import os
 import re
-import tempfile
 
 from .i18n import _
 from .node import (
@@ -36,8 +35,8 @@
     obsolete,
     patch,
     pathutil,
+    phases,
     pycompat,
-    registrar,
     revlog,
     rewriteutil,
     scmutil,
@@ -203,17 +202,21 @@
     return oldwrite
 
 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
-    if usecurses:
-        if testfile:
-            recordfn = crecordmod.testdecorator(testfile,
-                                                crecordmod.testchunkselector)
-        else:
-            recordfn = crecordmod.chunkselector
-
-        return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
-
-    else:
-        return patch.filterpatch(ui, originalhunks, operation)
+    try:
+        if usecurses:
+            if testfile:
+                recordfn = crecordmod.testdecorator(
+                    testfile, crecordmod.testchunkselector)
+            else:
+                recordfn = crecordmod.chunkselector
+
+            return crecordmod.filterpatch(ui, originalhunks, recordfn,
+                                          operation)
+    except crecordmod.fallbackerror as e:
+        ui.warn('%s\n' % e.message)
+        ui.warn(_('falling back to text mode\n'))
+
+    return patch.filterpatch(ui, originalhunks, operation)
 
 def recordfilter(ui, originalhunks, operation=None):
     """ Prompts the user to filter the originalhunks and return a list of
@@ -331,7 +334,7 @@
         try:
             # backup continues
             for f in tobackup:
-                fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
+                fd, tmpname = pycompat.mkstemp(prefix=f.replace('/', '_') + '.',
                                                dir=backupdir)
                 os.close(fd)
                 ui.debug('backup %r as %r\n' % (f, tmpname))
@@ -419,7 +422,7 @@
     Represent a directory in user working copy with information required for
     the purpose of tersing its status.
 
-    path is the path to the directory
+    path is the path to the directory, without a trailing '/'
 
     statuses is a set of statuses of all files in this directory (this includes
     all the files in all the subdirectories too)
@@ -456,7 +459,7 @@
 
             # does the dirnode object for subdir exists
             if subdir not in self.subdirs:
-                subdirpath = os.path.join(self.path, subdir)
+                subdirpath = pathutil.join(self.path, subdir)
                 self.subdirs[subdir] = dirnode(subdirpath)
 
             # try adding the file in subdir
@@ -471,7 +474,7 @@
     def iterfilepaths(self):
         """Yield (status, path) for files directly under this directory."""
         for f, st in self.files:
-            yield st, os.path.join(self.path, f)
+            yield st, pathutil.join(self.path, f)
 
     def tersewalk(self, terseargs):
         """
@@ -485,7 +488,7 @@
 
         1) All the files in the directory (including all the files in its
         subdirectories) share the same status and the user has asked us to terse
-        that status. -> yield (status, dirpath)
+        that status. -> yield (status, dirpath).  dirpath will end in '/'.
 
         2) Otherwise, we do following:
 
@@ -502,7 +505,7 @@
             # Making sure we terse only when the status abbreviation is
             # passed as terse argument
             if onlyst in terseargs:
-                yield onlyst, self.path + pycompat.ossep
+                yield onlyst, self.path + '/'
                 return
 
         # add the files to status list
@@ -591,8 +594,8 @@
     return _commentlines(msg)
 
 def _helpmessage(continuecmd, abortcmd):
-    msg = _('To continue:                %s\n'
-            'To abort:                   %s') % (continuecmd, abortcmd)
+    msg = _('To continue:    %s\n'
+            'To abort:       %s') % (continuecmd, abortcmd)
     return _commentlines(msg)
 
 def _rebasemsg():
@@ -606,7 +609,7 @@
 
 def _updatecleanmsg(dest=None):
     warning = _('warning: this will discard uncommitted changes')
-    return 'hg update --clean %s    (%s)' % (dest or '.', warning)
+    return 'hg update --clean %s (%s)' % (dest or '.', warning)
 
 def _graftmsg():
     # tweakdefaults requires `update` to have a rev hence the `.`
@@ -633,7 +636,7 @@
     ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
     ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
     ('graft', fileexistspredicate('graftstate'), _graftmsg),
-    ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
+    ('unshelve', fileexistspredicate('shelvedstate'), _unshelvemsg),
     ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
     # The merge state is part of a list that will be iterated over.
     # They need to be last because some of the other unfinished states may also
@@ -787,16 +790,12 @@
                                 extra=extra,
                                 branch=label)
 
-            commitphase = ctx.phase()
-            overrides = {('phases', 'new-commit'): commitphase}
-            with repo.ui.configoverride(overrides, 'branch-change'):
-                newnode = repo.commitctx(mc)
-
+            newnode = repo.commitctx(mc)
             replacements[ctx.node()] = (newnode,)
             ui.debug('new node id is %s\n' % hex(newnode))
 
         # create obsmarkers and move bookmarks
-        scmutil.cleanupnodes(repo, replacements, 'branch-change')
+        scmutil.cleanupnodes(repo, replacements, 'branch-change', fixphase=True)
 
         # move the working copy too
         wctx = repo[None]
@@ -1248,7 +1247,8 @@
                              dryrun=dryrun, cwd=cwd)
         if rename and not dryrun:
             if not after and srcexists and not samefile:
-                repo.wvfs.unlinkpath(abssrc)
+                rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
+                repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
             wctx.forget([abssrc])
 
     # pat: ossep
@@ -1685,7 +1685,7 @@
     fm.write('date', '(%s) ', fm.formatdate(marker.date()))
     meta = marker.metadata().copy()
     meta.pop('date', None)
-    smeta = util.rapply(pycompat.maybebytestr, meta)
+    smeta = pycompat.rapply(pycompat.maybebytestr, meta)
     fm.write('metadata', '{%s}', fm.formatdict(smeta, fmt='%r: %r', sep=', '))
     fm.plain('\n')
 
@@ -1884,10 +1884,14 @@
     yielding each context, the iterator will first call the prepare
     function on each context in the window in forward order.'''
 
+    allfiles = opts.get('all_files')
     follow = opts.get('follow') or opts.get('follow_first')
     revs = _walkrevs(repo, opts)
     if not revs:
         return []
+    if allfiles and len(revs) > 1:
+        raise error.Abort(_("multiple revisions not supported with "
+                            "--all-files"))
     wanted = set()
     slowpath = match.anypats() or (not match.always() and opts.get('removed'))
     fncache = {}
@@ -1993,7 +1997,11 @@
                 ctx = change(rev)
                 if not fns:
                     def fns_generator():
-                        for f in ctx.files():
+                        if allfiles:
+                            fiter = iter(ctx)
+                        else:
+                            fiter = ctx.files()
+                        for f in fiter:
                             if match(f):
                                 yield f
                     fns = fns_generator()
@@ -2137,15 +2145,13 @@
     return bad, forgot
 
 def files(ui, ctx, m, fm, fmt, subrepos):
-    rev = ctx.rev()
     ret = 1
-    ds = ctx.repo().dirstate
-
+
+    needsfctx = ui.verbose or {'size', 'flags'} & fm.datahint()
     for f in ctx.matches(m):
-        if rev is None and ds[f] == 'r':
-            continue
         fm.startitem()
-        if ui.verbose:
+        fm.context(ctx=ctx)
+        if needsfctx:
             fc = ctx[f]
             fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
         fm.data(abspath=f)
@@ -2181,13 +2187,12 @@
         warn = False
 
     subs = sorted(wctx.substate)
-    total = len(subs)
-    count = 0
+    progress = ui.makeprogress(_('searching'), total=len(subs),
+                               unit=_('subrepos'))
     for subpath in subs:
-        count += 1
         submatch = matchmod.subdirmatcher(subpath, m)
         if subrepos or m.exact(subpath) or any(submatch.files()):
-            ui.progress(_('searching'), count, total=total, unit=_('subrepos'))
+            progress.increment()
             sub = wctx.sub(subpath)
             try:
                 if sub.removefiles(submatch, prefix, after, force, subrepos,
@@ -2196,13 +2201,13 @@
             except error.LookupError:
                 warnings.append(_("skipping missing subrepository: %s\n")
                                % join(subpath))
-    ui.progress(_('searching'), None)
+    progress.complete()
 
     # warn about failure to delete explicit files/dirs
     deleteddirs = util.dirs(deleted)
     files = m.files()
-    total = len(files)
-    count = 0
+    progress = ui.makeprogress(_('deleting'), total=len(files),
+                               unit=_('files'))
     for f in files:
         def insubrepo():
             for subpath in wctx.substate:
@@ -2210,8 +2215,7 @@
                     return True
             return False
 
-        count += 1
-        ui.progress(_('deleting'), count, total=total, unit=_('files'))
+        progress.increment()
         isdir = f in deleteddirs or wctx.hasdir(f)
         if (f in repo.dirstate or isdir or f == '.'
             or insubrepo() or f in subs):
@@ -2226,50 +2230,47 @@
                         % m.rel(f))
         # missing files will generate a warning elsewhere
         ret = 1
-    ui.progress(_('deleting'), None)
+    progress.complete()
 
     if force:
         list = modified + deleted + clean + added
     elif after:
         list = deleted
         remaining = modified + added + clean
-        total = len(remaining)
-        count = 0
+        progress = ui.makeprogress(_('skipping'), total=len(remaining),
+                                   unit=_('files'))
         for f in remaining:
-            count += 1
-            ui.progress(_('skipping'), count, total=total, unit=_('files'))
+            progress.increment()
             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)
+        progress.complete()
     else:
         list = deleted + clean
-        total = len(modified) + len(added)
-        count = 0
+        progress = ui.makeprogress(_('skipping'),
+                                   total=(len(modified) + len(added)),
+                                   unit=_('files'))
         for f in modified:
-            count += 1
-            ui.progress(_('skipping'), count, total=total, unit=_('files'))
+            progress.increment()
             warnings.append(_('not removing %s: file is modified (use -f'
                       ' to force removal)\n') % m.rel(f))
             ret = 1
         for f in added:
-            count += 1
-            ui.progress(_('skipping'), count, total=total, unit=_('files'))
+            progress.increment()
             warnings.append(_("not removing %s: file has been marked for add"
                       " (use 'hg forget' to undo add)\n") % m.rel(f))
             ret = 1
-        ui.progress(_('skipping'), None)
+        progress.complete()
 
     list = sorted(list)
-    total = len(list)
-    count = 0
+    progress = ui.makeprogress(_('deleting'), total=len(list),
+                               unit=_('files'))
     for f in list:
-        count += 1
         if ui.verbose or not m.exact(f):
-            ui.progress(_('deleting'), count, total=total, unit=_('files'))
+            progress.increment()
             ui.status(_('removing %s\n') % m.rel(f))
-    ui.progress(_('deleting'), None)
+    progress.complete()
 
     if not dryrun:
         with repo.wlock():
@@ -2277,7 +2278,9 @@
                 for f in list:
                     if f in added:
                         continue # we never unlink added files on remove
-                    repo.wvfs.unlinkpath(f, ignoremissing=True)
+                    rmdir = repo.ui.configbool('experimental',
+                                               'removeemptydirs')
+                    repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
             repo[None].forget(list)
 
     if warn:
@@ -2295,6 +2298,7 @@
     if decode:
         data = ctx.repo().wwritedata(path, data)
     fm.startitem()
+    fm.context(ctx=ctx)
     fm.write('data', '%s', data)
     fm.data(abspath=path, path=matcher.rel(path))
 
@@ -2541,21 +2545,19 @@
             # This not what we expect from amend.
             return old.node()
 
+        commitphase = None
         if opts.get('secret'):
-            commitphase = 'secret'
-        else:
-            commitphase = old.phase()
-        overrides = {('phases', 'new-commit'): commitphase}
-        with ui.configoverride(overrides, 'amend'):
-            newid = repo.commitctx(new)
+            commitphase = phases.secret
+        newid = repo.commitctx(new)
 
         # Reroute the working copy parent to the new changeset
         repo.setparents(newid, nullid)
         mapping = {old.node(): (newid,)}
         obsmetadata = None
         if opts.get('note'):
-            obsmetadata = {'note': opts['note']}
-        scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
+            obsmetadata = {'note': encoding.fromlocal(opts['note'])}
+        scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata,
+                             fixphase=True, targetphase=commitphase)
 
         # Fixing the dirstate because localrepo.commitctx does not update
         # it. This is rather convenient because we did not need to update
@@ -3002,12 +3004,6 @@
 
         if not opts.get('dry_run'):
             needdata = ('revert', 'add', 'undelete')
-            if _revertprefetch is not _revertprefetchstub:
-                ui.deprecwarn("'cmdutil._revertprefetch' is deprecated, "
-                              "add a callback to 'scmutil.fileprefetchhooks'",
-                              '4.6', stacklevel=1)
-                _revertprefetch(repo, ctx,
-                                *[actions[name][0] for name in needdata])
             oplist = [actions[name][0] for name in needdata]
             prefetch = scmutil.prefetchfiles
             matchfiles = scmutil.matchfiles
@@ -3026,12 +3022,6 @@
                     raise error.Abort("subrepository '%s' does not exist in %s!"
                                       % (sub, short(ctx.node())))
 
-def _revertprefetchstub(repo, ctx, *files):
-    """Stub method for detecting extension wrapping of _revertprefetch(), to
-    issue a deprecation warning."""
-
-_revertprefetch = _revertprefetchstub
-
 def _performrevert(repo, parents, ctx, actions, interactive=False,
                    tobackup=None):
     """function that actually perform all the actions computed for revert
@@ -3051,7 +3041,8 @@
 
     def doremove(f):
         try:
-            repo.wvfs.unlinkpath(f)
+            rmdir = repo.ui.configbool('experimental', 'removeemptydirs')
+            repo.wvfs.unlinkpath(f, rmdir=rmdir)
         except OSError:
             pass
         repo.dirstate.remove(f)
@@ -3168,12 +3159,6 @@
         if f in copied:
             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 list of (ui, repo, otherpeer, opts, missing) functions called by
 # commands.outgoing.  "missing" is "missing" of the result of
 # "findcommonoutgoing()"
@@ -3198,7 +3183,7 @@
 # (state file, clearable, allowcommit, error, hint)
 unfinishedstates = [
     ('graftstate', True, False, _('graft in progress'),
-     _("use 'hg graft --continue' or 'hg update' to abort")),
+     _("use 'hg graft --continue' or 'hg graft --stop' to stop")),
     ('updatestate', True, False, _('last update was interrupted'),
      _("use 'hg update' to get a consistent checkout"))
     ]
@@ -3285,23 +3270,3 @@
     if after[1]:
         hint = after[0]
     raise error.Abort(_('no %s in progress') % task, hint=hint)
-
-class changeset_printer(logcmdutil.changesetprinter):
-
-    def __init__(self, ui, *args, **kwargs):
-        msg = ("'cmdutil.changeset_printer' is deprecated, "
-               "use 'logcmdutil.logcmdutil'")
-        ui.deprecwarn(msg, "4.6")
-        super(changeset_printer, self).__init__(ui, *args, **kwargs)
-
-def displaygraph(ui, *args, **kwargs):
-    msg = ("'cmdutil.displaygraph' is deprecated, "
-           "use 'logcmdutil.displaygraph'")
-    ui.deprecwarn(msg, "4.6")
-    return logcmdutil.displaygraph(ui, *args, **kwargs)
-
-def show_changeset(ui, *args, **kwargs):
-    msg = ("'cmdutil.show_changeset' is deprecated, "
-           "use 'logcmdutil.changesetdisplayer'")
-    ui.deprecwarn(msg, "4.6")
-    return logcmdutil.changesetdisplayer(ui, *args, **kwargs)
--- a/mercurial/commands.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/commands.py	Thu Jul 19 13:55:54 2018 -0400
@@ -40,7 +40,6 @@
     hbisect,
     help,
     hg,
-    lock as lockmod,
     logcmdutil,
     merge as mergemod,
     obsolete,
@@ -50,10 +49,12 @@
     pycompat,
     rcutil,
     registrar,
+    repair,
     revsetlang,
     rewriteutil,
     scmutil,
     server,
+    state as statemod,
     streamclone,
     tags as tagsmod,
     templatekw,
@@ -63,12 +64,9 @@
 )
 from .utils import (
     dateutil,
-    procutil,
     stringutil,
 )
 
-release = lockmod.release
-
 table = {}
 table.update(debugcommandsmod.command._table)
 
@@ -335,13 +333,13 @@
         formatrev = formathex = pycompat.bytestr
 
     opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
-             ('number', ' ', lambda x: x.fctx.rev(), formatrev),
-             ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
+             ('rev', ' ', lambda x: x.fctx.rev(), formatrev),
+             ('node', ' ', lambda x: hexfn(x.fctx.node()), formathex),
              ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
              ('file', ' ', lambda x: x.fctx.path(), pycompat.bytestr),
              ('line_number', ':', lambda x: x.lineno, pycompat.bytestr),
             ]
-    fieldnamemap = {'number': 'rev', 'changeset': 'node'}
+    opnamemap = {'rev': 'number', 'node': 'changeset'}
 
     if (not opts.get('user') and not opts.get('changeset')
         and not opts.get('date') and not opts.get('file')):
@@ -359,11 +357,12 @@
     else:
         def makefunc(get, fmt):
             return get
-    funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
-               if opts.get(op)]
+    datahint = rootfm.datahint()
+    funcmap = [(makefunc(get, fmt), sep) for fn, sep, get, fmt in opmap
+               if opts.get(opnamemap.get(fn, fn)) or fn in datahint]
     funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
-    fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
-                      if opts.get(op))
+    fields = ' '.join(fn for fn, sep, get, fmt in opmap
+                      if opts.get(opnamemap.get(fn, fn)) or fn in datahint)
 
     def bad(x, y):
         raise error.Abort("%s: %s" % (x, y))
@@ -560,13 +559,8 @@
     Returns 0 on success, 1 if nothing to backout or there are unresolved
     files.
     '''
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
+    with repo.wlock(), repo.lock():
         return _dobackout(ui, repo, node, rev, **opts)
-    finally:
-        release(lock, wlock)
 
 def _dobackout(ui, repo, node=None, rev=None, **opts):
     opts = pycompat.byteskwargs(opts)
@@ -617,21 +611,16 @@
     bheads = repo.branchheads(branch)
     rctx = scmutil.revsingle(repo, hex(parent))
     if not opts.get('merge') and op1 != node:
-        dsguard = dirstateguard.dirstateguard(repo, 'backout')
-        try:
-            ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
-                         'backout')
-            stats = mergemod.update(repo, parent, True, True, node, False)
+        with dirstateguard.dirstateguard(repo, 'backout'):
+            overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
+            with ui.configoverride(overrides, 'backout'):
+                stats = mergemod.update(repo, parent, True, True, node, False)
             repo.setparents(op1, op2)
-            dsguard.close()
-            hg._showstats(repo, stats)
-            if stats.unresolvedcount:
-                repo.ui.status(_("use 'hg resolve' to retry unresolved "
-                                 "file merges\n"))
-                return 1
-        finally:
-            ui.setconfig('ui', 'forcemerge', '', '')
-            lockmod.release(dsguard)
+        hg._showstats(repo, stats)
+        if stats.unresolvedcount:
+            repo.ui.status(_("use 'hg resolve' to retry unresolved "
+                             "file merges\n"))
+            return 1
     else:
         hg.clean(repo, node, show_stats=False)
         repo.dirstate.setbranch(branch)
@@ -667,12 +656,9 @@
         hg.clean(repo, op1, show_stats=False)
         ui.status(_('merging with changeset %s\n')
                   % nice(repo.changelog.tip()))
-        try:
-            ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
-                         'backout')
+        overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
+        with ui.configoverride(overrides, 'backout'):
             return hg.merge(repo, hex(repo.changelog.tip()))
-        finally:
-            ui.setconfig('ui', 'forcemerge', '', '')
     return 0
 
 @command('bisect',
@@ -1234,7 +1220,7 @@
         other = hg.peer(repo, opts, dest)
         revs = [repo[r].hex() for r in revs]
         revs, checkout = hg.addbranchrevs(repo, repo, branches, revs)
-        heads = revs and map(repo.lookup, revs) or revs
+        heads = revs and pycompat.maplist(repo.lookup, revs) or revs
         outgoing = discovery.findcommonoutgoing(repo, other,
                                                 onlyheads=heads,
                                                 force=opts.get('force'),
@@ -1536,13 +1522,8 @@
 
           hg commit --amend --date now
     """
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
+    with repo.wlock(), repo.lock():
         return _docommit(ui, repo, *pats, **opts)
-    finally:
-        release(lock, wlock)
 
 def _docommit(ui, repo, *pats, **opts):
     if opts.get(r'interactive'):
@@ -1895,7 +1876,9 @@
                               root=opts.get('root'))
 
 @command('^export',
-    [('o', 'output', '',
+    [('B', 'bookmark', '',
+     _('export changes only reachable by given bookmark')),
+    ('o', 'output', '',
      _('print output to file with formatted name'), _('FORMAT')),
     ('', 'switch-parent', None, _('diff against the second parent')),
     ('r', 'rev', [], _('revisions to export'), _('REV')),
@@ -1938,6 +1921,9 @@
     of files it detects as binary. With -a, export will generate a
     diff anyway, probably with undesirable results.
 
+    With -B/--bookmark changesets reachable by the given bookmark are
+    selected.
+
     Use the -g/--git option to generate diffs in the git extended diff
     format. See :hg:`help diffs` for more information.
 
@@ -1966,11 +1952,24 @@
     Returns 0 on success.
     """
     opts = pycompat.byteskwargs(opts)
+    bookmark = opts.get('bookmark')
     changesets += tuple(opts.get('rev', []))
-    if not changesets:
-        changesets = ['.']
-    repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
-    revs = scmutil.revrange(repo, changesets)
+
+    if bookmark and changesets:
+        raise error.Abort(_("-r and -B are mutually exclusive"))
+
+    if bookmark:
+        if bookmark not in repo._bookmarks:
+            raise error.Abort(_("bookmark '%s' not found") % bookmark)
+
+        revs = scmutil.bookmarkrevs(repo, bookmark)
+    else:
+        if not changesets:
+            changesets = ['.']
+
+        repo = scmutil.unhidehashlikerevs(repo, changesets, 'nowarn')
+        revs = scmutil.revrange(repo, changesets)
+
     if not revs:
         raise error.Abort(_("export requires at least one changeset"))
     if len(revs) > 1:
@@ -2108,8 +2107,12 @@
     'graft',
     [('r', 'rev', [], _('revisions to graft'), _('REV')),
      ('c', 'continue', False, _('resume interrupted graft')),
+     ('', 'stop', False, _('stop interrupted graft')),
+     ('', 'abort', False, _('abort interrupted graft')),
      ('e', 'edit', False, _('invoke editor on commit messages')),
      ('', 'log', None, _('append graft info to log message')),
+     ('', 'no-commit', None,
+      _("don't commit, just apply the changes in working directory")),
      ('f', 'force', False, _('force graft')),
      ('D', 'currentdate', False,
       _('record the current date as commit date')),
@@ -2143,10 +2146,7 @@
     Once all conflicts are addressed, the graft process can be
     continued with the -c/--continue option.
 
-    .. note::
-
-       The -c/--continue option does not reapply earlier options, except
-       for --force.
+    The -c/--continue option reapplies all the earlier options.
 
     .. container:: verbose
 
@@ -2188,6 +2188,10 @@
 
     revs = list(revs)
     revs.extend(opts.get('rev'))
+    # a dict of data to be stored in state file
+    statedata = {}
+    # list of new nodes created by ongoing graft
+    statedata['newnodes'] = []
 
     if not opts.get('user') and opts.get('currentuser'):
         opts['user'] = ui.username()
@@ -2198,17 +2202,62 @@
                                      **pycompat.strkwargs(opts))
 
     cont = False
-    if opts.get('continue'):
+    if opts.get('no_commit'):
+        if opts.get('edit'):
+            raise error.Abort(_("cannot specify --no-commit and "
+                                "--edit together"))
+        if opts.get('currentuser'):
+            raise error.Abort(_("cannot specify --no-commit and "
+                                "--currentuser together"))
+        if opts.get('currentdate'):
+            raise error.Abort(_("cannot specify --no-commit and "
+                                "--currentdate together"))
+        if opts.get('log'):
+            raise error.Abort(_("cannot specify --no-commit and "
+                                "--log together"))
+
+    graftstate = statemod.cmdstate(repo, 'graftstate')
+
+    if opts.get('stop'):
+        if opts.get('continue'):
+            raise error.Abort(_("cannot use '--continue' and "
+                                "'--stop' together"))
+        if opts.get('abort'):
+            raise error.Abort(_("cannot use '--abort' and '--stop' together"))
+
+        if any((opts.get('edit'), opts.get('log'), opts.get('user'),
+                opts.get('date'), opts.get('currentdate'),
+                opts.get('currentuser'), opts.get('rev'))):
+            raise error.Abort(_("cannot specify any other flag with '--stop'"))
+        return _stopgraft(ui, repo, graftstate)
+    elif opts.get('abort'):
+        if opts.get('continue'):
+            raise error.Abort(_("cannot use '--continue' and "
+                                "'--abort' together"))
+        if any((opts.get('edit'), opts.get('log'), opts.get('user'),
+                opts.get('date'), opts.get('currentdate'),
+                opts.get('currentuser'), opts.get('rev'))):
+            raise error.Abort(_("cannot specify any other flag with '--abort'"))
+
+        return _abortgraft(ui, repo, graftstate)
+    elif opts.get('continue'):
         cont = True
         if revs:
             raise error.Abort(_("can't specify --continue and revisions"))
         # read in unfinished revisions
-        try:
-            nodes = repo.vfs.read('graftstate').splitlines()
+        if graftstate.exists():
+            statedata = _readgraftstate(repo, graftstate)
+            if statedata.get('date'):
+                opts['date'] = statedata['date']
+            if statedata.get('user'):
+                opts['user'] = statedata['user']
+            if statedata.get('log'):
+                opts['log'] = True
+            if statedata.get('no_commit'):
+                opts['no_commit'] = statedata.get('no_commit')
+            nodes = statedata['nodes']
             revs = [repo[node].rev() for node in nodes]
-        except IOError as inst:
-            if inst.errno != errno.ENOENT:
-                raise
+        else:
             cmdutil.wrongtooltocontinue(repo, _('graft'))
     else:
         if not revs:
@@ -2292,6 +2341,8 @@
         if not revs:
             return -1
 
+    if opts.get('no_commit'):
+        statedata['no_commit'] = True
     for pos, ctx in enumerate(repo.set("%ld", revs)):
         desc = '%d:%s "%s"' % (ctx.rev(), ctx,
                                ctx.description().split('\n', 1)[0])
@@ -2312,60 +2363,134 @@
         user = ctx.user()
         if opts.get('user'):
             user = opts['user']
+            statedata['user'] = user
         date = ctx.date()
         if opts.get('date'):
             date = opts['date']
+            statedata['date'] = date
         message = ctx.description()
         if opts.get('log'):
             message += '\n(grafted from %s)' % ctx.hex()
+            statedata['log'] = True
 
         # we don't merge the first commit when continuing
         if not cont:
             # perform the graft merge with p1(rev) as 'ancestor'
-            try:
-                # ui.forcemerge is an internal variable, do not document
-                repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
-                                  'graft')
-                stats = mergemod.graft(repo, ctx, ctx.p1(),
-                                       ['local', 'graft'])
-            finally:
-                repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
+            overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
+            with ui.configoverride(overrides, 'graft'):
+                stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'graft'])
             # report any conflicts
             if stats.unresolvedcount > 0:
                 # write out state for --continue
-                nodelines = [repo[rev].hex() + "\n" for rev in revs[pos:]]
-                repo.vfs.write('graftstate', ''.join(nodelines))
-                extra = ''
-                if opts.get('user'):
-                    extra += ' --user %s' % procutil.shellquote(opts['user'])
-                if opts.get('date'):
-                    extra += ' --date %s' % procutil.shellquote(opts['date'])
-                if opts.get('log'):
-                    extra += ' --log'
-                hint=_("use 'hg resolve' and 'hg graft --continue%s'") % extra
+                nodes = [repo[rev].hex() for rev in revs[pos:]]
+                statedata['nodes'] = nodes
+                stateversion = 1
+                graftstate.save(stateversion, statedata)
+                hint = _("use 'hg resolve' and 'hg graft --continue'")
                 raise error.Abort(
                     _("unresolved conflicts, can't continue"),
                     hint=hint)
         else:
             cont = False
 
-        # commit
-        node = repo.commit(text=message, user=user,
-                    date=date, extra=extra, editor=editor)
-        if node is None:
-            ui.warn(
-                _('note: graft of %d:%s created no changes to commit\n') %
-                (ctx.rev(), ctx))
+        # commit if --no-commit is false
+        if not opts.get('no_commit'):
+            node = repo.commit(text=message, user=user, date=date, extra=extra,
+                               editor=editor)
+            if node is None:
+                ui.warn(
+                    _('note: graft of %d:%s created no changes to commit\n') %
+                    (ctx.rev(), ctx))
+            # checking that newnodes exist because old state files won't have it
+            elif statedata.get('newnodes') is not None:
+                statedata['newnodes'].append(node)
 
     # remove state when we complete successfully
     if not opts.get('dry_run'):
-        repo.vfs.unlinkpath('graftstate', ignoremissing=True)
-
+        graftstate.delete()
+
+    return 0
+
+def _abortgraft(ui, repo, graftstate):
+    """abort the interrupted graft and rollbacks to the state before interrupted
+    graft"""
+    if not graftstate.exists():
+        raise error.Abort(_("no interrupted graft to abort"))
+    statedata = _readgraftstate(repo, graftstate)
+    newnodes = statedata.get('newnodes')
+    if newnodes is None:
+        # and old graft state which does not have all the data required to abort
+        # the graft
+        raise error.Abort(_("cannot abort using an old graftstate"))
+
+    # changeset from which graft operation was started
+    startctx = None
+    if len(newnodes) > 0:
+        startctx = repo[newnodes[0]].p1()
+    else:
+        startctx = repo['.']
+    # whether to strip or not
+    cleanup = False
+    if newnodes:
+        newnodes = [repo[r].rev() for r in newnodes]
+        cleanup = True
+        # checking that none of the newnodes turned public or is public
+        immutable = [c for c in newnodes if not repo[c].mutable()]
+        if immutable:
+            repo.ui.warn(_("cannot clean up public changesets %s\n")
+                         % ', '.join(bytes(repo[r]) for r in immutable),
+                         hint=_("see 'hg help phases' for details"))
+            cleanup = False
+
+        # checking that no new nodes are created on top of grafted revs
+        desc = set(repo.changelog.descendants(newnodes))
+        if desc - set(newnodes):
+            repo.ui.warn(_("new changesets detected on destination "
+                           "branch, can't strip\n"))
+            cleanup = False
+
+        if cleanup:
+            with repo.wlock(), repo.lock():
+                hg.updaterepo(repo, startctx.node(), overwrite=True)
+                # stripping the new nodes created
+                strippoints = [c.node() for c in repo.set("roots(%ld)",
+                                                          newnodes)]
+                repair.strip(repo.ui, repo, strippoints, backup=False)
+
+    if not cleanup:
+        # we don't update to the startnode if we can't strip
+        startctx = repo['.']
+        hg.updaterepo(repo, startctx.node(), overwrite=True)
+
+    ui.status(_("graft aborted\n"))
+    ui.status(_("working directory is now at %s\n") % startctx.hex()[:12])
+    graftstate.delete()
+    return 0
+
+def _readgraftstate(repo, graftstate):
+    """read the graft state file and return a dict of the data stored in it"""
+    try:
+        return graftstate.read()
+    except error.CorruptedState:
+        nodes = repo.vfs.read('graftstate').splitlines()
+        return {'nodes': nodes}
+
+def _stopgraft(ui, repo, graftstate):
+    """stop the interrupted graft"""
+    if not graftstate.exists():
+        raise error.Abort(_("no interrupted graft found"))
+    pctx = repo['.']
+    hg.updaterepo(repo, pctx.node(), overwrite=True)
+    graftstate.delete()
+    ui.status(_("stopped the interrupted graft\n"))
+    ui.status(_("working directory is now at %s\n") % pctx.hex()[:12])
     return 0
 
 @command('grep',
     [('0', 'print0', None, _('end fields with NUL')),
-    ('', 'all', None, _('print all revisions that match')),
+    ('', 'all', None, _('print all revisions that match (DEPRECATED) ')),
+    ('', 'diff', None, _('print all revisions when the term was introduced '
+                         'or removed')),
     ('a', 'text', None, _('treat all files as text')),
     ('f', 'follow', None,
      _('follow changeset history,'
@@ -2376,6 +2501,8 @@
     ('n', 'line-number', None, _('print matching line numbers')),
     ('r', 'rev', [],
      _('only search files changed within revision range'), _('REV')),
+    ('', 'all-files', None,
+     _('include all files in the changeset while grepping (EXPERIMENTAL)')),
     ('u', 'user', None, _('list the author (long with -v)')),
     ('d', 'date', None, _('list the date (short with -q)')),
     ] + formatteropts + walkopts,
@@ -2392,7 +2519,7 @@
     file in which it finds a match. To get it to print every revision
     that contains a change in match status ("-" for a match that becomes
     a non-match, or "+" for a non-match that becomes a match), use the
-    --all flag.
+    --diff flag.
 
     PATTERN can be any Python (roughly Perl-compatible) regular
     expression.
@@ -2404,6 +2531,17 @@
     Returns 0 if a match is found, 1 otherwise.
     """
     opts = pycompat.byteskwargs(opts)
+    diff = opts.get('all') or opts.get('diff')
+    if diff and opts.get('all_files'):
+        raise error.Abort(_('--diff and --all-files are mutually exclusive'))
+    # TODO: remove "not opts.get('rev')" if --all-files -rMULTIREV gets working
+    if opts.get('all_files') is None and not opts.get('rev') and not diff:
+        # experimental config: commands.grep.all-files
+        opts['all_files'] = ui.configbool('commands', 'grep.all-files')
+    plaingrep = opts.get('all_files') and not opts.get('rev')
+    if plaingrep:
+        opts['rev'] = ['wdir()']
+
     reflags = re.M
     if opts.get('ignore_case'):
         reflags |= re.I
@@ -2481,7 +2619,7 @@
                     yield ('+', b[i])
 
     def display(fm, fn, ctx, pstates, states):
-        rev = ctx.rev()
+        rev = scmutil.intrev(ctx)
         if fm.isplain():
             formatuser = ui.shortuser
         else:
@@ -2494,22 +2632,27 @@
         @util.cachefunc
         def binary():
             flog = getfile(fn)
-            return stringutil.binary(flog.read(ctx.filenode(fn)))
+            try:
+                return stringutil.binary(flog.read(ctx.filenode(fn)))
+            except error.WdirUnsupported:
+                return ctx[fn].isbinary()
 
         fieldnamemap = {'filename': 'file', 'linenumber': 'line_number'}
-        if opts.get('all'):
+        if diff:
             iter = difflinestates(pstates, states)
         else:
             iter = [('', l) for l in states]
         for change, l in iter:
             fm.startitem()
-            fm.data(node=fm.hexfunc(ctx.node()))
+            fm.context(ctx=ctx)
+            fm.data(node=fm.hexfunc(scmutil.binnode(ctx)))
+
             cols = [
                 ('filename', fn, True),
-                ('rev', rev, True),
+                ('rev', rev, not plaingrep),
                 ('linenumber', l.linenum, opts.get('line_number')),
             ]
-            if opts.get('all'):
+            if diff:
                 cols.append(('change', change, True))
             cols.extend([
                 ('user', formatuser(ctx.user()), opts.get('user')),
@@ -2569,8 +2712,10 @@
                 fnode = ctx.filenode(fn)
             except error.LookupError:
                 continue
-
-            copied = flog.renamed(fnode)
+            try:
+                copied = flog.renamed(fnode)
+            except error.WdirUnsupported:
+                copied = ctx[fn].renamed()
             copy = follow and copied and copied[0]
             if copy:
                 copies.setdefault(rev, {})[fn] = copy
@@ -2581,7 +2726,11 @@
             files.append(fn)
 
             if fn not in matches[rev]:
-                grepbody(fn, rev, flog.read(fnode))
+                try:
+                    content = flog.read(fnode)
+                except error.WdirUnsupported:
+                    content = ctx[fn].data()
+                grepbody(fn, rev, content)
 
             pfn = copy or fn
             if pfn not in matches[parent]:
@@ -2607,7 +2756,7 @@
             if pstates or states:
                 r = display(fm, fn, ctx, pstates, states)
                 found = found or r
-                if r and not opts.get('all'):
+                if r and not diff:
                     skip[fn] = True
                     if copy:
                         skip[copy] = True
@@ -3071,69 +3220,62 @@
             raise error.Abort(_('cannot use --exact with --prefix'))
 
     base = opts["base"]
-    wlock = dsguard = lock = tr = None
     msgs = []
     ret = 0
 
-
-    try:
-        wlock = repo.wlock()
-
+    with repo.wlock():
         if update:
             cmdutil.checkunfinished(repo)
             if (exact or not opts.get('force')):
                 cmdutil.bailifchanged(repo)
 
         if not opts.get('no_commit'):
-            lock = repo.lock()
-            tr = repo.transaction('import')
+            lock = repo.lock
+            tr = lambda: repo.transaction('import')
+            dsguard = util.nullcontextmanager
         else:
-            dsguard = dirstateguard.dirstateguard(repo, 'import')
-        parents = repo[None].parents()
-        for patchurl in patches:
-            if patchurl == '-':
-                ui.status(_('applying patch from stdin\n'))
-                patchfile = ui.fin
-                patchurl = 'stdin'      # for error message
-            else:
-                patchurl = os.path.join(base, patchurl)
-                ui.status(_('applying %s\n') % patchurl)
-                patchfile = hg.openpath(ui, patchurl)
-
-            haspatch = False
-            for hunk in patch.split(patchfile):
-                with patch.extract(ui, hunk) as patchdata:
-                    msg, node, rej = cmdutil.tryimportone(ui, repo, patchdata,
-                                                          parents, opts,
-                                                          msgs, hg.clean)
-                if msg:
-                    haspatch = True
-                    ui.note(msg + '\n')
-                if update or exact:
-                    parents = repo[None].parents()
+            lock = util.nullcontextmanager
+            tr = util.nullcontextmanager
+            dsguard = lambda: dirstateguard.dirstateguard(repo, 'import')
+        with lock(), tr(), dsguard():
+            parents = repo[None].parents()
+            for patchurl in patches:
+                if patchurl == '-':
+                    ui.status(_('applying patch from stdin\n'))
+                    patchfile = ui.fin
+                    patchurl = 'stdin'      # for error message
                 else:
-                    parents = [repo[node]]
-                if rej:
-                    ui.write_err(_("patch applied partially\n"))
-                    ui.write_err(_("(fix the .rej files and run "
-                                   "`hg commit --amend`)\n"))
-                    ret = 1
-                    break
-
-            if not haspatch:
-                raise error.Abort(_('%s: no diffs found') % patchurl)
-
-        if tr:
-            tr.close()
-        if msgs:
-            repo.savecommitmessage('\n* * *\n'.join(msgs))
-        if dsguard:
-            dsguard.close()
+                    patchurl = os.path.join(base, patchurl)
+                    ui.status(_('applying %s\n') % patchurl)
+                    patchfile = hg.openpath(ui, patchurl)
+
+                haspatch = False
+                for hunk in patch.split(patchfile):
+                    with patch.extract(ui, hunk) as patchdata:
+                        msg, node, rej = cmdutil.tryimportone(ui, repo,
+                                                              patchdata,
+                                                              parents, opts,
+                                                              msgs, hg.clean)
+                    if msg:
+                        haspatch = True
+                        ui.note(msg + '\n')
+                    if update or exact:
+                        parents = repo[None].parents()
+                    else:
+                        parents = [repo[node]]
+                    if rej:
+                        ui.write_err(_("patch applied partially\n"))
+                        ui.write_err(_("(fix the .rej files and run "
+                                       "`hg commit --amend`)\n"))
+                        ret = 1
+                        break
+
+                if not haspatch:
+                    raise error.Abort(_('%s: no diffs found') % patchurl)
+
+            if msgs:
+                repo.savecommitmessage('\n* * *\n'.join(msgs))
         return ret
-    finally:
-        if tr:
-            tr.release()
-        release(lock, dsguard, wlock)
 
 @command('incoming|in',
     [('f', 'force', None,
@@ -3291,7 +3433,13 @@
                       badfn=lambda x, y: False)
 
     ui.pager('locate')
-    for abs in ctx.matches(m):
+    if ctx.rev() is None:
+        # When run on the working copy, "locate" includes removed files, so
+        # we get the list of files from the dirstate.
+        filesgen = sorted(repo.dirstate.matches(m))
+    else:
+        filesgen = ctx.matches(m)
+    for abs in filesgen:
         if opts.get('fullpath'):
             ui.write(repo.wjoin(abs), end)
         else:
@@ -3545,6 +3693,7 @@
     ui.pager('manifest')
     for f in ctx:
         fm.startitem()
+        fm.context(ctx=ctx)
         fl = ctx[f].flags()
         fm.condwrite(ui.debugflag, 'hash', '%s ', hex(mf[f]))
         fm.condwrite(ui.verbose, 'mode type', '%s %1s ', mode[fl], char[fl])
@@ -3623,15 +3772,13 @@
         displayer.close()
         return 0
 
-    try:
-        # ui.forcemerge is an internal variable, do not document
-        repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'merge')
+    # ui.forcemerge is an internal variable, do not document
+    overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
+    with ui.configoverride(overrides, 'merge'):
         force = opts.get('force')
         labels = ['working copy', 'merge rev']
         return hg.merge(repo, node, force=force, mergeforce=force,
                         labels=labels, abort=abort)
-    finally:
-        ui.setconfig('ui', 'forcemerge', '', 'merge')
 
 @command('outgoing|out',
     [('f', 'force', None, _('run even when the destination is unrelated')),
@@ -3679,6 +3826,13 @@
 
     Returns 0 if there are outgoing changes, 1 otherwise.
     """
+    # hg._outgoing() needs to re-resolve the path in order to handle #branch
+    # style URLs, so don't overwrite dest.
+    path = ui.paths.getpath(dest, default=('default-push', 'default'))
+    if not path:
+        raise error.Abort(_('default repository not configured!'),
+                          hint=_("see 'hg help config.paths'"))
+
     opts = pycompat.byteskwargs(opts)
     if opts.get('graph'):
         logcmdutil.checkunsupportedgraphflags([], opts)
@@ -3696,8 +3850,7 @@
         return 0
 
     if opts.get('bookmarks'):
-        dest = ui.expandpath(dest or 'default-push', dest or 'default')
-        dest, branches = hg.parseurl(dest, opts.get('branch'))
+        dest = path.pushloc or path.loc
         other = hg.peer(repo, opts, dest)
         if 'bookmarks' not in other.listkeys('namespaces'):
             ui.warn(_("remote doesn't support bookmarks\n"))
@@ -3706,7 +3859,7 @@
         ui.pager('outgoing')
         return bookmarks.outgoing(ui, repo, other)
 
-    repo._subtoppath = ui.expandpath(dest or 'default-push', dest or 'default')
+    repo._subtoppath = path.pushloc or path.loc
     try:
         return hg.outgoing(ui, repo, dest, opts)
     finally:
@@ -4391,7 +4544,8 @@
         ui.pager('resolve')
         fm = ui.formatter('resolve', opts)
         ms = mergemod.mergestate.read(repo)
-        m = scmutil.match(repo[None], pats, opts)
+        wctx = repo[None]
+        m = scmutil.match(wctx, pats, opts)
 
         # Labels and keys based on merge state.  Unresolved path conflicts show
         # as 'P'.  Resolved path conflicts show as 'R', the same as normal
@@ -4411,6 +4565,7 @@
 
             label, key = mergestateinfo[ms[f]]
             fm.startitem()
+            fm.context(ctx=wctx)
             fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
             fm.write('path', '%s\n', f, label=label)
         fm.end()
@@ -4488,15 +4643,14 @@
 
                 try:
                     # preresolve file
-                    ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
-                                 'resolve')
-                    complete, r = ms.preresolve(f, wctx)
+                    overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
+                    with ui.configoverride(overrides, 'resolve'):
+                        complete, r = ms.preresolve(f, wctx)
                     if not complete:
                         tocomplete.append(f)
                     elif r:
                         ret = 1
                 finally:
-                    ui.setconfig('ui', 'forcemerge', '', 'resolve')
                     ms.commit()
 
                 # replace filemerge's .orig file with our resolve file, but only
@@ -4512,13 +4666,12 @@
         for f in tocomplete:
             try:
                 # resolve file
-                ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
-                             'resolve')
-                r = ms.resolve(f, wctx)
+                overrides = {('ui', 'forcemerge'): opts.get('tool', '')}
+                with ui.configoverride(overrides, 'resolve'):
+                    r = ms.resolve(f, wctx)
                 if r:
                     ret = 1
             finally:
-                ui.setconfig('ui', 'forcemerge', '', 'resolve')
                 ms.commit()
 
             # replace filemerge's .orig file with our resolve file
@@ -4747,7 +4900,8 @@
     ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')),
     ('', 'style', '', _('template style to use'), _('STYLE')),
     ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')),
-    ('', 'certificate', '', _('SSL certificate file'), _('FILE'))]
+    ('', 'certificate', '', _('SSL certificate file'), _('FILE')),
+    ('', 'print-url', None, _('start and print only the URL'))]
      + subrepoopts,
     _('[OPTION]...'),
     optionalrepo=True)
@@ -4779,6 +4933,8 @@
     opts = pycompat.byteskwargs(opts)
     if opts["stdio"] and opts["cmdserver"]:
         raise error.Abort(_("cannot use --stdio with --cmdserver"))
+    if opts["print_url"] and ui.verbose:
+        raise error.Abort(_("cannot use --print-url with --verbose"))
 
     if opts["stdio"]:
         if repo is None:
@@ -4790,6 +4946,8 @@
     service = server.createservice(ui, repo, opts)
     return server.runservice(opts, initfn=service.init, runfn=service.run)
 
+_NOTTERSE = 'nothing'
+
 @command('^status|st',
     [('A', 'all', None, _('show status of all files')),
     ('m', 'modified', None, _('show only modified files')),
@@ -4800,7 +4958,7 @@
     ('u', 'unknown', None, _('show only unknown (not tracked) files')),
     ('i', 'ignored', None, _('show only ignored files')),
     ('n', 'no-status', None, _('hide status prefix')),
-    ('t', 'terse', '', _('show the terse output (EXPERIMENTAL)')),
+    ('t', 'terse', _NOTTERSE, _('show the terse output (EXPERIMENTAL)')),
     ('C', 'copies', None, _('show source of copied files')),
     ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
     ('', 'rev', [], _('show difference from revision'), _('REV')),
@@ -4898,6 +5056,11 @@
     revs = opts.get('rev')
     change = opts.get('change')
     terse = opts.get('terse')
+    if terse is _NOTTERSE:
+        if revs:
+            terse = ''
+        else:
+            terse = ui.config('commands', 'status.terse')
 
     if revs and change:
         msg = _('cannot specify --rev and --change at the same time')
@@ -4939,7 +5102,8 @@
         # we need to compute clean and unknown to terse
         stat = repo.status(ctx1.node(), ctx2.node(), m,
                            'ignored' in show or 'i' in terse,
-                            True, True, opts.get('subrepos'))
+                            clean=True, unknown=True,
+                            listsubrepos=opts.get('subrepos'))
 
         stat = cmdutil.tersedir(stat, terse)
     else:
@@ -4963,6 +5127,7 @@
             label = 'status.' + state
             for f in files:
                 fm.startitem()
+                fm.context(ctx=ctx2)
                 fm.condwrite(showchar, 'status', '%s ', char, label=label)
                 fm.write('path', fmt, repo.pathto(f, cwd), label=label)
                 if f in copy:
@@ -5301,10 +5466,7 @@
     Returns 0 on success.
     """
     opts = pycompat.byteskwargs(opts)
-    wlock = lock = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
+    with repo.wlock(), repo.lock():
         rev_ = "."
         names = [t.strip() for t in (name1,) + names]
         if len(names) != len(set(names)):
@@ -5375,8 +5537,6 @@
 
         tagsmod.tag(repo, names, node, message, opts.get('local'),
                     opts.get('user'), date, editor=editor)
-    finally:
-        release(lock, wlock)
 
 @command('tags', formatteropts, '', intents={INTENT_READONLY})
 def tags(ui, repo, **opts):
@@ -5392,6 +5552,7 @@
     opts = pycompat.byteskwargs(opts)
     ui.pager('tags')
     fm = ui.formatter('tags', opts)
+    contexthint = fm.contexthint('tag rev node type')
     hexfunc = fm.hexfunc
     tagtype = ""
 
@@ -5404,6 +5565,8 @@
             tagtype = 'local'
 
         fm.startitem()
+        if 'ctx' in contexthint:
+            fm.context(ctx=repo[n])
         fm.write('tag', '%s', t, label=label)
         fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
         fm.condwrite(not ui.quiet, 'rev node', fmt,
@@ -5583,18 +5746,19 @@
             repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
         ctx = scmutil.revsingle(repo, rev, rev)
         rev = ctx.rev()
-        if ctx.hidden():
+        hidden = ctx.hidden()
+        overrides = {('ui', 'forcemerge'): opts.get(r'tool', '')}
+        with ui.configoverride(overrides, 'update'):
+            ret = hg.updatetotally(ui, repo, rev, brev, clean=clean,
+                                   updatecheck=updatecheck)
+        if hidden:
             ctxstr = ctx.hex()[:12]
-            ui.warn(_("updating to a hidden changeset %s\n") % ctxstr)
+            ui.warn(_("updated to hidden changeset %s\n") % ctxstr)
 
             if ctx.obsolete():
                 obsfatemsg = obsutil._getfilteredreason(repo, ctxstr, ctx)
                 ui.warn("(%s)\n" % obsfatemsg)
-
-        repo.ui.setconfig('ui', 'forcemerge', opts.get(r'tool'), 'update')
-
-        return hg.updatetotally(ui, repo, rev, brev, clean=clean,
-                                updatecheck=updatecheck)
+        return ret
 
 @command('verify', [])
 def verify(ui, repo):
--- a/mercurial/commandserver.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/commandserver.py	Thu Jul 19 13:55:54 2018 -0400
@@ -256,7 +256,7 @@
                                self.cout, self.cerr)
 
         try:
-            ret = (dispatch.dispatch(req) or 0) & 255 # might return None
+            ret = dispatch.dispatch(req) & 255
             self.cresult.write(struct.pack('>i', int(ret)))
         finally:
             # restore old cwd
@@ -494,6 +494,8 @@
                     conn.close()  # release handle in parent process
             else:
                 try:
+                    selector.close()
+                    self._sock.close()
                     self._runworker(conn)
                     conn.close()
                     os._exit(0)
--- a/mercurial/config.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/config.py	Thu Jul 19 13:55:54 2018 -0400
@@ -215,7 +215,7 @@
             parts.append('')
         if s[offset:offset + 1] == '"' and not parts[-1]:
             return _parse_quote, parts, offset + 1
-        elif s[offset:offset + 1] == '"' and parts[-1][-1] == '\\':
+        elif s[offset:offset + 1] == '"' and parts[-1][-1:] == '\\':
             parts[-1] = parts[-1][:-1] + s[offset:offset + 1]
             return _parse_plain, parts, offset + 1
         parts[-1] += s[offset:offset + 1]
--- a/mercurial/configitems.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/configitems.py	Thu Jul 19 13:55:54 2018 -0400
@@ -147,6 +147,9 @@
 coreconfigitem('annotate', 'noprefix',
     default=False,
 )
+coreconfigitem('annotate', 'word-diff',
+    default=False,
+)
 coreconfigitem('auth', 'cookiefile',
     default=None,
 )
@@ -184,6 +187,9 @@
 coreconfigitem('color', 'pagermode',
     default=dynamicdefault,
 )
+coreconfigitem('commands', 'grep.all-files',
+    default=False,
+)
 coreconfigitem('commands', 'show.aliasprefix',
     default=list,
 )
@@ -193,13 +199,14 @@
 coreconfigitem('commands', 'status.skipstates',
     default=[],
 )
+coreconfigitem('commands', 'status.terse',
+    default='',
+)
 coreconfigitem('commands', 'status.verbose',
     default=False,
 )
 coreconfigitem('commands', 'update.check',
     default=None,
-    # Deprecated, remove after 4.4 release
-    alias=[('experimental', 'updatecheck')]
 )
 coreconfigitem('commands', 'update.requiredest',
     default=False,
@@ -208,6 +215,9 @@
     default=None,
     generic=True,
 )
+coreconfigitem('convert', 'bzr.saverev',
+    default=True,
+)
 coreconfigitem('convert', 'cvsps.cache',
     default=True,
 )
@@ -362,6 +372,9 @@
 coreconfigitem('devel', 'warn-config-unknown',
     default=None,
 )
+coreconfigitem('devel', 'debug.extensions',
+    default=False,
+)
 coreconfigitem('devel', 'debug.peer-request',
     default=False,
 )
@@ -395,6 +408,9 @@
 coreconfigitem('diff', 'noprefix',
     default=False,
 )
+coreconfigitem('diff', 'word-diff',
+    default=False,
+)
 coreconfigitem('email', 'bcc',
     default=None,
 )
@@ -508,9 +524,6 @@
 coreconfigitem('experimental', 'evolution.track-operation',
     default=True,
 )
-coreconfigitem('experimental', 'worddiff',
-    default=False,
-)
 coreconfigitem('experimental', 'maxdeltachainspan',
     default=-1,
 )
@@ -559,12 +572,18 @@
 coreconfigitem('experimental', 'mergedriver',
     default=None,
 )
+coreconfigitem('experimental', 'nointerrupt', default=False)
+coreconfigitem('experimental', 'nointerrupt-interactiveonly', default=True)
+
 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
     default=False,
 )
 coreconfigitem('experimental', 'remotenames',
     default=False,
 )
+coreconfigitem('experimental', 'removeemptydirs',
+    default=True,
+)
 coreconfigitem('experimental', 'revlogv2',
     default=None,
 )
@@ -581,10 +600,10 @@
     default=False,
 )
 coreconfigitem('experimental', 'sparse-read.density-threshold',
-    default=0.25,
+    default=0.50,
 )
 coreconfigitem('experimental', 'sparse-read.min-gap-size',
-    default='256K',
+    default='65K',
 )
 coreconfigitem('experimental', 'treemanifest',
     default=False,
@@ -604,6 +623,9 @@
 coreconfigitem('experimental', 'web.api.debugreflect',
     default=False,
 )
+coreconfigitem('experimental', 'worker.wdir-get-thread-safe',
+    default=False,
+)
 coreconfigitem('experimental', 'xdiff',
     default=False,
 )
@@ -615,9 +637,6 @@
     default=None,
     generic=True,
 )
-coreconfigitem('format', 'aggressivemergedeltas',
-    default=False,
-)
 coreconfigitem('format', 'chunkcachesize',
     default=None,
 )
@@ -636,6 +655,9 @@
 coreconfigitem('format', 'obsstore-version',
     default=None,
 )
+coreconfigitem('format', 'sparse-revlog',
+    default=False,
+)
 coreconfigitem('format', 'usefncache',
     default=True,
 )
@@ -866,6 +888,9 @@
 coreconfigitem('profiling', 'statformat',
     default='hotpath',
 )
+coreconfigitem('profiling', 'time-track',
+    default='cpu',
+)
 coreconfigitem('profiling', 'type',
     default='stat',
 )
@@ -902,6 +927,10 @@
 coreconfigitem('push', 'pushvars.server',
     default=False,
 )
+coreconfigitem('revlog', 'optimize-delta-parent-choice',
+    default=True,
+    # formely an experimental option: format.aggressivemergedeltas
+)
 coreconfigitem('server', 'bookmarks-pushkey-compat',
     default=True,
 )
@@ -932,16 +961,16 @@
 coreconfigitem('server', 'disablefullbundle',
     default=False,
 )
-coreconfigitem('server', 'streamunbundle',
-    default=False,
+coreconfigitem('server', 'maxhttpheaderlen',
+    default=1024,
 )
 coreconfigitem('server', 'pullbundle',
     default=False,
 )
-coreconfigitem('server', 'maxhttpheaderlen',
-    default=1024,
+coreconfigitem('server', 'preferuncompressed',
+    default=False,
 )
-coreconfigitem('server', 'preferuncompressed',
+coreconfigitem('server', 'streamunbundle',
     default=False,
 )
 coreconfigitem('server', 'uncompressed',
@@ -1065,6 +1094,9 @@
 coreconfigitem('ui', 'graphnodetemplate',
     default=None,
 )
+coreconfigitem('ui', 'history-editing-backup',
+    default=True,
+)
 coreconfigitem('ui', 'interactive',
     default=None,
 )
@@ -1074,6 +1106,9 @@
 coreconfigitem('ui', 'interface.chunkselector',
     default=None,
 )
+coreconfigitem('ui', 'large-file-limit',
+    default=10000000,
+)
 coreconfigitem('ui', 'logblockedtimes',
     default=False,
 )
@@ -1225,7 +1260,8 @@
 coreconfigitem('web', 'address',
     default='',
 )
-coreconfigitem('web', 'allow_archive',
+coreconfigitem('web', 'allow-archive',
+    alias=[('web', 'allow_archive')],
     default=list,
 )
 coreconfigitem('web', 'allow_read',
--- a/mercurial/context.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/context.py	Thu Jul 19 13:55:54 2018 -0400
@@ -10,7 +10,6 @@
 import errno
 import filecmp
 import os
-import re
 import stat
 
 from .i18n import _
@@ -24,7 +23,6 @@
     short,
     wdirfilenodeids,
     wdirid,
-    wdirrev,
 )
 from . import (
     dagop,
@@ -52,8 +50,6 @@
 
 propertycache = util.propertycache
 
-nonascii = re.compile(br'[^\x21-\x7f]').search
-
 class basectx(object):
     """A basectx object represents the common logic for its children:
     changectx: read-only context that is already present in the repo,
@@ -185,8 +181,8 @@
     def mutable(self):
         return self.phase() > phases.public
 
-    def getfileset(self, expr):
-        return fileset.getfileset(self, expr)
+    def matchfileset(self, expr, badfn=None):
+        return fileset.match(self, expr, badfn=badfn)
 
     def obsolete(self):
         """True if the changeset is obsolete"""
@@ -298,14 +294,18 @@
                               auditor=r.nofsauditor, ctx=self,
                               listsubrepos=listsubrepos, badfn=badfn)
 
-    def diff(self, ctx2=None, match=None, **opts):
+    def diff(self, ctx2=None, match=None, changes=None, opts=None,
+             losedatafn=None, prefix='', relroot='', copy=None,
+             hunksfilterfn=None):
         """Returns a diff generator for the given contexts and matcher"""
         if ctx2 is None:
             ctx2 = self.p1()
         if ctx2 is not None:
             ctx2 = self._repo[ctx2]
-        diffopts = patch.diffopts(self._repo.ui, pycompat.byteskwargs(opts))
-        return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
+        return patch.diff(self._repo, ctx2, self, match=match, changes=changes,
+                          opts=opts, losedatafn=losedatafn, prefix=prefix,
+                          relroot=relroot, copy=copy,
+                          hunksfilterfn=hunksfilterfn)
 
     def dirs(self):
         return self._manifest.dirs()
@@ -377,31 +377,6 @@
 
         return r
 
-def changectxdeprecwarn(repo):
-    # changectx's constructor will soon lose support for these forms of
-    # changeids:
-    #  * stringinfied ints
-    #  * bookmarks, tags, branches, and other namespace identifiers
-    #  * hex nodeid prefixes
-    #
-    # Depending on your use case, replace repo[x] by one of these:
-    #  * If you want to support general revsets, use scmutil.revsingle(x)
-    #  * If you know that "x" is a stringified int, use repo[int(x)]
-    #  * If you know that "x" is a bookmark, use repo._bookmarks.changectx(x)
-    #  * If you know that "x" is a tag, use repo[repo.tags()[x]]
-    #  * If you know that "x" is a branch or in some other namespace,
-    #    use the appropriate mechanism for that namespace
-    #  * If you know that "x" is a hex nodeid prefix, use
-    #    repo[scmutil.resolvehexnodeidprefix(repo, x)]
-    #  * If "x" is a string that can be any of the above, but you don't want
-    #    to allow general revsets (perhaps because "x" may come from a remote
-    #    user and the revset may be too costly), use scmutil.revsymbol(repo, x)
-    #  * If "x" can be a mix of the above, you'll have to figure it out
-    #    yourself
-    repo.ui.deprecwarn("changectx.__init__ is getting more limited, see "
-                       "context.changectxdeprecwarn() for details", "4.6",
-                       stacklevel=4)
-
 class changectx(basectx):
     """A changecontext object makes access to data related to a particular
     changeset convenient. It represents a read-only context already present in
@@ -415,22 +390,22 @@
                 self._node = repo.changelog.node(changeid)
                 self._rev = changeid
                 return
-            if changeid == 'null':
+            elif changeid == 'null':
                 self._node = nullid
                 self._rev = nullrev
                 return
-            if changeid == 'tip':
+            elif changeid == 'tip':
                 self._node = repo.changelog.tip()
                 self._rev = repo.changelog.rev(self._node)
                 return
-            if (changeid == '.'
-                or repo.local() and changeid == repo.dirstate.p1()):
+            elif (changeid == '.'
+                  or repo.local() and changeid == repo.dirstate.p1()):
                 # this is a hack to delay/avoid loading obsmarkers
                 # when we know that '.' won't be hidden
                 self._node = repo.dirstate.p1()
                 self._rev = repo.unfiltered().changelog.rev(self._node)
                 return
-            if len(changeid) == 20:
+            elif len(changeid) == 20:
                 try:
                     self._node = changeid
                     self._rev = repo.changelog.rev(changeid)
@@ -438,27 +413,17 @@
                 except error.FilteredLookupError:
                     raise
                 except LookupError:
-                    pass
+                    # check if it might have come from damaged dirstate
+                    #
+                    # XXX we could avoid the unfiltered if we had a recognizable
+                    # exception for filtered changeset access
+                    if (repo.local()
+                        and changeid in repo.unfiltered().dirstate.parents()):
+                        msg = _("working directory has unknown parent '%s'!")
+                        raise error.Abort(msg % short(changeid))
+                    changeid = hex(changeid) # for the error message
 
-            try:
-                r = int(changeid)
-                if '%d' % r != changeid:
-                    raise ValueError
-                l = len(repo.changelog)
-                if r < 0:
-                    r += l
-                if r < 0 or r >= l and r != wdirrev:
-                    raise ValueError
-                self._rev = r
-                self._node = repo.changelog.node(r)
-                changectxdeprecwarn(repo)
-                return
-            except error.FilteredIndexError:
-                raise
-            except (ValueError, OverflowError, IndexError):
-                pass
-
-            if len(changeid) == 40:
+            elif len(changeid) == 40:
                 try:
                     self._node = bin(changeid)
                     self._rev = repo.changelog.rev(self._node)
@@ -467,39 +432,15 @@
                     raise
                 except (TypeError, LookupError):
                     pass
-
-            # lookup bookmarks through the name interface
-            try:
-                self._node = repo.names.singlenode(repo, changeid)
-                self._rev = repo.changelog.rev(self._node)
-                changectxdeprecwarn(repo)
-                return
-            except KeyError:
-                pass
-
-            self._node = scmutil.resolvehexnodeidprefix(repo, changeid)
-            if self._node is not None:
-                self._rev = repo.changelog.rev(self._node)
-                changectxdeprecwarn(repo)
-                return
+            else:
+                raise error.ProgrammingError(
+                        "unsupported changeid '%s' of type %s" %
+                        (changeid, type(changeid)))
 
             # lookup failed
-            # check if it might have come from damaged dirstate
-            #
-            # XXX we could avoid the unfiltered if we had a recognizable
-            # exception for filtered changeset access
-            if (repo.local()
-                and changeid in repo.unfiltered().dirstate.parents()):
-                msg = _("working directory has unknown parent '%s'!")
-                raise error.Abort(msg % short(changeid))
-            try:
-                if len(changeid) == 20 and nonascii(changeid):
-                    changeid = hex(changeid)
-            except TypeError:
-                pass
         except (error.FilteredIndexError, error.FilteredLookupError):
             raise error.FilteredRepoLookupError(_("filtered revision '%s'")
-                                                % changeid)
+                                                % pycompat.bytestr(changeid))
         except error.FilteredRepoLookupError:
             raise
         except IndexError:
@@ -649,8 +590,14 @@
         return changectx(self._repo, anc)
 
     def descendant(self, other):
-        """True if other is descendant of this changeset"""
-        return self._repo.changelog.descendant(self._rev, other._rev)
+        msg = (b'ctx.descendant(other) is deprecated, '
+               'use ctx.isancestorof(other)')
+        self._repo.ui.deprecwarn(msg, b'4.7')
+        return self.isancestorof(other)
+
+    def isancestorof(self, other):
+        """True if this changeset is an ancestor of other"""
+        return self._repo.changelog.isancestorrev(self._rev, other._rev)
 
     def walk(self, match):
         '''Generates matching file names.'''
@@ -1294,7 +1241,8 @@
                                                unknown=True, ignored=False))
 
     def matches(self, match):
-        return sorted(self._repo.dirstate.matches(match))
+        ds = self._repo.dirstate
+        return sorted(f for f in ds.matches(match) if ds[f] != 'r')
 
     def ancestors(self):
         for p in self._parents:
@@ -1399,7 +1347,8 @@
                     ui.warn(_("%s does not exist!\n") % uipath(f))
                     rejected.append(f)
                     continue
-                if st.st_size > 10000000:
+                limit = ui.configbytes('ui', 'large-file-limit')
+                if limit != 0 and st.st_size > limit:
                     ui.warn(_("%s: up to %d MB of RAM may be required "
                               "to manage this file\n"
                               "(use 'hg revert %s' to cancel the "
@@ -1773,7 +1722,9 @@
 
     def remove(self, ignoremissing=False):
         """wraps unlink for a repo's working directory"""
-        self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing)
+        rmdir = self._repo.ui.configbool('experimental', 'removeemptydirs')
+        self._repo.wvfs.unlinkpath(self._path, ignoremissing=ignoremissing,
+                                   rmdir=rmdir)
 
     def write(self, data, flags, backgroundclose=False, **kwargs):
         """wraps repo.wwrite"""
--- a/mercurial/copies.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/copies.py	Thu Jul 19 13:55:54 2018 -0400
@@ -254,6 +254,11 @@
         repo.ui.debug("%s:\n   %s\n" % (header % 'local', "\n   ".join(u1)))
     if u2:
         repo.ui.debug("%s:\n   %s\n" % (header % 'other', "\n   ".join(u2)))
+
+    narrowmatch = repo.narrowmatch()
+    if not narrowmatch.always():
+        u1 = [f for f in u1 if narrowmatch(f)]
+        u2 = [f for f in u2 if narrowmatch(f)]
     return u1, u2
 
 def _makegetfctx(ctx):
@@ -411,14 +416,14 @@
     # common ancestor or not without explicitly checking it, it's better to
     # determine that here.
     #
-    # base.descendant(wc) and base.descendant(base) are False, work around that
+    # base.isancestorof(wc) is False, work around that
     _c1 = c1.p1() if c1.rev() is None else c1
     _c2 = c2.p1() if c2.rev() is None else c2
     # an endpoint is "dirty" if it isn't a descendant of the merge base
     # if we have a dirty endpoint, we need to trigger graft logic, and also
     # keep track of which endpoint is dirty
-    dirtyc1 = not (base == _c1 or base.descendant(_c1))
-    dirtyc2 = not (base == _c2 or base.descendant(_c2))
+    dirtyc1 = not base.isancestorof(_c1)
+    dirtyc2 = not base.isancestorof(_c2)
     graft = dirtyc1 or dirtyc2
     tca = base
     if graft:
--- a/mercurial/crecord.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/crecord.py	Thu Jul 19 13:55:54 2018 -0400
@@ -65,6 +65,11 @@
         # compiled with curses
         curses = False
 
+class fallbackerror(error.Abort):
+    """Error that indicates the client should try to fallback to text mode."""
+    # Inherits from error.Abort so that existing behavior is preserved if the
+    # calling code does not know how to fallback.
+
 def checkcurses(ui):
     """Return True if the user wants to use curses
 
@@ -529,8 +534,8 @@
         origsigtstp = signal.getsignal(signal.SIGTSTP)
     try:
         curses.wrapper(chunkselector.main)
-        if chunkselector.initerr is not None:
-            raise error.Abort(chunkselector.initerr)
+        if chunkselector.initexc is not None:
+            raise chunkselector.initexc
         # ncurses does not restore signal handler for SIGTSTP
     finally:
         if origsigtstp is not sentinel:
@@ -549,7 +554,7 @@
     """
     chunkselector = curseschunkselector(headerlist, ui, operation)
     if testfn and os.path.exists(testfn):
-        testf = open(testfn)
+        testf = open(testfn, 'rb')
         testcommands = [x.rstrip('\n') for x in testf.readlines()]
         testf.close()
         while True:
@@ -666,6 +671,7 @@
             nextitem = currentitem
 
         self.currentselecteditem = nextitem
+        self.recenterdisplayedarea()
 
     def downarrowevent(self):
         """
@@ -705,6 +711,7 @@
             nextitem = currentitem
 
         self.currentselecteditem = nextitem
+        self.recenterdisplayedarea()
 
     def rightarrowevent(self):
         """
@@ -1718,7 +1725,7 @@
         self.stdscr = stdscr
         # error during initialization, cannot be printed in the curses
         # interface, it should be printed by the calling code
-        self.initerr = None
+        self.initexc = None
         self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
 
         curses.start_color()
@@ -1751,7 +1758,8 @@
         try:
             self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
         except curses.error:
-            self.initerr = _('this diff is too large to be displayed')
+            self.initexc = fallbackerror(
+                _('this diff is too large to be displayed'))
             return
         # initialize selecteditemendline (initial start-line is 0)
         self.selecteditemendline = self.getnumlinesdisplayed(
--- a/mercurial/debugcommands.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/debugcommands.py	Thu Jul 19 13:55:54 2018 -0400
@@ -21,7 +21,6 @@
 import string
 import subprocess
 import sys
-import tempfile
 import time
 
 from .i18n import _
@@ -71,7 +70,6 @@
     scmutil,
     setdiscovery,
     simplemerge,
-    smartset,
     sshpeer,
     sslutil,
     streamclone,
@@ -183,18 +181,14 @@
         initialmergedlines.append("")
 
     tags = []
-
-    wlock = lock = tr = None
-    try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        tr = repo.transaction("builddag")
-
+    progress = ui.makeprogress(_('building'), unit=_('revisions'),
+                               total=total)
+    with progress, repo.wlock(), repo.lock(), repo.transaction("builddag"):
         at = -1
         atbranch = 'default'
         nodeids = []
         id = 0
-        ui.progress(_('building'), id, unit=_('revisions'), total=total)
+        progress.update(id)
         for type, data in dagparser.parsedag(text):
             if type == 'n':
                 ui.note(('node %s\n' % pycompat.bytestr(data)))
@@ -267,14 +261,10 @@
             elif type == 'a':
                 ui.note(('branch %s\n' % data))
                 atbranch = data
-            ui.progress(_('building'), id, unit=_('revisions'), total=total)
-        tr.close()
+            progress.update(id)
 
         if tags:
             repo.vfs.write("localtags", "".join(tags))
-    finally:
-        ui.progress(_('building'), None)
-        release(tr, lock, wlock)
 
 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
     indent_string = ' ' * indent
@@ -437,7 +427,7 @@
         'hg debugcolor')
 def debugcolor(ui, repo, **opts):
     """show available color, effects or style"""
-    ui.write(('color mode: %s\n') % ui._colormode)
+    ui.write(('color mode: %s\n') % stringutil.pprint(ui._colormode))
     if opts.get(r'style'):
         return _debugdisplaystyle(ui)
     else:
@@ -630,6 +620,8 @@
     opts = pycompat.byteskwargs(opts)
     r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
     index = r.index
+    start = r.start
+    length = r.length
     generaldelta = r.version & revlog.FLAG_GENERALDELTA
     withsparseread = getattr(r, '_withsparseread', False)
 
@@ -677,8 +669,6 @@
         comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
         chainbase = chain[0]
         chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
-        start = r.start
-        length = r.length
         basestart = start(chainbase)
         revstart = start(rev)
         lineardist = revstart + comp - basestart
@@ -688,8 +678,15 @@
         except IndexError:
             prevrev = -1
 
-        chainratio = float(chainsize) / float(uncomp)
-        extraratio = float(extradist) / float(chainsize)
+        if uncomp != 0:
+            chainratio = float(chainsize) / float(uncomp)
+        else:
+            chainratio = chainsize
+
+        if chainsize != 0:
+            extraratio = float(extradist) / float(chainsize)
+        else:
+            extraratio = extradist
 
         fm.startitem()
         fm.write('rev chainid chainlen prevrev deltatype compsize '
@@ -718,7 +715,10 @@
                 if largestblock < blksize:
                     largestblock = blksize
 
-            readdensity = float(chainsize) / float(readsize)
+            if readsize:
+                readdensity = float(chainsize) / float(readsize)
+            else:
+                readdensity = 1
 
             fm.write('readsize largestblock readdensity srchunks',
                      ' %10d %10d %9.5f %8d',
@@ -838,8 +838,8 @@
         if output:
             dest.close()
 
-@command('debugextensions', cmdutil.formatteropts, [], norepo=True)
-def debugextensions(ui, **opts):
+@command('debugextensions', cmdutil.formatteropts, [], optionalrepo=True)
+def debugextensions(ui, repo, **opts):
     '''show information about active extensions'''
     opts = pycompat.byteskwargs(opts)
     exts = extensions.extensions(ui)
@@ -885,16 +885,38 @@
     fm.end()
 
 @command('debugfileset',
-    [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
-    _('[-r REV] FILESPEC'))
+    [('r', 'rev', '', _('apply the filespec on this revision'), _('REV')),
+     ('', 'all-files', False,
+      _('test files from all revisions and working directory'))],
+    _('[-r REV] [--all-files] FILESPEC'))
 def debugfileset(ui, repo, expr, **opts):
     '''parse and apply a fileset specification'''
-    ctx = scmutil.revsingle(repo, opts.get(r'rev'), None)
+    opts = pycompat.byteskwargs(opts)
+    ctx = scmutil.revsingle(repo, opts.get('rev'), None)
     if ui.verbose:
         tree = fileset.parse(expr)
         ui.note(fileset.prettyformat(tree), "\n")
 
-    for f in ctx.getfileset(expr):
+    files = set()
+    if opts['all_files']:
+        for r in repo:
+            c = repo[r]
+            files.update(c.files())
+            files.update(c.substate)
+    if opts['all_files'] or ctx.rev() is None:
+        wctx = repo[None]
+        files.update(repo.dirstate.walk(scmutil.matchall(repo),
+                                        subrepos=list(wctx.substate),
+                                        unknown=True, ignored=True))
+        files.update(wctx.substate)
+    else:
+        files.update(ctx.files())
+        files.update(ctx.substate)
+
+    m = ctx.matchfileset(expr)
+    for f in sorted(files):
+        if not m(f):
+            continue
         ui.write("%s\n" % f)
 
 @command('debugformat',
@@ -971,7 +993,7 @@
     ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
     casesensitive = '(unknown)'
     try:
-        with tempfile.NamedTemporaryFile(prefix='.debugfsinfo', dir=path) as f:
+        with pycompat.namedtempfile(prefix='.debugfsinfo', dir=path) as f:
             casesensitive = util.fscasesensitive(f.name) and 'yes' or 'no'
     except OSError:
         pass
@@ -1143,7 +1165,7 @@
     opts = pycompat.byteskwargs(opts)
 
     def writetemp(contents):
-        (fd, name) = tempfile.mkstemp(prefix="hg-debuginstall-")
+        (fd, name) = pycompat.mkstemp(prefix="hg-debuginstall-")
         f = os.fdopen(fd, r"wb")
         f.write(contents)
         f.close()
@@ -1597,7 +1619,7 @@
         if opts['rev']:
             raise error.Abort('cannot select revision when creating marker')
         metadata = {}
-        metadata['user'] = opts['user'] or ui.username()
+        metadata['user'] = encoding.fromlocal(opts['user'] or ui.username())
         succs = tuple(parsenodeid(succ) for succ in successors)
         l = repo.lock()
         try:
@@ -2237,8 +2259,8 @@
         arevs = revset.makematcher(treebystage['analyzed'])(repo)
         brevs = revset.makematcher(treebystage['optimized'])(repo)
         if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
-            ui.write(("* analyzed set:\n"), smartset.prettyformat(arevs), "\n")
-            ui.write(("* optimized set:\n"), smartset.prettyformat(brevs), "\n")
+            ui.write(("* analyzed set:\n"), stringutil.prettyrepr(arevs), "\n")
+            ui.write(("* optimized set:\n"), stringutil.prettyrepr(brevs), "\n")
         arevs = list(arevs)
         brevs = list(brevs)
         if arevs == brevs:
@@ -2261,7 +2283,7 @@
     func = revset.makematcher(tree)
     revs = func(repo)
     if opts['show_set'] or (opts['show_set'] is None and ui.verbose):
-        ui.write(("* set:\n"), smartset.prettyformat(revs), "\n")
+        ui.write(("* set:\n"), stringutil.prettyrepr(revs), "\n")
     if not opts['show_revs']:
         return
     for c in revs:
@@ -2291,7 +2313,13 @@
 
     if opts['logiofd']:
         # Line buffered because output is line based.
-        logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
+        try:
+            logfh = os.fdopen(int(opts['logiofd']), r'ab', 1)
+        except OSError as e:
+            if e.errno != errno.ESPIPE:
+                raise
+            # can't seek a pipe, so `ab` mode fails on py3
+            logfh = os.fdopen(int(opts['logiofd']), r'wb', 1)
     elif opts['logiofile']:
         logfh = open(opts['logiofile'], 'ab', 1)
 
@@ -2484,9 +2512,17 @@
     if revs is None:
         tres = formatter.templateresources(ui, repo)
         t = formatter.maketemplater(ui, tmpl, resources=tres)
+        if ui.verbose:
+            kwds, funcs = t.symbolsuseddefault()
+            ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
+            ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
         ui.write(t.renderdefault(props))
     else:
         displayer = logcmdutil.maketemplater(ui, repo, tmpl)
+        if ui.verbose:
+            kwds, funcs = displayer.t.symbolsuseddefault()
+            ui.write(("* keywords: %s\n") % ', '.join(sorted(kwds)))
+            ui.write(("* functions: %s\n") % ', '.join(sorted(funcs)))
         for r in revs:
             displayer.show(repo[r], **pycompat.strkwargs(props))
         displayer.close()
@@ -2544,7 +2580,8 @@
     """show how files match on given patterns"""
     opts = pycompat.byteskwargs(opts)
     m = scmutil.match(repo[None], pats, opts)
-    ui.write(('matcher: %r\n' % m))
+    if ui.verbose:
+        ui.write(('* matcher:\n'), stringutil.prettyrepr(m), '\n')
     items = list(repo[None].walk(m))
     if not items:
         return
@@ -3018,10 +3055,12 @@
 
                 if isinstance(res, wireprotov2peer.commandresponse):
                     val = list(res.cborobjects())
-                    ui.status(_('response: %s\n') % stringutil.pprint(val))
+                    ui.status(_('response: %s\n') %
+                              stringutil.pprint(val, bprefix=True))
 
                 else:
-                    ui.status(_('response: %s\n') % stringutil.pprint(res))
+                    ui.status(_('response: %s\n') %
+                              stringutil.pprint(res, bprefix=True))
 
         elif action == 'batchbegin':
             if batchedcommands is not None:
@@ -3093,7 +3132,8 @@
                 continue
 
             if res.headers.get('Content-Type') == 'application/mercurial-cbor':
-                ui.write(_('cbor> %s\n') % stringutil.pprint(cbor.loads(body)))
+                ui.write(_('cbor> %s\n') %
+                         stringutil.pprint(cbor.loads(body), bprefix=True))
 
         elif action == 'close':
             peer.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/diffutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,105 @@
+# diffutil.py - utility functions related to diff and patch
+#
+# Copyright 2006 Brendan Cully <brendan@kublai.com>
+# Copyright 2007 Chris Mason <chris.mason@oracle.com>
+# Copyright 2018 Octobus <octobus@octobus.net>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from .i18n import _
+
+from . import (
+    mdiff,
+    pycompat,
+)
+
+def diffallopts(ui, opts=None, untrusted=False, section='diff'):
+    '''return diffopts with all features supported and parsed'''
+    return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
+                           git=True, whitespace=True, formatchanging=True)
+
+def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
+                    whitespace=False, formatchanging=False):
+    '''return diffopts with only opted-in features parsed
+
+    Features:
+    - git: git-style diffs
+    - whitespace: whitespace options like ignoreblanklines and ignorews
+    - formatchanging: options that will likely break or cause correctness issues
+      with most diff parsers
+    '''
+    def get(key, name=None, getter=ui.configbool, forceplain=None):
+        if opts:
+            v = opts.get(key)
+            # diffopts flags are either None-default (which is passed
+            # through unchanged, so we can identify unset values), or
+            # some other falsey default (eg --unified, which defaults
+            # to an empty string). We only want to override the config
+            # entries from hgrc with command line values if they
+            # appear to have been set, which is any truthy value,
+            # True, or False.
+            if v or isinstance(v, bool):
+                return v
+        if forceplain is not None and ui.plain():
+            return forceplain
+        return getter(section, name or key, untrusted=untrusted)
+
+    # core options, expected to be understood by every diff parser
+    buildopts = {
+        'nodates': get('nodates'),
+        'showfunc': get('show_function', 'showfunc'),
+        'context': get('unified', getter=ui.config),
+    }
+    buildopts['xdiff'] = ui.configbool('experimental', 'xdiff')
+
+    if git:
+        buildopts['git'] = get('git')
+
+        # since this is in the experimental section, we need to call
+        # ui.configbool directory
+        buildopts['showsimilarity'] = ui.configbool('experimental',
+                                                    'extendedheader.similarity')
+
+        # need to inspect the ui object instead of using get() since we want to
+        # test for an int
+        hconf = ui.config('experimental', 'extendedheader.index')
+        if hconf is not None:
+            hlen = None
+            try:
+                # the hash config could be an integer (for length of hash) or a
+                # word (e.g. short, full, none)
+                hlen = int(hconf)
+                if hlen < 0 or hlen > 40:
+                    msg = _("invalid length for extendedheader.index: '%d'\n")
+                    ui.warn(msg % hlen)
+            except ValueError:
+                # default value
+                if hconf == 'short' or hconf == '':
+                    hlen = 12
+                elif hconf == 'full':
+                    hlen = 40
+                elif hconf != 'none':
+                    msg = _("invalid value for extendedheader.index: '%s'\n")
+                    ui.warn(msg % hconf)
+            finally:
+                buildopts['index'] = hlen
+
+    if whitespace:
+        buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
+        buildopts['ignorewsamount'] = get('ignore_space_change',
+                                          'ignorewsamount')
+        buildopts['ignoreblanklines'] = get('ignore_blank_lines',
+                                            'ignoreblanklines')
+        buildopts['ignorewseol'] = get('ignore_space_at_eol', 'ignorewseol')
+    if formatchanging:
+        buildopts['text'] = opts and opts.get('text')
+        binary = None if opts is None else opts.get('binary')
+        buildopts['nobinary'] = (not binary if binary is not None
+                                 else get('nobinary', forceplain=False))
+        buildopts['noprefix'] = get('noprefix', forceplain=False)
+        buildopts['worddiff'] = get('word_diff', 'word-diff', forceplain=False)
+
+    return mdiff.diffopts(**pycompat.strkwargs(buildopts))
--- a/mercurial/dispatch.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/dispatch.py	Thu Jul 19 13:55:54 2018 -0400
@@ -83,20 +83,23 @@
 
 def run():
     "run the command in sys.argv"
-    _initstdio()
+    initstdio()
     req = request(pycompat.sysargv[1:])
     err = None
     try:
-        status = (dispatch(req) or 0)
+        status = dispatch(req)
     except error.StdioError as e:
         err = e
         status = -1
+
+    # In all cases we try to flush stdio streams.
     if util.safehasattr(req.ui, 'fout'):
         try:
             req.ui.fout.flush()
         except IOError as e:
             err = e
             status = -1
+
     if util.safehasattr(req.ui, 'ferr'):
         try:
             if err is not None and err.errno != errno.EPIPE:
@@ -112,7 +115,7 @@
     sys.exit(status & 255)
 
 if pycompat.ispy3:
-    def _initstdio():
+    def initstdio():
         pass
 
     def _silencestdio():
@@ -132,7 +135,7 @@
             except IOError:
                 pass
 else:
-    def _initstdio():
+    def initstdio():
         for fp in (sys.stdin, sys.stdout, sys.stderr):
             procutil.setbinary(fp)
 
@@ -172,7 +175,7 @@
     return ' '.join(procutil.shellquote(a) for a in args)
 
 def dispatch(req):
-    "run the command specified in req.args"
+    """run the command specified in req.args; returns an integer status code"""
     if req.ferr:
         ferr = req.ferr
     elif req.ui:
@@ -205,9 +208,9 @@
 
     msg = _formatargs(req.args)
     starttime = util.timer()
-    ret = None
+    ret = 1  # default of Python exit code on unhandled exception
     try:
-        ret = _runcatch(req)
+        ret = _runcatch(req) or 0
     except error.ProgrammingError as inst:
         req.ui.warn(_('** ProgrammingError: %s\n') % inst)
         if inst.hint:
@@ -236,7 +239,7 @@
             req.ui.log('uiblocked', 'ui blocked ms',
                        **pycompat.strkwargs(req.ui._blockedtimes))
         req.ui.log("commandfinish", "%s exited %d after %0.2f seconds\n",
-                   msg, ret or 0, duration)
+                   msg, ret & 255, duration)
         try:
             req._runexithandlers()
         except: # exiting, so no re-raises
@@ -285,8 +288,8 @@
                 req.args[2] != 'serve' or
                 req.args[3] != '--stdio'):
                 raise error.Abort(
-                    _('potentially unsafe serve --stdio invocation: %r') %
-                    (req.args,))
+                    _('potentially unsafe serve --stdio invocation: %s') %
+                    (stringutil.pprint(req.args),))
 
         try:
             debugger = 'pdb'
@@ -808,6 +811,13 @@
     if req.repo:
         uis.add(req.repo.ui)
 
+    if (req.earlyoptions['verbose'] or req.earlyoptions['debug']
+            or req.earlyoptions['quiet']):
+        for opt in ('verbose', 'debug', 'quiet'):
+            val = pycompat.bytestr(bool(req.earlyoptions[opt]))
+            for ui_ in uis:
+                ui_.setconfig('ui', opt, val, '--' + opt)
+
     if req.earlyoptions['profile']:
         for ui_ in uis:
             ui_.setconfig('profiling', 'enabled', 'true', '--profile')
@@ -873,8 +883,11 @@
         if options["profile"]:
             profiler.start()
 
+        # if abbreviated version of this were used, take them in account, now
         if options['verbose'] or options['debug'] or options['quiet']:
             for opt in ('verbose', 'debug', 'quiet'):
+                if options[opt] == req.earlyoptions[opt]:
+                    continue
                 val = pycompat.bytestr(bool(options[opt]))
                 for ui_ in uis:
                     ui_.setconfig('ui', opt, val, '--' + opt)
@@ -1025,7 +1038,7 @@
                      '** which supports versions %s of Mercurial.\n'
                      '** Please disable %s and try your action again.\n'
                      '** If that fixes the bug please report it to %s\n')
-                   % (name, testedwith, name, report))
+                   % (name, testedwith, name, stringutil.forcebytestr(report)))
     else:
         bugtracker = ui.config('ui', 'supportcontact')
         if bugtracker is None:
--- a/mercurial/encoding.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/encoding.py	Thu Jul 19 13:55:54 2018 -0400
@@ -98,6 +98,16 @@
     def __hash__(self):
         return hash(self._utf8) # avoid collisions in local string space
 
+class safelocalstr(bytes):
+    """Tagged string denoting it was previously an internal UTF-8 string,
+    and can be converted back to UTF-8 losslessly
+
+    >>> assert safelocalstr(b'\\xc3') == b'\\xc3'
+    >>> assert b'\\xc3' == safelocalstr(b'\\xc3')
+    >>> assert b'\\xc3' in {safelocalstr(b'\\xc3'): 0}
+    >>> assert safelocalstr(b'\\xc3') in {b'\\xc3': 0}
+    """
+
 def tolocal(s):
     """
     Convert a string from internal UTF-8 to local encoding
@@ -145,7 +155,7 @@
             r = u.encode(_sysstr(encoding), u"replace")
             if u == r.decode(_sysstr(encoding)):
                 # r is a safe, non-lossy encoding of s
-                return r
+                return safelocalstr(r)
             return localstr(s, r)
         except UnicodeDecodeError:
             # we should only get here if we're looking at an ancient changeset
@@ -154,7 +164,7 @@
                 r = u.encode(_sysstr(encoding), u"replace")
                 if u == r.decode(_sysstr(encoding)):
                     # r is a safe, non-lossy encoding of s
-                    return r
+                    return safelocalstr(r)
                 return localstr(u.encode('UTF-8'), r)
             except UnicodeDecodeError:
                 u = s.decode("utf-8", "replace") # last ditch
@@ -407,7 +417,7 @@
     JSON is problematic for us because it doesn't support non-Unicode
     bytes. To deal with this, we take the following approach:
 
-    - localstr objects are converted back to UTF-8
+    - localstr/safelocalstr objects are converted back to UTF-8
     - valid UTF-8/ASCII strings are passed as-is
     - other strings are converted to UTF-8b surrogate encoding
     - apply JSON-specified string escaping
@@ -500,6 +510,7 @@
     - local strings that have a cached known UTF-8 encoding (aka
       localstr) get sent as UTF-8 so Unicode-oriented clients get the
       Unicode data they want
+    - non-lossy local strings (aka safelocalstr) get sent as UTF-8 as well
     - because we must preserve UTF-8 bytestring in places such as
       filenames, metadata can't be roundtripped without help
 
@@ -509,11 +520,17 @@
     internal surrogate encoding as a UTF-8 string.)
     '''
 
-    if not isinstance(s, localstr) and isasciistr(s):
+    if isinstance(s, localstr):
+        # assume that the original UTF-8 sequence would never contain
+        # invalid characters in U+DCxx range
+        return s._utf8
+    elif isinstance(s, safelocalstr):
+        # already verified that s is non-lossy in legacy encoding, which
+        # shouldn't contain characters in U+DCxx range
+        return fromlocal(s)
+    elif isasciistr(s):
         return s
     if "\xed" not in s:
-        if isinstance(s, localstr):
-            return s._utf8
         try:
             s.decode('utf-8', _utf8strict)
             return s
--- a/mercurial/error.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/error.py	Thu Jul 19 13:55:54 2018 -0400
@@ -241,7 +241,7 @@
                 if val is None:
                     entries.append(val)
                 else:
-                    entries.append("%s=%r" % (par, val))
+                    entries.append("%s=%r" % (par, pycompat.maybebytestr(val)))
         if entries:
             msg = '%s - %s' % (msg, ', '.join(entries))
         ValueError.__init__(self, msg)
--- a/mercurial/exchange.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/exchange.py	Thu Jul 19 13:55:54 2018 -0400
@@ -531,6 +531,9 @@
         _pushobsolete(pushop)
         _pushbookmark(pushop)
 
+    if repo.ui.configbool('experimental', 'remotenames'):
+        logexchange.pullremotenames(repo, remote)
+
     return pushop
 
 # list of steps to perform discovery before push
@@ -658,7 +661,7 @@
     ui.debug("checking for updated bookmarks\n")
     ancestors = ()
     if pushop.revs:
-        revnums = map(repo.changelog.rev, pushop.revs)
+        revnums = pycompat.maplist(repo.changelog.rev, pushop.revs)
         ancestors = repo.changelog.ancestors(revnums, inclusive=True)
 
     remotebookmark = listkeys(remote, 'bookmarks')
--- a/mercurial/extensions.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/extensions.py	Thu Jul 19 13:55:54 2018 -0400
@@ -7,6 +7,8 @@
 
 from __future__ import absolute_import
 
+import ast
+import collections
 import functools
 import imp
 import inspect
@@ -121,10 +123,11 @@
 def _reportimporterror(ui, err, failed, next):
     # note: this ui.debug happens before --debug is processed,
     #       Use --config ui.debug=1 to see them.
-    ui.debug('could not import %s (%s): trying %s\n'
-             % (failed, stringutil.forcebytestr(err), next))
-    if ui.debugflag:
-        ui.traceback()
+    if ui.configbool('devel', 'debug.extensions'):
+        ui.debug('could not import %s (%s): trying %s\n'
+                 % (failed, stringutil.forcebytestr(err), next))
+        if ui.debugflag:
+            ui.traceback()
 
 def _rejectunicode(name, xs):
     if isinstance(xs, (list, set, tuple)):
@@ -145,9 +148,6 @@
     """Check if extension commands have required attributes"""
     for c, e in cmdtable.iteritems():
         f = e[0]
-        if getattr(f, '_deprecatedregistrar', False):
-            ui.deprecwarn("cmdutil.command is deprecated, use "
-                          "registrar.command to register '%s'" % c, '4.6')
         missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
         if not missing:
             continue
@@ -541,9 +541,8 @@
         fn = getattr(fn, '_origfunc', None)
     return result
 
-def _disabledpaths(strip_init=False):
-    '''find paths of disabled extensions. returns a dict of {name: path}
-    removes /__init__.py from packages if strip_init is True'''
+def _disabledpaths():
+    '''find paths of disabled extensions. returns a dict of {name: path}'''
     import hgext
     extpath = os.path.dirname(
         os.path.abspath(pycompat.fsencode(hgext.__file__)))
@@ -562,8 +561,6 @@
             path = os.path.join(extpath, e, '__init__.py')
             if not os.path.exists(path):
                 continue
-            if strip_init:
-                path = os.path.dirname(path)
         if name in exts or name in _order or name == '__init__':
             continue
         exts[name] = path
@@ -609,12 +606,10 @@
 def _disabledhelp(path):
     '''retrieve help synopsis of a disabled extension (without importing)'''
     try:
-        file = open(path)
+        with open(path, 'rb') as src:
+            doc = _moduledoc(src)
     except IOError:
         return
-    else:
-        doc = _moduledoc(file)
-        file.close()
 
     if doc: # extracting localized synopsis
         return gettext(doc)
@@ -658,48 +653,82 @@
     if name in paths:
         return _disabledhelp(paths[name])
 
+def _walkcommand(node):
+    """Scan @command() decorators in the tree starting at node"""
+    todo = collections.deque([node])
+    while todo:
+        node = todo.popleft()
+        if not isinstance(node, ast.FunctionDef):
+            todo.extend(ast.iter_child_nodes(node))
+            continue
+        for d in node.decorator_list:
+            if not isinstance(d, ast.Call):
+                continue
+            if not isinstance(d.func, ast.Name):
+                continue
+            if d.func.id != r'command':
+                continue
+            yield d
+
+def _disabledcmdtable(path):
+    """Construct a dummy command table without loading the extension module
+
+    This may raise IOError or SyntaxError.
+    """
+    with open(path, 'rb') as src:
+        root = ast.parse(src.read(), path)
+    cmdtable = {}
+    for node in _walkcommand(root):
+        if not node.args:
+            continue
+        a = node.args[0]
+        if isinstance(a, ast.Str):
+            name = pycompat.sysbytes(a.s)
+        elif pycompat.ispy3 and isinstance(a, ast.Bytes):
+            name = a.s
+        else:
+            continue
+        cmdtable[name] = (None, [], b'')
+    return cmdtable
+
+def _finddisabledcmd(ui, cmd, name, path, strict):
+    try:
+        cmdtable = _disabledcmdtable(path)
+    except (IOError, SyntaxError):
+        return
+    try:
+        aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
+    except (error.AmbiguousCommand, error.UnknownCommand):
+        return
+    for c in aliases:
+        if c.startswith(cmd):
+            cmd = c
+            break
+    else:
+        cmd = aliases[0]
+    doc = _disabledhelp(path)
+    return (cmd, name, doc)
+
 def disabledcmd(ui, cmd, strict=False):
-    '''import disabled extensions until cmd is found.
-    returns (cmdname, extname, module)'''
+    '''find cmd from disabled extensions without importing.
+    returns (cmdname, extname, doc)'''
 
-    paths = _disabledpaths(strip_init=True)
+    paths = _disabledpaths()
     if not paths:
         raise error.UnknownCommand(cmd)
 
-    def findcmd(cmd, name, path):
-        try:
-            mod = loadpath(path, 'hgext.%s' % name)
-        except Exception:
-            return
-        try:
-            aliases, entry = cmdutil.findcmd(cmd,
-                getattr(mod, 'cmdtable', {}), strict)
-        except (error.AmbiguousCommand, error.UnknownCommand):
-            return
-        except Exception:
-            ui.warn(_('warning: error finding commands in %s\n') % path)
-            ui.traceback()
-            return
-        for c in aliases:
-            if c.startswith(cmd):
-                cmd = c
-                break
-        else:
-            cmd = aliases[0]
-        return (cmd, name, mod)
-
     ext = None
     # first, search for an extension with the same name as the command
     path = paths.pop(cmd, None)
     if path:
-        ext = findcmd(cmd, cmd, path)
+        ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
     if not ext:
         # otherwise, interrogate each extension until there's a match
         for name, path in paths.iteritems():
-            ext = findcmd(cmd, name, path)
+            ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
             if ext:
                 break
-    if ext and 'DEPRECATED' not in ext.__doc__:
+    if ext:
         return ext
 
     raise error.UnknownCommand(cmd)
@@ -729,7 +758,7 @@
     else:
         version = ''
     if isinstance(version, (list, tuple)):
-        version = '.'.join(str(o) for o in version)
+        version = '.'.join(pycompat.bytestr(o) for o in version)
     return version
 
 def ismoduleinternal(module):
--- a/mercurial/filelog.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/filelog.py	Thu Jul 19 13:55:54 2018 -0400
@@ -215,12 +215,12 @@
         self._revlog._lazydeltabase = value
 
     @property
-    def _aggressivemergedeltas(self):
-        return self._revlog._aggressivemergedeltas
+    def _deltabothparents(self):
+        return self._revlog._deltabothparents
 
-    @_aggressivemergedeltas.setter
-    def _aggressivemergedeltas(self, value):
-        self._revlog._aggressivemergedeltas = value
+    @_deltabothparents.setter
+    def _deltabothparents(self, value):
+        self._revlog._deltabothparents = value
 
     @property
     def _inline(self):
--- a/mercurial/filemerge.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/filemerge.py	Thu Jul 19 13:55:54 2018 -0400
@@ -11,7 +11,6 @@
 import os
 import re
 import shutil
-import tempfile
 
 from .i18n import _
 from .node import nullid, short
@@ -114,8 +113,16 @@
 def _findtool(ui, tool):
     if tool in internals:
         return tool
+    cmd = _toolstr(ui, tool, "executable", tool)
+    if cmd.startswith('python:'):
+        return cmd
     return findexternaltool(ui, tool)
 
+def _quotetoolpath(cmd):
+    if cmd.startswith('python:'):
+        return cmd
+    return procutil.shellquote(cmd)
+
 def findexternaltool(ui, tool):
     for kn in ("regkey", "regkeyalt"):
         k = _toolstr(ui, tool, kn)
@@ -165,7 +172,7 @@
             return ":prompt", None
         else:
             if toolpath:
-                return (force, procutil.shellquote(toolpath))
+                return (force, _quotetoolpath(toolpath))
             else:
                 # mimic HGMERGE if given tool not found
                 return (force, force)
@@ -183,7 +190,7 @@
         mf = match.match(repo.root, '', [pat])
         if mf(path) and check(tool, pat, symlink, False, changedelete):
             toolpath = _findtool(ui, tool)
-            return (tool, procutil.shellquote(toolpath))
+            return (tool, _quotetoolpath(toolpath))
 
     # then merge tools
     tools = {}
@@ -208,7 +215,7 @@
     for p, t in tools:
         if check(t, None, symlink, binary, changedelete):
             toolpath = _findtool(ui, t)
-            return (t, procutil.shellquote(toolpath))
+            return (t, _quotetoolpath(toolpath))
 
     # internal merge or prompt as last resort
     if symlink or binary or changedelete:
@@ -325,7 +332,7 @@
         return filectx
 
 def _premerge(repo, fcd, fco, fca, toolconf, files, labels=None):
-    tool, toolpath, binary, symlink = toolconf
+    tool, toolpath, binary, symlink, scriptfn = toolconf
     if symlink or fcd.isabsent() or fco.isabsent():
         return 1
     unused, unused, unused, back = files
@@ -361,7 +368,7 @@
     return 1 # continue merging
 
 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
-    tool, toolpath, binary, symlink = toolconf
+    tool, toolpath, binary, symlink, scriptfn = toolconf
     if symlink:
         repo.ui.warn(_('warning: internal %s cannot merge symlinks '
                        'for %s\n') % (tool, fcd.path()))
@@ -430,7 +437,7 @@
     Generic driver for _imergelocal and _imergeother
     """
     assert localorother is not None
-    tool, toolpath, binary, symlink = toolconf
+    tool, toolpath, binary, symlink, scriptfn = toolconf
     r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
                                 localorother=localorother)
     return True, r
@@ -510,7 +517,7 @@
                                             'external merge tools')
 
 def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
-    tool, toolpath, binary, symlink = toolconf
+    tool, toolpath, binary, symlink, scriptfn = toolconf
     if fcd.isabsent() or fco.isabsent():
         repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
                        'for %s\n') % (tool, fcd.path()))
@@ -551,12 +558,36 @@
         args = util.interpolate(
             br'\$', replace, args,
             lambda s: procutil.shellquote(util.localpath(s)))
-        cmd = toolpath + ' ' + args
         if _toolbool(ui, tool, "gui"):
             repo.ui.status(_('running merge tool %s for file %s\n') %
                            (tool, fcd.path()))
-        repo.ui.debug('launching merge tool: %s\n' % cmd)
-        r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
+        if scriptfn is None:
+            cmd = toolpath + ' ' + args
+            repo.ui.debug('launching merge tool: %s\n' % cmd)
+            r = ui.system(cmd, cwd=repo.root, environ=env,
+                          blockedtag='mergetool')
+        else:
+            repo.ui.debug('launching python merge script: %s:%s\n' %
+                          (toolpath, scriptfn))
+            r = 0
+            try:
+                # avoid cycle cmdutil->merge->filemerge->extensions->cmdutil
+                from . import extensions
+                mod = extensions.loadpath(toolpath, 'hgmerge.%s' % tool)
+            except Exception:
+                raise error.Abort(_("loading python merge script failed: %s") %
+                                  toolpath)
+            mergefn = getattr(mod, scriptfn, None)
+            if mergefn is None:
+                raise error.Abort(_("%s does not have function: %s") %
+                                  (toolpath, scriptfn))
+            argslist = procutil.shellsplit(args)
+            # avoid cycle cmdutil->merge->filemerge->hook->extensions->cmdutil
+            from . import hook
+            ret, raised = hook.pythonhook(ui, repo, "merge", toolpath,
+                                          mergefn, {'args': argslist}, True)
+            if raised:
+                r = 1
         repo.ui.debug('merge tool returned: %d\n' % r)
         return True, r, False
 
@@ -681,7 +712,7 @@
     tmproot = None
     tmprootprefix = repo.ui.config('experimental', 'mergetempdirprefix')
     if tmprootprefix:
-        tmproot = tempfile.mkdtemp(prefix=tmprootprefix)
+        tmproot = pycompat.mkdtemp(prefix=tmprootprefix)
 
     def maketempfrompath(prefix, path):
         fullbase, ext = os.path.splitext(path)
@@ -692,7 +723,7 @@
                 name += ext
             f = open(name, r"wb")
         else:
-            fd, name = tempfile.mkstemp(prefix=pre + '.', suffix=ext)
+            fd, name = pycompat.mkstemp(prefix=pre + '.', suffix=ext)
             f = os.fdopen(fd, r"wb")
         return f, name
 
@@ -751,9 +782,24 @@
     symlink = 'l' in fcd.flags() + fco.flags()
     changedelete = fcd.isabsent() or fco.isabsent()
     tool, toolpath = _picktool(repo, ui, fd, binary, symlink, changedelete)
+    scriptfn = None
     if tool in internals and tool.startswith('internal:'):
         # normalize to new-style names (':merge' etc)
         tool = tool[len('internal'):]
+    if toolpath and toolpath.startswith('python:'):
+        invalidsyntax = False
+        if toolpath.count(':') >= 2:
+            script, scriptfn = toolpath[7:].rsplit(':', 1)
+            if not scriptfn:
+                invalidsyntax = True
+            # missing :callable can lead to spliting on windows drive letter
+            if '\\' in scriptfn or '/' in scriptfn:
+                invalidsyntax = True
+        else:
+            invalidsyntax = True
+        if invalidsyntax:
+            raise error.Abort(_("invalid 'python:' syntax: %s") % toolpath)
+        toolpath = script
     ui.debug("picked tool '%s' for %s (binary %s symlink %s changedelete %s)\n"
              % (tool, fd, pycompat.bytestr(binary), pycompat.bytestr(symlink),
                     pycompat.bytestr(changedelete)))
@@ -774,7 +820,7 @@
         precheck = None
         isexternal = True
 
-    toolconf = tool, toolpath, binary, symlink
+    toolconf = tool, toolpath, binary, symlink, scriptfn
 
     if mergetype == nomerge:
         r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
--- a/mercurial/fileset.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/fileset.py	Thu Jul 19 13:55:54 2018 -0400
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import errno
 import re
 
 from .i18n import _
@@ -126,148 +127,6 @@
         return _getkindpat(x[1], x[2], allkinds, err)
     return getstring(x, err)
 
-def getset(mctx, x):
-    if not x:
-        raise error.ParseError(_("missing argument"))
-    return methods[x[0]](mctx, *x[1:])
-
-def stringset(mctx, x):
-    m = mctx.matcher([x])
-    return [f for f in mctx.subset if m(f)]
-
-def kindpatset(mctx, x, y):
-    return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
-                                       _("pattern must be a string")))
-
-def andset(mctx, x, y):
-    return getset(mctx.narrow(getset(mctx, x)), y)
-
-def orset(mctx, x, y):
-    # needs optimizing
-    xl = getset(mctx, x)
-    yl = getset(mctx, y)
-    return xl + [f for f in yl if f not in xl]
-
-def notset(mctx, x):
-    s = set(getset(mctx, x))
-    return [r for r in mctx.subset if r not in s]
-
-def minusset(mctx, x, y):
-    xl = getset(mctx, x)
-    yl = set(getset(mctx, y))
-    return [f for f in xl if f not in yl]
-
-def negateset(mctx, x):
-    raise error.ParseError(_("can't use negate operator in this context"))
-
-def listset(mctx, a, b):
-    raise error.ParseError(_("can't use a list in this context"),
-                           hint=_('see hg help "filesets.x or y"'))
-
-# symbols are callable like:
-#  fun(mctx, x)
-# with:
-#  mctx - current matchctx instance
-#  x - argument in tree form
-symbols = {}
-
-# filesets using matchctx.status()
-_statuscallers = set()
-
-# filesets using matchctx.existing()
-_existingcallers = set()
-
-predicate = registrar.filesetpredicate()
-
-@predicate('modified()', callstatus=True)
-def modified(mctx, x):
-    """File that is modified according to :hg:`status`.
-    """
-    # i18n: "modified" is a keyword
-    getargs(x, 0, 0, _("modified takes no arguments"))
-    s = set(mctx.status().modified)
-    return [f for f in mctx.subset if f in s]
-
-@predicate('added()', callstatus=True)
-def added(mctx, x):
-    """File that is added according to :hg:`status`.
-    """
-    # i18n: "added" is a keyword
-    getargs(x, 0, 0, _("added takes no arguments"))
-    s = set(mctx.status().added)
-    return [f for f in mctx.subset if f in s]
-
-@predicate('removed()', callstatus=True)
-def removed(mctx, x):
-    """File that is removed according to :hg:`status`.
-    """
-    # i18n: "removed" is a keyword
-    getargs(x, 0, 0, _("removed takes no arguments"))
-    s = set(mctx.status().removed)
-    return [f for f in mctx.subset if f in s]
-
-@predicate('deleted()', callstatus=True)
-def deleted(mctx, x):
-    """Alias for ``missing()``.
-    """
-    # i18n: "deleted" is a keyword
-    getargs(x, 0, 0, _("deleted takes no arguments"))
-    s = set(mctx.status().deleted)
-    return [f for f in mctx.subset if f in s]
-
-@predicate('missing()', callstatus=True)
-def missing(mctx, x):
-    """File that is missing according to :hg:`status`.
-    """
-    # i18n: "missing" is a keyword
-    getargs(x, 0, 0, _("missing takes no arguments"))
-    s = set(mctx.status().deleted)
-    return [f for f in mctx.subset if f in s]
-
-@predicate('unknown()', callstatus=True)
-def unknown(mctx, x):
-    """File that is unknown according to :hg:`status`. These files will only be
-    considered if this predicate is used.
-    """
-    # i18n: "unknown" is a keyword
-    getargs(x, 0, 0, _("unknown takes no arguments"))
-    s = set(mctx.status().unknown)
-    return [f for f in mctx.subset if f in s]
-
-@predicate('ignored()', callstatus=True)
-def ignored(mctx, x):
-    """File that is ignored according to :hg:`status`. These files will only be
-    considered if this predicate is used.
-    """
-    # i18n: "ignored" is a keyword
-    getargs(x, 0, 0, _("ignored takes no arguments"))
-    s = set(mctx.status().ignored)
-    return [f for f in mctx.subset if f in s]
-
-@predicate('clean()', callstatus=True)
-def clean(mctx, x):
-    """File that is clean according to :hg:`status`.
-    """
-    # i18n: "clean" is a keyword
-    getargs(x, 0, 0, _("clean takes no arguments"))
-    s = set(mctx.status().clean)
-    return [f for f in mctx.subset if f in s]
-
-def func(mctx, a, b):
-    funcname = getsymbol(a)
-    if funcname in symbols:
-        enabled = mctx._existingenabled
-        mctx._existingenabled = funcname in _existingcallers
-        try:
-            return symbols[funcname](mctx, b)
-        finally:
-            mctx._existingenabled = enabled
-
-    keep = lambda fn: getattr(fn, '__doc__', None) is not None
-
-    syms = [s for (s, fn) in symbols.items() if keep(fn)]
-    raise error.UnknownIdentifier(funcname, syms)
-
 def getlist(x):
     if not x:
         return []
@@ -281,29 +140,169 @@
         raise error.ParseError(err)
     return l
 
-@predicate('binary()', callexisting=True)
+def getmatch(mctx, x):
+    if not x:
+        raise error.ParseError(_("missing argument"))
+    return methods[x[0]](mctx, *x[1:])
+
+def stringmatch(mctx, x):
+    return mctx.matcher([x])
+
+def kindpatmatch(mctx, x, y):
+    return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
+                                         _("pattern must be a string")))
+
+def andmatch(mctx, x, y):
+    xm = getmatch(mctx, x)
+    ym = getmatch(mctx, y)
+    return matchmod.intersectmatchers(xm, ym)
+
+def ormatch(mctx, x, y):
+    xm = getmatch(mctx, x)
+    ym = getmatch(mctx, y)
+    return matchmod.unionmatcher([xm, ym])
+
+def notmatch(mctx, x):
+    m = getmatch(mctx, x)
+    return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
+
+def minusmatch(mctx, x, y):
+    xm = getmatch(mctx, x)
+    ym = getmatch(mctx, y)
+    return matchmod.differencematcher(xm, ym)
+
+def negatematch(mctx, x):
+    raise error.ParseError(_("can't use negate operator in this context"))
+
+def listmatch(mctx, x, y):
+    raise error.ParseError(_("can't use a list in this context"),
+                           hint=_('see hg help "filesets.x or y"'))
+
+def func(mctx, a, b):
+    funcname = getsymbol(a)
+    if funcname in symbols:
+        return symbols[funcname](mctx, b)
+
+    keep = lambda fn: getattr(fn, '__doc__', None) is not None
+
+    syms = [s for (s, fn) in symbols.items() if keep(fn)]
+    raise error.UnknownIdentifier(funcname, syms)
+
+# symbols are callable like:
+#  fun(mctx, x)
+# with:
+#  mctx - current matchctx instance
+#  x - argument in tree form
+symbols = {}
+
+# filesets using matchctx.status()
+_statuscallers = set()
+
+predicate = registrar.filesetpredicate()
+
+@predicate('modified()', callstatus=True)
+def modified(mctx, x):
+    """File that is modified according to :hg:`status`.
+    """
+    # i18n: "modified" is a keyword
+    getargs(x, 0, 0, _("modified takes no arguments"))
+    s = set(mctx.status().modified)
+    return mctx.predicate(s.__contains__, predrepr='modified')
+
+@predicate('added()', callstatus=True)
+def added(mctx, x):
+    """File that is added according to :hg:`status`.
+    """
+    # i18n: "added" is a keyword
+    getargs(x, 0, 0, _("added takes no arguments"))
+    s = set(mctx.status().added)
+    return mctx.predicate(s.__contains__, predrepr='added')
+
+@predicate('removed()', callstatus=True)
+def removed(mctx, x):
+    """File that is removed according to :hg:`status`.
+    """
+    # i18n: "removed" is a keyword
+    getargs(x, 0, 0, _("removed takes no arguments"))
+    s = set(mctx.status().removed)
+    return mctx.predicate(s.__contains__, predrepr='removed')
+
+@predicate('deleted()', callstatus=True)
+def deleted(mctx, x):
+    """Alias for ``missing()``.
+    """
+    # i18n: "deleted" is a keyword
+    getargs(x, 0, 0, _("deleted takes no arguments"))
+    s = set(mctx.status().deleted)
+    return mctx.predicate(s.__contains__, predrepr='deleted')
+
+@predicate('missing()', callstatus=True)
+def missing(mctx, x):
+    """File that is missing according to :hg:`status`.
+    """
+    # i18n: "missing" is a keyword
+    getargs(x, 0, 0, _("missing takes no arguments"))
+    s = set(mctx.status().deleted)
+    return mctx.predicate(s.__contains__, predrepr='deleted')
+
+@predicate('unknown()', callstatus=True)
+def unknown(mctx, x):
+    """File that is unknown according to :hg:`status`."""
+    # i18n: "unknown" is a keyword
+    getargs(x, 0, 0, _("unknown takes no arguments"))
+    s = set(mctx.status().unknown)
+    return mctx.predicate(s.__contains__, predrepr='unknown')
+
+@predicate('ignored()', callstatus=True)
+def ignored(mctx, x):
+    """File that is ignored according to :hg:`status`."""
+    # i18n: "ignored" is a keyword
+    getargs(x, 0, 0, _("ignored takes no arguments"))
+    s = set(mctx.status().ignored)
+    return mctx.predicate(s.__contains__, predrepr='ignored')
+
+@predicate('clean()', callstatus=True)
+def clean(mctx, x):
+    """File that is clean according to :hg:`status`.
+    """
+    # i18n: "clean" is a keyword
+    getargs(x, 0, 0, _("clean takes no arguments"))
+    s = set(mctx.status().clean)
+    return mctx.predicate(s.__contains__, predrepr='clean')
+
+@predicate('tracked()')
+def tracked(mctx, x):
+    """File that is under Mercurial control."""
+    # i18n: "tracked" is a keyword
+    getargs(x, 0, 0, _("tracked takes no arguments"))
+    return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
+
+@predicate('binary()')
 def binary(mctx, x):
     """File that appears to be binary (contains NUL bytes).
     """
     # i18n: "binary" is a keyword
     getargs(x, 0, 0, _("binary takes no arguments"))
-    return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
+    return mctx.fpredicate(lambda fctx: fctx.isbinary(),
+                           predrepr='binary', cache=True)
 
-@predicate('exec()', callexisting=True)
+@predicate('exec()')
 def exec_(mctx, x):
     """File that is marked as executable.
     """
     # i18n: "exec" is a keyword
     getargs(x, 0, 0, _("exec takes no arguments"))
-    return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
+    ctx = mctx.ctx
+    return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
 
-@predicate('symlink()', callexisting=True)
+@predicate('symlink()')
 def symlink(mctx, x):
     """File that is marked as a symlink.
     """
     # i18n: "symlink" is a keyword
     getargs(x, 0, 0, _("symlink takes no arguments"))
-    return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
+    ctx = mctx.ctx
+    return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
 
 @predicate('resolved()')
 def resolved(mctx, x):
@@ -312,9 +311,10 @@
     # i18n: "resolved" is a keyword
     getargs(x, 0, 0, _("resolved takes no arguments"))
     if mctx.ctx.rev() is not None:
-        return []
+        return mctx.never()
     ms = merge.mergestate.read(mctx.ctx.repo())
-    return [f for f in mctx.subset if f in ms and ms[f] == 'r']
+    return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
+                          predrepr='resolved')
 
 @predicate('unresolved()')
 def unresolved(mctx, x):
@@ -323,9 +323,10 @@
     # i18n: "unresolved" is a keyword
     getargs(x, 0, 0, _("unresolved takes no arguments"))
     if mctx.ctx.rev() is not None:
-        return []
+        return mctx.never()
     ms = merge.mergestate.read(mctx.ctx.repo())
-    return [f for f in mctx.subset if f in ms and ms[f] == 'u']
+    return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
+                          predrepr='unresolved')
 
 @predicate('hgignore()')
 def hgignore(mctx, x):
@@ -333,8 +334,7 @@
     """
     # i18n: "hgignore" is a keyword
     getargs(x, 0, 0, _("hgignore takes no arguments"))
-    ignore = mctx.ctx.repo().dirstate._ignore
-    return [f for f in mctx.subset if ignore(f)]
+    return mctx.ctx.repo().dirstate._ignore
 
 @predicate('portable()')
 def portable(mctx, x):
@@ -343,10 +343,10 @@
     """
     # i18n: "portable" is a keyword
     getargs(x, 0, 0, _("portable takes no arguments"))
-    checkwinfilename = util.checkwinfilename
-    return [f for f in mctx.subset if checkwinfilename(f) is None]
+    return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
+                          predrepr='portable')
 
-@predicate('grep(regex)', callexisting=True)
+@predicate('grep(regex)')
 def grep(mctx, x):
     """File contains the given regular expression.
     """
@@ -354,8 +354,10 @@
         # i18n: "grep" is a keyword
         r = re.compile(getstring(x, _("grep requires a pattern")))
     except re.error as e:
-        raise error.ParseError(_('invalid match pattern: %s') % e)
-    return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
+        raise error.ParseError(_('invalid match pattern: %s') %
+                               stringutil.forcebytestr(e))
+    return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
+                           predrepr=('grep(%r)', r.pattern), cache=True)
 
 def _sizetomax(s):
     try:
@@ -373,11 +375,9 @@
     except ValueError:
         raise error.ParseError(_("couldn't parse size: %s") % s)
 
-def sizematcher(x):
+def sizematcher(expr):
     """Return a function(size) -> bool from the ``size()`` expression"""
-
-    # i18n: "size" is a keyword
-    expr = getstring(x, _("size requires an expression")).strip()
+    expr = expr.strip()
     if '-' in expr: # do we have a range?
         a, b = expr.split('-', 1)
         a = util.sizetoint(a)
@@ -400,7 +400,7 @@
         b = _sizetomax(expr)
         return lambda x: x >= a and x <= b
 
-@predicate('size(expression)', callexisting=True)
+@predicate('size(expression)')
 def size(mctx, x):
     """File size matches the given expression. Examples:
 
@@ -409,10 +409,13 @@
     - size('>= .5MB') - files at least 524288 bytes
     - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
     """
-    m = sizematcher(x)
-    return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
+    # i18n: "size" is a keyword
+    expr = getstring(x, _("size requires an expression"))
+    m = sizematcher(expr)
+    return mctx.fpredicate(lambda fctx: m(fctx.size()),
+                           predrepr=('size(%r)', expr), cache=True)
 
-@predicate('encoding(name)', callexisting=True)
+@predicate('encoding(name)')
 def encoding(mctx, x):
     """File can be successfully decoded with the given character
     encoding. May not be useful for encodings other than ASCII and
@@ -422,20 +425,19 @@
     # i18n: "encoding" is a keyword
     enc = getstring(x, _("encoding requires an encoding name"))
 
-    s = []
-    for f in mctx.existing():
-        d = mctx.ctx[f].data()
+    def encp(fctx):
+        d = fctx.data()
         try:
-            d.decode(enc)
+            d.decode(pycompat.sysstr(enc))
+            return True
         except LookupError:
             raise error.Abort(_("unknown encoding '%s'") % enc)
         except UnicodeDecodeError:
-            continue
-        s.append(f)
+            return False
 
-    return s
+    return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
 
-@predicate('eol(style)', callexisting=True)
+@predicate('eol(style)')
 def eol(mctx, x):
     """File contains newlines of the given style (dos, unix, mac). Binary
     files are excluded, files with mixed line endings match multiple
@@ -445,18 +447,18 @@
     # i18n: "eol" is a keyword
     enc = getstring(x, _("eol requires a style name"))
 
-    s = []
-    for f in mctx.existing():
-        d = mctx.ctx[f].data()
-        if stringutil.binary(d):
-            continue
+    def eolp(fctx):
+        if fctx.isbinary():
+            return False
+        d = fctx.data()
         if (enc == 'dos' or enc == 'win') and '\r\n' in d:
-            s.append(f)
+            return True
         elif enc == 'unix' and re.search('(?<!\r)\n', d):
-            s.append(f)
+            return True
         elif enc == 'mac' and re.search('\r(?!\n)', d):
-            s.append(f)
-    return s
+            return True
+        return False
+    return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
 
 @predicate('copied()')
 def copied(mctx, x):
@@ -464,13 +466,10 @@
     """
     # i18n: "copied" is a keyword
     getargs(x, 0, 0, _("copied takes no arguments"))
-    s = []
-    for f in mctx.subset:
-        if f in mctx.ctx:
-            p = mctx.ctx[f].parents()
-            if p and p[0].path() != f:
-                s.append(f)
-    return s
+    def copiedp(fctx):
+        p = fctx.parents()
+        return p and p[0].path() != fctx.path()
+    return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
 
 @predicate('revs(revs, pattern)')
 def revs(mctx, x):
@@ -484,15 +483,15 @@
     repo = mctx.ctx.repo()
     revs = scmutil.revrange(repo, [revspec])
 
-    found = set()
-    result = []
+    matchers = []
     for r in revs:
         ctx = repo[r]
-        for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
-            if f not in found:
-                found.add(f)
-                result.append(f)
-    return result
+        matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
+    if not matchers:
+        return mctx.never()
+    if len(matchers) == 1:
+        return matchers[0]
+    return matchmod.unionmatcher(matchers)
 
 @predicate('status(base, rev, pattern)')
 def status(mctx, x):
@@ -514,7 +513,7 @@
     if not revspec:
         raise error.ParseError(reverr)
     basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
-    return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
+    return getmatch(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
 
 @predicate('subrepo([pattern])')
 def subrepo(mctx, x):
@@ -523,7 +522,7 @@
     # i18n: "subrepo" is a keyword
     getargs(x, 0, 1, _("subrepo takes at most one argument"))
     ctx = mctx.ctx
-    sstate = sorted(ctx.substate)
+    sstate = ctx.substate
     if x:
         pat = getpattern(x, matchmod.allpatternkinds,
                          # i18n: "subrepo" is a keyword
@@ -534,60 +533,85 @@
                 return (s == pat)
         else:
             m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
-        return [sub for sub in sstate if m(sub)]
+        return mctx.predicate(lambda f: f in sstate and m(f),
+                              predrepr=('subrepo(%r)', pat))
     else:
-        return [sub for sub in sstate]
+        return mctx.predicate(sstate.__contains__, predrepr='subrepo')
 
 methods = {
-    'string': stringset,
-    'symbol': stringset,
-    'kindpat': kindpatset,
-    'and': andset,
-    'or': orset,
-    'minus': minusset,
-    'negate': negateset,
-    'list': listset,
-    'group': getset,
-    'not': notset,
+    'string': stringmatch,
+    'symbol': stringmatch,
+    'kindpat': kindpatmatch,
+    'and': andmatch,
+    'or': ormatch,
+    'minus': minusmatch,
+    'negate': negatematch,
+    'list': listmatch,
+    'group': getmatch,
+    'not': notmatch,
     'func': func,
 }
 
 class matchctx(object):
-    def __init__(self, ctx, subset, status=None):
+    def __init__(self, ctx, status=None, badfn=None):
         self.ctx = ctx
-        self.subset = subset
         self._status = status
-        self._existingenabled = False
+        self._badfn = badfn
+
     def status(self):
         return self._status
+
     def matcher(self, patterns):
-        return self.ctx.match(patterns)
-    def filter(self, files):
-        return [f for f in files if f in self.subset]
-    def existing(self):
-        assert self._existingenabled, 'unexpected existing() invocation'
-        if self._status is not None:
-            removed = set(self._status[3])
-            unknown = set(self._status[4] + self._status[5])
+        return self.ctx.match(patterns, badfn=self._badfn)
+
+    def predicate(self, predfn, predrepr=None, cache=False):
+        """Create a matcher to select files by predfn(filename)"""
+        if cache:
+            predfn = util.cachefunc(predfn)
+        repo = self.ctx.repo()
+        return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
+                                         predrepr=predrepr, badfn=self._badfn)
+
+    def fpredicate(self, predfn, predrepr=None, cache=False):
+        """Create a matcher to select files by predfn(fctx) at the current
+        revision
+
+        Missing files are ignored.
+        """
+        ctx = self.ctx
+        if ctx.rev() is None:
+            def fctxpredfn(f):
+                try:
+                    fctx = ctx[f]
+                except error.LookupError:
+                    return False
+                try:
+                    fctx.audit()
+                except error.Abort:
+                    return False
+                try:
+                    return predfn(fctx)
+                except (IOError, OSError) as e:
+                    if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EISDIR):
+                        return False
+                    raise
         else:
-            removed = set()
-            unknown = set()
-        return (f for f in self.subset
-                if (f in self.ctx and f not in removed) or f in unknown)
-    def narrow(self, files):
-        return matchctx(self.ctx, self.filter(files), self._status)
+            def fctxpredfn(f):
+                try:
+                    fctx = ctx[f]
+                except error.LookupError:
+                    return False
+                return predfn(fctx)
+        return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
+
+    def never(self):
+        """Create a matcher to select nothing"""
+        repo = self.ctx.repo()
+        return matchmod.nevermatcher(repo.root, repo.getcwd(),
+                                     badfn=self._badfn)
+
     def switch(self, ctx, status=None):
-        subset = self.filter(_buildsubset(ctx, status))
-        return matchctx(ctx, subset, status)
-
-class fullmatchctx(matchctx):
-    """A match context where any files in any revisions should be valid"""
-
-    def __init__(self, ctx, status=None):
-        subset = _buildsubset(ctx, status)
-        super(fullmatchctx, self).__init__(ctx, subset, status)
-    def switch(self, ctx, status=None):
-        return fullmatchctx(ctx, status)
+        return matchctx(ctx, status, self._badfn)
 
 # filesets using matchctx.switch()
 _switchcallers = [
@@ -608,29 +632,16 @@
                 return True
     return False
 
-def _buildsubset(ctx, status):
-    if status:
-        subset = []
-        for c in status:
-            subset.extend(c)
-        return subset
-    else:
-        return list(ctx.walk(ctx.match([])))
-
-def getfileset(ctx, expr):
+def match(ctx, expr, badfn=None):
+    """Create a matcher for a single fileset expression"""
     tree = parse(expr)
-    return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
+    mctx = matchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
+    return getmatch(mctx, tree)
 
 def _buildstatus(ctx, tree, basectx=None):
     # do we need status info?
 
-    # temporaty boolean to simplify the next conditional
-    purewdir = ctx.rev() is None and basectx is None
-
-    if (_intree(_statuscallers, tree) or
-        # Using matchctx.existing() on a workingctx requires us to check
-        # for deleted files.
-        (purewdir and _intree(_existingcallers, tree))):
+    if _intree(_statuscallers, tree):
         unknown = _intree(['unknown'], tree)
         ignored = _intree(['ignored'], tree)
 
@@ -652,10 +663,8 @@
         symbols[name] = func
         if func._callstatus:
             _statuscallers.add(name)
-        if func._callexisting:
-            _existingcallers.add(name)
 
-# load built-in predicates explicitly to setup _statuscallers/_existingcallers
+# load built-in predicates explicitly to setup _statuscallers
 loadpredicate(None, None, predicate)
 
 # tell hggettext to extract docstrings from these functions:
--- a/mercurial/formatter.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/formatter.py	Thu Jul 19 13:55:54 2018 -0400
@@ -107,7 +107,6 @@
 
 from __future__ import absolute_import, print_function
 
-import collections
 import contextlib
 import itertools
 import os
@@ -117,11 +116,15 @@
     hex,
     short,
 )
+from .thirdparty import (
+    attr,
+)
 
 from . import (
     error,
     pycompat,
     templatefilters,
+    templatefuncs,
     templatekw,
     templater,
     templateutil,
@@ -190,12 +193,18 @@
         # name is mandatory argument for now, but it could be optional if
         # we have default template keyword, e.g. {item}
         return self._converter.formatlist(data, name, fmt, sep)
+    def contexthint(self, datafields):
+        '''set of context object keys to be required given datafields set'''
+        return set()
     def context(self, **ctxs):
         '''insert context objects to be used to render template keywords'''
         ctxs = pycompat.byteskwargs(ctxs)
         assert all(k in {'ctx', 'fctx'} for k in ctxs)
         if self._converter.storecontext:
             self._item.update(ctxs)
+    def datahint(self):
+        '''set of field names to be referenced'''
+        return set()
     def data(self, **data):
         '''insert data into item that's not shown in default output'''
         data = pycompat.byteskwargs(data)
@@ -365,7 +374,7 @@
     @staticmethod
     def formatdate(date, fmt):
         '''return date tuple'''
-        return date
+        return templateutil.date(date)
     @staticmethod
     def formatdict(data, key, value, fmt, sep):
         '''build object that can be evaluated as either plain string or dict'''
@@ -409,12 +418,41 @@
         ref = self._parts[part]
         self._out.write(self._t.render(ref, item))
 
+    @util.propertycache
+    def _symbolsused(self):
+        return self._t.symbolsused(self._tref)
+
+    def contexthint(self, datafields):
+        '''set of context object keys to be required by the template, given
+        datafields overridden by immediate values'''
+        requires = set()
+        ksyms, fsyms = self._symbolsused
+        ksyms = ksyms - set(datafields.split())  # exclude immediate fields
+        symtables = [(ksyms, templatekw.keywords),
+                     (fsyms, templatefuncs.funcs)]
+        for syms, table in symtables:
+            for k in syms:
+                f = table.get(k)
+                if not f:
+                    continue
+                requires.update(getattr(f, '_requires', ()))
+        if 'repo' in requires:
+            requires.add('ctx')  # there's no API to pass repo to formatter
+        return requires & {'ctx', 'fctx'}
+
+    def datahint(self):
+        '''set of field names to be referenced from the template'''
+        return self._symbolsused[0]
+
     def end(self):
         baseformatter.end(self)
         self._renderitem('docfooter', {})
 
-templatespec = collections.namedtuple(r'templatespec',
-                                      r'ref tmpl mapfile')
+@attr.s(frozen=True)
+class templatespec(object):
+    ref = attr.ib()
+    tmpl = attr.ib()
+    mapfile = attr.ib()
 
 def lookuptemplate(ui, topic, tmpl):
     """Find the template matching the given -T/--template spec 'tmpl'
--- a/mercurial/graphmod.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/graphmod.py	Thu Jul 19 13:55:54 2018 -0400
@@ -341,6 +341,22 @@
         'graphshorten': False,
     }
 
+def outputgraph(ui, graph):
+    """outputs an ASCII graph of a DAG
+
+    this is a helper function for 'ascii' below.
+
+    takes the following arguments:
+
+    - ui to write to
+    - graph data: list of { graph nodes/edges, text }
+
+    this function can be monkey-patched by extensions to alter graph display
+    without needing to mimic all of the edge-fixup logic in ascii()
+    """
+    for (ln, logstr) in graph:
+        ui.write((ln + logstr).rstrip() + "\n")
+
 def ascii(ui, state, type, char, text, coldata):
     """prints an ASCII graph of the DAG
 
@@ -469,9 +485,8 @@
 
     # print lines
     indentation_level = max(ncols, ncols + coldiff)
-    for (line, logstr) in zip(lines, text):
-        ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
-        ui.write(ln.rstrip() + '\n')
+    lines = ["%-*s " % (2 * indentation_level, "".join(line)) for line in lines]
+    outputgraph(ui, zip(lines, text))
 
     # ... and start over
     state['lastcoldiff'] = coldiff
--- a/mercurial/help.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/help.py	Thu Jul 19 13:55:54 2018 -0400
@@ -232,6 +232,7 @@
     (['bundlespec'], _("Bundle File Formats"), loaddoc('bundlespec')),
     (['color'], _("Colorizing Outputs"), loaddoc('color')),
     (["config", "hgrc"], _("Configuration Files"), loaddoc('config')),
+    (['deprecated'], _("Deprecated Features"), loaddoc('deprecated')),
     (["dates"], _("Date Formats"), loaddoc('dates')),
     (["flags"], _("Command-line flags"), loaddoc('flags')),
     (["patterns"], _("File Name Patterns"), loaddoc('patterns')),
@@ -574,9 +575,9 @@
         return rst
 
     def helpextcmd(name, subtopic=None):
-        cmd, ext, mod = extensions.disabledcmd(ui, name,
+        cmd, ext, doc = extensions.disabledcmd(ui, name,
                                                ui.configbool('ui', 'strict'))
-        doc = gettext(pycompat.getdoc(mod)).splitlines()[0]
+        doc = doc.splitlines()[0]
 
         rst = listexts(_("'%s' is provided by the following "
                               "extension:") % cmd, {ext: doc}, indent=4,
--- a/mercurial/help/config.txt	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/help/config.txt	Thu Jul 19 13:55:54 2018 -0400
@@ -442,6 +442,10 @@
     Make paths in :hg:`status` output relative to the current directory.
     (default: False)
 
+``status.terse``
+    Default value for the --terse flag, which condenes status output.
+    (default: empty)
+
 ``update.check``
     Determines what level of checking :hg:`update` will perform before moving
     to a destination revision. Valid values are ``abort``, ``none``,
@@ -698,6 +702,9 @@
 ``unified``
     Number of lines of context to show.
 
+``word-diff``
+    Highlight changed words.
+
 ``email``
 ---------
 
@@ -783,6 +790,14 @@
 ``format``
 ----------
 
+Configuration that controls the repository format. Newer format options are more
+powerful but incompatible with some older versions of Mercurial. Format options
+are considered at repository initialization only. You need to make a new clone
+for config change to be taken into account.
+
+For more details about repository format and version compatibility, see
+https://www.mercurial-scm.org/wiki/MissingRequirement
+
 ``usegeneraldelta``
     Enable or disable the "generaldelta" repository format which improves
     repository compression by allowing "revlog" to store delta against arbitrary
@@ -882,6 +897,23 @@
 of the hook in the config, respectively. In the example above, this will
 be ``$HG_HOOKTYPE=incoming`` and ``$HG_HOOKNAME=incoming.email``.
 
+.. container:: windows
+
+  Some basic Unix syntax can be enabled for portability, including ``$VAR``
+  and ``${VAR}`` style variables.  A ``~`` followed by ``\`` or ``/`` will
+  be expanded to ``%USERPROFILE%`` to simulate a subset of tilde expansion
+  on Unix.  To use a literal ``$`` or ``~``, it must be escaped with a back
+  slash or inside of a strong quote.  Strong quotes will be replaced by
+  double quotes after processing.
+
+  This feature is enabled by adding a prefix of ``tonative.`` to the hook
+  name on a new line, and setting it to ``True``.  For example::
+
+    [hooks]
+    incoming.autobuild = /my/build/hook
+    # enable translation to cmd.exe syntax for autobuild hook
+    tonative.incoming.autobuild = True
+
 ``changegroup``
   Run after a changegroup has been added via push, pull or unbundle.  The ID of
   the first new changeset is in ``$HG_NODE`` and last is in ``$HG_NODE_LAST``.
@@ -1648,6 +1680,10 @@
     ``inlinetime``.
     (default: inlinetime)
 
+``time-track``
+    Control if the stat profiler track ``cpu`` or ``real`` time.
+    (default: ``cpu``)
+
 ``limit``
     Number of lines to show. Specific to the ``ls`` instrumenting profiler.
     (default: 30)
@@ -1737,6 +1773,20 @@
 
 Alias definitions for revsets. See :hg:`help revsets` for details.
 
+``revlog``
+----------
+
+Control the strategy Mercurial uses internally to store history. Options in this
+category impact performance and repository size.
+
+``optimize-delta-parent-choice``
+    When storing a merge revision, both parents will be equally considered as
+    a possible delta base. This results in better delta selection and improved
+    revlog compression. This option is enabled by default.
+
+    Turning this option off can result in large increase of repository size for
+    repository with many merges.
+
 ``server``
 ----------
 
@@ -2121,6 +2171,11 @@
     Possible values are 'text' and 'curses'.
     This config overrides the interface specified by ui.interface.
 
+``large-file-limit``
+    Largest file size that gives no memory use warning.
+    Possible values are integers or 0 to disable the check.
+    (default: 10000000)
+
 ``logtemplate``
     Template string for commands that print changesets.
 
@@ -2325,7 +2380,7 @@
 ``address``
     Interface address to bind to. (default: all)
 
-``allow_archive``
+``allow-archive``
     List of archive format (bz2, gz, zip) allowed for downloading.
     (default: empty)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/help/deprecated.txt	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,30 @@
+Mercurial evolves over time, some features, options, commands may be replaced by
+better and more secure alternatives. This topic will help you migrating your
+existing usage and/or configuration to newer features.
+
+Commands
+========
+
+The following commands are still available but their use are not recommended:
+
+``locate``
+
+This command has been replaced by `hg files`.
+
+``parents``
+
+This command can be replaced by `hg summary` or `hg log` with appropriate
+revsets. See `hg help revsets` for more information.
+
+``tip``
+
+The recommended alternative is `hg heads`.
+
+Options
+=======
+
+``web.allowpull``
+    Renamed to `allow-pull`.
+
+``web.allow_push``
+    Renamed to `allow-push`.
--- a/mercurial/hg.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/hg.py	Thu Jul 19 13:55:54 2018 -0400
@@ -372,13 +372,9 @@
     destlock = None
     try:
         hardlink = None
+        topic = _('linking') if hardlink else _('copying')
+        progress = ui.makeprogress(topic)
         num = 0
-        closetopic = [None]
-        def prog(topic, pos):
-            if pos is None:
-                closetopic[0] = topic
-            else:
-                ui.progress(topic, pos + num)
         srcpublishing = srcrepo.publishing()
         srcvfs = vfsmod.vfs(srcrepo.sharedpath)
         dstvfs = vfsmod.vfs(destpath)
@@ -395,16 +391,13 @@
                     # lock to avoid premature writing to the target
                     destlock = lock.lock(dstvfs, lockfile)
                 hardlink, n = util.copyfiles(srcvfs.join(f), dstvfs.join(f),
-                                             hardlink, progress=prog)
+                                             hardlink, progress)
                 num += n
         if hardlink:
             ui.debug("linked %d files\n" % num)
-            if closetopic[0]:
-                ui.progress(closetopic[0], None)
         else:
             ui.debug("copied %d files\n" % num)
-            if closetopic[0]:
-                ui.progress(closetopic[0], None)
+        progress.complete()
         return destlock
     except: # re-raises
         release(destlock)
--- a/mercurial/hgweb/__init__.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/hgweb/__init__.py	Thu Jul 19 13:55:54 2018 -0400
@@ -57,7 +57,9 @@
         procutil.setsignalhandler()
         self.httpd = server.create_server(self.ui, self.app)
 
-        if self.opts['port'] and not self.ui.verbose:
+        if (self.opts['port'] and
+            not self.ui.verbose and
+            not self.opts['print_url']):
             return
 
         if self.httpd.prefix:
@@ -78,13 +80,18 @@
         fqaddr = self.httpd.fqaddr
         if r':' in fqaddr:
             fqaddr = r'[%s]' % fqaddr
-        if self.opts['port']:
-            write = self.ui.status
+
+        url = 'http://%s%s/%s' % (
+            pycompat.sysbytes(fqaddr), pycompat.sysbytes(port), prefix)
+        if self.opts['print_url']:
+            self.ui.write('%s\n' % url)
         else:
-            write = self.ui.write
-        write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
-              (pycompat.sysbytes(fqaddr), pycompat.sysbytes(port),
-               prefix, pycompat.sysbytes(bindaddr), self.httpd.port))
+            if self.opts['port']:
+                write = self.ui.status
+            else:
+                write = self.ui.write
+            write(_('listening at %s (bound to %s:%d)\n') %
+                  (url, pycompat.sysbytes(bindaddr), self.httpd.port))
         self.ui.flush()  # avoid buffering of status message
 
     def run(self):
--- a/mercurial/hgweb/hgweb_mod.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/hgweb/hgweb_mod.py	Thu Jul 19 13:55:54 2018 -0400
@@ -441,6 +441,8 @@
             res.headers['Content-Type'] = ctype
             return rctx.sendtemplate('error', error=pycompat.bytestr(e))
         except ErrorResponse as e:
+            for k, v in e.headers:
+                res.headers[k] = v
             res.status = statusmessage(e.code, pycompat.bytestr(e))
             res.headers['Content-Type'] = ctype
             return rctx.sendtemplate('error', error=pycompat.bytestr(e))
--- a/mercurial/hgweb/server.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/hgweb/server.py	Thu Jul 19 13:55:54 2018 -0400
@@ -125,8 +125,9 @@
         # Ensure the slicing of path below is valid
         if (path != self.server.prefix
             and not path.startswith(self.server.prefix + b'/')):
-            self._start_response(common.statusmessage(404), [])
-            self._write("Not Found")
+            self._start_response(pycompat.strurl(common.statusmessage(404)),
+                                 [])
+            self._write(b"Not Found")
             self._done()
             return
 
@@ -214,6 +215,7 @@
         self.sent_headers = True
 
     def _start_response(self, http_status, headers, exc_info=None):
+        assert isinstance(http_status, str)
         code, msg = http_status.split(None, 1)
         code = int(code)
         self.saved_status = http_status
@@ -244,7 +246,7 @@
 
     def version_string(self):
         if self.server.serverheader:
-            return self.server.serverheader
+            return encoding.strfromlocal(self.server.serverheader)
         return httpservermod.basehttprequesthandler.version_string(self)
 
 class _httprequesthandlerssl(_httprequesthandler):
--- a/mercurial/hgweb/webcommands.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/hgweb/webcommands.py	Thu Jul 19 13:55:54 2018 -0400
@@ -13,7 +13,7 @@
 import re
 
 from ..i18n import _
-from ..node import hex, nullid, short
+from ..node import hex, short
 
 from .common import (
     ErrorResponse,
@@ -149,7 +149,7 @@
         mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
         text = '(binary:%s)' % mt
 
-    def lines():
+    def lines(context):
         for lineno, t in enumerate(text.splitlines(True)):
             yield {"line": t,
                    "lineid": "l%d" % (lineno + 1),
@@ -160,7 +160,7 @@
         'filerevision',
         file=f,
         path=webutil.up(f),
-        text=lines(),
+        text=templateutil.mappinggenerator(lines),
         symrev=webutil.symrevorshortnode(web.req, fctx),
         rename=webutil.renamelink(fctx),
         permissions=fctx.manifest().flags(f),
@@ -295,9 +295,8 @@
         for ctx in searchfunc[0](funcarg):
             count += 1
             n = ctx.node()
-            showtags = webutil.showtag(web.repo, web.tmpl, 'changelogtag', n)
-            files = webutil.listfilediffs(web.tmpl, ctx.files(), n,
-                                          web.maxfiles)
+            showtags = webutil.showtag(web.repo, 'changelogtag', n)
+            files = webutil.listfilediffs(ctx.files(), n, web.maxfiles)
 
             lm = webutil.commonentry(web.repo, ctx)
             lm.update({
@@ -399,14 +398,8 @@
         revs = []
         if pos != -1:
             revs = web.repo.changelog.revs(pos, 0)
-        curcount = 0
-        for rev in revs:
-            curcount += 1
-            if curcount > revcount + 1:
-                break
 
-            entry = webutil.changelistentry(web, web.repo[rev])
-            entry['parity'] = next(parity)
+        for entry in webutil.changelistentries(web, revs, revcount, parity):
             yield entry
 
     if shortlog:
@@ -448,9 +441,9 @@
         rev=pos,
         symrev=symrev,
         changesets=count,
-        entries=entries,
-        latestentry=latestentry,
-        nextentry=nextentry,
+        entries=templateutil.mappinglist(entries),
+        latestentry=templateutil.mappinglist(latestentry),
+        nextentry=templateutil.mappinglist(nextentry),
         archives=web.archivelist('tip'),
         revcount=revcount,
         morevars=morevars,
@@ -563,7 +556,7 @@
     if mf and not files and not dirs:
         raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
 
-    def filelist(**map):
+    def filelist(context):
         for f in sorted(files):
             full = files[f]
 
@@ -575,7 +568,7 @@
                    "size": fctx.size(),
                    "permissions": mf.flags(full)}
 
-    def dirlist(**map):
+    def dirlist(context):
         for d in sorted(dirs):
 
             emptydirs = []
@@ -598,8 +591,8 @@
         path=abspath,
         up=webutil.up(abspath),
         upparity=next(parity),
-        fentries=filelist,
-        dentries=dirlist,
+        fentries=templateutil.mappinggenerator(filelist),
+        dentries=templateutil.mappinggenerator(dirlist),
         archives=web.archivelist(hex(node)),
         **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)))
 
@@ -618,7 +611,7 @@
     i = list(reversed(web.repo.tagslist()))
     parity = paritygen(web.stripecount)
 
-    def entries(notip, latestonly, **map):
+    def entries(context, notip, latestonly):
         t = i
         if notip:
             t = [(k, n) for k, n in i if k != "tip"]
@@ -633,9 +626,10 @@
     return web.sendtemplate(
         'tags',
         node=hex(web.repo.changelog.tip()),
-        entries=lambda **x: entries(False, False, **x),
-        entriesnotip=lambda **x: entries(True, False, **x),
-        latestentry=lambda **x: entries(True, True, **x))
+        entries=templateutil.mappinggenerator(entries, args=(False, False)),
+        entriesnotip=templateutil.mappinggenerator(entries,
+                                                   args=(True, False)),
+        latestentry=templateutil.mappinggenerator(entries, args=(True, True)))
 
 @webcommand('bookmarks')
 def bookmarks(web):
@@ -654,7 +648,7 @@
     i = sorted(i, key=sortkey, reverse=True)
     parity = paritygen(web.stripecount)
 
-    def entries(latestonly, **map):
+    def entries(context, latestonly):
         t = i
         if latestonly:
             t = i[:1]
@@ -668,13 +662,14 @@
         latestrev = i[0][1]
     else:
         latestrev = -1
+    lastdate = web.repo[latestrev].date()
 
     return web.sendtemplate(
         'bookmarks',
         node=hex(web.repo.changelog.tip()),
-        lastchange=[{'date': web.repo[latestrev].date()}],
-        entries=lambda **x: entries(latestonly=False, **x),
-        latestentry=lambda **x: entries(latestonly=True, **x))
+        lastchange=templateutil.mappinglist([{'date': lastdate}]),
+        entries=templateutil.mappinggenerator(entries, args=(False,)),
+        latestentry=templateutil.mappinggenerator(entries, args=(True,)))
 
 @webcommand('branches')
 def branches(web):
@@ -732,7 +727,7 @@
                 'date': web.repo[n].date(),
             }
 
-    def bookmarks(**map):
+    def bookmarks(context):
         parity = paritygen(web.stripecount)
         marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
         sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
@@ -774,7 +769,7 @@
         owner=get_contact(web.config) or 'unknown',
         lastchange=tip.date(),
         tags=templateutil.mappinggenerator(tagentries, name='tagentry'),
-        bookmarks=bookmarks,
+        bookmarks=templateutil.mappinggenerator(bookmarks),
         branches=webutil.branchentries(web.repo, web.stripecount, 10),
         shortlog=templateutil.mappinggenerator(changelist,
                                                name='shortlogentry'),
@@ -819,7 +814,7 @@
         rename = webutil.renamelink(fctx)
         ctx = fctx
     else:
-        rename = []
+        rename = templateutil.mappinglist([])
         ctx = ctx
 
     return web.sendtemplate(
@@ -887,12 +882,12 @@
         pfctx = ctx.parents()[0][path]
         leftlines = filelines(pfctx)
 
-    comparison = webutil.compare(web.tmpl, context, leftlines, rightlines)
+    comparison = webutil.compare(context, leftlines, rightlines)
     if fctx is not None:
         rename = webutil.renamelink(fctx)
         ctx = fctx
     else:
-        rename = []
+        rename = templateutil.mappinglist([])
         ctx = ctx
 
     return web.sendtemplate(
@@ -934,7 +929,7 @@
     # TODO there are still redundant operations within basefilectx.parents()
     # and from the fctx.annotate() call itself that could be cached.
     parentscache = {}
-    def parents(f):
+    def parents(context, f):
         rev = f.rev()
         if rev not in parentscache:
             parentscache[rev] = []
@@ -948,7 +943,7 @@
         for p in parentscache[rev]:
             yield p
 
-    def annotate(**map):
+    def annotate(context):
         if fctx.isbinary():
             mt = (mimetypes.guess_type(fctx.path())[0]
                   or 'application/octet-stream')
@@ -972,7 +967,7 @@
                    "node": f.hex(),
                    "rev": rev,
                    "author": f.user(),
-                   "parents": parents(f),
+                   "parents": templateutil.mappinggenerator(parents, args=(f,)),
                    "desc": f.description(),
                    "extra": f.extra(),
                    "file": f.path(),
@@ -991,13 +986,13 @@
     return web.sendtemplate(
         'fileannotate',
         file=f,
-        annotate=annotate,
+        annotate=templateutil.mappinggenerator(annotate),
         path=webutil.up(f),
         symrev=webutil.symrevorshortnode(web.req, fctx),
         rename=webutil.renamelink(fctx),
         permissions=fctx.manifest().flags(f),
         ishead=int(ishead),
-        diffopts=diffopts,
+        diffopts=templateutil.hybriddict(diffopts),
         **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)))
 
 @webcommand('filelog')
@@ -1095,13 +1090,16 @@
                 diffs = diff(c, linerange=lr)
             # follow renames accross filtered (not in range) revisions
             path = c.path()
-            entries.append(dict(
-                parity=next(parity),
-                filerev=c.rev(),
-                file=path,
-                diff=diffs,
-                linerange=webutil.formatlinerange(*lr),
-                **pycompat.strkwargs(webutil.commonentry(repo, c))))
+            lm = webutil.commonentry(repo, c)
+            lm.update({
+                'parity': next(parity),
+                'filerev': c.rev(),
+                'file': path,
+                'diff': diffs,
+                'linerange': webutil.formatlinerange(*lr),
+                'rename': templateutil.mappinglist([]),
+            })
+            entries.append(lm)
             if i == revcount:
                 break
         lessvars['linerange'] = webutil.formatlinerange(*lrange)
@@ -1112,13 +1110,15 @@
             diffs = None
             if patch:
                 diffs = diff(iterfctx)
-            entries.append(dict(
-                parity=next(parity),
-                filerev=i,
-                file=f,
-                diff=diffs,
-                rename=webutil.renamelink(iterfctx),
-                **pycompat.strkwargs(webutil.commonentry(repo, iterfctx))))
+            lm = webutil.commonentry(repo, iterfctx)
+            lm.update({
+                'parity': next(parity),
+                'filerev': i,
+                'file': f,
+                'diff': diffs,
+                'rename': webutil.renamelink(iterfctx),
+            })
+            entries.append(lm)
         entries.reverse()
         revnav = webutil.filerevnav(web.repo, fctx.path())
         nav = revnav.gen(end - 1, revcount, count)
@@ -1130,10 +1130,10 @@
         file=f,
         nav=nav,
         symrev=webutil.symrevorshortnode(web.req, fctx),
-        entries=entries,
+        entries=templateutil.mappinglist(entries),
         descend=descend,
         patch=patch,
-        latestentry=latestentry,
+        latestentry=templateutil.mappinglist(latestentry),
         linerange=linerange,
         revcount=revcount,
         morevars=morevars,
@@ -1162,7 +1162,7 @@
     """
 
     type_ = web.req.qsparams.get('type')
-    allowed = web.configlist("web", "allow_archive")
+    allowed = web.configlist("web", "allow-archive")
     key = web.req.qsparams['node']
 
     if type_ not in webutil.archivespecs:
@@ -1314,24 +1314,6 @@
         tree = list(item for item in graphmod.colored(dag, web.repo)
                     if item[1] == graphmod.CHANGESET)
 
-    def nodecurrent(ctx):
-        wpnodes = web.repo.dirstate.parents()
-        if wpnodes[1] == nullid:
-            wpnodes = wpnodes[:1]
-        if ctx.node() in wpnodes:
-            return '@'
-        return ''
-
-    def nodesymbol(ctx):
-        if ctx.obsolete():
-            return 'x'
-        elif ctx.isunstable():
-            return '*'
-        elif ctx.closesbranch():
-            return '_'
-        else:
-            return 'o'
-
     def fulltree():
         pos = web.repo[graphtop].rev()
         tree = []
@@ -1342,14 +1324,14 @@
                         if item[1] == graphmod.CHANGESET)
         return tree
 
-    def jsdata():
-        return [{'node': pycompat.bytestr(ctx),
-                 'graphnode': nodecurrent(ctx) + nodesymbol(ctx),
-                 'vertex': vtx,
-                 'edges': edges}
-                for (id, type, ctx, vtx, edges) in fulltree()]
+    def jsdata(context):
+        for (id, type, ctx, vtx, edges) in fulltree():
+            yield {'node': pycompat.bytestr(ctx),
+                   'graphnode': webutil.getgraphnode(web.repo, ctx),
+                   'vertex': vtx,
+                   'edges': edges}
 
-    def nodes():
+    def nodes(context):
         parity = paritygen(web.stripecount)
         for row, (id, type, ctx, vtx, edges) in enumerate(tree):
             entry = webutil.commonentry(web.repo, ctx)
@@ -1363,7 +1345,7 @@
             entry.update({'col': vtx[0],
                           'color': (vtx[1] - 1) % 6 + 1,
                           'parity': next(parity),
-                          'edges': edgedata,
+                          'edges': templateutil.mappinglist(edgedata),
                           'row': row,
                           'nextrow': row + 1})
 
@@ -1384,10 +1366,11 @@
         rows=rows,
         bg_height=bg_height,
         changesets=count,
-        nextentry=nextentry,
-        jsdata=lambda **x: jsdata(),
-        nodes=lambda **x: nodes(),
+        nextentry=templateutil.mappinglist(nextentry),
+        jsdata=templateutil.mappinggenerator(jsdata),
+        nodes=templateutil.mappinggenerator(nodes),
         node=ctx.hex(),
+        archives=web.archivelist('tip'),
         changenav=changenav)
 
 def _getdoc(e):
@@ -1417,7 +1400,7 @@
 
     topicname = web.req.qsparams.get('node')
     if not topicname:
-        def topics(**map):
+        def topics(context):
             for entries, summary, _doc in helpmod.helptable:
                 yield {'topic': entries[0], 'summary': summary}
 
@@ -1436,19 +1419,19 @@
         early.sort()
         other.sort()
 
-        def earlycommands(**map):
+        def earlycommands(context):
             for c, doc in early:
                 yield {'topic': c, 'summary': doc}
 
-        def othercommands(**map):
+        def othercommands(context):
             for c, doc in other:
                 yield {'topic': c, 'summary': doc}
 
         return web.sendtemplate(
             'helptopics',
-            topics=topics,
-            earlycommands=earlycommands,
-            othercommands=othercommands,
+            topics=templateutil.mappinggenerator(topics),
+            earlycommands=templateutil.mappinggenerator(earlycommands),
+            othercommands=templateutil.mappinggenerator(othercommands),
             title='Index')
 
     # Render an index of sub-topics.
@@ -1463,7 +1446,7 @@
 
         return web.sendtemplate(
             'helptopics',
-            topics=topics,
+            topics=templateutil.mappinglist(topics),
             title=topicname,
             subindex=True)
 
--- a/mercurial/hgweb/webutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/hgweb/webutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -25,6 +25,7 @@
 
 from .. import (
     context,
+    diffutil,
     error,
     match,
     mdiff,
@@ -51,7 +52,7 @@
 ))
 
 def archivelist(ui, nodeid, url=None):
-    allowed = ui.configlist('web', 'allow_archive', untrusted=True)
+    allowed = ui.configlist('web', 'allow-archive', untrusted=True)
     archives = []
 
     for typ, spec in archivespecs.iteritems():
@@ -206,8 +207,8 @@
     return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
 
 def difffeatureopts(req, ui, section):
-    diffopts = patch.difffeatureopts(ui, untrusted=True,
-                                     section=section, whitespace=True)
+    diffopts = diffutil.difffeatureopts(ui, untrusted=True,
+                                        section=section, whitespace=True)
 
     for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
         v = req.qsparams.get(k)
@@ -234,14 +235,14 @@
 def renamelink(fctx):
     r = fctx.renamed()
     if r:
-        return [{'file': r[0], 'node': hex(r[1])}]
-    return []
+        return templateutil.mappinglist([{'file': r[0], 'node': hex(r[1])}])
+    return templateutil.mappinglist([])
 
 def nodetagsdict(repo, node):
-    return [{"name": i} for i in repo.nodetags(node)]
+    return templateutil.hybridlist(repo.nodetags(node), name='name')
 
 def nodebookmarksdict(repo, node):
-    return [{"name": i} for i in repo.nodebookmarks(node)]
+    return templateutil.hybridlist(repo.nodebookmarks(node), name='name')
 
 def nodebranchdict(repo, ctx):
     branches = []
@@ -253,8 +254,8 @@
     except error.RepoLookupError:
         branchnode = None
     if branchnode == ctx.node():
-        branches.append({"name": branch})
-    return branches
+        branches.append(branch)
+    return templateutil.hybridlist(branches, name='name')
 
 def nodeinbranch(repo, ctx):
     branches = []
@@ -264,29 +265,27 @@
     except error.RepoLookupError:
         branchnode = None
     if branch != 'default' and branchnode != ctx.node():
-        branches.append({"name": branch})
-    return branches
+        branches.append(branch)
+    return templateutil.hybridlist(branches, name='name')
 
 def nodebranchnodefault(ctx):
     branches = []
     branch = ctx.branch()
     if branch != 'default':
-        branches.append({"name": branch})
-    return branches
+        branches.append(branch)
+    return templateutil.hybridlist(branches, name='name')
+
+def _nodenamesgen(context, f, node, name):
+    for t in f(node):
+        yield {name: t}
 
-def showtag(repo, tmpl, t1, node=nullid, **args):
-    args = pycompat.byteskwargs(args)
-    for t in repo.nodetags(node):
-        lm = args.copy()
-        lm['tag'] = t
-        yield tmpl.generate(t1, lm)
+def showtag(repo, t1, node=nullid):
+    args = (repo.nodetags, node, 'tag')
+    return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
 
-def showbookmark(repo, tmpl, t1, node=nullid, **args):
-    args = pycompat.byteskwargs(args)
-    for t in repo.nodebookmarks(node):
-        lm = args.copy()
-        lm['bookmark'] = t
-        yield tmpl.generate(t1, lm)
+def showbookmark(repo, t1, node=nullid):
+    args = (repo.nodebookmarks, node, 'bookmark')
+    return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
 
 def branchentries(repo, stripecount, limit=0):
     tips = []
@@ -294,7 +293,7 @@
     parity = paritygen(stripecount)
     sortkey = lambda item: (not item[1], item[0].rev())
 
-    def entries(**map):
+    def entries(context):
         count = 0
         if not tips:
             for tag, hs, tip, closed in repo.branchmap().iterbranches():
@@ -317,7 +316,7 @@
                 'date': ctx.date()
             }
 
-    return entries
+    return templateutil.mappinggenerator(entries)
 
 def cleanpath(repo, path):
     path = path.lstrip('/')
@@ -380,7 +379,7 @@
 def formatlinerange(fromline, toline):
     return '%d:%d' % (fromline + 1, toline)
 
-def succsandmarkers(context, mapping):
+def _succsandmarkersgen(context, mapping):
     repo = context.resource(mapping, 'repo')
     itemmappings = templatekw.showsuccsandmarkers(context, mapping)
     for item in itemmappings.tovalue(context, mapping):
@@ -388,10 +387,13 @@
                                        for successor in item['successors'])
         yield item
 
+def succsandmarkers(context, mapping):
+    return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
+
 # teach templater succsandmarkers is switched to (context, mapping) API
 succsandmarkers._requires = {'repo', 'ctx'}
 
-def whyunstable(context, mapping):
+def _whyunstablegen(context, mapping):
     repo = context.resource(mapping, 'repo')
     ctx = context.resource(mapping, 'ctx')
 
@@ -401,6 +403,9 @@
             entry['divergentnodes'] = _siblings(entry['divergentnodes'])
         yield entry
 
+def whyunstable(context, mapping):
+    return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
+
 whyunstable._requires = {'repo', 'ctx'}
 
 def commonentry(repo, ctx):
@@ -419,7 +424,8 @@
         'phase': ctx.phasestr(),
         'obsolete': ctx.obsolete(),
         'succsandmarkers': succsandmarkers,
-        'instabilities': [{"instability": i} for i in ctx.instabilities()],
+        'instabilities': templateutil.hybridlist(ctx.instabilities(),
+                                                 name='instability'),
         'whyunstable': whyunstable,
         'branch': nodebranchnodefault(ctx),
         'inbranch': nodeinbranch(repo, ctx),
@@ -439,8 +445,8 @@
     repo = web.repo
     rev = ctx.rev()
     n = ctx.node()
-    showtags = showtag(repo, web.tmpl, 'changelogtag', n)
-    files = listfilediffs(web.tmpl, ctx.files(), n, web.maxfiles)
+    showtags = showtag(repo, 'changelogtag', n)
+    files = listfilediffs(ctx.files(), n, web.maxfiles)
 
     entry = commonentry(repo, ctx)
     entry.update(
@@ -452,30 +458,45 @@
     )
     return entry
 
+def changelistentries(web, revs, maxcount, parityfn):
+    """Emit up to N records for an iterable of revisions."""
+    repo = web.repo
+
+    count = 0
+    for rev in revs:
+        if count >= maxcount:
+            break
+
+        count += 1
+
+        entry = changelistentry(web, repo[rev])
+        entry['parity'] = next(parityfn)
+
+        yield entry
+
 def symrevorshortnode(req, ctx):
     if 'node' in req.qsparams:
         return templatefilters.revescape(req.qsparams['node'])
     else:
         return short(ctx.node())
 
-def changesetentry(web, ctx):
-    '''Obtain a dictionary to be used to render the "changeset" template.'''
-
-    showtags = showtag(web.repo, web.tmpl, 'changesettag', ctx.node())
-    showbookmarks = showbookmark(web.repo, web.tmpl, 'changesetbookmark',
-                                 ctx.node())
-    showbranch = nodebranchnodefault(ctx)
-
-    files = []
-    parity = paritygen(web.stripecount)
+def _listfilesgen(context, ctx, stripecount):
+    parity = paritygen(stripecount)
     for blockno, f in enumerate(ctx.files()):
         template = 'filenodelink' if f in ctx else 'filenolink'
-        files.append(web.tmpl.generate(template, {
+        yield context.process(template, {
             'node': ctx.hex(),
             'file': f,
             'blockno': blockno + 1,
             'parity': next(parity),
-        }))
+        })
+
+def changesetentry(web, ctx):
+    '''Obtain a dictionary to be used to render the "changeset" template.'''
+
+    showtags = showtag(web.repo, 'changesettag', ctx.node())
+    showbookmarks = showbookmark(web.repo, 'changesetbookmark', ctx.node())
+    showbranch = nodebranchnodefault(ctx)
 
     basectx = basechangectx(web.repo, web.req)
     if basectx is None:
@@ -488,8 +509,8 @@
     diff = diffs(web, ctx, basectx, None, style)
 
     parity = paritygen(web.stripecount)
-    diffstatsgen = diffstatgen(ctx, basectx)
-    diffstats = diffstat(web.tmpl, ctx, diffstatsgen, parity)
+    diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
+    diffstats = diffstat(ctx, diffstatsgen, parity)
 
     return dict(
         diff=diff,
@@ -498,40 +519,43 @@
         changesettag=showtags,
         changesetbookmark=showbookmarks,
         changesetbranch=showbranch,
-        files=files,
+        files=templateutil.mappedgenerator(_listfilesgen,
+                                           args=(ctx, web.stripecount)),
         diffsummary=lambda **x: diffsummary(diffstatsgen),
         diffstat=diffstats,
         archives=web.archivelist(ctx.hex()),
         **pycompat.strkwargs(commonentry(web.repo, ctx)))
 
-def listfilediffs(tmpl, files, node, max):
+def _listfilediffsgen(context, files, node, max):
     for f in files[:max]:
-        yield tmpl.generate('filedifflink', {'node': hex(node), 'file': f})
+        yield context.process('filedifflink', {'node': hex(node), 'file': f})
     if len(files) > max:
-        yield tmpl.generate('fileellipses', {})
+        yield context.process('fileellipses', {})
 
-def diffs(web, ctx, basectx, files, style, linerange=None,
-          lineidprefix=''):
+def listfilediffs(files, node, max):
+    return templateutil.mappedgenerator(_listfilediffsgen,
+                                        args=(files, node, max))
 
-    def prettyprintlines(lines, blockno):
-        for lineno, l in enumerate(lines, 1):
-            difflineno = "%d.%d" % (blockno, lineno)
-            if l.startswith('+'):
-                ltype = "difflineplus"
-            elif l.startswith('-'):
-                ltype = "difflineminus"
-            elif l.startswith('@'):
-                ltype = "difflineat"
-            else:
-                ltype = "diffline"
-            yield web.tmpl.generate(ltype, {
-                'line': l,
-                'lineno': lineno,
-                'lineid': lineidprefix + "l%s" % difflineno,
-                'linenumber': "% 8s" % difflineno,
-            })
+def _prettyprintdifflines(context, lines, blockno, lineidprefix):
+    for lineno, l in enumerate(lines, 1):
+        difflineno = "%d.%d" % (blockno, lineno)
+        if l.startswith('+'):
+            ltype = "difflineplus"
+        elif l.startswith('-'):
+            ltype = "difflineminus"
+        elif l.startswith('@'):
+            ltype = "difflineat"
+        else:
+            ltype = "diffline"
+        yield context.process(ltype, {
+            'line': l,
+            'lineno': lineno,
+            'lineid': lineidprefix + "l%s" % difflineno,
+            'linenumber': "% 8s" % difflineno,
+        })
 
-    repo = web.repo
+def _diffsgen(context, repo, ctx, basectx, files, style, stripecount,
+              linerange, lineidprefix):
     if files:
         m = match.exact(repo.root, repo.getcwd(), files)
     else:
@@ -540,7 +564,7 @@
     diffopts = patch.diffopts(repo.ui, untrusted=True)
     node1 = basectx.node()
     node2 = ctx.node()
-    parity = paritygen(web.stripecount)
+    parity = paritygen(stripecount)
 
     diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
     for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
@@ -554,70 +578,89 @@
                     continue
             lines.extend(hunklines)
         if lines:
-            yield web.tmpl.generate('diffblock', {
+            l = templateutil.mappedgenerator(_prettyprintdifflines,
+                                             args=(lines, blockno,
+                                                   lineidprefix))
+            yield {
                 'parity': next(parity),
                 'blockno': blockno,
-                'lines': prettyprintlines(lines, blockno),
-            })
+                'lines': l,
+            }
 
-def compare(tmpl, context, leftlines, rightlines):
-    '''Generator function that provides side-by-side comparison data.'''
+def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=''):
+    args = (web.repo, ctx, basectx, files, style, web.stripecount,
+            linerange, lineidprefix)
+    return templateutil.mappinggenerator(_diffsgen, args=args, name='diffblock')
 
-    def compline(type, leftlineno, leftline, rightlineno, rightline):
-        lineid = leftlineno and ("l%d" % leftlineno) or ''
-        lineid += rightlineno and ("r%d" % rightlineno) or ''
-        llno = '%d' % leftlineno if leftlineno else ''
-        rlno = '%d' % rightlineno if rightlineno else ''
-        return tmpl.generate('comparisonline', {
-            'type': type,
-            'lineid': lineid,
-            'leftlineno': leftlineno,
-            'leftlinenumber': "% 6s" % llno,
-            'leftline': leftline or '',
-            'rightlineno': rightlineno,
-            'rightlinenumber': "% 6s" % rlno,
-            'rightline': rightline or '',
-        })
+def _compline(type, leftlineno, leftline, rightlineno, rightline):
+    lineid = leftlineno and ("l%d" % leftlineno) or ''
+    lineid += rightlineno and ("r%d" % rightlineno) or ''
+    llno = '%d' % leftlineno if leftlineno else ''
+    rlno = '%d' % rightlineno if rightlineno else ''
+    return {
+        'type': type,
+        'lineid': lineid,
+        'leftlineno': leftlineno,
+        'leftlinenumber': "% 6s" % llno,
+        'leftline': leftline or '',
+        'rightlineno': rightlineno,
+        'rightlinenumber': "% 6s" % rlno,
+        'rightline': rightline or '',
+    }
 
-    def getblock(opcodes):
-        for type, llo, lhi, rlo, rhi in opcodes:
-            len1 = lhi - llo
-            len2 = rhi - rlo
-            count = min(len1, len2)
-            for i in xrange(count):
-                yield compline(type=type,
-                               leftlineno=llo + i + 1,
-                               leftline=leftlines[llo + i],
-                               rightlineno=rlo + i + 1,
-                               rightline=rightlines[rlo + i])
-            if len1 > len2:
-                for i in xrange(llo + count, lhi):
-                    yield compline(type=type,
-                                   leftlineno=i + 1,
-                                   leftline=leftlines[i],
-                                   rightlineno=None,
-                                   rightline=None)
-            elif len2 > len1:
-                for i in xrange(rlo + count, rhi):
-                    yield compline(type=type,
-                                   leftlineno=None,
-                                   leftline=None,
-                                   rightlineno=i + 1,
-                                   rightline=rightlines[i])
+def _getcompblockgen(context, leftlines, rightlines, opcodes):
+    for type, llo, lhi, rlo, rhi in opcodes:
+        len1 = lhi - llo
+        len2 = rhi - rlo
+        count = min(len1, len2)
+        for i in xrange(count):
+            yield _compline(type=type,
+                            leftlineno=llo + i + 1,
+                            leftline=leftlines[llo + i],
+                            rightlineno=rlo + i + 1,
+                            rightline=rightlines[rlo + i])
+        if len1 > len2:
+            for i in xrange(llo + count, lhi):
+                yield _compline(type=type,
+                                leftlineno=i + 1,
+                                leftline=leftlines[i],
+                                rightlineno=None,
+                                rightline=None)
+        elif len2 > len1:
+            for i in xrange(rlo + count, rhi):
+                yield _compline(type=type,
+                                leftlineno=None,
+                                leftline=None,
+                                rightlineno=i + 1,
+                                rightline=rightlines[i])
 
+def _getcompblock(leftlines, rightlines, opcodes):
+    args = (leftlines, rightlines, opcodes)
+    return templateutil.mappinggenerator(_getcompblockgen, args=args,
+                                         name='comparisonline')
+
+def _comparegen(context, contextnum, leftlines, rightlines):
+    '''Generator function that provides side-by-side comparison data.'''
     s = difflib.SequenceMatcher(None, leftlines, rightlines)
-    if context < 0:
-        yield tmpl.generate('comparisonblock',
-                            {'lines': getblock(s.get_opcodes())})
+    if contextnum < 0:
+        l = _getcompblock(leftlines, rightlines, s.get_opcodes())
+        yield {'lines': l}
     else:
-        for oc in s.get_grouped_opcodes(n=context):
-            yield tmpl.generate('comparisonblock', {'lines': getblock(oc)})
+        for oc in s.get_grouped_opcodes(n=contextnum):
+            l = _getcompblock(leftlines, rightlines, oc)
+            yield {'lines': l}
 
-def diffstatgen(ctx, basectx):
+def compare(contextnum, leftlines, rightlines):
+    args = (contextnum, leftlines, rightlines)
+    return templateutil.mappinggenerator(_comparegen, args=args,
+                                         name='comparisonblock')
+
+def diffstatgen(ui, ctx, basectx):
     '''Generator function that provides the diffstat data.'''
 
+    diffopts = patch.diffopts(ui, {'noprefix': False})
     stats = patch.diffstatdata(
-        util.iterlines(ctx.diff(basectx, noprefix=False)))
+        util.iterlines(ctx.diff(basectx, opts=diffopts)))
     maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
     while True:
         yield stats, maxname, maxtotal, addtotal, removetotal, binary
@@ -629,9 +672,7 @@
     return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
              len(stats), addtotal, removetotal)
 
-def diffstat(tmpl, ctx, statgen, parity):
-    '''Return a diffstat template for each file in the diff.'''
-
+def _diffstattmplgen(context, ctx, statgen, parity):
     stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
     files = ctx.files()
 
@@ -645,7 +686,7 @@
         template = 'diffstatlink' if filename in files else 'diffstatnolink'
         total = adds + removes
         fileno += 1
-        yield tmpl.generate(template, {
+        yield context.process(template, {
             'node': ctx.hex(),
             'file': filename,
             'fileno': fileno,
@@ -655,6 +696,11 @@
             'parity': next(parity),
         })
 
+def diffstat(ctx, statgen, parity):
+    '''Return a diffstat template for each file in the diff.'''
+    args = (ctx, statgen, parity)
+    return templateutil.mappedgenerator(_diffstattmplgen, args=args)
+
 class sessionvars(templateutil.wrapped):
     def __init__(self, vars, start='?'):
         self._start = start
@@ -669,6 +715,24 @@
     def __copy__(self):
         return sessionvars(copy.copy(self._vars), self._start)
 
+    def contains(self, context, mapping, item):
+        item = templateutil.unwrapvalue(context, mapping, item)
+        return item in self._vars
+
+    def getmember(self, context, mapping, key):
+        key = templateutil.unwrapvalue(context, mapping, key)
+        return self._vars.get(key)
+
+    def getmin(self, context, mapping):
+        raise error.ParseError(_('not comparable'))
+
+    def getmax(self, context, mapping):
+        raise error.ParseError(_('not comparable'))
+
+    def filter(self, context, mapping, select):
+        # implement if necessary
+        raise error.ParseError(_('not filterable'))
+
     def itermaps(self, context):
         separator = self._start
         for key, value in sorted(self._vars.iteritems()):
@@ -685,6 +749,9 @@
     def show(self, context, mapping):
         return self.join(context, '')
 
+    def tobool(self, context, mapping):
+        return bool(self._vars)
+
     def tovalue(self, context, mapping):
         return self._vars
 
@@ -701,7 +768,7 @@
     for key, pattern in websubdefs:
         # grab the delimiter from the character after the "s"
         unesc = pattern[1:2]
-        delim = re.escape(unesc)
+        delim = stringutil.reescape(unesc)
 
         # identify portions of the pattern, taking care to avoid escaped
         # delimiters. the replace format and flags are optional, but
@@ -733,3 +800,7 @@
             repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
                          % (key, regexp))
     return websubtable
+
+def getgraphnode(repo, ctx):
+    return (templatekw.getgraphnodecurrent(repo, ctx) +
+            templatekw.getgraphnodesymbol(ctx))
--- a/mercurial/hook.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/hook.py	Thu Jul 19 13:55:54 2018 -0400
@@ -24,7 +24,7 @@
     stringutil,
 )
 
-def _pythonhook(ui, repo, htype, hname, funcname, args, throw):
+def pythonhook(ui, repo, htype, hname, funcname, args, throw):
     '''call python hook. hook is callable object, looked up as
     name in python module. if callable returns "true", hook
     fails, else passes. if hook raises exception, treated as
@@ -120,8 +120,6 @@
     return r, False
 
 def _exthook(ui, repo, htype, name, cmd, args, throw):
-    ui.note(_("running hook %s: %s\n") % (name, cmd))
-
     starttime = util.timer()
     env = {}
 
@@ -138,9 +136,17 @@
         if callable(v):
             v = v()
         if isinstance(v, (dict, list)):
-            v = stringutil.pprint(v, bprefix=False)
+            v = stringutil.pprint(v)
         env['HG_' + k.upper()] = v
 
+    if ui.configbool('hooks', 'tonative.%s' % name, False):
+        oldcmd = cmd
+        cmd = procutil.shelltonative(cmd, env)
+        if cmd != oldcmd:
+            ui.note(_('converting hook "%s" to native\n') % name)
+
+    ui.note(_("running hook %s: %s\n") % (name, cmd))
+
     if repo:
         cwd = repo.root
     else:
@@ -179,9 +185,11 @@
     """return all hooks items ready to be sorted"""
     hooks = {}
     for name, cmd in ui.configitems('hooks', untrusted=_untrusted):
-        if not name.startswith('priority'):
-            priority = ui.configint('hooks', 'priority.%s' % name, 0)
-            hooks[name] = (-priority, len(hooks), name, cmd)
+        if name.startswith('priority.') or name.startswith('tonative.'):
+            continue
+
+        priority = ui.configint('hooks', 'priority.%s' % name, 0)
+        hooks[name] = (-priority, len(hooks), name, cmd)
     return hooks
 
 _redirect = False
@@ -242,7 +250,7 @@
                 r = 1
                 raised = False
             elif callable(cmd):
-                r, raised = _pythonhook(ui, repo, htype, hname, cmd, args,
+                r, raised = pythonhook(ui, repo, htype, hname, cmd, args,
                                         throw)
             elif cmd.startswith('python:'):
                 if cmd.count(':') >= 2:
@@ -258,7 +266,7 @@
                     hookfn = getattr(mod, cmd)
                 else:
                     hookfn = cmd[7:].strip()
-                r, raised = _pythonhook(ui, repo, htype, hname, hookfn, args,
+                r, raised = pythonhook(ui, repo, htype, hname, hookfn, args,
                                         throw)
             else:
                 r = _exthook(ui, repo, htype, hname, cmd, args, throw)
--- a/mercurial/httpconnection.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/httpconnection.py	Thu Jul 19 13:55:54 2018 -0400
@@ -38,21 +38,21 @@
         self.write = self._data.write
         self.length = os.fstat(self._data.fileno()).st_size
         self._pos = 0
-        self._total = self.length // 1024 * 2
-
-    def read(self, *args, **kwargs):
-        ret = self._data.read(*args, **kwargs)
-        if not ret:
-            self.ui.progress(_('sending'), None)
-            return ret
-        self._pos += len(ret)
         # We pass double the max for total because we currently have
         # to send the bundle twice in the case of a server that
         # requires authentication. Since we can't know until we try
         # once whether authentication will be required, just lie to
         # the user and maybe the push succeeds suddenly at 50%.
-        self.ui.progress(_('sending'), self._pos // 1024,
-                         unit=_('kb'), total=self._total)
+        self._progress = ui.makeprogress(_('sending'), unit=_('kb'),
+                                         total=(self.length // 1024 * 2))
+
+    def read(self, *args, **kwargs):
+        ret = self._data.read(*args, **kwargs)
+        if not ret:
+            self._progress.complete()
+            return ret
+        self._pos += len(ret)
+        self._progress.update(self._pos // 1024)
         return ret
 
     def __enter__(self):
--- a/mercurial/httppeer.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/httppeer.py	Thu Jul 19 13:55:54 2018 -0400
@@ -13,7 +13,6 @@
 import os
 import socket
 import struct
-import tempfile
 import weakref
 
 from .i18n import _
@@ -307,6 +306,7 @@
 
         start = util.timer()
 
+    res = None
     try:
         res = opener.open(req)
     except urlerr.httperror as inst:
@@ -320,8 +320,9 @@
         raise IOError(None, inst)
     finally:
         if ui.debugflag and ui.configbool('devel', 'debug.peer-request'):
+            code = res.code if res else -1
             dbg(line % '  finished in %.4f seconds (%d)'
-                % (util.timer() - start, res.code))
+                % (util.timer() - start, code))
 
     # Insert error handlers for common I/O failures.
     _wraphttpresponse(res)
@@ -519,7 +520,7 @@
         filename = None
         try:
             # dump bundle to disk
-            fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
+            fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
             fh = os.fdopen(fd, r"wb")
             d = fp.read(4096)
             while d:
--- a/mercurial/i18n.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/i18n.py	Thu Jul 19 13:55:54 2018 -0400
@@ -23,11 +23,6 @@
 else:
     module = pycompat.fsencode(__file__)
 
-try:
-    unicode
-except NameError:
-    unicode = str
-
 _languages = None
 if (pycompat.iswindows
     and 'LANGUAGE' not in encoding.environ
@@ -76,7 +71,7 @@
 
     cache = _msgcache.setdefault(encoding.encoding, {})
     if message not in cache:
-        if type(message) is unicode:
+        if type(message) is pycompat.unicode:
             # goofy unicode docstrings in test
             paragraphs = message.split(u'\n\n')
         else:
--- a/mercurial/localrepo.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/localrepo.py	Thu Jul 19 13:55:54 2018 -0400
@@ -354,6 +354,15 @@
 # clients.
 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
 
+# A repository with the sparserevlog feature will have delta chains that
+# can spread over a larger span. Sparse reading cuts these large spans into
+# pieces, so that each piece isn't too big.
+# Without the sparserevlog capability, reading from the repository could use
+# huge amounts of memory, because the whole span would be read at once,
+# including all the intermediate revisions that aren't pertinent for the chain.
+# This is why once a repository has enabled sparse-read, it becomes required.
+SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
+
 # Functions receiving (ui, features) that extensions can register to impact
 # the ability to load repositories with custom requirements. Only
 # functions defined in loaded extensions are called.
@@ -376,6 +385,7 @@
         'generaldelta',
         'treemanifest',
         REVLOGV2_REQUIREMENT,
+        SPARSEREVLOG_REQUIREMENT,
     }
     _basesupported = supportedformats | {
         'store',
@@ -658,10 +668,9 @@
         manifestcachesize = self.ui.configint('format', 'manifestcachesize')
         if manifestcachesize is not None:
             self.svfs.options['manifestcachesize'] = manifestcachesize
-        # experimental config: format.aggressivemergedeltas
-        aggressivemergedeltas = self.ui.configbool('format',
-                                                   'aggressivemergedeltas')
-        self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
+        deltabothparents = self.ui.configbool('revlog',
+                                              'optimize-delta-parent-choice')
+        self.svfs.options['deltabothparents'] = deltabothparents
         self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
         chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan')
         if 0 <= chainspan:
@@ -678,6 +687,8 @@
         self.svfs.options['with-sparse-read'] = withsparseread
         self.svfs.options['sparse-read-density-threshold'] = srdensitythres
         self.svfs.options['sparse-read-min-gap-size'] = srmingapsize
+        sparserevlog = SPARSEREVLOG_REQUIREMENT in self.requirements
+        self.svfs.options['sparse-revlog'] = sparserevlog
 
         for r in self.requirements:
             if r.startswith('exp-compression-'):
@@ -778,6 +789,10 @@
 
     @repofilecache('dirstate')
     def dirstate(self):
+        return self._makedirstate()
+
+    def _makedirstate(self):
+        """Extension point for wrapping the dirstate per-repo."""
         sparsematchfn = lambda: sparse.matcher(self)
 
         return dirstate.dirstate(self.vfs, self.ui, self.root,
@@ -1029,11 +1044,7 @@
 
     def nodebookmarks(self, node):
         """return the list of bookmarks pointing to the specified node"""
-        marks = []
-        for bookmark, n in self._bookmarks.iteritems():
-            if n == node:
-                marks.append(bookmark)
-        return sorted(marks)
+        return self._bookmarks.names(node)
 
     def branchmap(self):
         '''returns a dictionary {branch: [branchheads]} with branchheads
@@ -2370,6 +2381,9 @@
         requirements.add('generaldelta')
     if ui.configbool('experimental', 'treemanifest'):
         requirements.add('treemanifest')
+    # experimental config: format.sparse-revlog
+    if ui.configbool('format', 'sparse-revlog'):
+        requirements.add(SPARSEREVLOG_REQUIREMENT)
 
     revlogv2 = ui.config('experimental', 'revlogv2')
     if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
--- a/mercurial/lock.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/lock.py	Thu Jul 19 13:55:54 2018 -0400
@@ -209,7 +209,7 @@
 
     def __del__(self):
         if self.held:
-            warnings.warn("use lock.release instead of del lock",
+            warnings.warn(r"use lock.release instead of del lock",
                     category=DeprecationWarning,
                     stacklevel=2)
 
--- a/mercurial/logcmdutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/logcmdutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -76,9 +76,9 @@
         if not ui.plain():
             width = ui.termwidth()
 
-    chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
-                        prefix=prefix, relroot=relroot,
-                        hunksfilterfn=hunksfilterfn)
+    chunks = repo[node2].diff(repo[node1], match, changes, opts=diffopts,
+                              prefix=prefix, relroot=relroot,
+                              hunksfilterfn=hunksfilterfn)
 
     if fp is not None or ui.canwritewithoutlabels():
         out = fp or ui
@@ -154,7 +154,9 @@
         self.repo = repo
         self.buffered = buffered
         self._differ = differ or changesetdiffer()
-        self.diffopts = diffopts or {}
+        self._diffopts = patch.diffallopts(ui, diffopts)
+        self._includestat = diffopts and diffopts.get('stat')
+        self._includediff = diffopts and diffopts.get('patch')
         self.header = {}
         self.hunk = {}
         self.lastheader = None
@@ -226,7 +228,7 @@
 
         if self.ui.debugflag and rev is not None:
             mnode = ctx.manifestnode()
-            mrev = self.repo.manifestlog._revlog.rev(mnode)
+            mrev = self.repo.manifestlog.rev(mnode)
             self.ui.write(columns['manifest']
                           % scmutil.formatrevnode(self.ui, mrev, mnode),
                           label='ui.debug log.manifest')
@@ -298,16 +300,13 @@
         '''
 
     def _showpatch(self, ctx):
-        stat = self.diffopts.get('stat')
-        diff = self.diffopts.get('patch')
-        diffopts = patch.diffallopts(self.ui, self.diffopts)
-        if stat:
-            self._differ.showdiff(self.ui, ctx, diffopts, stat=True)
-        if stat and diff:
+        if self._includestat:
+            self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
+        if self._includestat and self._includediff:
             self.ui.write("\n")
-        if diff:
-            self._differ.showdiff(self.ui, ctx, diffopts, stat=False)
-        if stat or diff:
+        if self._includediff:
+            self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
+        if self._includestat or self._includediff:
             self.ui.write("\n")
 
 class changesetformatter(changesetprinter):
@@ -316,6 +315,7 @@
     def __init__(self, ui, repo, fm, differ=None, diffopts=None,
                  buffered=False):
         changesetprinter.__init__(self, ui, repo, differ, diffopts, buffered)
+        self._diffopts = patch.difffeatureopts(ui, diffopts, git=True)
         self._fm = fm
 
     def close(self):
@@ -367,16 +367,13 @@
                 fm.data(copies=fm.formatdict(copies,
                                              key='name', value='source'))
 
-        stat = self.diffopts.get('stat')
-        diff = self.diffopts.get('patch')
-        diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
-        if stat:
+        if self._includestat:
             self.ui.pushbuffer()
-            self._differ.showdiff(self.ui, ctx, diffopts, stat=True)
+            self._differ.showdiff(self.ui, ctx, self._diffopts, stat=True)
             fm.data(diffstat=self.ui.popbuffer())
-        if diff:
+        if self._includediff:
             self.ui.pushbuffer()
-            self._differ.showdiff(self.ui, ctx, diffopts, stat=False)
+            self._differ.showdiff(self.ui, ctx, self._diffopts, stat=False)
             fm.data(diff=self.ui.popbuffer())
 
 class changesettemplater(changesetprinter):
@@ -868,7 +865,7 @@
             for fn in ctx.files():
                 rename = getrenamed(fn, ctx.rev())
                 if rename:
-                    copies.append((fn, rename[0]))
+                    copies.append((fn, rename))
         edges = edgefn(type, char, state, rev, parents)
         firstedge = next(edges)
         width = firstedge[2]
@@ -896,7 +893,7 @@
             for fn in ctx.files():
                 rename = getrenamed(fn, rev)
                 if rename:
-                    copies.append((fn, rename[0]))
+                    copies.append((fn, rename))
         displayer.show(ctx, copies=copies)
         displayer.flush(ctx)
     displayer.close()
--- a/mercurial/logexchange.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/logexchange.py	Thu Jul 19 13:55:54 2018 -0400
@@ -112,8 +112,8 @@
     # represent the remotepath with user defined path name if exists
     for path, url in repo.ui.configitems('paths'):
         # remove auth info from user defined url
-        url = util.removeauth(url)
-        if url == rpath:
+        noauthurl = util.removeauth(url)
+        if url == rpath or noauthurl == rpath:
             rpath = path
             break
 
--- a/mercurial/mail.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/mail.py	Thu Jul 19 13:55:54 2018 -0400
@@ -11,6 +11,8 @@
 import email.charset
 import email.header
 import email.message
+import email.parser
+import io
 import os
 import smtplib
 import socket
@@ -322,6 +324,23 @@
         s, cs = _encode(ui, s, charsets)
     return mimetextqp(s, 'plain', cs)
 
+if pycompat.ispy3:
+    def parse(fp):
+        ep = email.parser.Parser()
+        # disable the "universal newlines" mode, which isn't binary safe.
+        # I have no idea if ascii/surrogateescape is correct, but that's
+        # what the standard Python email parser does.
+        fp = io.TextIOWrapper(fp, encoding=r'ascii',
+                              errors=r'surrogateescape', newline=chr(10))
+        try:
+            return ep.parse(fp)
+        finally:
+            fp.detach()
+else:
+    def parse(fp):
+        ep = email.parser.Parser()
+        return ep.parse(fp)
+
 def headdecode(s):
     '''Decodes RFC-2047 header'''
     uparts = []
--- a/mercurial/manifest.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/manifest.py	Thu Jul 19 13:55:54 2018 -0400
@@ -20,9 +20,14 @@
     error,
     mdiff,
     policy,
+    pycompat,
+    repository,
     revlog,
     util,
 )
+from .utils import (
+    interfaceutil,
+)
 
 parsers = policy.importmod(r'parsers')
 propertycache = util.propertycache
@@ -362,6 +367,7 @@
 except AttributeError:
     pass
 
+@interfaceutil.implementer(repository.imanifestdict)
 class manifestdict(object):
     def __init__(self, data=''):
         self._lm = _lazymanifest(data)
@@ -528,7 +534,8 @@
     def items(self):
         return (x[:2] for x in self._lm.iterentries())
 
-    iteritems = items
+    def iteritems(self):
+        return (x[:2] for x in self._lm.iterentries())
 
     def iterentries(self):
         return self._lm.iterentries()
@@ -635,7 +642,8 @@
     for f in l:
         if '\n' in f or '\r' in f:
             raise error.RevlogError(
-                _("'\\n' and '\\r' disallowed in filenames: %r") % f)
+                _("'\\n' and '\\r' disallowed in filenames: %r")
+                % pycompat.bytestr(f))
 
 
 # apply the changes collected during the bisect loop to our addlist
@@ -1260,6 +1268,7 @@
         m.setnode(n)
         return n
 
+@interfaceutil.implementer(repository.imanifestlog)
 class manifestlog(object):
     """A collection class representing the collection of manifest snapshots
     referenced by commits in the repository.
@@ -1285,7 +1294,7 @@
         self._dirmancache = {}
         self._dirmancache[''] = util.lrucachedict(cachesize)
 
-        self.cachesize = cachesize
+        self._cachesize = cachesize
 
     def __getitem__(self, node):
         """Retrieves the manifest instance for the given node. Throws a
@@ -1331,7 +1340,7 @@
         if node != revlog.nullid:
             mancache = self._dirmancache.get(dir)
             if not mancache:
-                mancache = util.lrucachedict(self.cachesize)
+                mancache = util.lrucachedict(self._cachesize)
                 self._dirmancache[dir] = mancache
             mancache[node] = m
         return m
@@ -1340,6 +1349,13 @@
         self._dirmancache.clear()
         self._revlog.clearcaches()
 
+    def rev(self, node):
+        return self._revlog.rev(node)
+
+    def addgroup(self, deltas, linkmapper, transaction):
+        return self._revlog.addgroup(deltas, linkmapper, transaction)
+
+@interfaceutil.implementer(repository.imanifestrevisionwritable)
 class memmanifestctx(object):
     def __init__(self, manifestlog):
         self._manifestlog = manifestlog
@@ -1363,6 +1379,7 @@
         return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
                                   added, removed)
 
+@interfaceutil.implementer(repository.imanifestrevisionstored)
 class manifestctx(object):
     """A class representing a single revision of a manifest, including its
     contents, its parent revs, and its linkrev.
@@ -1439,6 +1456,7 @@
     def find(self, key):
         return self.read().find(key)
 
+@interfaceutil.implementer(repository.imanifestrevisionwritable)
 class memtreemanifestctx(object):
     def __init__(self, manifestlog, dir=''):
         self._manifestlog = manifestlog
@@ -1465,6 +1483,7 @@
         return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
                                   added, removed, readtree=readtree)
 
+@interfaceutil.implementer(repository.imanifestrevisionstored)
 class treemanifestctx(object):
     def __init__(self, manifestlog, dir, node):
         self._manifestlog = manifestlog
--- a/mercurial/match.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/match.py	Thu Jul 19 13:55:54 2018 -0400
@@ -40,9 +40,9 @@
     except AttributeError:
         return m.match
 
-def _expandsets(kindpats, ctx, listsubrepos):
-    '''Returns the kindpats list with the 'set' patterns expanded.'''
-    fset = set()
+def _expandsets(root, cwd, kindpats, ctx, listsubrepos, badfn):
+    '''Returns the kindpats list with the 'set' patterns expanded to matchers'''
+    matchers = []
     other = []
 
     for kind, pat, source in kindpats:
@@ -50,17 +50,17 @@
             if not ctx:
                 raise error.ProgrammingError("fileset expression with no "
                                              "context")
-            s = ctx.getfileset(pat)
-            fset.update(s)
+            matchers.append(ctx.matchfileset(pat, badfn=badfn))
 
             if listsubrepos:
                 for subpath in ctx.substate:
-                    s = ctx.sub(subpath).getfileset(pat)
-                    fset.update(subpath + '/' + f for f in s)
+                    sm = ctx.sub(subpath).matchfileset(pat, badfn=badfn)
+                    pm = prefixdirmatcher(root, cwd, subpath, sm, badfn=badfn)
+                    matchers.append(pm)
 
             continue
         other.append((kind, pat, source))
-    return fset, other
+    return matchers, other
 
 def _expandsubinclude(kindpats, root):
     '''Returns the list of subinclude matcher args and the kindpats without the
@@ -95,6 +95,23 @@
             return False
     return True
 
+def _buildkindpatsmatcher(matchercls, root, cwd, kindpats, ctx=None,
+                          listsubrepos=False, badfn=None):
+    matchers = []
+    fms, kindpats = _expandsets(root, cwd, kindpats, ctx=ctx,
+                                listsubrepos=listsubrepos, badfn=badfn)
+    if kindpats:
+        m = matchercls(root, cwd, kindpats, listsubrepos=listsubrepos,
+                       badfn=badfn)
+        matchers.append(m)
+    if fms:
+        matchers.extend(fms)
+    if not matchers:
+        return nevermatcher(root, cwd, badfn=badfn)
+    if len(matchers) == 1:
+        return matchers[0]
+    return unionmatcher(matchers)
+
 def match(root, cwd, patterns=None, include=None, exclude=None, default='glob',
           exact=False, auditor=None, ctx=None, listsubrepos=False, warn=None,
           badfn=None, icasefs=False):
@@ -159,8 +176,9 @@
         if _kindpatsalwaysmatch(kindpats):
             m = alwaysmatcher(root, cwd, badfn, relativeuipath=True)
         else:
-            m = patternmatcher(root, cwd, kindpats, ctx=ctx,
-                               listsubrepos=listsubrepos, badfn=badfn)
+            m = _buildkindpatsmatcher(patternmatcher, root, cwd, kindpats,
+                                      ctx=ctx, listsubrepos=listsubrepos,
+                                      badfn=badfn)
     else:
         # It's a little strange that no patterns means to match everything.
         # Consider changing this to match nothing (probably using nevermatcher).
@@ -168,13 +186,13 @@
 
     if include:
         kindpats = normalize(include, 'glob', root, cwd, auditor, warn)
-        im = includematcher(root, cwd, kindpats, ctx=ctx,
-                            listsubrepos=listsubrepos, badfn=None)
+        im = _buildkindpatsmatcher(includematcher, root, cwd, kindpats, ctx=ctx,
+                                   listsubrepos=listsubrepos, badfn=None)
         m = intersectmatchers(m, im)
     if exclude:
         kindpats = normalize(exclude, 'glob', root, cwd, auditor, warn)
-        em = includematcher(root, cwd, kindpats, ctx=ctx,
-                            listsubrepos=listsubrepos, badfn=None)
+        em = _buildkindpatsmatcher(includematcher, root, cwd, kindpats, ctx=ctx,
+                                   listsubrepos=listsubrepos, badfn=None)
         m = differencematcher(m, em)
     return m
 
@@ -375,15 +393,28 @@
     def __repr__(self):
         return r'<nevermatcher>'
 
+class predicatematcher(basematcher):
+    """A matcher adapter for a simple boolean function"""
+
+    def __init__(self, root, cwd, predfn, predrepr=None, badfn=None):
+        super(predicatematcher, self).__init__(root, cwd, badfn)
+        self.matchfn = predfn
+        self._predrepr = predrepr
+
+    @encoding.strmethod
+    def __repr__(self):
+        s = (stringutil.buildrepr(self._predrepr)
+             or pycompat.byterepr(self.matchfn))
+        return '<predicatenmatcher pred=%s>' % s
+
 class patternmatcher(basematcher):
 
-    def __init__(self, root, cwd, kindpats, ctx=None, listsubrepos=False,
-                 badfn=None):
+    def __init__(self, root, cwd, kindpats, listsubrepos=False, badfn=None):
         super(patternmatcher, self).__init__(root, cwd, badfn)
 
         self._files = _explicitfiles(kindpats)
         self._prefix = _prefix(kindpats)
-        self._pats, self.matchfn = _buildmatch(ctx, kindpats, '$', listsubrepos,
+        self._pats, self.matchfn = _buildmatch(kindpats, '$', listsubrepos,
                                                root)
 
     @propertycache
@@ -404,15 +435,14 @@
 
     @encoding.strmethod
     def __repr__(self):
-        return ('<patternmatcher patterns=%r>' % self._pats)
+        return ('<patternmatcher patterns=%r>' % pycompat.bytestr(self._pats))
 
 class includematcher(basematcher):
 
-    def __init__(self, root, cwd, kindpats, ctx=None, listsubrepos=False,
-                 badfn=None):
+    def __init__(self, root, cwd, kindpats, listsubrepos=False, badfn=None):
         super(includematcher, self).__init__(root, cwd, badfn)
 
-        self._pats, self.matchfn = _buildmatch(ctx, kindpats, '(?:/|$)',
+        self._pats, self.matchfn = _buildmatch(kindpats, '(?:/|$)',
                                                listsubrepos, root)
         self._prefix = _prefix(kindpats)
         roots, dirs = _rootsanddirs(kindpats)
@@ -653,6 +683,78 @@
         return ('<subdirmatcher path=%r, matcher=%r>' %
                 (self._path, self._matcher))
 
+class prefixdirmatcher(basematcher):
+    """Adapt a matcher to work on a parent directory.
+
+    The matcher's non-matching-attributes (root, cwd, bad, explicitdir,
+    traversedir) are ignored.
+
+    The prefix path should usually be the relative path from the root of
+    this matcher to the root of the wrapped matcher.
+
+    >>> m1 = match(b'root/d/e', b'f', [b'../a.txt', b'b.txt'])
+    >>> m2 = prefixdirmatcher(b'root', b'd/e/f', b'd/e', m1)
+    >>> bool(m2(b'a.txt'),)
+    False
+    >>> bool(m2(b'd/e/a.txt'))
+    True
+    >>> bool(m2(b'd/e/b.txt'))
+    False
+    >>> m2.files()
+    ['d/e/a.txt', 'd/e/f/b.txt']
+    >>> m2.exact(b'd/e/a.txt')
+    True
+    >>> m2.visitdir(b'd')
+    True
+    >>> m2.visitdir(b'd/e')
+    True
+    >>> m2.visitdir(b'd/e/f')
+    True
+    >>> m2.visitdir(b'd/e/g')
+    False
+    >>> m2.visitdir(b'd/ef')
+    False
+    """
+
+    def __init__(self, root, cwd, path, matcher, badfn=None):
+        super(prefixdirmatcher, self).__init__(root, cwd, badfn)
+        if not path:
+            raise error.ProgrammingError('prefix path must not be empty')
+        self._path = path
+        self._pathprefix = path + '/'
+        self._matcher = matcher
+
+    @propertycache
+    def _files(self):
+        return [self._pathprefix + f for f in self._matcher._files]
+
+    def matchfn(self, f):
+        if not f.startswith(self._pathprefix):
+            return False
+        return self._matcher.matchfn(f[len(self._pathprefix):])
+
+    @propertycache
+    def _pathdirs(self):
+        return set(util.finddirs(self._path)) | {'.'}
+
+    def visitdir(self, dir):
+        if dir == self._path:
+            return self._matcher.visitdir('.')
+        if dir.startswith(self._pathprefix):
+            return self._matcher.visitdir(dir[len(self._pathprefix):])
+        return dir in self._pathdirs
+
+    def isexact(self):
+        return self._matcher.isexact()
+
+    def prefix(self):
+        return self._matcher.prefix()
+
+    @encoding.strmethod
+    def __repr__(self):
+        return ('<prefixdirmatcher path=%r, matcher=%r>'
+                % (pycompat.bytestr(self._path), self._matcher))
+
 class unionmatcher(basematcher):
     """A matcher that is the union of several matchers.
 
@@ -714,7 +816,7 @@
     >>> bprint(_globre(br'**/a'))
     (?:.*/)?a
     >>> bprint(_globre(br'a/**/b'))
-    a\/(?:.*/)?b
+    a/(?:.*/)?b
     >>> bprint(_globre(br'[a*?!^][^b][!c]'))
     [a*?!^][\^b][^c]
     >>> bprint(_globre(br'{a,b}'))
@@ -725,7 +827,7 @@
     i, n = 0, len(pat)
     res = ''
     group = 0
-    escape = util.re.escape
+    escape = util.stringutil.reescape
     def peek():
         return i < n and pat[i:i + 1]
     while i < n:
@@ -790,13 +892,13 @@
     if kind in ('path', 'relpath'):
         if pat == '.':
             return ''
-        return util.re.escape(pat) + '(?:/|$)'
+        return util.stringutil.reescape(pat) + '(?:/|$)'
     if kind == 'rootfilesin':
         if pat == '.':
             escaped = ''
         else:
             # Pattern is a directory name.
-            escaped = util.re.escape(pat) + '/'
+            escaped = util.stringutil.reescape(pat) + '/'
         # Anything after the pattern must be a non-directory.
         return escaped + '[^/]+$'
     if kind == 'relglob':
@@ -805,9 +907,11 @@
         if pat.startswith('^'):
             return pat
         return '.*' + pat
-    return _globre(pat) + globsuffix
+    if kind == 'glob':
+        return _globre(pat) + globsuffix
+    raise error.ProgrammingError('not a regex pattern: %s:%s' % (kind, pat))
 
-def _buildmatch(ctx, kindpats, globsuffix, listsubrepos, root):
+def _buildmatch(kindpats, globsuffix, listsubrepos, root):
     '''Return regexp string and a matcher function for kindpats.
     globsuffix is appended to the regexp of globs.'''
     matchfuncs = []
@@ -828,10 +932,6 @@
             return False
         matchfuncs.append(matchsubinclude)
 
-    fset, kindpats = _expandsets(kindpats, ctx, listsubrepos)
-    if fset:
-        matchfuncs.append(fset.__contains__)
-
     regex = ''
     if kindpats:
         regex, mf = _buildregexmatch(kindpats, globsuffix)
--- a/mercurial/merge.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/merge.py	Thu Jul 19 13:55:54 2018 -0400
@@ -903,6 +903,23 @@
     return actions
 
 def _checkcollision(repo, wmf, actions):
+    """
+    Check for case-folding collisions.
+    """
+
+    # If the repo is narrowed, filter out files outside the narrowspec.
+    narrowmatch = repo.narrowmatch()
+    if not narrowmatch.always():
+        wmf = wmf.matches(narrowmatch)
+        if actions:
+            narrowactions = {}
+            for m, actionsfortype in actions.iteritems():
+                narrowactions[m] = []
+                for (f, args, msg) in actionsfortype:
+                    if narrowmatch(f):
+                        narrowactions[m].append((f, args, msg))
+            actions = narrowactions
+
     # build provisional merged manifest up
     pmmf = set(wmf)
 
@@ -1072,6 +1089,33 @@
             repo.ui.warn(_("%s: is both a file and a directory\n") % p)
         raise error.Abort(_("destination manifest contains path conflicts"))
 
+def _filternarrowactions(narrowmatch, branchmerge, actions):
+    """
+    Filters out actions that can ignored because the repo is narrowed.
+
+    Raise an exception if the merge cannot be completed because the repo is
+    narrowed.
+    """
+    nooptypes = set(['k']) # TODO: handle with nonconflicttypes
+    nonconflicttypes = set('a am c cm f g r e'.split())
+    # We mutate the items in the dict during iteration, so iterate
+    # over a copy.
+    for f, action in list(actions.items()):
+        if narrowmatch(f):
+            pass
+        elif not branchmerge:
+            del actions[f] # just updating, ignore changes outside clone
+        elif action[0] in nooptypes:
+            del actions[f] # merge does not affect file
+        elif action[0] in nonconflicttypes:
+            raise error.Abort(_('merge affects file \'%s\' outside narrow, '
+                                'which is not yet supported') % f,
+                              hint=_('merging in the other direction '
+                                     'may work'))
+        else:
+            raise error.Abort(_('conflict in file \'%s\' is outside '
+                                'narrow clone') % f)
+
 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
                   acceptremote, followcopies, forcefulldiff=False):
     """
@@ -1258,6 +1302,11 @@
         # If we are merging, look for path conflicts.
         checkpathconflicts(repo, wctx, p2, actions)
 
+    narrowmatch = repo.narrowmatch()
+    if not narrowmatch.always():
+        # Updates "actions" in place
+        _filternarrowactions(narrowmatch, branchmerge, actions)
+
     return actions, diverge, renamedelete
 
 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
@@ -1492,27 +1541,6 @@
         return (not self.updatedcount and not self.mergedcount
                 and not self.removedcount and not self.unresolvedcount)
 
-    # TODO remove container emulation once consumers switch to new API.
-
-    def __getitem__(self, x):
-        util.nouideprecwarn('access merge.update() results by name instead of '
-                            'index', '4.6', 2)
-        if x == 0:
-            return self.updatedcount
-        elif x == 1:
-            return self.mergedcount
-        elif x == 2:
-            return self.removedcount
-        elif x == 3:
-            return self.unresolvedcount
-        else:
-            raise IndexError('can only access items 0-3')
-
-    def __len__(self):
-        util.nouideprecwarn('access merge.update() results by name instead of '
-                            'index', '4.6', 2)
-        return 4
-
 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
     """apply the merge action list to the working directory
 
@@ -1558,10 +1586,6 @@
         if f1 != f and move:
             moves.append(f1)
 
-    _updating = _('updating')
-    _files = _('files')
-    progress = repo.ui.progress
-
     # remove renamed files after safely stored
     for f in moves:
         if wctx[f].lexists():
@@ -1571,7 +1595,8 @@
 
     numupdates = sum(len(l) for m, l in actions.items()
                      if m != ACTION_KEEP)
-    z = 0
+    progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
+                                    total=numupdates)
 
     if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
         subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
@@ -1588,8 +1613,7 @@
             s(_("the remote file has been renamed to %s\n") % f1)
         s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
         ms.addpath(f, f1, fo)
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
+        progress.increment(item=f)
 
     # When merging in-memory, we can't support worker processes, so set the
     # per-item cost at 0 in that case.
@@ -1599,8 +1623,7 @@
     prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
                          actions[ACTION_REMOVE])
     for i, item in prog:
-        z += i
-        progress(_updating, z, item=item, total=numupdates, unit=_files)
+        progress.increment(step=i, item=item)
     removed = len(actions[ACTION_REMOVE])
 
     # resolve path conflicts (must come before getting)
@@ -1612,15 +1635,16 @@
             wctx[f].audit()
             wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
             wctx[f0].remove()
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
+        progress.increment(item=f)
 
-    # get in parallel
+    # get in parallel.
+    threadsafe = repo.ui.configbool('experimental',
+                                    'worker.wdir-get-thread-safe')
     prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
-                         actions[ACTION_GET])
+                         actions[ACTION_GET],
+                         threadsafe=threadsafe)
     for i, item in prog:
-        z += i
-        progress(_updating, z, item=item, total=numupdates, unit=_files)
+        progress.increment(step=i, item=item)
     updated = len(actions[ACTION_GET])
 
     if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
@@ -1629,20 +1653,17 @@
     # forget (manifest only, just log it) (must come first)
     for f, args, msg in actions[ACTION_FORGET]:
         repo.ui.debug(" %s: %s -> f\n" % (f, msg))
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
+        progress.increment(item=f)
 
     # re-add (manifest only, just log it)
     for f, args, msg in actions[ACTION_ADD]:
         repo.ui.debug(" %s: %s -> a\n" % (f, msg))
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
+        progress.increment(item=f)
 
     # re-add/mark as modified (manifest only, just log it)
     for f, args, msg in actions[ACTION_ADD_MODIFIED]:
         repo.ui.debug(" %s: %s -> am\n" % (f, msg))
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
+        progress.increment(item=f)
 
     # keep (noop, just log it)
     for f, args, msg in actions[ACTION_KEEP]:
@@ -1652,8 +1673,7 @@
     # directory rename, move local
     for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
         repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
+        progress.increment(item=f)
         f0, flags = args
         repo.ui.note(_("moving %s to %s\n") % (f0, f))
         wctx[f].audit()
@@ -1664,8 +1684,7 @@
     # local directory rename, get
     for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
         repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
+        progress.increment(item=f)
         f0, flags = args
         repo.ui.note(_("getting %s to %s\n") % (f0, f))
         wctx[f].write(mctx.filectx(f0).data(), flags)
@@ -1674,8 +1693,7 @@
     # exec
     for f, args, msg in actions[ACTION_EXEC]:
         repo.ui.debug(" %s: %s -> e\n" % (f, msg))
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
+        progress.increment(item=f)
         flags, = args
         wctx[f].audit()
         wctx[f].setflags('l' in flags, 'x' in flags)
@@ -1710,8 +1728,7 @@
         tocomplete = []
         for f, args, msg in mergeactions:
             repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
-            z += 1
-            progress(_updating, z, item=f, total=numupdates, unit=_files)
+            progress.increment(item=f)
             if f == '.hgsubstate': # subrepo states need updating
                 subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
                                      overwrite, labels)
@@ -1725,8 +1742,7 @@
         # merge
         for f, args, msg in tocomplete:
             repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
-            z += 1
-            progress(_updating, z, item=f, total=numupdates, unit=_files)
+            progress.increment(item=f, total=numupdates)
             ms.resolve(f, wctx)
 
     finally:
@@ -1774,7 +1790,7 @@
         actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
                                  if a[0] in mfiles]
 
-    progress(_updating, None, total=numupdates, unit=_files)
+    progress.complete()
     return updateresult(updated, merged, removed, unresolved)
 
 def recordupdates(repo, actions, branchmerge):
@@ -2181,7 +2197,8 @@
                   error=stats.unresolvedcount)
     return stats
 
-def graft(repo, ctx, pctx, labels, keepparent=False):
+def graft(repo, ctx, pctx, labels, keepparent=False,
+          keepconflictparent=False):
     """Do a graft-like merge.
 
     This is a merge where the merge ancestor is chosen such that one
@@ -2194,6 +2211,7 @@
     pctx - merge base, usually ctx.p1()
     labels - merge labels eg ['local', 'graft']
     keepparent - keep second parent if any
+    keepparent - if unresolved, keep parent used for the merge
 
     """
     # If we're grafting a descendant onto an ancestor, be sure to pass
@@ -2207,11 +2225,15 @@
     stats = update(repo, ctx.node(), True, True, pctx.node(),
                    mergeancestor=mergeancestor, labels=labels)
 
-    pother = nullid
-    parents = ctx.parents()
-    if keepparent and len(parents) == 2 and pctx in parents:
-        parents.remove(pctx)
-        pother = parents[0].node()
+
+    if keepconflictparent and stats.unresolvedcount:
+        pother = ctx.node()
+    else:
+        pother = nullid
+        parents = ctx.parents()
+        if keepparent and len(parents) == 2 and pctx in parents:
+            parents.remove(pctx)
+            pother = parents[0].node()
 
     with repo.dirstate.parentchange():
         repo.setparents(repo['.'].node(), pother)
--- a/mercurial/minifileset.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/minifileset.py	Thu Jul 19 13:55:54 2018 -0400
@@ -11,8 +11,14 @@
 from . import (
     error,
     fileset,
+    pycompat,
 )
 
+def _sizep(x):
+    # i18n: "size" is a keyword
+    expr = fileset.getstring(x, _("size requires an expression"))
+    return fileset.sizematcher(expr)
+
 def _compile(tree):
     if not tree:
         raise error.ParseError(_("missing argument"))
@@ -21,14 +27,15 @@
         name = fileset.getpattern(tree, {'path'}, _('invalid file pattern'))
         if name.startswith('**'): # file extension test, ex. "**.tar.gz"
             ext = name[2:]
-            for c in ext:
+            for c in pycompat.bytestr(ext):
                 if c in '*{}[]?/\\':
                     raise error.ParseError(_('reserved character: %s') % c)
             return lambda n, s: n.endswith(ext)
         elif name.startswith('path:'): # directory or full path test
             p = name[5:] # prefix
             pl = len(p)
-            f = lambda n, s: n.startswith(p) and (len(n) == pl or n[pl] == '/')
+            f = lambda n, s: n.startswith(p) and (len(n) == pl
+                                                  or n[pl:pl + 1] == '/')
             return f
         raise error.ParseError(_("unsupported file pattern: %s") % name,
                                hint=_('paths must be prefixed with "path:"'))
@@ -48,7 +55,7 @@
         symbols = {
             'all': lambda n, s: True,
             'none': lambda n, s: False,
-            'size': lambda n, s: fileset.sizematcher(tree[2])(s),
+            'size': lambda n, s: _sizep(tree[2])(s),
         }
 
         name = fileset.getsymbol(tree[1])
--- a/mercurial/minirst.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/minirst.py	Thu Jul 19 13:55:54 2018 -0400
@@ -169,7 +169,7 @@
         if not itemre.match(line1):
             return False
         if singleline:
-            return line2 == '' or line2[0] == ' ' or itemre.match(line2)
+            return line2 == '' or line2[0:1] == ' ' or itemre.match(line2)
         else:
             return line2.startswith(' ')
 
--- a/mercurial/namespaces.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/namespaces.py	Thu Jul 19 13:55:54 2018 -0400
@@ -95,21 +95,16 @@
 
     def singlenode(self, repo, name):
         """
-        Return the 'best' node for the given name. Best means the first node
-        in the first nonempty list returned by a name-to-nodes mapping function
-        in the defined precedence order.
+        Return the 'best' node for the given name. What's best is defined
+        by the namespace's singlenode() function. The first match returned by
+        a namespace in the defined precedence order is used.
 
         Raises a KeyError if there is no such node.
         """
         for ns, v in self._names.iteritems():
-            n = v.namemap(repo, name)
+            n = v.singlenode(repo, name)
             if n:
-                # return max revision number
-                if len(n) > 1:
-                    cl = repo.changelog
-                    maxrev = max(cl.rev(node) for node in n)
-                    return cl.node(maxrev)
-                return n[0]
+                return n
         raise KeyError(_('no such name: %s') % name)
 
 class namespace(object):
@@ -142,7 +137,7 @@
 
     def __init__(self, name, templatename=None, logname=None, colorname=None,
                  logfmt=None, listnames=None, namemap=None, nodemap=None,
-                 deprecated=None, builtin=False):
+                 deprecated=None, builtin=False, singlenode=None):
         """create a namespace
 
         name: the namespace to be registered (in plural form)
@@ -158,6 +153,7 @@
         nodemap: function that inputs a node, output name(s)
         deprecated: set of names to be masked for ordinary use
         builtin: whether namespace is implemented by core Mercurial
+        singlenode: function that inputs a name, output best node (or None)
         """
         self.name = name
         self.templatename = templatename
@@ -167,6 +163,8 @@
         self.listnames = listnames
         self.namemap = namemap
         self.nodemap = nodemap
+        if singlenode:
+            self.singlenode = singlenode
 
         # if logname is not specified, use the template name as backup
         if self.logname is None:
@@ -199,3 +197,18 @@
 
         """
         return sorted(self.namemap(repo, name))
+
+    def singlenode(self, repo, name):
+        """returns the best node for the given name
+
+        By default, the best node is the node from nodes() with the highest
+        revision number. It can be overriden by the namespace."""
+        n = self.namemap(repo, name)
+        if n:
+            # return max revision number
+            if len(n) > 1:
+                cl = repo.changelog
+                maxrev = max(cl.rev(node) for node in n)
+                return cl.node(maxrev)
+            return n[0]
+        return None
--- a/mercurial/obsolete.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/obsolete.py	Thu Jul 19 13:55:54 2018 -0400
@@ -74,11 +74,13 @@
 
 from .i18n import _
 from . import (
+    encoding,
     error,
     node,
     obsutil,
     phases,
     policy,
+    pycompat,
     util,
 )
 from .utils import dateutil
@@ -526,7 +528,7 @@
     # prec:    nodeid, predecessors changesets
     # succs:   tuple of nodeid, successor changesets (0-N length)
     # flag:    integer, flag field carrying modifier for the markers (see doc)
-    # meta:    binary blob, encoded metadata dictionary
+    # meta:    binary blob in UTF-8, encoded metadata dictionary
     # date:    (float, int) tuple, date of marker creation
     # parents: (tuple of nodeid) or None, parents of predecessors
     #          None is used when no data has been recorded
@@ -599,6 +601,16 @@
             raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
 
         metadata = tuple(sorted(metadata.iteritems()))
+        for k, v in metadata:
+            try:
+                # might be better to reject non-ASCII keys
+                k.decode('utf-8')
+                v.decode('utf-8')
+            except UnicodeDecodeError:
+                raise error.ProgrammingError(
+                    'obsstore metadata must be valid UTF-8 sequence '
+                    '(key = %r, value = %r)'
+                    % (pycompat.bytestr(k), pycompat.bytestr(v)))
 
         marker = (bytes(prec), tuple(succs), int(flag), metadata, date, parents)
         return bool(self.add(transaction, [marker]))
@@ -853,7 +865,7 @@
 
 def _mutablerevs(repo):
     """the set of mutable revision in the repository"""
-    return repo._phasecache.getrevset(repo, (phases.draft, phases.secret))
+    return repo._phasecache.getrevset(repo, phases.mutablephases)
 
 @cachefor('obsolete')
 def _computeobsoleteset(repo):
@@ -950,7 +962,8 @@
     <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
     tuple. `old` and `news` are changectx. metadata is an optional dictionary
     containing metadata for this marker only. It is merged with the global
-    metadata specified through the `metadata` argument of this function,
+    metadata specified through the `metadata` argument of this function.
+    Any string values in metadata must be UTF-8 bytes.
 
     Trying to obsolete a public changeset will raise an exception.
 
@@ -964,11 +977,8 @@
     if metadata is None:
         metadata = {}
     if 'user' not in metadata:
-        develuser = repo.ui.config('devel', 'user.obsmarker')
-        if develuser:
-            metadata['user'] = develuser
-        else:
-            metadata['user'] = repo.ui.username()
+        luser = repo.ui.config('devel', 'user.obsmarker') or repo.ui.username()
+        metadata['user'] = encoding.fromlocal(luser)
 
     # Operation metadata handling
     useoperation = repo.ui.configbool('experimental',
--- a/mercurial/obsutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/obsutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -11,11 +11,15 @@
 
 from .i18n import _
 from . import (
+    diffutil,
+    encoding,
     node as nodemod,
     phases,
     util,
 )
-from .utils import dateutil
+from .utils import (
+    dateutil,
+)
 
 ### obsolescence marker flag
 
@@ -392,13 +396,13 @@
 
     This is a first and basic implementation, with many shortcoming.
     """
-
+    diffopts = diffutil.diffallopts(leftctx.repo().ui, {'git': True})
     # Leftctx or right ctx might be filtered, so we need to use the contexts
     # with an unfiltered repository to safely compute the diff
     leftunfi = leftctx._repo.unfiltered()[leftctx.rev()]
-    leftdiff = leftunfi.diff(git=1)
+    leftdiff = leftunfi.diff(opts=diffopts)
     rightunfi = rightctx._repo.unfiltered()[rightctx.rev()]
-    rightdiff = rightunfi.diff(git=1)
+    rightdiff = rightunfi.diff(opts=diffopts)
 
     left, right = (0, 0)
     while None not in (left, right):
@@ -819,7 +823,8 @@
     """ Returns a sorted list of markers users without duplicates
     """
     markersmeta = [dict(m[3]) for m in markers]
-    users = set(meta.get('user') for meta in markersmeta if meta.get('user'))
+    users = set(encoding.tolocal(meta['user']) for meta in markersmeta
+                if meta.get('user'))
 
     return sorted(users)
 
--- a/mercurial/patch.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/patch.py	Thu Jul 19 13:55:54 2018 -0400
@@ -18,7 +18,6 @@
 import posixpath
 import re
 import shutil
-import tempfile
 import zlib
 
 from .i18n import _
@@ -29,6 +28,7 @@
 from . import (
     copies,
     diffhelper,
+    diffutil,
     encoding,
     error,
     mail,
@@ -51,7 +51,7 @@
 gitre = re.compile(br'diff --git a/(.*) b/(.*)')
 tabsplitter = re.compile(br'(\t+|[^\t]+)')
 wordsplitter = re.compile(br'(\t+| +|[a-zA-Z0-9_\x80-\xff]+|'
-                          '[^ \ta-zA-Z0-9_\x80-\xff])')
+                          b'[^ \ta-zA-Z0-9_\x80-\xff])')
 
 PatchError = error.PatchError
 
@@ -113,7 +113,7 @@
             cur.append(line)
         c = chunk(cur)
 
-        m = pycompat.emailparser().parse(c)
+        m = mail.parse(c)
         if not m.is_multipart():
             yield msgfp(m)
         else:
@@ -211,7 +211,7 @@
     Any item can be missing from the dictionary. If filename is missing,
     fileobj did not contain a patch. Caller must unlink filename when done.'''
 
-    fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
+    fd, tmpname = pycompat.mkstemp(prefix='hg-patch-')
     tmpfp = os.fdopen(fd, r'wb')
     try:
         yield _extract(ui, fileobj, tmpname, tmpfp)
@@ -231,7 +231,7 @@
 
     data = {}
 
-    msg = pycompat.emailparser().parse(fileobj)
+    msg = mail.parse(fileobj)
 
     subject = msg[r'Subject'] and mail.headdecode(msg[r'Subject'])
     data['user'] = msg[r'From'] and mail.headdecode(msg[r'From'])
@@ -498,7 +498,8 @@
                 self.opener.setflags(fname, False, True)
 
     def unlink(self, fname):
-        self.opener.unlinkpath(fname, ignoremissing=True)
+        rmdir = self.ui.configbool('experimental', 'removeemptydirs')
+        self.opener.unlinkpath(fname, ignoremissing=True, rmdir=rmdir)
 
     def writerej(self, fname, failed, total, lines):
         fname = fname + ".rej"
@@ -573,7 +574,7 @@
             self.size += len(data)
         else:
             if self.opener is None:
-                root = tempfile.mkdtemp(prefix='hg-patch-')
+                root = pycompat.mkdtemp(prefix='hg-patch-')
                 self.opener = vfsmod.vfs(root)
             # Avoid filename issues with these simple names
             fn = '%d' % self.created
@@ -708,7 +709,7 @@
         if self.eolmode != 'strict' and eol and eol != '\n':
             rawlines = []
             for l in lines:
-                if l and l[-1] == '\n':
+                if l and l.endswith('\n'):
                     l = l[:-1] + eol
                 rawlines.append(l)
             lines = rawlines
@@ -1109,7 +1110,7 @@
 all lines of the hunk are removed, then the edit is aborted and
 the hunk is left unchanged.
 """)
-                (patchfd, patchfn) = tempfile.mkstemp(prefix="hg-editor-",
+                (patchfd, patchfn) = pycompat.mkstemp(prefix="hg-editor-",
                                                       suffix=".diff")
                 ncpatchfp = None
                 try:
@@ -1946,7 +1947,7 @@
     """
     def deltahead(binchunk):
         i = 0
-        for c in binchunk:
+        for c in pycompat.bytestr(binchunk):
             i += 1
             if not (ord(c) & 0x80):
                 return i
@@ -1958,31 +1959,31 @@
     binchunk = binchunk[s:]
     i = 0
     while i < len(binchunk):
-        cmd = ord(binchunk[i])
+        cmd = ord(binchunk[i:i + 1])
         i += 1
         if (cmd & 0x80):
             offset = 0
             size = 0
             if (cmd & 0x01):
-                offset = ord(binchunk[i])
+                offset = ord(binchunk[i:i + 1])
                 i += 1
             if (cmd & 0x02):
-                offset |= ord(binchunk[i]) << 8
+                offset |= ord(binchunk[i:i + 1]) << 8
                 i += 1
             if (cmd & 0x04):
-                offset |= ord(binchunk[i]) << 16
+                offset |= ord(binchunk[i:i + 1]) << 16
                 i += 1
             if (cmd & 0x08):
-                offset |= ord(binchunk[i]) << 24
+                offset |= ord(binchunk[i:i + 1]) << 24
                 i += 1
             if (cmd & 0x10):
-                size = ord(binchunk[i])
+                size = ord(binchunk[i:i + 1])
                 i += 1
             if (cmd & 0x20):
-                size |= ord(binchunk[i]) << 8
+                size |= ord(binchunk[i:i + 1]) << 8
                 i += 1
             if (cmd & 0x40):
-                size |= ord(binchunk[i]) << 16
+                size |= ord(binchunk[i:i + 1]) << 16
                 i += 1
             if size == 0:
                 size = 0x10000
@@ -2113,6 +2114,7 @@
         args.append('-d %s' % procutil.shellquote(cwd))
     cmd = ('%s %s -p%d < %s'
            % (patcher, ' '.join(args), strip, procutil.shellquote(patchname)))
+    ui.debug('Using external patch tool: %s\n' % cmd)
     fp = procutil.popen(cmd, 'rb')
     try:
         for line in util.iterfile(fp):
@@ -2231,95 +2233,9 @@
 class GitDiffRequired(Exception):
     pass
 
-def diffallopts(ui, opts=None, untrusted=False, section='diff'):
-    '''return diffopts with all features supported and parsed'''
-    return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
-                           git=True, whitespace=True, formatchanging=True)
-
-diffopts = diffallopts
-
-def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
-                    whitespace=False, formatchanging=False):
-    '''return diffopts with only opted-in features parsed
-
-    Features:
-    - git: git-style diffs
-    - whitespace: whitespace options like ignoreblanklines and ignorews
-    - formatchanging: options that will likely break or cause correctness issues
-      with most diff parsers
-    '''
-    def get(key, name=None, getter=ui.configbool, forceplain=None):
-        if opts:
-            v = opts.get(key)
-            # diffopts flags are either None-default (which is passed
-            # through unchanged, so we can identify unset values), or
-            # some other falsey default (eg --unified, which defaults
-            # to an empty string). We only want to override the config
-            # entries from hgrc with command line values if they
-            # appear to have been set, which is any truthy value,
-            # True, or False.
-            if v or isinstance(v, bool):
-                return v
-        if forceplain is not None and ui.plain():
-            return forceplain
-        return getter(section, name or key, untrusted=untrusted)
-
-    # core options, expected to be understood by every diff parser
-    buildopts = {
-        'nodates': get('nodates'),
-        'showfunc': get('show_function', 'showfunc'),
-        'context': get('unified', getter=ui.config),
-    }
-    buildopts['worddiff'] = ui.configbool('experimental', 'worddiff')
-    buildopts['xdiff'] = ui.configbool('experimental', 'xdiff')
-
-    if git:
-        buildopts['git'] = get('git')
-
-        # since this is in the experimental section, we need to call
-        # ui.configbool directory
-        buildopts['showsimilarity'] = ui.configbool('experimental',
-                                                    'extendedheader.similarity')
-
-        # need to inspect the ui object instead of using get() since we want to
-        # test for an int
-        hconf = ui.config('experimental', 'extendedheader.index')
-        if hconf is not None:
-            hlen = None
-            try:
-                # the hash config could be an integer (for length of hash) or a
-                # word (e.g. short, full, none)
-                hlen = int(hconf)
-                if hlen < 0 or hlen > 40:
-                    msg = _("invalid length for extendedheader.index: '%d'\n")
-                    ui.warn(msg % hlen)
-            except ValueError:
-                # default value
-                if hconf == 'short' or hconf == '':
-                    hlen = 12
-                elif hconf == 'full':
-                    hlen = 40
-                elif hconf != 'none':
-                    msg = _("invalid value for extendedheader.index: '%s'\n")
-                    ui.warn(msg % hconf)
-            finally:
-                buildopts['index'] = hlen
-
-    if whitespace:
-        buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
-        buildopts['ignorewsamount'] = get('ignore_space_change',
-                                          'ignorewsamount')
-        buildopts['ignoreblanklines'] = get('ignore_blank_lines',
-                                            'ignoreblanklines')
-        buildopts['ignorewseol'] = get('ignore_space_at_eol', 'ignorewseol')
-    if formatchanging:
-        buildopts['text'] = opts and opts.get('text')
-        binary = None if opts is None else opts.get('binary')
-        buildopts['nobinary'] = (not binary if binary is not None
-                                 else get('nobinary', forceplain=False))
-        buildopts['noprefix'] = get('noprefix', forceplain=False)
-
-    return mdiff.diffopts(**pycompat.strkwargs(buildopts))
+diffopts = diffutil.diffallopts
+diffallopts = diffutil.diffallopts
+difffeatureopts = diffutil.difffeatureopts
 
 def diff(repo, node1=None, node2=None, match=None, changes=None,
          opts=None, losedatafn=None, prefix='', relroot='', copy=None,
@@ -2489,17 +2405,17 @@
     """yield tokens for a list of lines in a single hunk"""
     for line in hunklines:
         # chomp
-        chompline = line.rstrip('\n')
+        chompline = line.rstrip('\r\n')
         # highlight tabs and trailing whitespace
         stripline = chompline.rstrip()
-        if line[0] == '-':
+        if line.startswith('-'):
             label = 'diff.deleted'
-        elif line[0] == '+':
+        elif line.startswith('+'):
             label = 'diff.inserted'
         else:
             raise error.ProgrammingError('unexpected hunk line: %s' % line)
         for token in tabsplitter.findall(stripline):
-            if '\t' == token[0]:
+            if token.startswith('\t'):
                 yield (token, 'diff.tab')
             else:
                 yield (token, label)
@@ -2557,6 +2473,9 @@
             isendofline = token.endswith('\n')
             if isendofline:
                 chomp = token[:-1] # chomp
+                if chomp.endswith('\r'):
+                    chomp = chomp[:-1]
+                endofline = token[len(chomp):]
                 token = chomp.rstrip() # detect spaces at the end
                 endspaces = chomp[len(token):]
             # scan tabs
@@ -2572,7 +2491,7 @@
             if isendofline:
                 if endspaces:
                     yield (endspaces, 'diff.trailingwhitespace')
-                yield ('\n', '')
+                yield (endofline, '')
                 nextisnewline = True
 
 def difflabel(func, *args, **kw):
--- a/mercurial/pathutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/pathutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -226,7 +226,7 @@
             if cwd != root:
                 canonpath(root, root, myname, auditor)
                 relpath = util.pathto(root, cwd, '')
-                if relpath[-1] == pycompat.ossep:
+                if relpath.endswith(pycompat.ossep):
                     relpath = relpath[:-1]
                 hint = (_("consider using '--cwd %s'") % relpath)
         except error.Abort:
--- a/mercurial/phases.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/phases.py	Thu Jul 19 13:55:54 2018 -0400
@@ -126,6 +126,8 @@
 allphases = public, draft, secret = range(3)
 trackedphases = allphases[1:]
 phasenames = ['public', 'draft', 'secret']
+mutablephases = tuple(allphases[1:])
+remotehiddenphases = tuple(allphases[2:])
 
 def _readroots(repo, phasedefaults=None):
     """Read phase roots from disk
@@ -352,10 +354,14 @@
                 _trackphasechange(phasetracking, rev, None, revphase)
         repo.invalidatevolatilesets()
 
-    def advanceboundary(self, repo, tr, targetphase, nodes):
+    def advanceboundary(self, repo, tr, targetphase, nodes, dryrun=None):
         """Set all 'nodes' to phase 'targetphase'
 
         Nodes with a phase lower than 'targetphase' are not affected.
+
+        If dryrun is True, no actions will be performed
+
+        Returns a set of revs whose phase is changed or should be changed
         """
         # Be careful to preserve shallow-copied values: do not update
         # phaseroots values, replace them.
@@ -366,6 +372,7 @@
 
         repo = repo.unfiltered()
 
+        changes = set() # set of revisions to be changed
         delroots = [] # set of root deleted by this path
         for phase in xrange(targetphase + 1, len(allphases)):
             # filter nodes that are not in a compatible phase already
@@ -377,6 +384,9 @@
             olds = self.phaseroots[phase]
 
             affected = repo.revs('%ln::%ln', olds, nodes)
+            changes.update(affected)
+            if dryrun:
+                continue
             for r in affected:
                 _trackphasechange(phasetracking, r, self.phase(repo, r),
                                   targetphase)
@@ -387,10 +397,12 @@
                 self._updateroots(phase, roots, tr)
                 # some roots may need to be declared for lower phases
                 delroots.extend(olds - roots)
-        # declare deleted root in the target phase
-        if targetphase != 0:
-            self._retractboundary(repo, tr, targetphase, delroots)
-        repo.invalidatevolatilesets()
+        if not dryrun:
+            # declare deleted root in the target phase
+            if targetphase != 0:
+                self._retractboundary(repo, tr, targetphase, delroots)
+            repo.invalidatevolatilesets()
+        return changes
 
     def retractboundary(self, repo, tr, targetphase, nodes):
         oldroots = self.phaseroots[:targetphase + 1]
@@ -478,16 +490,24 @@
         # (see branchmap one)
         self.invalidate()
 
-def advanceboundary(repo, tr, targetphase, nodes):
+def advanceboundary(repo, tr, targetphase, nodes, dryrun=None):
     """Add nodes to a phase changing other nodes phases if necessary.
 
     This function move boundary *forward* this means that all nodes
     are set in the target phase or kept in a *lower* phase.
 
-    Simplify boundary to contains phase roots only."""
+    Simplify boundary to contains phase roots only.
+
+    If dryrun is True, no actions will be performed
+
+    Returns a set of revs whose phase is changed or should be changed
+    """
     phcache = repo._phasecache.copy()
-    phcache.advanceboundary(repo, tr, targetphase, nodes)
-    repo._phasecache.replace(phcache)
+    changes = phcache.advanceboundary(repo, tr, targetphase, nodes,
+                                      dryrun=dryrun)
+    if not dryrun:
+        repo._phasecache.replace(phcache)
+    return changes
 
 def retractboundary(repo, tr, targetphase, nodes):
     """Set nodes back to a phase changing other nodes phases if
@@ -645,10 +665,8 @@
     * `heads`: define the first subset
     * `roots`: define the second we subtract from the first"""
     repo = repo.unfiltered()
-    revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
-                      heads, roots, roots, heads)
-    return [c.node() for c in revset]
-
+    revs = repo.revs('heads(::%ln - (%ln::%ln))', heads, roots, heads)
+    return pycompat.maplist(repo.changelog.node, revs)
 
 def newcommitphase(ui):
     """helper to get the target phase of new commit
--- a/mercurial/policy.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/policy.py	Thu Jul 19 13:55:54 2018 -0400
@@ -69,7 +69,7 @@
     (r'cext', r'bdiff'): 3,
     (r'cext', r'mpatch'): 1,
     (r'cext', r'osutil'): 4,
-    (r'cext', r'parsers'): 4,
+    (r'cext', r'parsers'): 5,
 }
 
 # map import request to other package or module
--- a/mercurial/posix.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/posix.py	Thu Jul 19 13:55:54 2018 -0400
@@ -216,7 +216,7 @@
             # check directly in path and don't leave checkisexec behind
             checkdir = path
             checkisexec = None
-        fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-')
+        fh, fn = pycompat.mkstemp(dir=checkdir, prefix='hg-checkexec-')
         try:
             os.close(fh)
             m = os.stat(fn).st_mode
@@ -249,16 +249,15 @@
         else:
             checkdir = path
             cachedir = None
-        fscheckdir = pycompat.fsdecode(checkdir)
-        name = tempfile.mktemp(dir=fscheckdir,
+        name = tempfile.mktemp(dir=pycompat.fsdecode(checkdir),
                                prefix=r'checklink-')
         name = pycompat.fsencode(name)
         try:
             fd = None
             if cachedir is None:
-                fd = tempfile.NamedTemporaryFile(dir=fscheckdir,
-                                                 prefix=r'hg-checklink-')
-                target = pycompat.fsencode(os.path.basename(fd.name))
+                fd = pycompat.namedtempfile(dir=checkdir,
+                                            prefix='hg-checklink-')
+                target = os.path.basename(fd.name)
             else:
                 # create a fixed file to link to; doesn't matter if it
                 # already exists.
@@ -287,7 +286,7 @@
                 return True
             except OSError as inst:
                 # link creation might race, try again
-                if inst[0] == errno.EEXIST:
+                if inst.errno == errno.EEXIST:
                     continue
                 raise
             finally:
@@ -297,7 +296,7 @@
             return False
         except OSError as inst:
             # sshfs might report failure while successfully creating the link
-            if inst[0] == errno.EIO and os.path.exists(name):
+            if inst.errno == errno.EIO and os.path.exists(name):
                 unlink(name)
             return False
 
@@ -542,9 +541,9 @@
     if uid is None:
         uid = os.getuid()
     try:
-        return pwd.getpwuid(uid)[0]
+        return pycompat.fsencode(pwd.getpwuid(uid)[0])
     except KeyError:
-        return str(uid)
+        return b'%d' % uid
 
 def groupname(gid=None):
     """Return the name of the group with the given gid.
--- a/mercurial/profiling.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/profiling.py	Thu Jul 19 13:55:54 2018 -0400
@@ -101,7 +101,8 @@
     else:
         ui.warn(_("invalid sampling frequency '%s' - ignoring\n") % freq)
 
-    statprof.start(mechanism='thread')
+    track = ui.config('profiling', 'time-track')
+    statprof.start(mechanism='thread', track=track)
 
     try:
         yield
--- a/mercurial/progress.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/progress.py	Thu Jul 19 13:55:54 2018 -0400
@@ -264,39 +264,40 @@
             self.starttimes[topic] = now - interval
 
     def progress(self, topic, pos, item='', unit='', total=None):
+        if pos is None:
+            self.closetopic(topic)
+            return
         now = time.time()
-        self._refreshlock.acquire()
-        try:
-            if pos is None:
-                self.starttimes.pop(topic, None)
-                self.startvals.pop(topic, None)
-                self.topicstates.pop(topic, None)
-                # reset the progress bar if this is the outermost topic
-                if self.topics and self.topics[0] == topic and self.printed:
-                    self.complete()
-                    self.resetstate()
-                # truncate the list of topics assuming all topics within
-                # this one are also closed
-                if topic in self.topics:
-                    self.topics = self.topics[:self.topics.index(topic)]
-                    # reset the last topic to the one we just unwound to,
-                    # so that higher-level topics will be stickier than
-                    # lower-level topics
-                    if self.topics:
-                        self.lasttopic = self.topics[-1]
-                    else:
-                        self.lasttopic = None
-            else:
-                if topic not in self.topics:
-                    self.starttimes[topic] = now
-                    self.startvals[topic] = pos
-                    self.topics.append(topic)
-                self.topicstates[topic] = pos, item, unit, total
-                self.curtopic = topic
-                self._calibrateestimate(topic, now, pos)
-                if now - self.lastprint >= self.refresh and self.topics:
-                    if self._oktoprint(now):
-                        self.lastprint = now
-                        self.show(now, topic, *self.topicstates[topic])
-        finally:
-            self._refreshlock.release()
+        with self._refreshlock:
+            if topic not in self.topics:
+                self.starttimes[topic] = now
+                self.startvals[topic] = pos
+                self.topics.append(topic)
+            self.topicstates[topic] = pos, item, unit, total
+            self.curtopic = topic
+            self._calibrateestimate(topic, now, pos)
+            if now - self.lastprint >= self.refresh and self.topics:
+                if self._oktoprint(now):
+                    self.lastprint = now
+                    self.show(now, topic, *self.topicstates[topic])
+
+    def closetopic(self, topic):
+        with self._refreshlock:
+            self.starttimes.pop(topic, None)
+            self.startvals.pop(topic, None)
+            self.topicstates.pop(topic, None)
+            # reset the progress bar if this is the outermost topic
+            if self.topics and self.topics[0] == topic and self.printed:
+                self.complete()
+                self.resetstate()
+            # truncate the list of topics assuming all topics within
+            # this one are also closed
+            if topic in self.topics:
+                self.topics = self.topics[:self.topics.index(topic)]
+                # reset the last topic to the one we just unwound to,
+                # so that higher-level topics will be stickier than
+                # lower-level topics
+                if self.topics:
+                    self.lasttopic = self.topics[-1]
+                else:
+                    self.lasttopic = None
--- a/mercurial/pycompat.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/pycompat.py	Thu Jul 19 13:55:54 2018 -0400
@@ -15,6 +15,7 @@
 import os
 import shlex
 import sys
+import tempfile
 
 ispy3 = (sys.version_info[0] >= 3)
 ispypy = (r'__pypy__' in sys.builtin_module_names)
@@ -23,7 +24,7 @@
     import cookielib
     import cPickle as pickle
     import httplib
-    import Queue as _queue
+    import Queue as queue
     import SocketServer as socketserver
     import xmlrpclib
 
@@ -36,19 +37,49 @@
     import http.cookiejar as cookielib
     import http.client as httplib
     import pickle
-    import queue as _queue
+    import queue as queue
     import socketserver
     import xmlrpc.client as xmlrpclib
 
     def future_set_exception_info(f, exc_info):
         f.set_exception(exc_info[0])
 
-empty = _queue.Empty
-queue = _queue.Queue
-
 def identity(a):
     return a
 
+def _rapply(f, xs):
+    if xs is None:
+        # assume None means non-value of optional data
+        return xs
+    if isinstance(xs, (list, set, tuple)):
+        return type(xs)(_rapply(f, x) for x in xs)
+    if isinstance(xs, dict):
+        return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
+    return f(xs)
+
+def rapply(f, xs):
+    """Apply function recursively to every item preserving the data structure
+
+    >>> def f(x):
+    ...     return 'f(%s)' % x
+    >>> rapply(f, None) is None
+    True
+    >>> rapply(f, 'a')
+    'f(a)'
+    >>> rapply(f, {'a'}) == {'f(a)'}
+    True
+    >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
+    ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
+
+    >>> xs = [object()]
+    >>> rapply(identity, xs) is xs
+    True
+    """
+    if f is identity:
+        # fast path mainly for py2
+        return xs
+    return _rapply(f, xs)
+
 if ispy3:
     import builtins
     import functools
@@ -297,13 +328,10 @@
         ret = shlex.split(s.decode('latin-1'), comments, posix)
         return [a.encode('latin-1') for a in ret]
 
-    def emailparser(*args, **kwargs):
-        import email.parser
-        return email.parser.BytesParser(*args, **kwargs)
-
 else:
     import cStringIO
 
+    unicode = unicode
     bytechr = chr
     byterepr = repr
     bytestr = str
@@ -372,10 +400,6 @@
     rawinput = raw_input
     getargspec = inspect.getargspec
 
-    def emailparser(*args, **kwargs):
-        import email.parser
-        return email.parser.Parser(*args, **kwargs)
-
 isjython = sysplatform.startswith('java')
 
 isdarwin = sysplatform == 'darwin'
@@ -387,3 +411,18 @@
 
 def gnugetoptb(args, shortlist, namelist):
     return _getoptbwrapper(getopt.gnu_getopt, args, shortlist, namelist)
+
+def mkdtemp(suffix=b'', prefix=b'tmp', dir=None):
+    return tempfile.mkdtemp(suffix, prefix, dir)
+
+# text=True is not supported; use util.from/tonativeeol() instead
+def mkstemp(suffix=b'', prefix=b'tmp', dir=None):
+    return tempfile.mkstemp(suffix, prefix, dir)
+
+# mode must include 'b'ytes as encoding= is not supported
+def namedtempfile(mode=b'w+b', bufsize=-1, suffix=b'', prefix=b'tmp', dir=None,
+                  delete=True):
+    mode = sysstr(mode)
+    assert r'b' in mode
+    return tempfile.NamedTemporaryFile(mode, bufsize, suffix=suffix,
+                                       prefix=prefix, dir=dir, delete=delete)
--- a/mercurial/registrar.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/registrar.py	Thu Jul 19 13:55:54 2018 -0400
@@ -247,10 +247,6 @@
      implies 'matchctx.status()' at runtime or not (False, by
      default).
 
-    Optional argument 'callexisting' indicates whether a predicate
-    implies 'matchctx.existing()' at runtime or not (False, by
-    default).
-
     'filesetpredicate' instance in example above can be used to
     decorate multiple functions.
 
@@ -263,9 +259,8 @@
     _getname = _funcregistrarbase._parsefuncdecl
     _docformat = "``%s``\n    %s"
 
-    def _extrasetup(self, name, func, callstatus=False, callexisting=False):
+    def _extrasetup(self, name, func, callstatus=False):
         func._callstatus = callstatus
-        func._callexisting = callexisting
 
 class _templateregistrarbase(_funcregistrarbase):
     """Base of decorator to register functions as template specific one
@@ -351,7 +346,8 @@
 
         templatefunc = registrar.templatefunc()
 
-        @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3')
+        @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
+                      requires={'ctx'})
         def myfuncfunc(context, mapping, args):
             '''Explanation of this template function ....
             '''
@@ -363,6 +359,9 @@
     a dict of named arguments. Otherwise 'args' is a list of positional
     arguments.
 
+    Optional argument 'requires' should be a collection of resource names
+    which the template function depends on.
+
     'templatefunc' instance in example above can be used to
     decorate multiple functions.
 
@@ -374,8 +373,9 @@
     """
     _getname = _funcregistrarbase._parsefuncdecl
 
-    def _extrasetup(self, name, func, argspec=None):
+    def _extrasetup(self, name, func, argspec=None, requires=()):
         func._argspec = argspec
+        func._requires = requires
 
 class internalmerge(_funcregistrarbase):
     """Decorator to register in-process merge tool
--- a/mercurial/repair.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/repair.py	Thu Jul 19 13:55:54 2018 -0400
@@ -186,76 +186,77 @@
         tmpbundlefile = backupbundle(repo, savebases, saveheads, node, 'temp',
                                      compress=False, obsolescence=False)
 
-    try:
-        with repo.transaction("strip") as tr:
-            offset = len(tr.entries)
+    with ui.uninterruptable():
+        try:
+            with repo.transaction("strip") as tr:
+                offset = len(tr.entries)
 
-            tr.startgroup()
-            cl.strip(striprev, tr)
-            stripmanifest(repo, striprev, tr, files)
-
-            for fn in files:
-                repo.file(fn).strip(striprev, tr)
-            tr.endgroup()
+                tr.startgroup()
+                cl.strip(striprev, tr)
+                stripmanifest(repo, striprev, tr, files)
 
-            for i in xrange(offset, len(tr.entries)):
-                file, troffset, ignore = tr.entries[i]
-                with repo.svfs(file, 'a', checkambig=True) as fp:
-                    fp.truncate(troffset)
-                if troffset == 0:
-                    repo.store.markremoved(file)
+                for fn in files:
+                    repo.file(fn).strip(striprev, tr)
+                tr.endgroup()
 
-            deleteobsmarkers(repo.obsstore, stripobsidx)
-            del repo.obsstore
-            repo.invalidatevolatilesets()
-            repo._phasecache.filterunknown(repo)
+                for i in xrange(offset, len(tr.entries)):
+                    file, troffset, ignore = tr.entries[i]
+                    with repo.svfs(file, 'a', checkambig=True) as fp:
+                        fp.truncate(troffset)
+                    if troffset == 0:
+                        repo.store.markremoved(file)
+
+                deleteobsmarkers(repo.obsstore, stripobsidx)
+                del repo.obsstore
+                repo.invalidatevolatilesets()
+                repo._phasecache.filterunknown(repo)
 
-        if tmpbundlefile:
-            ui.note(_("adding branch\n"))
-            f = vfs.open(tmpbundlefile, "rb")
-            gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
-            if not repo.ui.verbose:
-                # silence internal shuffling chatter
-                repo.ui.pushbuffer()
-            tmpbundleurl = 'bundle:' + vfs.join(tmpbundlefile)
-            txnname = 'strip'
-            if not isinstance(gen, bundle2.unbundle20):
-                txnname = "strip\n%s" % util.hidepassword(tmpbundleurl)
-            with repo.transaction(txnname) as tr:
-                bundle2.applybundle(repo, gen, tr, source='strip',
-                                    url=tmpbundleurl)
-            if not repo.ui.verbose:
-                repo.ui.popbuffer()
-            f.close()
+            if tmpbundlefile:
+                ui.note(_("adding branch\n"))
+                f = vfs.open(tmpbundlefile, "rb")
+                gen = exchange.readbundle(ui, f, tmpbundlefile, vfs)
+                if not repo.ui.verbose:
+                    # silence internal shuffling chatter
+                    repo.ui.pushbuffer()
+                tmpbundleurl = 'bundle:' + vfs.join(tmpbundlefile)
+                txnname = 'strip'
+                if not isinstance(gen, bundle2.unbundle20):
+                    txnname = "strip\n%s" % util.hidepassword(tmpbundleurl)
+                with repo.transaction(txnname) as tr:
+                    bundle2.applybundle(repo, gen, tr, source='strip',
+                                        url=tmpbundleurl)
+                if not repo.ui.verbose:
+                    repo.ui.popbuffer()
+                f.close()
 
-        with repo.transaction('repair') as tr:
-            bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
-            bm.applychanges(repo, tr, bmchanges)
+            with repo.transaction('repair') as tr:
+                bmchanges = [(m, repo[newbmtarget].node()) for m in updatebm]
+                bm.applychanges(repo, tr, bmchanges)
 
-        # remove undo files
-        for undovfs, undofile in repo.undofiles():
-            try:
-                undovfs.unlink(undofile)
-            except OSError as e:
-                if e.errno != errno.ENOENT:
-                    ui.warn(_('error removing %s: %s\n') %
-                            (undovfs.join(undofile),
-                             stringutil.forcebytestr(e)))
+            # remove undo files
+            for undovfs, undofile in repo.undofiles():
+                try:
+                    undovfs.unlink(undofile)
+                except OSError as e:
+                    if e.errno != errno.ENOENT:
+                        ui.warn(_('error removing %s: %s\n') %
+                                (undovfs.join(undofile),
+                                 stringutil.forcebytestr(e)))
 
-    except: # re-raises
-        if backupfile:
-            ui.warn(_("strip failed, backup bundle stored in '%s'\n")
-                    % vfs.join(backupfile))
-        if tmpbundlefile:
-            ui.warn(_("strip failed, unrecovered changes stored in '%s'\n")
-                    % vfs.join(tmpbundlefile))
-            ui.warn(_("(fix the problem, then recover the changesets with "
-                      "\"hg unbundle '%s'\")\n") % vfs.join(tmpbundlefile))
-        raise
-    else:
-        if tmpbundlefile:
-            # Remove temporary bundle only if there were no exceptions
-            vfs.unlink(tmpbundlefile)
+        except: # re-raises
+            if backupfile:
+                ui.warn(_("strip failed, backup bundle stored in '%s'\n")
+                        % vfs.join(backupfile))
+            if tmpbundlefile:
+                ui.warn(_("strip failed, unrecovered changes stored in '%s'\n")
+                        % vfs.join(tmpbundlefile))
+                ui.warn(_("(fix the problem, then recover the changesets with "
+                          "\"hg unbundle '%s'\")\n") % vfs.join(tmpbundlefile))
+            raise
+        else:
+            if tmpbundlefile:
+                # Remove temporary bundle only if there were no exceptions
+                vfs.unlink(tmpbundlefile)
 
     repo.destroyed()
     # return the backup file path (or None if 'backup' was False) so
@@ -355,10 +356,10 @@
         newentries = set()
         seenfiles = set()
 
-        repolen = len(repo)
+        progress = ui.makeprogress(_('rebuilding'), unit=_('changesets'),
+                                   total=len(repo))
         for rev in repo:
-            ui.progress(_('rebuilding'), rev, total=repolen,
-                        unit=_('changesets'))
+            progress.update(rev)
 
             ctx = repo[rev]
             for f in ctx.files():
@@ -375,7 +376,7 @@
                 if repo.store._exists(d):
                     newentries.add(d)
 
-        ui.progress(_('rebuilding'), None)
+        progress.complete()
 
         if 'treemanifest' in repo.requirements: # safe but unnecessary otherwise
             for dir in util.dirs(seenfiles):
@@ -405,18 +406,6 @@
         else:
             ui.write(_('fncache already up to date\n'))
 
-def stripbmrevset(repo, mark):
-    """
-    The revset to strip when strip is called with -B mark
-
-    Needs to live here so extensions can use it and wrap it even when strip is
-    not enabled or not present on a box.
-    """
-    return repo.revs("ancestors(bookmark(%s)) - "
-                     "ancestors(head() and not bookmark(%s)) - "
-                     "ancestors(bookmark() and not bookmark(%s))",
-                     mark, mark, mark)
-
 def deleteobsmarkers(obsstore, indices):
     """Delete some obsmarkers from obsstore and return how many were deleted
 
--- a/mercurial/repository.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/repository.py	Thu Jul 19 13:55:54 2018 -0400
@@ -642,6 +642,308 @@
         TODO this is used by verify and it should not be part of the interface.
         """
 
+class idirs(interfaceutil.Interface):
+    """Interface representing a collection of directories from paths.
+
+    This interface is essentially a derived data structure representing
+    directories from a collection of paths.
+    """
+
+    def addpath(path):
+        """Add a path to the collection.
+
+        All directories in the path will be added to the collection.
+        """
+
+    def delpath(path):
+        """Remove a path from the collection.
+
+        If the removal was the last path in a particular directory, the
+        directory is removed from the collection.
+        """
+
+    def __iter__():
+        """Iterate over the directories in this collection of paths."""
+
+    def __contains__(path):
+        """Whether a specific directory is in this collection."""
+
+class imanifestdict(interfaceutil.Interface):
+    """Interface representing a manifest data structure.
+
+    A manifest is effectively a dict mapping paths to entries. Each entry
+    consists of a binary node and extra flags affecting that entry.
+    """
+
+    def __getitem__(path):
+        """Returns the binary node value for a path in the manifest.
+
+        Raises ``KeyError`` if the path does not exist in the manifest.
+
+        Equivalent to ``self.find(path)[0]``.
+        """
+
+    def find(path):
+        """Returns the entry for a path in the manifest.
+
+        Returns a 2-tuple of (node, flags).
+
+        Raises ``KeyError`` if the path does not exist in the manifest.
+        """
+
+    def __len__():
+        """Return the number of entries in the manifest."""
+
+    def __nonzero__():
+        """Returns True if the manifest has entries, False otherwise."""
+
+    __bool__ = __nonzero__
+
+    def __setitem__(path, node):
+        """Define the node value for a path in the manifest.
+
+        If the path is already in the manifest, its flags will be copied to
+        the new entry.
+        """
+
+    def __contains__(path):
+        """Whether a path exists in the manifest."""
+
+    def __delitem__(path):
+        """Remove a path from the manifest.
+
+        Raises ``KeyError`` if the path is not in the manifest.
+        """
+
+    def __iter__():
+        """Iterate over paths in the manifest."""
+
+    def iterkeys():
+        """Iterate over paths in the manifest."""
+
+    def keys():
+        """Obtain a list of paths in the manifest."""
+
+    def filesnotin(other, match=None):
+        """Obtain the set of paths in this manifest but not in another.
+
+        ``match`` is an optional matcher function to be applied to both
+        manifests.
+
+        Returns a set of paths.
+        """
+
+    def dirs():
+        """Returns an object implementing the ``idirs`` interface."""
+
+    def hasdir(dir):
+        """Returns a bool indicating if a directory is in this manifest."""
+
+    def matches(match):
+        """Generate a new manifest filtered through a matcher.
+
+        Returns an object conforming to the ``imanifestdict`` interface.
+        """
+
+    def walk(match):
+        """Generator of paths in manifest satisfying a matcher.
+
+        This is equivalent to ``self.matches(match).iterkeys()`` except a new
+        manifest object is not created.
+
+        If the matcher has explicit files listed and they don't exist in
+        the manifest, ``match.bad()`` is called for each missing file.
+        """
+
+    def diff(other, match=None, clean=False):
+        """Find differences between this manifest and another.
+
+        This manifest is compared to ``other``.
+
+        If ``match`` is provided, the two manifests are filtered against this
+        matcher and only entries satisfying the matcher are compared.
+
+        If ``clean`` is True, unchanged files are included in the returned
+        object.
+
+        Returns a dict with paths as keys and values of 2-tuples of 2-tuples of
+        the form ``((node1, flag1), (node2, flag2))`` where ``(node1, flag1)``
+        represents the node and flags for this manifest and ``(node2, flag2)``
+        are the same for the other manifest.
+        """
+
+    def setflag(path, flag):
+        """Set the flag value for a given path.
+
+        Raises ``KeyError`` if the path is not already in the manifest.
+        """
+
+    def get(path, default=None):
+        """Obtain the node value for a path or a default value if missing."""
+
+    def flags(path, default=''):
+        """Return the flags value for a path or a default value if missing."""
+
+    def copy():
+        """Return a copy of this manifest."""
+
+    def items():
+        """Returns an iterable of (path, node) for items in this manifest."""
+
+    def iteritems():
+        """Identical to items()."""
+
+    def iterentries():
+        """Returns an iterable of (path, node, flags) for this manifest.
+
+        Similar to ``iteritems()`` except items are a 3-tuple and include
+        flags.
+        """
+
+    def text():
+        """Obtain the raw data representation for this manifest.
+
+        Result is used to create a manifest revision.
+        """
+
+    def fastdelta(base, changes):
+        """Obtain a delta between this manifest and another given changes.
+
+        ``base`` in the raw data representation for another manifest.
+
+        ``changes`` is an iterable of ``(path, to_delete)``.
+
+        Returns a 2-tuple containing ``bytearray(self.text())`` and the
+        delta between ``base`` and this manifest.
+        """
+
+class imanifestrevisionbase(interfaceutil.Interface):
+    """Base interface representing a single revision of a manifest.
+
+    Should not be used as a primary interface: should always be inherited
+    as part of a larger interface.
+    """
+
+    def new():
+        """Obtain a new manifest instance.
+
+        Returns an object conforming to the ``imanifestrevisionwritable``
+        interface. The instance will be associated with the same
+        ``imanifestlog`` collection as this instance.
+        """
+
+    def copy():
+        """Obtain a copy of this manifest instance.
+
+        Returns an object conforming to the ``imanifestrevisionwritable``
+        interface. The instance will be associated with the same
+        ``imanifestlog`` collection as this instance.
+        """
+
+    def read():
+        """Obtain the parsed manifest data structure.
+
+        The returned object conforms to the ``imanifestdict`` interface.
+        """
+
+class imanifestrevisionstored(imanifestrevisionbase):
+    """Interface representing a manifest revision committed to storage."""
+
+    def node():
+        """The binary node for this manifest."""
+
+    parents = interfaceutil.Attribute(
+        """List of binary nodes that are parents for this manifest revision."""
+    )
+
+    def readdelta(shallow=False):
+        """Obtain the manifest data structure representing changes from parent.
+
+        This manifest is compared to its 1st parent. A new manifest representing
+        those differences is constructed.
+
+        The returned object conforms to the ``imanifestdict`` interface.
+        """
+
+    def readfast(shallow=False):
+        """Calls either ``read()`` or ``readdelta()``.
+
+        The faster of the two options is called.
+        """
+
+    def find(key):
+        """Calls self.read().find(key)``.
+
+        Returns a 2-tuple of ``(node, flags)`` or raises ``KeyError``.
+        """
+
+class imanifestrevisionwritable(imanifestrevisionbase):
+    """Interface representing a manifest revision that can be committed."""
+
+    def write(transaction, linkrev, p1node, p2node, added, removed):
+        """Add this revision to storage.
+
+        Takes a transaction object, the changeset revision number it will
+        be associated with, its parent nodes, and lists of added and
+        removed paths.
+
+        Returns the binary node of the created revision.
+        """
+
+class imanifestlog(interfaceutil.Interface):
+    """Interface representing a collection of manifest snapshots."""
+
+    def __getitem__(node):
+        """Obtain a manifest instance for a given binary node.
+
+        Equivalent to calling ``self.get('', node)``.
+
+        The returned object conforms to the ``imanifestrevisionstored``
+        interface.
+        """
+
+    def get(dir, node, verify=True):
+        """Retrieve the manifest instance for a given directory and binary node.
+
+        ``node`` always refers to the node of the root manifest (which will be
+        the only manifest if flat manifests are being used).
+
+        If ``dir`` is the empty string, the root manifest is returned. Otherwise
+        the manifest for the specified directory will be returned (requires
+        tree manifests).
+
+        If ``verify`` is True, ``LookupError`` is raised if the node is not
+        known.
+
+        The returned object conforms to the ``imanifestrevisionstored``
+        interface.
+        """
+
+    def clearcaches():
+        """Clear caches associated with this collection."""
+
+    def rev(node):
+        """Obtain the revision number for a binary node.
+
+        Raises ``error.LookupError`` if the node is not known.
+        """
+
+    def addgroup(deltas, linkmapper, transaction):
+        """Process a series of deltas for storage.
+
+        ``deltas`` is an iterable of 7-tuples of
+        (node, p1, p2, linknode, deltabase, delta, flags) defining revisions
+        to add.
+
+        The ``delta`` field contains ``mpatch`` data to apply to a base
+        revision, identified by ``deltabase``. The base node can be
+        ``nullid``, in which case the header from the delta can be ignored
+        and the delta used as the fulltext.
+
+        Returns a list of nodes that were processed. A node will be in the list
+        even if it existed in the store previously.
+        """
+
 class completelocalrepository(interfaceutil.Interface):
     """Monolithic interface for local repositories.
 
@@ -757,7 +1059,10 @@
         """A handle on the changelog revlog.""")
 
     manifestlog = interfaceutil.Attribute(
-        """A handle on the root manifest revlog.""")
+        """An instance conforming to the ``imanifestlog`` interface.
+
+        Provides access to manifests for the repository.
+        """)
 
     dirstate = interfaceutil.Attribute(
         """Working directory state.""")
@@ -863,7 +1168,10 @@
         """Calls self.vfs.reljoin(self.root, f, *insidef)"""
 
     def file(f):
-        """Obtain a filelog for a tracked path."""
+        """Obtain a filelog for a tracked path.
+
+        The returned type conforms to the ``ifilestorage`` interface.
+        """
 
     def setparents(p1, p2):
         """Set the parent nodes of the working directory."""
--- a/mercurial/repoview.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/repoview.py	Thu Jul 19 13:55:54 2018 -0400
@@ -77,8 +77,7 @@
         if visibilityexceptions:
             hidden -= visibilityexceptions
         pfunc = repo.changelog.parentrevs
-        mutablephases = (phases.draft, phases.secret)
-        mutable = repo._phasecache.getrevset(repo, mutablephases)
+        mutable = repo._phasecache.getrevset(repo, phases.mutablephases)
 
         visible = mutable - hidden
         _revealancestors(pfunc, hidden, visible)
@@ -92,13 +91,8 @@
     # fast path in simple case to avoid impact of non optimised code
     hiddens = filterrevs(repo, 'visible')
     if phases.hassecret(repo):
-        cl = repo.changelog
-        secret = phases.secret
-        getphase = repo._phasecache.phase
-        first = min(cl.rev(n) for n in repo._phasecache.phaseroots[secret])
-        revs = cl.revs(start=first)
-        secrets = set(r for r in revs if getphase(repo, r) >= secret)
-        return frozenset(hiddens | secrets)
+        secrets = repo._phasecache.getrevset(repo, phases.remotehiddenphases)
+        return frozenset(hiddens | frozenset(secrets))
     else:
         return hiddens
 
--- a/mercurial/revlog.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/revlog.py	Thu Jul 19 13:55:54 2018 -0400
@@ -196,8 +196,66 @@
     s.update(text)
     return s.digest()
 
+class _testrevlog(object):
+    """minimalist fake revlog to use in doctests"""
+
+    def __init__(self, data, density=0.5, mingap=0):
+        """data is an list of revision payload boundaries"""
+        self._data = data
+        self._srdensitythreshold = density
+        self._srmingapsize = mingap
+
+    def start(self, rev):
+        if rev == 0:
+            return 0
+        return self._data[rev - 1]
+
+    def end(self, rev):
+        return self._data[rev]
+
+    def length(self, rev):
+        return self.end(rev) - self.start(rev)
+
+    def __len__(self):
+        return len(self._data)
+
 def _trimchunk(revlog, revs, startidx, endidx=None):
     """returns revs[startidx:endidx] without empty trailing revs
+
+    Doctest Setup
+    >>> revlog = _testrevlog([
+    ...  5,  #0
+    ...  10, #1
+    ...  12, #2
+    ...  12, #3 (empty)
+    ...  17, #4
+    ...  21, #5
+    ...  21, #6 (empty)
+    ... ])
+
+    Contiguous cases:
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
+    [0, 1, 2, 3, 4, 5]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
+    [0, 1, 2, 3, 4]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
+    [0, 1, 2]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
+    [2]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
+    [3, 4, 5]
+    >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
+    [3, 4]
+
+    Discontiguous cases:
+    >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
+    [1, 3, 5]
+    >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
+    [1]
+    >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
+    [3, 5]
+    >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
+    [3, 5]
     """
     length = revlog.length
 
@@ -210,11 +268,231 @@
 
     return revs[startidx:endidx]
 
-def _slicechunk(revlog, revs):
+def _segmentspan(revlog, revs):
+    """Get the byte span of a segment of revisions
+
+    revs is a sorted array of revision numbers
+
+    >>> revlog = _testrevlog([
+    ...  5,  #0
+    ...  10, #1
+    ...  12, #2
+    ...  12, #3 (empty)
+    ...  17, #4
+    ... ])
+
+    >>> _segmentspan(revlog, [0, 1, 2, 3, 4])
+    17
+    >>> _segmentspan(revlog, [0, 4])
+    17
+    >>> _segmentspan(revlog, [3, 4])
+    5
+    >>> _segmentspan(revlog, [1, 2, 3,])
+    7
+    >>> _segmentspan(revlog, [1, 3])
+    7
+    """
+    if not revs:
+        return 0
+    return revlog.end(revs[-1]) - revlog.start(revs[0])
+
+def _slicechunk(revlog, revs, deltainfo=None, targetsize=None):
     """slice revs to reduce the amount of unrelated data to be read from disk.
 
     ``revs`` is sliced into groups that should be read in one time.
     Assume that revs are sorted.
+
+    The initial chunk is sliced until the overall density (payload/chunks-span
+    ratio) is above `revlog._srdensitythreshold`. No gap smaller than
+    `revlog._srmingapsize` is skipped.
+
+    If `targetsize` is set, no chunk larger than `targetsize` will be yield.
+    For consistency with other slicing choice, this limit won't go lower than
+    `revlog._srmingapsize`.
+
+    If individual revisions chunk are larger than this limit, they will still
+    be raised individually.
+
+    >>> revlog = _testrevlog([
+    ...  5,  #00 (5)
+    ...  10, #01 (5)
+    ...  12, #02 (2)
+    ...  12, #03 (empty)
+    ...  27, #04 (15)
+    ...  31, #05 (4)
+    ...  31, #06 (empty)
+    ...  42, #07 (11)
+    ...  47, #08 (5)
+    ...  47, #09 (empty)
+    ...  48, #10 (1)
+    ...  51, #11 (3)
+    ...  74, #12 (23)
+    ...  85, #13 (11)
+    ...  86, #14 (1)
+    ...  91, #15 (5)
+    ... ])
+
+    >>> list(_slicechunk(revlog, list(range(16))))
+    [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
+    >>> list(_slicechunk(revlog, [0, 15]))
+    [[0], [15]]
+    >>> list(_slicechunk(revlog, [0, 11, 15]))
+    [[0], [11], [15]]
+    >>> list(_slicechunk(revlog, [0, 11, 13, 15]))
+    [[0], [11, 13, 15]]
+    >>> list(_slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
+    [[1, 2], [5, 8, 10, 11], [14]]
+
+    Slicing with a maximum chunk size
+    >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
+    [[0], [11], [13], [15]]
+    >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
+    [[0], [11], [13, 15]]
+    """
+    if targetsize is not None:
+        targetsize = max(targetsize, revlog._srmingapsize)
+    # targetsize should not be specified when evaluating delta candidates:
+    # * targetsize is used to ensure we stay within specification when reading,
+    # * deltainfo is used to pick are good delta chain when writing.
+    if not (deltainfo is None or targetsize is None):
+        msg = 'cannot use `targetsize` with a `deltainfo`'
+        raise error.ProgrammingError(msg)
+    for chunk in _slicechunktodensity(revlog, revs,
+                                      deltainfo,
+                                      revlog._srdensitythreshold,
+                                      revlog._srmingapsize):
+        for subchunk in _slicechunktosize(revlog, chunk, targetsize):
+            yield subchunk
+
+def _slicechunktosize(revlog, revs, targetsize=None):
+    """slice revs to match the target size
+
+    This is intended to be used on chunk that density slicing selected by that
+    are still too large compared to the read garantee of revlog. This might
+    happens when "minimal gap size" interrupted the slicing or when chain are
+    built in a way that create large blocks next to each other.
+
+    >>> revlog = _testrevlog([
+    ...  3,  #0 (3)
+    ...  5,  #1 (2)
+    ...  6,  #2 (1)
+    ...  8,  #3 (2)
+    ...  8,  #4 (empty)
+    ...  11, #5 (3)
+    ...  12, #6 (1)
+    ...  13, #7 (1)
+    ...  14, #8 (1)
+    ... ])
+
+    Cases where chunk is already small enough
+    >>> list(_slicechunktosize(revlog, [0], 3))
+    [[0]]
+    >>> list(_slicechunktosize(revlog, [6, 7], 3))
+    [[6, 7]]
+    >>> list(_slicechunktosize(revlog, [0], None))
+    [[0]]
+    >>> list(_slicechunktosize(revlog, [6, 7], None))
+    [[6, 7]]
+
+    cases where we need actual slicing
+    >>> list(_slicechunktosize(revlog, [0, 1], 3))
+    [[0], [1]]
+    >>> list(_slicechunktosize(revlog, [1, 3], 3))
+    [[1], [3]]
+    >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
+    [[1, 2], [3]]
+    >>> list(_slicechunktosize(revlog, [3, 5], 3))
+    [[3], [5]]
+    >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
+    [[3], [5]]
+    >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
+    [[5], [6, 7, 8]]
+    >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
+    [[0], [1, 2], [3], [5], [6, 7, 8]]
+
+    Case with too large individual chunk (must return valid chunk)
+    >>> list(_slicechunktosize(revlog, [0, 1], 2))
+    [[0], [1]]
+    >>> list(_slicechunktosize(revlog, [1, 3], 1))
+    [[1], [3]]
+    >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
+    [[3], [5]]
+    """
+    assert targetsize is None or 0 <= targetsize
+    if targetsize is None or _segmentspan(revlog, revs) <= targetsize:
+        yield revs
+        return
+
+    startrevidx = 0
+    startdata = revlog.start(revs[0])
+    endrevidx = 0
+    iterrevs = enumerate(revs)
+    next(iterrevs) # skip first rev.
+    for idx, r in iterrevs:
+        span = revlog.end(r) - startdata
+        if span <= targetsize:
+            endrevidx = idx
+        else:
+            chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
+            if chunk:
+                yield chunk
+            startrevidx = idx
+            startdata = revlog.start(r)
+            endrevidx = idx
+    yield _trimchunk(revlog, revs, startrevidx)
+
+def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
+                         mingapsize=0):
+    """slice revs to reduce the amount of unrelated data to be read from disk.
+
+    ``revs`` is sliced into groups that should be read in one time.
+    Assume that revs are sorted.
+
+    ``deltainfo`` is a _deltainfo instance of a revision that we would append
+    to the top of the revlog.
+
+    The initial chunk is sliced until the overall density (payload/chunks-span
+    ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
+    skipped.
+
+    >>> revlog = _testrevlog([
+    ...  5,  #00 (5)
+    ...  10, #01 (5)
+    ...  12, #02 (2)
+    ...  12, #03 (empty)
+    ...  27, #04 (15)
+    ...  31, #05 (4)
+    ...  31, #06 (empty)
+    ...  42, #07 (11)
+    ...  47, #08 (5)
+    ...  47, #09 (empty)
+    ...  48, #10 (1)
+    ...  51, #11 (3)
+    ...  74, #12 (23)
+    ...  85, #13 (11)
+    ...  86, #14 (1)
+    ...  91, #15 (5)
+    ... ])
+
+    >>> list(_slicechunktodensity(revlog, list(range(16))))
+    [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
+    >>> list(_slicechunktodensity(revlog, [0, 15]))
+    [[0], [15]]
+    >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
+    [[0], [11], [15]]
+    >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
+    [[0], [11, 13, 15]]
+    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
+    [[1, 2], [5, 8, 10, 11], [14]]
+    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
+    ...                           mingapsize=20))
+    [[1, 2, 3, 5, 8, 10, 11], [14]]
+    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
+    ...                           targetdensity=0.95))
+    [[1, 2], [5], [8, 10, 11], [14]]
+    >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
+    ...                           targetdensity=0.95, mingapsize=12))
+    [[1, 2], [5, 8, 10, 11], [14]]
     """
     start = revlog.start
     length = revlog.length
@@ -223,24 +501,46 @@
         yield revs
         return
 
-    startbyte = start(revs[0])
-    endbyte = start(revs[-1]) + length(revs[-1])
-    readdata = deltachainspan = endbyte - startbyte
-
-    chainpayload = sum(length(r) for r in revs)
+    nextrev = len(revlog)
+    nextoffset = revlog.end(nextrev - 1)
+
+    if deltainfo is None:
+        deltachainspan = _segmentspan(revlog, revs)
+        chainpayload = sum(length(r) for r in revs)
+    else:
+        deltachainspan = deltainfo.distance
+        chainpayload = deltainfo.compresseddeltalen
+
+    if deltachainspan < mingapsize:
+        yield revs
+        return
+
+    readdata = deltachainspan
 
     if deltachainspan:
         density = chainpayload / float(deltachainspan)
     else:
         density = 1.0
 
+    if density >= targetdensity:
+        yield revs
+        return
+
+    if deltainfo is not None:
+        revs = list(revs)
+        revs.append(nextrev)
+
     # Store the gaps in a heap to have them sorted by decreasing size
     gapsheap = []
     heapq.heapify(gapsheap)
     prevend = None
     for i, rev in enumerate(revs):
-        revstart = start(rev)
-        revlen = length(rev)
+        if rev < nextrev:
+            revstart = start(rev)
+            revlen = length(rev)
+        else:
+            revstart = nextoffset
+            revlen = deltainfo.deltalen
 
         # Skip empty revisions to form larger holes
         if revlen == 0:
@@ -249,7 +549,7 @@
         if prevend is not None:
             gapsize = revstart - prevend
             # only consider holes that are large enough
-            if gapsize > revlog._srmingapsize:
+            if gapsize > mingapsize:
                 heapq.heappush(gapsheap, (-gapsize, i))
 
         prevend = revstart + revlen
@@ -257,7 +557,7 @@
     # Collect the indices of the largest holes until the density is acceptable
     indicesheap = []
     heapq.heapify(indicesheap)
-    while gapsheap and density < revlog._srdensitythreshold:
+    while gapsheap and density < targetdensity:
         oppgapsize, gapidx = heapq.heappop(gapsheap)
 
         heapq.heappush(indicesheap, gapidx)
@@ -305,6 +605,7 @@
         grouped by level of easiness.
         """
         revlog = self.revlog
+        gdelta = revlog._generaldelta
         curr = len(revlog)
         prev = curr - 1
         p1r, p2r = revlog.rev(p1), revlog.rev(p2)
@@ -316,27 +617,35 @@
             # changegroup data into a generaldelta repo. The only time it
             # isn't true is if this is the first revision in a delta chain
             # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
-            if cachedelta and revlog._generaldelta and revlog._lazydeltabase:
+            if cachedelta and gdelta and revlog._lazydeltabase:
                 # Assume what we received from the server is a good choice
                 # build delta will reuse the cache
                 yield (cachedelta[0],)
                 tested.add(cachedelta[0])
 
-            if revlog._generaldelta:
+            if gdelta:
                 # exclude already lazy tested base if any
                 parents = [p for p in (p1r, p2r)
                            if p != nullrev and p not in tested]
-                if parents and not revlog._aggressivemergedeltas:
-                    # Pick whichever parent is closer to us (to minimize the
-                    # chance of having to build a fulltext).
-                    parents = [max(parents)]
-                tested.update(parents)
-                yield parents
+
+                if not revlog._deltabothparents and len(parents) == 2:
+                    parents.sort()
+                    # To minimize the chance of having to build a fulltext,
+                    # pick first whichever parent is closest to us (max rev)
+                    yield (parents[1],)
+                    # then the other one (min rev) if the first did not fit
+                    yield (parents[0],)
+                    tested.update(parents)
+                elif len(parents) > 0:
+                    # Test all parents (1 or 2), and keep the best candidate
+                    yield parents
+                    tested.update(parents)
 
             if prev not in tested:
                 # other approach failed try against prev to hopefully save us a
                 # fulltext.
                 yield (prev,)
+                tested.add(prev)
 
     def buildtext(self, revinfo, fh):
         """Builds a fulltext version of a revision
@@ -441,7 +750,7 @@
                 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
                     continue
                 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
-                if revlog._isgooddeltainfo(candidatedelta, revinfo.textlen):
+                if revlog._isgooddeltainfo(candidatedelta, revinfo):
                     nominateddeltas.append(candidatedelta)
             if nominateddeltas:
                 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
@@ -606,7 +915,7 @@
         # How much data to read and cache into the raw revlog data cache.
         self._chunkcachesize = 65536
         self._maxchainlen = None
-        self._aggressivemergedeltas = False
+        self._deltabothparents = True
         self.index = []
         # Mapping of partial identifiers to full nodes.
         self._pcache = {}
@@ -616,7 +925,8 @@
         self._compengine = 'zlib'
         self._maxdeltachainspan = -1
         self._withsparseread = False
-        self._srdensitythreshold = 0.25
+        self._sparserevlog = False
+        self._srdensitythreshold = 0.50
         self._srmingapsize = 262144
 
         mmapindexthreshold = None
@@ -635,8 +945,8 @@
                 self._chunkcachesize = opts['chunkcachesize']
             if 'maxchainlen' in opts:
                 self._maxchainlen = opts['maxchainlen']
-            if 'aggressivemergedeltas' in opts:
-                self._aggressivemergedeltas = opts['aggressivemergedeltas']
+            if 'deltabothparents' in opts:
+                self._deltabothparents = opts['deltabothparents']
             self._lazydeltabase = bool(opts.get('lazydeltabase', False))
             if 'compengine' in opts:
                 self._compengine = opts['compengine']
@@ -644,7 +954,10 @@
                 self._maxdeltachainspan = opts['maxdeltachainspan']
             if mmaplargeindex and 'mmapindexthreshold' in opts:
                 mmapindexthreshold = opts['mmapindexthreshold']
-            self._withsparseread = bool(opts.get('with-sparse-read', False))
+            self._sparserevlog = bool(opts.get('sparse-revlog', False))
+            withsparseread = bool(opts.get('with-sparse-read', False))
+            # sparse-revlog forces sparse-read
+            self._withsparseread = self._sparserevlog or withsparseread
             if 'sparse-read-density-threshold' in opts:
                 self._srdensitythreshold = opts['sparse-read-density-threshold']
             if 'sparse-read-min-gap-size' in opts:
@@ -868,10 +1181,11 @@
             return base
 
         index = self.index
-        base = index[rev][3]
-        while base != rev:
-            rev = base
-            base = index[rev][3]
+        iterrev = rev
+        base = index[iterrev][3]
+        while base != iterrev:
+            iterrev = base
+            base = index[iterrev][3]
 
         self._chainbasecache[rev] = base
         return base
@@ -1365,31 +1679,46 @@
                 c.append(self.node(r))
         return c
 
-    def descendant(self, start, end):
-        if start == nullrev:
-            return True
-        for i in self.descendants([start]):
-            if i == end:
-                return True
-            elif i > end:
-                break
-        return False
-
     def commonancestorsheads(self, a, b):
         """calculate all the heads of the common ancestors of nodes a and b"""
         a, b = self.rev(a), self.rev(b)
+        ancs = self._commonancestorsheads(a, b)
+        return pycompat.maplist(self.node, ancs)
+
+    def _commonancestorsheads(self, *revs):
+        """calculate all the heads of the common ancestors of revs"""
         try:
-            ancs = self.index.commonancestorsheads(a, b)
+            ancs = self.index.commonancestorsheads(*revs)
         except (AttributeError, OverflowError): # C implementation failed
-            ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
-        return pycompat.maplist(self.node, ancs)
+            ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
+        return ancs
 
     def isancestor(self, a, b):
         """return True if node a is an ancestor of node b
 
+        A revision is considered an ancestor of itself."""
+        a, b = self.rev(a), self.rev(b)
+        return self.isancestorrev(a, b)
+
+    def descendant(self, a, b):
+        msg = (b'revlog.descendant is deprecated, use revlog.isancestorrev')
+        self._repo.ui.deprecwarn(msg, b'4.7')
+        return self.isancestorrev(a, b)
+
+    def isancestorrev(self, a, b):
+        """return True if revision a is an ancestor of revision b
+
+        A revision is considered an ancestor of itself.
+
         The implementation of this is trivial but the use of
         commonancestorsheads is not."""
-        return a in self.commonancestorsheads(a, b)
+        if a == nullrev:
+            return True
+        elif a == b:
+            return True
+        elif a > b:
+            return False
+        return a in self._commonancestorsheads(a, b)
 
     def ancestor(self, a, b):
         """calculate the "best" common ancestor of nodes a and b"""
@@ -1502,42 +1831,51 @@
 
     def shortest(self, node, minlength=1):
         """Find the shortest unambiguous prefix that matches node."""
-        def isvalid(test):
+        def isvalid(prefix):
             try:
-                if self._partialmatch(test) is None:
-                    return False
-
-                try:
-                    i = int(test)
-                    # if we are a pure int, then starting with zero will not be
-                    # confused as a rev; or, obviously, if the int is larger
-                    # than the value of the tip rev
-                    if test[0] == '0' or i > len(self):
-                        return True
-                    return False
-                except ValueError:
-                    return True
+                node = self._partialmatch(prefix)
             except error.RevlogError:
                 return False
             except error.WdirUnsupported:
                 # single 'ff...' match
                 return True
+            if node is None:
+                raise LookupError(node, self.indexfile, _('no node'))
+            return True
+
+        def maybewdir(prefix):
+            return all(c == 'f' for c in prefix)
 
         hexnode = hex(node)
-        shortest = hexnode
-        startlength = max(6, minlength)
-        length = startlength
-        while True:
-            test = hexnode[:length]
-            if isvalid(test):
-                shortest = test
-                if length == minlength or length > startlength:
-                    return shortest
-                length -= 1
-            else:
-                length += 1
-                if len(shortest) <= length:
-                    return shortest
+
+        def disambiguate(hexnode, minlength):
+            """Disambiguate against wdirid."""
+            for length in range(minlength, 41):
+                prefix = hexnode[:length]
+                if not maybewdir(prefix):
+                    return prefix
+
+        if not getattr(self, 'filteredrevs', None):
+            try:
+                length = max(self.index.shortest(node), minlength)
+                return disambiguate(hexnode, length)
+            except RevlogError:
+                if node != wdirid:
+                    raise LookupError(node, self.indexfile, _('no node'))
+            except AttributeError:
+                # Fall through to pure code
+                pass
+
+        if node == wdirid:
+            for length in range(minlength, 41):
+                prefix = hexnode[:length]
+                if isvalid(prefix):
+                    return prefix
+
+        for length in range(minlength, 41):
+            prefix = hexnode[:length]
+            if isvalid(prefix):
+                return disambiguate(hexnode, length)
 
     def cmp(self, node, text):
         """compare text with a given file revision
@@ -1654,7 +1992,7 @@
         """
         return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
 
-    def _chunks(self, revs, df=None):
+    def _chunks(self, revs, df=None, targetsize=None):
         """Obtain decompressed chunks for the specified revisions.
 
         Accepts an iterable of numeric revisions that are assumed to be in
@@ -1681,7 +2019,7 @@
         if not self._withsparseread:
             slicedchunks = (revs,)
         else:
-            slicedchunks = _slicechunk(self, revs)
+            slicedchunks = _slicechunk(self, revs, targetsize=targetsize)
 
         for revschunk in slicedchunks:
             firstrev = revschunk[0]
@@ -1784,7 +2122,12 @@
             # drop cache to save memory
             self._cache = None
 
-            bins = self._chunks(chain, df=_df)
+            targetsize = None
+            rawsize = self.index[rev][2]
+            if 0 <= rawsize:
+                targetsize = 4 * rawsize
+
+            bins = self._chunks(chain, df=_df, targetsize=targetsize)
             if rawtext is None:
                 rawtext = bytes(bins[0])
                 bins = bins[1:]
@@ -2076,26 +2419,49 @@
 
         return compressor.decompress(data)
 
-    def _isgooddeltainfo(self, d, textlen):
+    def _isgooddeltainfo(self, deltainfo, revinfo):
         """Returns True if the given delta is good. Good means that it is within
         the disk span, disk size, and chain length bounds that we know to be
         performant."""
-        if d is None:
+        if deltainfo is None:
             return False
 
-        # - 'd.distance' is the distance from the base revision -- bounding it
-        #   limits the amount of I/O we need to do.
-        # - 'd.compresseddeltalen' is the sum of the total size of deltas we
-        #   need to apply -- bounding it limits the amount of CPU we consume.
-
+        # - 'deltainfo.distance' is the distance from the base revision --
+        #   bounding it limits the amount of I/O we need to do.
+        # - 'deltainfo.compresseddeltalen' is the sum of the total size of
+        #   deltas we need to apply -- bounding it limits the amount of CPU
+        #   we consume.
+
+        if self._sparserevlog:
+            # As sparse-read will be used, we can consider that the distance,
+            # instead of being the span of the whole chunk,
+            # is the span of the largest read chunk
+            base = deltainfo.base
+
+            if base != nullrev:
+                deltachain = self._deltachain(base)[0]
+            else:
+                deltachain = []
+
+            chunks = _slicechunk(self, deltachain, deltainfo)
+            distance = max(map(lambda revs:_segmentspan(self, revs), chunks))
+        else:
+            distance = deltainfo.distance
+
+        textlen = revinfo.textlen
         defaultmax = textlen * 4
         maxdist = self._maxdeltachainspan
         if not maxdist:
-            maxdist = d.distance # ensure the conditional pass
+            maxdist = distance # ensure the conditional pass
         maxdist = max(maxdist, defaultmax)
-        if (d.distance > maxdist or d.deltalen > textlen or
-            d.compresseddeltalen > textlen * 2 or
-            (self._maxchainlen and d.chainlen > self._maxchainlen)):
+        if self._sparserevlog and maxdist < self._srmingapsize:
+            # In multiple place, we are ignoring irrelevant data range below a
+            # certain size. Be also apply this tradeoff here and relax span
+            # constraint for small enought content.
+            maxdist = self._srmingapsize
+        if (distance > maxdist or deltainfo.deltalen > textlen or
+            deltainfo.compresseddeltalen > textlen * 2 or
+            (self._maxchainlen and deltainfo.chainlen > self._maxchainlen)):
             return False
 
         return True
@@ -2477,7 +2843,7 @@
     DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
 
     def clone(self, tr, destrevlog, addrevisioncb=None,
-              deltareuse=DELTAREUSESAMEREVS, aggressivemergedeltas=None):
+              deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
         """Copy this revlog to another, possibly with format changes.
 
         The destination revlog will contain the same revisions and nodes.
@@ -2511,7 +2877,7 @@
         deltas will be recomputed if the delta's parent isn't a parent of the
         revision.
 
-        In addition to the delta policy, the ``aggressivemergedeltas`` argument
+        In addition to the delta policy, the ``deltabothparents`` argument
         controls whether to compute deltas against both parents for merges.
         By default, the current default is used.
         """
@@ -2528,7 +2894,7 @@
 
         # lazydeltabase controls whether to reuse a cached delta, if possible.
         oldlazydeltabase = destrevlog._lazydeltabase
-        oldamd = destrevlog._aggressivemergedeltas
+        oldamd = destrevlog._deltabothparents
 
         try:
             if deltareuse == self.DELTAREUSEALWAYS:
@@ -2536,7 +2902,7 @@
             elif deltareuse == self.DELTAREUSESAMEREVS:
                 destrevlog._lazydeltabase = False
 
-            destrevlog._aggressivemergedeltas = aggressivemergedeltas or oldamd
+            destrevlog._deltabothparents = deltabothparents or oldamd
 
             populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
                                                 self.DELTAREUSESAMEREVS)
@@ -2591,4 +2957,4 @@
                     addrevisioncb(self, rev, node)
         finally:
             destrevlog._lazydeltabase = oldlazydeltabase
-            destrevlog._aggressivemergedeltas = oldamd
+            destrevlog._deltabothparents = oldamd
--- a/mercurial/revset.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/revset.py	Thu Jul 19 13:55:54 2018 -0400
@@ -13,6 +13,7 @@
 from . import (
     dagop,
     destutil,
+    diffutil,
     encoding,
     error,
     hbisect,
@@ -111,7 +112,7 @@
     return None
 
 def _sortedb(xs):
-    return sorted(util.rapply(pycompat.maybebytestr, xs))
+    return sorted(pycompat.rapply(pycompat.maybebytestr, xs))
 
 # operator methods
 
@@ -203,6 +204,8 @@
 
 def orset(repo, subset, x, order):
     xs = getlist(x)
+    if not xs:
+        return baseset()
     if order == followorder:
         # slow path to take the subset order
         return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
@@ -309,21 +312,17 @@
     Will return empty list when passed no args.
     Greatest common ancestor of a single changeset is that changeset.
     """
-    # i18n: "ancestor" is a keyword
-    l = getlist(x)
-    rl = fullreposet(repo)
-    anc = None
+    reviter = iter(orset(repo, fullreposet(repo), x, order=anyorder))
+    try:
+        anc = repo[next(reviter)]
+    except StopIteration:
+        return baseset()
+    for r in reviter:
+        anc = anc.ancestor(repo[r])
 
-    # (getset(repo, rl, i) for i in l) generates a list of lists
-    for revs in (getset(repo, rl, i) for i in l):
-        for r in revs:
-            if anc is None:
-                anc = repo[r]
-            else:
-                anc = anc.ancestor(repo[r])
-
-    if anc is not None and anc.rev() in subset:
-        return baseset([anc.rev()])
+    r = scmutil.intrev(anc)
+    if r in subset:
+        return baseset([r])
     return baseset()
 
 def _ancestors(repo, subset, x, followfirst=False, startdepth=None,
@@ -609,6 +608,38 @@
     return subset.filter(lambda r: repo[r].closesbranch(),
                          condrepr='<branch closed>')
 
+# for internal use
+@predicate('_commonancestorheads(set)', safe=True)
+def _commonancestorheads(repo, subset, x):
+    # This is an internal method is for quickly calculating "heads(::x and
+    # ::y)"
+
+    # These greatest common ancestors are the same ones that the consesus bid
+    # merge will find.
+    h = heads(repo, fullreposet(repo), x, anyorder)
+
+    ancs = repo.changelog._commonancestorsheads(*list(h))
+    return subset & baseset(ancs)
+
+@predicate('commonancestors(set)', safe=True)
+def commonancestors(repo, subset, x):
+    """Returns all common ancestors of the set.
+
+    This method is for calculating "::x and ::y" (i.e. all the ancestors that
+    are common to both x and y) in an easy and optimized way. We can't quite
+    use "::head()" because that revset returns "::x + ::y + ..." for each head
+    in the repo (whereas we want "::x *and* ::y").
+
+    """
+    # only wants the heads of the set passed in
+    h = heads(repo, fullreposet(repo), x, anyorder)
+    if not h:
+        return baseset()
+    for r in h:
+        subset &= dagop.revancestors(repo, baseset([r]))
+
+    return subset
+
 @predicate('contains(pattern)', weight=100)
 def contains(repo, subset, x):
     """The revision's manifest contains a file matching pattern (but might not
@@ -1129,11 +1160,14 @@
         hs.update(cl.rev(h) for h in ls)
     return subset & baseset(hs)
 
-@predicate('heads(set)', safe=True)
-def heads(repo, subset, x):
+@predicate('heads(set)', safe=True, takeorder=True)
+def heads(repo, subset, x, order):
     """Members of set with no children in set.
     """
-    s = getset(repo, subset, x)
+    # argument set should never define order
+    if order == defineorder:
+        order = followorder
+    s = getset(repo, subset, x, order=order)
     ps = parents(repo, subset, x)
     return s - ps
 
@@ -1333,9 +1367,11 @@
     else:
         rn = None
         try:
-            pm = repo.changelog._partialmatch(n)
+            pm = scmutil.resolvehexnodeidprefix(repo, n)
             if pm is not None:
                 rn = repo.changelog.rev(pm)
+        except LookupError:
+            pass
         except error.WdirUnsupported:
             rn = node.wdirrev
 
@@ -1344,6 +1380,14 @@
     result = baseset([rn])
     return result & subset
 
+@predicate('none()', safe=True)
+def none(repo, subset, x):
+    """No changesets.
+    """
+    # i18n: "none" is a keyword
+    getargs(x, 0, 0, _("none takes no arguments"))
+    return baseset()
+
 @predicate('obsolete()', safe=True)
 def obsolete(repo, subset, x):
     """Mutable changeset with a newer version."""
@@ -1792,7 +1836,8 @@
         'phase': lambda r: repo[r].phase(),
         'substate': lambda r: repo[r].substate,
         'summary': lambda r: repo[r].description().splitlines()[0],
-        'diff': lambda r: list(repo[r].diff(git=True),)
+        'diff': lambda r: list(repo[r].diff(
+            opts=diffutil.diffallopts(repo.ui, {'git': True}))),
     }
     for info in fields:
         getfield = _funcs.get(info, None)
--- a/mercurial/revsetlang.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/revsetlang.py	Thu Jul 19 13:55:54 2018 -0400
@@ -459,6 +459,12 @@
         f = getsymbol(x[1])
         wa, ta = _optimize(x[2])
         w = getattr(symbols.get(f), '_weight', 1)
+        m = _match('commonancestors(_)', ta)
+
+        # Optimize heads(commonancestors(_)) because we have a fast version
+        if f == 'heads' and m:
+            return w + wa, _build('_commonancestorheads(_)', m[1])
+
         return w + wa, (op, x[1], ta)
     raise ValueError('invalid operator %r' % op)
 
--- a/mercurial/scmutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/scmutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -104,8 +104,9 @@
         return self[6]
 
     def __repr__(self, *args, **kwargs):
-        return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
-                 'unknown=%r, ignored=%r, clean=%r>') % self)
+        return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
+                 r'unknown=%s, ignored=%s, clean=%s>') %
+                tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
 
 def itersubrepos(ctx1, ctx2):
     """find subrepos in ctx1 or ctx2"""
@@ -200,7 +201,7 @@
         elif not msg:
             ui.warn(_(" empty string\n"))
         else:
-            ui.warn("\n%r\n" % stringutil.ellipsis(msg))
+            ui.warn("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
     except error.CensoredNodeError as inst:
         ui.warn(_("abort: file censored %s!\n") % inst)
     except error.RevlogError as inst:
@@ -232,7 +233,7 @@
             except (AttributeError, IndexError):
                 # it might be anything, for example a string
                 reason = inst.reason
-            if isinstance(reason, unicode):
+            if isinstance(reason, pycompat.unicode):
                 # SSLError of Python 2.7.9 contains a unicode
                 reason = encoding.unitolocal(reason)
             ui.warn(_("abort: error: %s\n") % reason)
@@ -286,7 +287,8 @@
 def checkfilename(f):
     '''Check that the filename f is an acceptable filename for a tracked file'''
     if '\r' in f or '\n' in f:
-        raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
+        raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
+                          % pycompat.bytestr(f))
 
 def checkportable(ui, f):
     '''Check if filename f is portable and warn or abort depending on config'''
@@ -448,7 +450,32 @@
     # _partialmatch() of filtered changelog could take O(len(repo)) time,
     # which would be unacceptably slow. so we look for hash collision in
     # unfiltered space, which means some hashes may be slightly longer.
-    return repo.unfiltered().changelog.shortest(node, minlength)
+    cl = repo.unfiltered().changelog
+
+    def isrev(prefix):
+        try:
+            i = int(prefix)
+            # if we are a pure int, then starting with zero will not be
+            # confused as a rev; or, obviously, if the int is larger
+            # than the value of the tip rev
+            if prefix[0:1] == b'0' or i > len(cl):
+                return False
+            return True
+        except ValueError:
+            return False
+
+    def disambiguate(prefix):
+        """Disambiguate against revnums."""
+        hexnode = hex(node)
+        for length in range(len(prefix), len(hexnode) + 1):
+            prefix = hexnode[:length]
+            if not isrev(prefix):
+                return prefix
+
+    try:
+        return disambiguate(cl.shortest(node, minlength))
+    except error.LookupError:
+        raise error.RepoLookupError()
 
 def isrevsymbol(repo, symbol):
     """Checks if a symbol exists in the repo.
@@ -561,11 +588,6 @@
     tree = revsetlang.parse(revspec)
     return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
 
-def revpairnodes(repo, revs):
-    repo.ui.deprecwarn("revpairnodes is deprecated, please use revpair", "4.6")
-    ctx1, ctx2 = revpair(repo, revs)
-    return ctx1.node(), ctx2.node()
-
 def revpair(repo, revs):
     if not revs:
         return repo['.'], repo[None]
@@ -757,7 +779,8 @@
     def __contains__(self, node):
         return self._revcontains(self._torev(node))
 
-def cleanupnodes(repo, replacements, operation, moves=None, metadata=None):
+def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
+                 fixphase=False, targetphase=None):
     """do common cleanups when old nodes are replaced by new nodes
 
     That includes writing obsmarkers or stripping nodes, and moving bookmarks.
@@ -773,6 +796,7 @@
     metadata is dictionary containing metadata to be stored in obsmarker if
     obsolescence is enabled.
     """
+    assert fixphase or targetphase is None
     if not replacements and not moves:
         return
 
@@ -803,18 +827,45 @@
             newnode = newnodes[0]
         moves[oldnode] = newnode
 
+    allnewnodes = [n for ns in replacements.values() for n in ns]
+    toretract = {}
+    toadvance = {}
+    if fixphase:
+        precursors = {}
+        for oldnode, newnodes in replacements.items():
+            for newnode in newnodes:
+                precursors.setdefault(newnode, []).append(oldnode)
+
+        allnewnodes.sort(key=lambda n: unfi[n].rev())
+        newphases = {}
+        def phase(ctx):
+            return newphases.get(ctx.node(), ctx.phase())
+        for newnode in allnewnodes:
+            ctx = unfi[newnode]
+            parentphase = max(phase(p) for p in ctx.parents())
+            if targetphase is None:
+                oldphase = max(unfi[oldnode].phase()
+                               for oldnode in precursors[newnode])
+                newphase = max(oldphase, parentphase)
+            else:
+                newphase = max(targetphase, parentphase)
+            newphases[newnode] = newphase
+            if newphase > ctx.phase():
+                toretract.setdefault(newphase, []).append(newnode)
+            elif newphase < ctx.phase():
+                toadvance.setdefault(newphase, []).append(newnode)
+
     with repo.transaction('cleanup') as tr:
         # Move bookmarks
         bmarks = repo._bookmarks
         bmarkchanges = []
-        allnewnodes = [n for ns in replacements.values() for n in ns]
         for oldnode, newnode in moves.items():
             oldbmarks = repo.nodebookmarks(oldnode)
             if not oldbmarks:
                 continue
             from . import bookmarks # avoid import cycle
             repo.ui.debug('moving bookmarks %r from %s to %s\n' %
-                          (util.rapply(pycompat.maybebytestr, oldbmarks),
+                          (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
                            hex(oldnode), hex(newnode)))
             # Delete divergent bookmarks being parents of related newnodes
             deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
@@ -828,6 +879,11 @@
         if bmarkchanges:
             bmarks.applychanges(repo, tr, bmarkchanges)
 
+        for phase, nodes in toretract.items():
+            phases.retractboundary(repo, tr, phase, nodes)
+        for phase, nodes in toadvance.items():
+            phases.advanceboundary(repo, tr, phase, nodes)
+
         # Obsolete or strip nodes
         if obsolete.isenabled(repo, obsolete.createmarkersopt):
             # If a node is already obsoleted, and we want to obsolete it
@@ -1110,21 +1166,32 @@
             entry.refresh()
 
 class filecache(object):
-    '''A property like decorator that tracks files under .hg/ for updates.
+    """A property like decorator that tracks files under .hg/ for updates.
 
-    Records stat info when called in _filecache.
+    On first access, the files defined as arguments are stat()ed and the
+    results cached. The decorated function is called. The results are stashed
+    away in a ``_filecache`` dict on the object whose method is decorated.
 
-    On subsequent calls, compares old stat info with new info, and recreates the
-    object when any of the files changes, updating the new stat info in
-    _filecache.
+    On subsequent access, the cached result is returned.
+
+    On external property set operations, stat() calls are performed and the new
+    value is cached.
+
+    On property delete operations, cached data is removed.
 
-    Mercurial either atomic renames or appends for files under .hg,
-    so to ensure the cache is reliable we need the filesystem to be able
-    to tell us if a file has been replaced. If it can't, we fallback to
-    recreating the object on every call (essentially the same behavior as
-    propertycache).
+    When using the property API, cached data is always returned, if available:
+    no stat() is performed to check if the file has changed and if the function
+    needs to be called to reflect file changes.
 
-    '''
+    Others can muck about with the state of the ``_filecache`` dict. e.g. they
+    can populate an entry before the property's getter is called. In this case,
+    entries in ``_filecache`` will be used during property operations,
+    if available. If the underlying file changes, it is up to external callers
+    to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
+    method result as well as possibly calling ``del obj._filecache[attr]`` to
+    remove the ``filecacheentry``.
+    """
+
     def __init__(self, *paths):
         self.paths = paths
 
@@ -1139,7 +1206,8 @@
 
     def __call__(self, func):
         self.func = func
-        self.name = func.__name__.encode('ascii')
+        self.sname = func.__name__
+        self.name = pycompat.sysbytes(self.sname)
         return self
 
     def __get__(self, obj, type=None):
@@ -1147,9 +1215,9 @@
         if obj is None:
             return self
         # do we need to check if the file changed?
-        if self.name in obj.__dict__:
+        if self.sname in obj.__dict__:
             assert self.name in obj._filecache, self.name
-            return obj.__dict__[self.name]
+            return obj.__dict__[self.sname]
 
         entry = obj._filecache.get(self.name)
 
@@ -1166,7 +1234,7 @@
 
             obj._filecache[self.name] = entry
 
-        obj.__dict__[self.name] = entry.obj
+        obj.__dict__[self.sname] = entry.obj
         return entry.obj
 
     def __set__(self, obj, value):
@@ -1180,13 +1248,13 @@
             ce = obj._filecache[self.name]
 
         ce.obj = value # update cached copy
-        obj.__dict__[self.name] = value # update copy returned by obj.x
+        obj.__dict__[self.sname] = value # update copy returned by obj.x
 
     def __delete__(self, obj):
         try:
-            del obj.__dict__[self.name]
+            del obj.__dict__[self.sname]
         except KeyError:
-            raise AttributeError(self.name)
+            raise AttributeError(self.sname)
 
 def extdatasource(repo, source):
     """Gather a map of rev -> value dict from the specified source
@@ -1262,6 +1330,37 @@
     return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
                     **kwargs)
 
+class progress(object):
+    def __init__(self, ui, topic, unit="", total=None):
+        self.ui = ui
+        self.pos = 0
+        self.topic = topic
+        self.unit = unit
+        self.total = total
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, exc_tb):
+        self.complete()
+
+    def update(self, pos, item="", total=None):
+        assert pos is not None
+        if total:
+            self.total = total
+        self.pos = pos
+        self._print(item)
+
+    def increment(self, step=1, item="", total=None):
+        self.update(self.pos + step, item, total)
+
+    def complete(self):
+        self.ui.progress(self.topic, None)
+
+    def _print(self, item):
+        self.ui.progress(self.topic, self.pos, item, self.unit,
+                         self.total)
+
 def gdinitconfig(ui):
     """helper function to know if a repo should be created as general delta
     """
@@ -1434,9 +1533,9 @@
             for instability, revset in instabilitytypes:
                 delta = (newinstabilitycounts[instability] -
                          oldinstabilitycounts[instability])
-                if delta > 0:
-                    repo.ui.warn(_('%i new %s changesets\n') %
-                                 (delta, instability))
+                msg = getinstabilitymessage(delta, instability)
+                if msg:
+                    repo.ui.warn(msg)
 
     if txmatch(_reportnewcssource):
         @reportsummary
@@ -1460,6 +1559,32 @@
                 revrange = '%s:%s' % (minrev, maxrev)
             repo.ui.status(_('new changesets %s\n') % revrange)
 
+        @reportsummary
+        def reportphasechanges(repo, tr):
+            """Report statistics of phase changes for changesets pre-existing
+            pull/unbundle.
+            """
+            newrevs = tr.changes.get('revs', xrange(0, 0))
+            phasetracking = tr.changes.get('phases', {})
+            if not phasetracking:
+                return
+            published = [
+                rev for rev, (old, new) in phasetracking.iteritems()
+                if new == phases.public and rev not in newrevs
+            ]
+            if not published:
+                return
+            repo.ui.status(_('%d local changesets published\n')
+                           % len(published))
+
+def getinstabilitymessage(delta, instability):
+    """function to return the message to show warning about new instabilities
+
+    exists as a separate function so that extension can wrap to show more
+    information like how to fix instabilities"""
+    if delta > 0:
+        return _('%i new %s changesets\n') % (delta, instability)
+
 def nodesummaries(repo, nodes, maxnumnodes=4):
     if len(nodes) <= maxnumnodes or repo.ui.verbose:
         return ' '.join(short(h) for h in nodes)
@@ -1538,7 +1663,6 @@
     unficl = unfi.changelog
     cl = repo.changelog
     tiprev = len(unficl)
-    pmatch = unficl._partialmatch
     allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
     for s in symbols:
         try:
@@ -1554,7 +1678,7 @@
             pass
 
         try:
-            s = pmatch(s)
+            s = resolvehexnodeidprefix(unfi, s)
         except (error.LookupError, error.WdirUnsupported):
             s = None
 
@@ -1564,3 +1688,12 @@
                 revs.add(rev)
 
     return revs
+
+def bookmarkrevs(repo, mark):
+    """
+    Select revisions reachable by a given bookmark
+    """
+    return repo.revs("ancestors(bookmark(%s)) - "
+                     "ancestors(head() and not bookmark(%s)) - "
+                     "ancestors(bookmark() and not bookmark(%s))",
+                     mark, mark, mark)
--- a/mercurial/server.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/server.py	Thu Jul 19 13:55:54 2018 -0400
@@ -8,7 +8,6 @@
 from __future__ import absolute_import
 
 import os
-import tempfile
 
 from .i18n import _
 
@@ -72,7 +71,7 @@
 
     if opts['daemon'] and not opts['daemon_postexec']:
         # Signal child process startup with file removal
-        lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
+        lockfd, lockpath = pycompat.mkstemp(prefix='hg-service-')
         os.close(lockfd)
         try:
             if not runargs:
--- a/mercurial/setdiscovery.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/setdiscovery.py	Thu Jul 19 13:55:54 2018 -0400
@@ -197,6 +197,7 @@
     missing = set()
 
     full = False
+    progress = ui.makeprogress(_('searching'), unit=_('queries'))
     while undecided:
 
         if sample:
@@ -226,7 +227,7 @@
             sample = samplefunc(dag, undecided, targetsize)
 
         roundtrips += 1
-        ui.progress(_('searching'), roundtrips, unit=_('queries'))
+        progress.update(roundtrips)
         ui.debug("query %i; still undecided: %i, sample size is: %i\n"
                  % (roundtrips, len(undecided), len(sample)))
         # indices between sample and externalized version must match
@@ -251,7 +252,7 @@
     # return any heads in that case, so discard that
     result.discard(nullrev)
     elapsed = util.timer() - start
-    ui.progress(_('searching'), None)
+    progress.complete()
     ui.debug("%d total queries in %.4fs\n" % (roundtrips, elapsed))
     msg = ('found %d common and %d unknown server heads,'
            ' %d roundtrips in %.4fs\n')
--- a/mercurial/similar.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/similar.py	Thu Jul 19 13:55:54 2018 -0400
@@ -18,14 +18,14 @@
     Takes a list of new filectxs and a list of removed filectxs, and yields
     (before, after) tuples of exact matches.
     '''
-    numfiles = len(added) + len(removed)
-
     # Build table of removed files: {hash(fctx.data()): [fctx, ...]}.
     # We use hash() to discard fctx.data() from memory.
     hashes = {}
-    for i, fctx in enumerate(removed):
-        repo.ui.progress(_('searching for exact renames'), i, total=numfiles,
-                         unit=_('files'))
+    progress = repo.ui.makeprogress(_('searching for exact renames'),
+                                    total=(len(added) + len(removed)),
+                                    unit=_('files'))
+    for fctx in removed:
+        progress.increment()
         h = hash(fctx.data())
         if h not in hashes:
             hashes[h] = [fctx]
@@ -33,9 +33,8 @@
             hashes[h].append(fctx)
 
     # For each added file, see if it corresponds to a removed file.
-    for i, fctx in enumerate(added):
-        repo.ui.progress(_('searching for exact renames'), i + len(removed),
-                total=numfiles, unit=_('files'))
+    for fctx in added:
+        progress.increment()
         adata = fctx.data()
         h = hash(adata)
         for rfctx in hashes.get(h, []):
@@ -45,7 +44,7 @@
                 break
 
     # Done
-    repo.ui.progress(_('searching for exact renames'), None)
+    progress.complete()
 
 def _ctxdata(fctx):
     # lazily load text
@@ -76,10 +75,10 @@
     (before, after, score) tuples of partial matches.
     '''
     copies = {}
-    for i, r in enumerate(removed):
-        repo.ui.progress(_('searching for similar files'), i,
-                         total=len(removed), unit=_('files'))
-
+    progress = repo.ui.makeprogress(_('searching for similar files'),
+                         unit=_('files'), total=len(removed))
+    for r in removed:
+        progress.increment()
         data = None
         for a in added:
             bestscore = copies.get(a, (None, threshold))[1]
@@ -88,7 +87,7 @@
             myscore = _score(a, data)
             if myscore > bestscore:
                 copies[a] = (r, myscore)
-    repo.ui.progress(_('searching'), None)
+    progress.complete()
 
     for dest, v in copies.iteritems():
         source, bscore = v
--- a/mercurial/smartset.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/smartset.py	Thu Jul 19 13:55:54 2018 -0400
@@ -13,29 +13,9 @@
     pycompat,
     util,
 )
-
-def _formatsetrepr(r):
-    """Format an optional printable representation of a set
-
-    ========  =================================
-    type(r)   example
-    ========  =================================
-    tuple     ('<not %r>', other)
-    bytes     '<branch closed>'
-    callable  lambda: '<branch %r>' % sorted(b)
-    object    other
-    ========  =================================
-    """
-    if r is None:
-        return ''
-    elif isinstance(r, tuple):
-        return r[0] % util.rapply(pycompat.maybebytestr, r[1:])
-    elif isinstance(r, bytes):
-        return r
-    elif callable(r):
-        return r()
-    else:
-        return pycompat.byterepr(r)
+from .utils import (
+    stringutil,
+)
 
 def _typename(o):
     return pycompat.sysbytes(type(o).__name__).lstrip('_')
@@ -392,7 +372,7 @@
     @encoding.strmethod
     def __repr__(self):
         d = {None: '', False: '-', True: '+'}[self._ascending]
-        s = _formatsetrepr(self._datarepr)
+        s = stringutil.buildrepr(self._datarepr)
         if not s:
             l = self._list
             # if _list has been built from a set, it might have a different
@@ -514,7 +494,7 @@
     @encoding.strmethod
     def __repr__(self):
         xs = [pycompat.byterepr(self._subset)]
-        s = _formatsetrepr(self._condrepr)
+        s = stringutil.buildrepr(self._condrepr)
         if s:
             xs.append(s)
         return '<%s %s>' % (_typename(self), ', '.join(xs))
@@ -1129,17 +1109,3 @@
 
         other.sort(reverse=self.isdescending())
         return other
-
-def prettyformat(revs):
-    lines = []
-    rs = pycompat.byterepr(revs)
-    p = 0
-    while p < len(rs):
-        q = rs.find('<', p + 1)
-        if q < 0:
-            q = len(rs)
-        l = rs.count('<', 0, p) - rs.count('>', 0, p)
-        assert l >= 0
-        lines.append((l, rs[p:q].rstrip()))
-        p = q
-    return '\n'.join('  ' * l + s for l, s in lines)
--- a/mercurial/sshpeer.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/sshpeer.py	Thu Jul 19 13:55:54 2018 -0400
@@ -22,6 +22,7 @@
 )
 from .utils import (
     procutil,
+    stringutil,
 )
 
 def _serverquote(s):
@@ -98,6 +99,17 @@
             _forwardoutput(self._ui, self._side)
         return r
 
+    def unbufferedread(self, size):
+        r = self._call('unbufferedread', size)
+        if size != 0 and not r:
+            # We've observed a condition that indicates the
+            # stdout closed unexpectedly. Check stderr one
+            # more time and snag anything that's there before
+            # letting anyone know the main part of the pipe
+            # closed prematurely.
+            _forwardoutput(self._ui, self._side)
+        return r
+
     def readline(self):
         return self._call('readline')
 
@@ -273,7 +285,7 @@
 
     # Assume version 1 of wire protocol by default.
     protoname = wireprototypes.SSHV1
-    reupgraded = re.compile(b'^upgraded %s (.*)$' % re.escape(token))
+    reupgraded = re.compile(b'^upgraded %s (.*)$' % stringutil.reescape(token))
 
     lines = ['', 'dummy']
     max_noise = 500
--- a/mercurial/sslutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/sslutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -618,14 +618,14 @@
         # The client SHOULD NOT attempt to match a presented identifier
         # where the wildcard character is embedded within an A-label or
         # U-label of an internationalized domain name.
-        pats.append(re.escape(leftmost))
+        pats.append(stringutil.reescape(leftmost))
     else:
         # Otherwise, '*' matches any dotless string, e.g. www*
-        pats.append(re.escape(leftmost).replace(br'\*', '[^.]*'))
+        pats.append(stringutil.reescape(leftmost).replace(br'\*', '[^.]*'))
 
     # add the remaining fragments, ignore any wildcards
     for frag in remainder:
-        pats.append(re.escape(frag))
+        pats.append(stringutil.reescape(frag))
 
     pat = re.compile(br'\A' + br'\.'.join(pats) + br'\Z', re.IGNORECASE)
     return pat.match(hostname) is not None
@@ -640,9 +640,9 @@
         return _('no certificate received')
 
     dnsnames = []
-    san = cert.get('subjectAltName', [])
+    san = cert.get(r'subjectAltName', [])
     for key, value in san:
-        if key == 'DNS':
+        if key == r'DNS':
             try:
                 if _dnsnamematch(value, hostname):
                     return
@@ -672,6 +672,7 @@
 
                     dnsnames.append(value)
 
+    dnsnames = [pycompat.bytesurl(d) for d in dnsnames]
     if len(dnsnames) > 1:
         return _('certificate is for %s') % ', '.join(dnsnames)
     elif len(dnsnames) == 1:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/state.py	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,84 @@
+# state.py - writing and reading state files in Mercurial
+#
+# Copyright 2018 Pulkit Goyal <pulkitmgoyal@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""
+This file contains class to wrap the state for commands and other
+related logic.
+
+All the data related to the command state is stored as dictionary in the object.
+The class has methods using which the data can be stored to disk in a file under
+.hg/ directory.
+
+We store the data on disk in cbor, for which we use the third party cbor library
+to serialize and deserialize data.
+"""
+
+from __future__ import absolute_import
+
+from .thirdparty import cbor
+
+from . import (
+    error,
+    util,
+)
+
+class cmdstate(object):
+    """a wrapper class to store the state of commands like `rebase`, `graft`,
+    `histedit`, `shelve` etc. Extensions can also use this to write state files.
+
+    All the data for the state is stored in the form of key-value pairs in a
+    dictionary.
+
+    The class object can write all the data to a file in .hg/ directory and
+    can populate the object data reading that file.
+
+    Uses cbor to serialize and deserialize data while writing and reading from
+    disk.
+    """
+
+    def __init__(self, repo, fname):
+        """ repo is the repo object
+        fname is the file name in which data should be stored in .hg directory
+        """
+        self._repo = repo
+        self.fname = fname
+
+    def read(self):
+        """read the existing state file and return a dict of data stored"""
+        return self._read()
+
+    def save(self, version, data):
+        """write all the state data stored to .hg/<filename> file
+
+        we use third-party library cbor to serialize data to write in the file.
+        """
+        if not isinstance(version, int):
+            raise error.ProgrammingError("version of state file should be"
+                                         " an integer")
+
+        with self._repo.vfs(self.fname, 'wb', atomictemp=True) as fp:
+            fp.write('%d\n' % version)
+            cbor.dump(data, fp, canonical=True)
+
+    def _read(self):
+        """reads the state file and returns a dictionary which contain
+        data in the same format as it was before storing"""
+        with self._repo.vfs(self.fname, 'rb') as fp:
+            try:
+                int(fp.readline())
+            except ValueError:
+                raise error.CorruptedState("unknown version of state file"
+                                           " found")
+            return cbor.load(fp)
+
+    def delete(self):
+        """drop the state file if exists"""
+        util.unlinkpath(self._repo.vfs.join(self.fname), ignoremissing=True)
+
+    def exists(self):
+        """check whether the state file exists or not"""
+        return self._repo.vfs.exists(self.fname)
--- a/mercurial/statprof.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/statprof.py	Thu Jul 19 13:55:54 2018 -0400
@@ -112,7 +112,6 @@
 import os
 import signal
 import sys
-import tempfile
 import threading
 import time
 
@@ -140,7 +139,7 @@
 
 def clock():
     times = os.times()
-    return times[0] + times[1]
+    return (times[0] + times[1], times[4])
 
 
 ###########################################################################
@@ -149,10 +148,11 @@
 class ProfileState(object):
     def __init__(self, frequency=None):
         self.reset(frequency)
+        self.track = 'cpu'
 
     def reset(self, frequency=None):
         # total so far
-        self.accumulated_time = 0.0
+        self.accumulated_time = (0.0, 0.0)
         # start_time when timer is active
         self.last_start_time = None
         # a float
@@ -171,10 +171,23 @@
         self.samples = []
 
     def accumulate_time(self, stop_time):
-        self.accumulated_time += stop_time - self.last_start_time
+        increment = (
+            stop_time[0] - self.last_start_time[0],
+            stop_time[1] - self.last_start_time[1],
+        )
+        self.accumulated_time = (
+            self.accumulated_time[0] + increment[0],
+            self.accumulated_time[1] + increment[1],
+        )
 
     def seconds_per_sample(self):
-        return self.accumulated_time / len(self.samples)
+        return self.accumulated_time[self.timeidx] / len(self.samples)
+
+    @property
+    def timeidx(self):
+        if self.track == 'real':
+            return 1
+        return 0
 
 state = ProfileState()
 
@@ -262,7 +275,8 @@
         now = clock()
         state.accumulate_time(now)
 
-        state.samples.append(Sample.from_frame(frame, state.accumulated_time))
+        timestamp = state.accumulated_time[state.timeidx]
+        state.samples.append(Sample.from_frame(frame, timestamp))
 
         signal.setitimer(signal.ITIMER_PROF,
             state.sample_interval, 0.0)
@@ -275,7 +289,9 @@
         state.accumulate_time(now)
 
         frame = sys._current_frames()[tid]
-        state.samples.append(Sample.from_frame(frame, state.accumulated_time))
+
+        timestamp = state.accumulated_time[state.timeidx]
+        state.samples.append(Sample.from_frame(frame, timestamp))
 
         state.last_start_time = now
         time.sleep(state.sample_interval)
@@ -289,8 +305,9 @@
     return state.profile_level > 0
 
 lastmechanism = None
-def start(mechanism='thread'):
+def start(mechanism='thread', track='cpu'):
     '''Install the profiling signal handler, and start profiling.'''
+    state.track = track # note: nesting different mode won't work
     state.profile_level += 1
     if state.profile_level == 1:
         state.last_start_time = clock()
@@ -333,7 +350,7 @@
 
 def save_data(path):
     with open(path, 'w+') as file:
-        file.write(str(state.accumulated_time) + '\n')
+        file.write("%f %f\n" % state.accumulated_time)
         for sample in state.samples:
             time = str(sample.time)
             stack = sample.stack
@@ -344,7 +361,7 @@
 def load_data(path):
     lines = open(path, 'r').read().splitlines()
 
-    state.accumulated_time = float(lines[0])
+    state.accumulated_time = [float(value) for value in lines[0].split()]
     state.samples = []
     for line in lines[1:]:
         parts = line.split('\0')
@@ -437,7 +454,8 @@
 
 def display(fp=None, format=3, data=None, **kwargs):
     '''Print statistics, either to stdout or the given file object.'''
-    data = data or state
+    if data is None:
+        data = state
 
     if fp is None:
         import sys
@@ -466,7 +484,8 @@
     if format not in (DisplayFormats.Json, DisplayFormats.Chrome):
         print('---', file=fp)
         print('Sample count: %d' % len(data.samples), file=fp)
-        print('Total time: %f seconds' % data.accumulated_time, file=fp)
+        print('Total time: %f seconds (%f wall)' % data.accumulated_time,
+              file=fp)
 
 def display_by_line(data, fp):
     '''Print the profiler data with each sample line represented
@@ -691,7 +710,7 @@
               file=fp)
         return
 
-    fd, path = tempfile.mkstemp()
+    fd, path = pycompat.mkstemp()
 
     file = open(path, "w+")
 
--- a/mercurial/store.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/store.py	Thu Jul 19 13:55:54 2018 -0400
@@ -449,6 +449,7 @@
 
     def write(self, tr):
         if self._dirty:
+            assert self.entries is not None
             tr.addbackup('fncache')
             fp = self.vfs('fncache', mode='wb', atomictemp=True)
             if self.entries:
@@ -489,10 +490,20 @@
         self.encode = encode
 
     def __call__(self, path, mode='r', *args, **kw):
+        encoded = self.encode(path)
         if mode not in ('r', 'rb') and (path.startswith('data/') or
                                         path.startswith('meta/')):
-            self.fncache.add(path)
-        return self.vfs(self.encode(path), mode, *args, **kw)
+            # do not trigger a fncache load when adding a file that already is
+            # known to exist.
+            notload = self.fncache.entries is None and self.vfs.exists(encoded)
+            if notload and 'a' in mode and not self.vfs.stat(encoded).st_size:
+                # when appending to an existing file, if the file has size zero,
+                # it should be considered as missing. Such zero-size files are
+                # the result of truncation when a transaction is aborted.
+                notload = False
+            if not notload:
+                self.fncache.add(path)
+        return self.vfs(encoded, mode, *args, **kw)
 
     def join(self, path):
         if path:
--- a/mercurial/streamclone.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/streamclone.py	Thu Jul 19 13:55:54 2018 -0400
@@ -10,7 +10,6 @@
 import contextlib
 import os
 import struct
-import tempfile
 import warnings
 
 from .i18n import _
@@ -19,6 +18,7 @@
     cacheutil,
     error,
     phases,
+    pycompat,
     store,
     util,
 )
@@ -313,16 +313,15 @@
         # This is where we'll add compression in the future.
         assert compression == 'UN'
 
-        seen = 0
-        repo.ui.progress(_('bundle'), 0, total=bytecount, unit=_('bytes'))
+        progress = repo.ui.makeprogress(_('bundle'), total=bytecount,
+                                        unit=_('bytes'))
+        progress.update(0)
 
         for chunk in it:
-            seen += len(chunk)
-            repo.ui.progress(_('bundle'), seen, total=bytecount,
-                             unit=_('bytes'))
+            progress.increment(step=len(chunk))
             yield chunk
 
-        repo.ui.progress(_('bundle'), None)
+        progress.complete()
 
     return requirements, gen()
 
@@ -338,8 +337,9 @@
     with repo.lock():
         repo.ui.status(_('%d files to transfer, %s of data\n') %
                        (filecount, util.bytecount(bytecount)))
-        handled_bytes = 0
-        repo.ui.progress(_('clone'), 0, total=bytecount, unit=_('bytes'))
+        progress = repo.ui.makeprogress(_('clone'), total=bytecount,
+                                        unit=_('bytes'))
+        progress.update(0)
         start = util.timer()
 
         # TODO: get rid of (potential) inconsistency
@@ -374,9 +374,7 @@
                     path = store.decodedir(name)
                     with repo.svfs(path, 'w', backgroundclose=True) as ofp:
                         for chunk in util.filechunkiter(fp, limit=size):
-                            handled_bytes += len(chunk)
-                            repo.ui.progress(_('clone'), handled_bytes,
-                                             total=bytecount, unit=_('bytes'))
+                            progress.increment(step=len(chunk))
                             ofp.write(chunk)
 
             # force @filecache properties to be reloaded from
@@ -386,7 +384,7 @@
         elapsed = util.timer() - start
         if elapsed <= 0:
             elapsed = 0.001
-        repo.ui.progress(_('clone'), None)
+        progress.complete()
         repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
                        (util.bytecount(bytecount), elapsed,
                         util.bytecount(bytecount / elapsed)))
@@ -469,7 +467,7 @@
     files = []
     try:
         def copy(src):
-            fd, dst = tempfile.mkstemp()
+            fd, dst = pycompat.mkstemp()
             os.close(fd)
             files.append(dst)
             util.copyfiles(src, dst, hardlink=True)
@@ -494,41 +492,38 @@
 def _emit2(repo, entries, totalfilesize):
     """actually emit the stream bundle"""
     vfsmap = _makemap(repo)
-    progress = repo.ui.progress
-    progress(_('bundle'), 0, total=totalfilesize, unit=_('bytes'))
-    with maketempcopies() as copy:
-        try:
-            # copy is delayed until we are in the try
-            entries = [_filterfull(e, copy, vfsmap) for e in entries]
-            yield None # this release the lock on the repository
-            seen = 0
+    progress = repo.ui.makeprogress(_('bundle'), total=totalfilesize,
+                                    unit=_('bytes'))
+    progress.update(0)
+    with maketempcopies() as copy, progress:
+        # copy is delayed until we are in the try
+        entries = [_filterfull(e, copy, vfsmap) for e in entries]
+        yield None # this release the lock on the repository
+        seen = 0
 
-            for src, name, ftype, data in entries:
-                vfs = vfsmap[src]
-                yield src
-                yield util.uvarintencode(len(name))
-                if ftype == _fileappend:
-                    fp = vfs(name)
-                    size = data
-                elif ftype == _filefull:
-                    fp = open(data, 'rb')
-                    size = util.fstat(fp).st_size
-                try:
-                    yield util.uvarintencode(size)
-                    yield name
-                    if size <= 65536:
-                        chunks = (fp.read(size),)
-                    else:
-                        chunks = util.filechunkiter(fp, limit=size)
-                    for chunk in chunks:
-                        seen += len(chunk)
-                        progress(_('bundle'), seen, total=totalfilesize,
-                                 unit=_('bytes'))
-                        yield chunk
-                finally:
-                    fp.close()
-        finally:
-            progress(_('bundle'), None)
+        for src, name, ftype, data in entries:
+            vfs = vfsmap[src]
+            yield src
+            yield util.uvarintencode(len(name))
+            if ftype == _fileappend:
+                fp = vfs(name)
+                size = data
+            elif ftype == _filefull:
+                fp = open(data, 'rb')
+                size = util.fstat(fp).st_size
+            try:
+                yield util.uvarintencode(size)
+                yield name
+                if size <= 65536:
+                    chunks = (fp.read(size),)
+                else:
+                    chunks = util.filechunkiter(fp, limit=size)
+                for chunk in chunks:
+                    seen += len(chunk)
+                    progress.update(seen)
+                    yield chunk
+            finally:
+                fp.close()
 
 def generatev2(repo):
     """Emit content for version 2 of a streaming clone.
@@ -589,10 +584,9 @@
                        (filecount, util.bytecount(filesize)))
 
         start = util.timer()
-        handledbytes = 0
-        progress = repo.ui.progress
-
-        progress(_('clone'), handledbytes, total=filesize, unit=_('bytes'))
+        progress = repo.ui.makeprogress(_('clone'), total=filesize,
+                                        unit=_('bytes'))
+        progress.update(0)
 
         vfsmap = _makemap(repo)
 
@@ -614,9 +608,7 @@
 
                     with vfs(name, 'w') as ofp:
                         for chunk in util.filechunkiter(fp, limit=datalen):
-                            handledbytes += len(chunk)
-                            progress(_('clone'), handledbytes, total=filesize,
-                                     unit=_('bytes'))
+                            progress.increment(step=len(chunk))
                             ofp.write(chunk)
 
             # force @filecache properties to be reloaded from
@@ -626,10 +618,10 @@
         elapsed = util.timer() - start
         if elapsed <= 0:
             elapsed = 0.001
-        progress(_('clone'), None)
         repo.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
-                       (util.bytecount(handledbytes), elapsed,
-                        util.bytecount(handledbytes / elapsed)))
+                       (util.bytecount(progress.pos), elapsed,
+                        util.bytecount(progress.pos / elapsed)))
+        progress.complete()
 
 def applybundlev2(repo, fp, filecount, filesize, requirements):
     missingreqs = [r for r in requirements if r not in repo.supported]
--- a/mercurial/subrepo.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/subrepo.py	Thu Jul 19 13:55:54 2018 -0400
@@ -318,9 +318,9 @@
         """return file flags"""
         return ''
 
-    def getfileset(self, expr):
+    def matchfileset(self, expr, badfn=None):
         """Resolve the fileset expression for this repo"""
-        return set()
+        return matchmod.nevermatcher(self.wvfs.base, '', badfn=badfn)
 
     def printfiles(self, ui, m, fm, fmt, subrepos):
         """handle the files command for this subrepo"""
@@ -333,17 +333,17 @@
             files = self.files()
         total = len(files)
         relpath = subrelpath(self)
-        self.ui.progress(_('archiving (%s)') % relpath, 0,
-                         unit=_('files'), total=total)
-        for i, name in enumerate(files):
+        progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
+                                        unit=_('files'), total=total)
+        progress.update(0)
+        for name in files:
             flags = self.fileflags(name)
             mode = 'x' in flags and 0o755 or 0o644
             symlink = 'l' in flags
             archiver.addfile(prefix + self._path + '/' + name,
                              mode, symlink, self.filedata(name, decode))
-            self.ui.progress(_('archiving (%s)') % relpath, i + 1,
-                             unit=_('files'), total=total)
-        self.ui.progress(_('archiving (%s)') % relpath, None)
+            progress.increment()
+        progress.complete()
         return total
 
     def walk(self, match):
@@ -792,24 +792,30 @@
         return cmdutil.files(ui, ctx, m, fm, fmt, subrepos)
 
     @annotatesubrepoerror
-    def getfileset(self, expr):
+    def matchfileset(self, expr, badfn=None):
+        repo = self._repo
         if self._ctx.rev() is None:
-            ctx = self._repo[None]
+            ctx = repo[None]
         else:
             rev = self._state[1]
-            ctx = self._repo[rev]
+            ctx = repo[rev]
 
-        files = ctx.getfileset(expr)
+        matchers = [ctx.matchfileset(expr, badfn=badfn)]
 
         for subpath in ctx.substate:
             sub = ctx.sub(subpath)
 
             try:
-                files.extend(subpath + '/' + f for f in sub.getfileset(expr))
+                sm = sub.matchfileset(expr, badfn=badfn)
+                pm = matchmod.prefixdirmatcher(repo.root, repo.getcwd(),
+                                               subpath, sm, badfn=badfn)
+                matchers.append(pm)
             except error.LookupError:
                 self.ui.status(_("skipping missing subrepository: %s\n")
                                % self.wvfs.reljoin(reporelpath(self), subpath))
-        return files
+        if len(matchers) == 1:
+            return matchers[0]
+        return matchmod.unionmatcher(matchers)
 
     def walk(self, match):
         ctx = self._repo[None]
@@ -1640,8 +1646,10 @@
         tarstream = self._gitcommand(['archive', revision], stream=True)
         tar = tarfile.open(fileobj=tarstream, mode=r'r|')
         relpath = subrelpath(self)
-        self.ui.progress(_('archiving (%s)') % relpath, 0, unit=_('files'))
-        for i, info in enumerate(tar):
+        progress = self.ui.makeprogress(_('archiving (%s)') % relpath,
+                                        unit=_('files'))
+        progress.update(0)
+        for info in tar:
             if info.isdir():
                 continue
             if match and not match(info.name):
@@ -1653,9 +1661,8 @@
             archiver.addfile(prefix + self._path + '/' + info.name,
                              info.mode, info.issym(), data)
             total += 1
-            self.ui.progress(_('archiving (%s)') % relpath, i + 1,
-                             unit=_('files'))
-        self.ui.progress(_('archiving (%s)') % relpath, None)
+            progress.increment()
+        progress.complete()
         return total
 
 
@@ -1695,7 +1702,7 @@
             tab = line.find('\t')
             if tab == -1:
                 continue
-            status, f = line[tab - 1], line[tab + 1:]
+            status, f = line[tab - 1:tab], line[tab + 1:]
             if status == 'M':
                 modified.append(f)
             elif status == 'A':
--- a/mercurial/templatefilters.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templatefilters.py	Thu Jul 19 13:55:54 2018 -0400
@@ -99,6 +99,45 @@
     """
     return os.path.basename(path)
 
+@templatefilter('commondir')
+def commondir(filelist):
+    """List of text. Treats each list item as file name with /
+    as path separator and returns the longest common directory
+    prefix shared by all list items.
+    Returns the empty string if no common prefix exists.
+
+    The list items are not normalized, i.e. "foo/../bar" is handled as
+    file "bar" in the directory "foo/..". Leading slashes are ignored.
+
+    For example, ["foo/bar/baz", "foo/baz/bar"] becomes "foo" and
+    ["foo/bar", "baz"] becomes "".
+    """
+    def common(a, b):
+        if len(a) > len(b):
+            a = b[:len(a)]
+        elif len(b) > len(a):
+            b = b[:len(a)]
+        if a == b:
+            return a
+        for i in xrange(len(a)):
+            if a[i] != b[i]:
+                return a[:i]
+        return a
+    try:
+        if not filelist:
+            return ""
+        dirlist = [f.lstrip('/').split('/')[:-1] for f in filelist]
+        if len(dirlist) == 1:
+            return '/'.join(dirlist[0])
+        a = min(dirlist)
+        b = max(dirlist)
+        # The common prefix of a and b is shared with all
+        # elements of the list since Python sorts lexicographical
+        # and [1, x] after [1].
+        return '/'.join(common(a, b))
+    except TypeError:
+        raise error.ParseError(_('argument is not a list of text'))
+
 @templatefilter('count')
 def count(i):
     """List or text. Returns the length as an integer."""
@@ -238,6 +277,7 @@
 
 @templatefilter('json')
 def json(obj, paranoid=True):
+    """Any object. Serializes the object to a JSON formatted text."""
     if obj is None:
         return 'null'
     elif obj is False:
@@ -248,13 +288,9 @@
         return pycompat.bytestr(obj)
     elif isinstance(obj, bytes):
         return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
-    elif isinstance(obj, str):
-        # This branch is unreachable on Python 2, because bytes == str
-        # and we'll return in the next-earlier block in the elif
-        # ladder. On Python 3, this helps us catch bugs before they
-        # hurt someone.
+    elif isinstance(obj, type(u'')):
         raise error.ProgrammingError(
-            'Mercurial only does output with bytes on Python 3: %r' % obj)
+            'Mercurial only does output with bytes: %r' % obj)
     elif util.safehasattr(obj, 'keys'):
         out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
                              json(v, paranoid))
--- a/mercurial/templatefuncs.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templatefuncs.py	Thu Jul 19 13:55:54 2018 -0400
@@ -12,6 +12,7 @@
 from .i18n import _
 from .node import (
     bin,
+    wdirid,
 )
 from . import (
     color,
@@ -19,7 +20,6 @@
     error,
     minirst,
     obsutil,
-    pycompat,
     registrar,
     revset as revsetmod,
     revsetlang,
@@ -35,6 +35,7 @@
 )
 
 evalrawexp = templateutil.evalrawexp
+evalwrapped = templateutil.evalwrapped
 evalfuncarg = templateutil.evalfuncarg
 evalboolean = templateutil.evalboolean
 evaldate = templateutil.evaldate
@@ -84,7 +85,7 @@
                 for k, v in args['kwargs'].iteritems())
     return templateutil.hybriddict(data)
 
-@templatefunc('diff([includepattern [, excludepattern]])')
+@templatefunc('diff([includepattern [, excludepattern]])', requires={'ctx'})
 def diff(context, mapping, args):
     """Show a diff, optionally
     specifying files to include or exclude."""
@@ -104,7 +105,7 @@
 
     return ''.join(chunks)
 
-@templatefunc('extdata(source)', argspec='source')
+@templatefunc('extdata(source)', argspec='source', requires={'ctx', 'cache'})
 def extdata(context, mapping, args):
     """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
     if 'source' not in args:
@@ -112,6 +113,13 @@
         raise error.ParseError(_('extdata expects one argument'))
 
     source = evalstring(context, mapping, args['source'])
+    if not source:
+        sym = templateutil.findsymbolicname(args['source'])
+        if sym:
+            raise error.ParseError(_('empty data source specified'),
+                                   hint=_("did you mean extdata('%s')?") % sym)
+        else:
+            raise error.ParseError(_('empty data source specified'))
     cache = context.resource(mapping, 'cache').setdefault('extdata', {})
     ctx = context.resource(mapping, 'ctx')
     if source in cache:
@@ -120,7 +128,7 @@
         data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
     return data.get(ctx.rev(), '')
 
-@templatefunc('files(pattern)')
+@templatefunc('files(pattern)', requires={'ctx'})
 def files(context, mapping, args):
     """All files of the current changeset matching the pattern. See
     :hg:`help patterns`."""
@@ -158,7 +166,26 @@
 
     return templatefilters.fill(text, width, initindent, hangindent)
 
-@templatefunc('formatnode(node)')
+@templatefunc('filter(iterable[, expr])')
+def filter_(context, mapping, args):
+    """Remove empty elements from a list or a dict. If expr specified, it's
+    applied to each element to test emptiness."""
+    if not (1 <= len(args) <= 2):
+        # i18n: "filter" is a keyword
+        raise error.ParseError(_("filter expects one or two arguments"))
+    iterable = evalwrapped(context, mapping, args[0])
+    if len(args) == 1:
+        def select(w):
+            return w.tobool(context, mapping)
+    else:
+        def select(w):
+            if not isinstance(w, templateutil.mappable):
+                raise error.ParseError(_("not filterable by expression"))
+            lm = context.overlaymap(mapping, w.tomap(context))
+            return evalboolean(context, lm, args[1])
+    return iterable.filter(context, mapping, select)
+
+@templatefunc('formatnode(node)', requires={'ui'})
 def formatnode(context, mapping, args):
     """Obtain the preferred form of a changeset hash. (DEPRECATED)"""
     if len(args) != 1:
@@ -171,7 +198,7 @@
         return node
     return templatefilters.short(node)
 
-@templatefunc('mailmap(author)')
+@templatefunc('mailmap(author)', requires={'repo', 'cache'})
 def mailmap(context, mapping, args):
     """Return the author, updated according to the value
     set in the .mailmap file"""
@@ -252,13 +279,14 @@
         # i18n: "get" is a keyword
         raise error.ParseError(_("get() expects two arguments"))
 
-    dictarg = evalfuncarg(context, mapping, args[0])
-    if not util.safehasattr(dictarg, 'get'):
+    dictarg = evalwrapped(context, mapping, args[0])
+    key = evalrawexp(context, mapping, args[1])
+    try:
+        return dictarg.getmember(context, mapping, key)
+    except error.ParseError as err:
         # i18n: "get" is a keyword
-        raise error.ParseError(_("get() expects a dict as first argument"))
-
-    key = evalfuncarg(context, mapping, args[1])
-    return templateutil.getdictitem(dictarg, key)
+        hint = _("get() expects a dict as first argument")
+        raise error.ParseError(bytes(err), hint=hint)
 
 @templatefunc('if(expr, then[, else])')
 def if_(context, mapping, args):
@@ -282,13 +310,10 @@
         # i18n: "ifcontains" is a keyword
         raise error.ParseError(_("ifcontains expects three or four arguments"))
 
-    haystack = evalfuncarg(context, mapping, args[1])
-    keytype = getattr(haystack, 'keytype', None)
+    haystack = evalwrapped(context, mapping, args[1])
     try:
         needle = evalrawexp(context, mapping, args[0])
-        needle = templateutil.unwrapastype(context, mapping, needle,
-                                           keytype or bytes)
-        found = (needle in haystack)
+        found = haystack.contains(context, mapping, needle)
     except error.ParseError:
         found = False
 
@@ -319,18 +344,13 @@
         # i18n: "join" is a keyword
         raise error.ParseError(_("join expects one or two arguments"))
 
-    joinset = evalrawexp(context, mapping, args[0])
+    joinset = evalwrapped(context, mapping, args[0])
     joiner = " "
     if len(args) > 1:
         joiner = evalstring(context, mapping, args[1])
-    if isinstance(joinset, templateutil.wrapped):
-        return joinset.join(context, mapping, joiner)
-    # TODO: perhaps a generator should be stringify()-ed here, but we can't
-    # because hgweb abuses it as a keyword that returns a list of dicts.
-    joinset = templateutil.unwrapvalue(context, mapping, joinset)
-    return templateutil.joinitems(pycompat.maybebytestr(joinset), joiner)
+    return joinset.join(context, mapping, joiner)
 
-@templatefunc('label(label, expr)')
+@templatefunc('label(label, expr)', requires={'ui'})
 def label(context, mapping, args):
     """Apply a label to generated content. Content with
     a label applied can result in additional post-processing, such as
@@ -352,7 +372,9 @@
     """The global tags matching the given pattern on the
     most recent globally tagged ancestor of this changeset.
     If no such tags exist, the "{tag}" template resolves to
-    the string "null"."""
+    the string "null". See :hg:`help revisions.patterns` for the pattern
+    syntax.
+    """
     if len(args) > 1:
         # i18n: "latesttag" is a keyword
         raise error.ParseError(_("latesttag expects at most one argument"))
@@ -388,7 +410,7 @@
                 raise error.ParseError(_("localdate expects a timezone"))
     else:
         tzoffset = dateutil.makedate()[1]
-    return (date[0], tzoffset)
+    return templateutil.date((date[0], tzoffset))
 
 @templatefunc('max(iterable)')
 def max_(context, mapping, args, **kwargs):
@@ -397,13 +419,13 @@
         # i18n: "max" is a keyword
         raise error.ParseError(_("max expects one argument"))
 
-    iterable = evalfuncarg(context, mapping, args[0])
+    iterable = evalwrapped(context, mapping, args[0])
     try:
-        x = max(pycompat.maybebytestr(iterable))
-    except (TypeError, ValueError):
+        return iterable.getmax(context, mapping)
+    except error.ParseError as err:
         # i18n: "max" is a keyword
-        raise error.ParseError(_("max first argument should be an iterable"))
-    return templateutil.wraphybridvalue(iterable, x, x)
+        hint = _("max first argument should be an iterable")
+        raise error.ParseError(bytes(err), hint=hint)
 
 @templatefunc('min(iterable)')
 def min_(context, mapping, args, **kwargs):
@@ -412,13 +434,13 @@
         # i18n: "min" is a keyword
         raise error.ParseError(_("min expects one argument"))
 
-    iterable = evalfuncarg(context, mapping, args[0])
+    iterable = evalwrapped(context, mapping, args[0])
     try:
-        x = min(pycompat.maybebytestr(iterable))
-    except (TypeError, ValueError):
+        return iterable.getmin(context, mapping)
+    except error.ParseError as err:
         # i18n: "min" is a keyword
-        raise error.ParseError(_("min first argument should be an iterable"))
-    return templateutil.wraphybridvalue(iterable, x, x)
+        hint = _("min first argument should be an iterable")
+        raise error.ParseError(bytes(err), hint=hint)
 
 @templatefunc('mod(a, b)')
 def mod(context, mapping, args):
@@ -458,6 +480,7 @@
     markers = evalfuncarg(context, mapping, args[0])
 
     try:
+        # TODO: maybe this has to be a wrapped list of date wrappers?
         data = obsutil.markersdates(markers)
         return templateutil.hybridlist(data, name='date', fmt='%d %d')
     except (TypeError, KeyError):
@@ -500,7 +523,7 @@
         errmsg = _("obsfateverb first argument should be countable")
         raise error.ParseError(errmsg)
 
-@templatefunc('relpath(path)')
+@templatefunc('relpath(path)', requires={'repo'})
 def relpath(context, mapping, args):
     """Convert a repository-absolute path into a filesystem path relative to
     the current working directory."""
@@ -508,11 +531,11 @@
         # i18n: "relpath" is a keyword
         raise error.ParseError(_("relpath expects one argument"))
 
-    repo = context.resource(mapping, 'ctx').repo()
+    repo = context.resource(mapping, 'repo')
     path = evalstring(context, mapping, args[0])
     return repo.pathto(path)
 
-@templatefunc('revset(query[, formatargs...])')
+@templatefunc('revset(query[, formatargs...])', requires={'repo', 'cache'})
 def revset(context, mapping, args):
     """Execute a revision set query. See
     :hg:`help revset`."""
@@ -521,8 +544,7 @@
         raise error.ParseError(_("revset expects one or more arguments"))
 
     raw = evalstring(context, mapping, args[0])
-    ctx = context.resource(mapping, 'ctx')
-    repo = ctx.repo()
+    repo = context.resource(mapping, 'repo')
 
     def query(expr):
         m = revsetmod.match(repo.ui, expr, lookup=revsetmod.lookupfn(repo))
@@ -574,7 +596,7 @@
             yield sep
         yield argstr
 
-@templatefunc('shortest(node, minlength=4)')
+@templatefunc('shortest(node, minlength=4)', requires={'repo'})
 def shortest(context, mapping, args):
     """Obtain the shortest representation of
     a node."""
@@ -590,7 +612,7 @@
                                 # i18n: "shortest" is a keyword
                                 _("shortest() expects an integer minlength"))
 
-    repo = context.resource(mapping, 'ctx')._repo
+    repo = context.resource(mapping, 'repo')
     if len(hexnode) > 40:
         return hexnode
     elif len(hexnode) == 40:
@@ -601,11 +623,16 @@
     else:
         try:
             node = scmutil.resolvehexnodeidprefix(repo, hexnode)
-        except (error.LookupError, error.WdirUnsupported):
+        except error.WdirUnsupported:
+            node = wdirid
+        except error.LookupError:
             return hexnode
         if not node:
             return hexnode
-    return scmutil.shortesthexnodeidprefix(repo, node, minlength)
+    try:
+        return scmutil.shortesthexnodeidprefix(repo, node, minlength)
+    except error.RepoLookupError:
+        return hexnode
 
 @templatefunc('strip(text[, chars])')
 def strip(context, mapping, args):
--- a/mercurial/templatekw.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templatekw.py	Thu Jul 19 13:55:54 2018 -0400
@@ -14,6 +14,7 @@
 )
 
 from . import (
+    diffutil,
     encoding,
     error,
     hbisect,
@@ -31,41 +32,12 @@
 )
 
 _hybrid = templateutil.hybrid
-_mappable = templateutil.mappable
 hybriddict = templateutil.hybriddict
 hybridlist = templateutil.hybridlist
 compatdict = templateutil.compatdict
 compatlist = templateutil.compatlist
 _showcompatlist = templateutil._showcompatlist
 
-def _showlist(name, values, templ, mapping, plural=None, separator=' '):
-    ui = mapping.get('ui')
-    if ui:
-        ui.deprecwarn("templatekw._showlist() is deprecated, use "
-                      "templateutil._showcompatlist()", '4.6')
-    context = templ  # this is actually a template context, not a templater
-    return _showcompatlist(context, mapping, name, values, plural, separator)
-
-def showdict(name, data, mapping, plural=None, key='key', value='value',
-             fmt=None, separator=' '):
-    ui = mapping.get('ui')
-    if ui:
-        ui.deprecwarn("templatekw.showdict() is deprecated, use "
-                      "templateutil.compatdict()", '4.6')
-    c = [{key: k, value: v} for k, v in data.iteritems()]
-    f = _showlist(name, c, mapping['templ'], mapping, plural, separator)
-    return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
-
-def showlist(name, values, mapping, plural=None, element=None, separator=' '):
-    ui = mapping.get('ui')
-    if ui:
-        ui.deprecwarn("templatekw.showlist() is deprecated, use "
-                      "templateutil.compatlist()", '4.6')
-    if not element:
-        element = name
-    f = _showlist(name, values, mapping['templ'], mapping, plural, separator)
-    return hybridlist(values, name=element, gen=f)
-
 def getlatesttags(context, mapping, pattern=None):
     '''return date, distance and name for the latest tag of rev'''
     repo = context.resource(mapping, 'repo')
@@ -139,7 +111,7 @@
             for i in fl:
                 lr = fl.linkrev(i)
                 renamed = fl.renamed(fl.node(i))
-                rcache[fn][lr] = renamed
+                rcache[fn][lr] = renamed and renamed[0]
                 if lr >= endrev:
                     break
         if rev in rcache[fn]:
@@ -148,7 +120,8 @@
         # If linkrev != rev (i.e. rev not found in rcache) fallback to
         # filectx logic.
         try:
-            return repo[rev][fn].renamed()
+            renamed = repo[rev][fn].renamed()
+            return renamed and renamed[0]
         except error.LookupError:
             return None
 
@@ -268,7 +241,9 @@
 def showdate(context, mapping):
     """Date information. The date when the changeset was committed."""
     ctx = context.resource(mapping, 'ctx')
-    return ctx.date()
+    # the default string format is '<float(unixtime)><tzoffset>' because
+    # python-hglib splits date at decimal separator.
+    return templateutil.date(ctx.date(), showfmt='%d.0%d')
 
 @templatekeyword('desc', requires={'ctx'})
 def showdescription(context, mapping):
@@ -278,16 +253,21 @@
     if isinstance(s, encoding.localstr):
         # try hard to preserve utf-8 bytes
         return encoding.tolocal(encoding.fromlocal(s).strip())
+    elif isinstance(s, encoding.safelocalstr):
+        return encoding.safelocalstr(s.strip())
     else:
         return s.strip()
 
-@templatekeyword('diffstat', requires={'ctx'})
+@templatekeyword('diffstat', requires={'ui', 'ctx'})
 def showdiffstat(context, mapping):
     """String. Statistics of changes with the following format:
     "modified files: +added/-removed lines"
     """
+    ui = context.resource(mapping, 'ui')
     ctx = context.resource(mapping, 'ctx')
-    stats = patch.diffstatdata(util.iterlines(ctx.diff(noprefix=False)))
+    diffopts = diffutil.diffallopts(ui, {'noprefix': False})
+    diff = ctx.diff(opts=diffopts)
+    stats = patch.diffstatdata(util.iterlines(diff))
     maxname, maxtotal, adds, removes, binary = patch.diffstatsum(stats)
     return '%d: +%d/-%d' % (len(stats), adds, removes)
 
@@ -344,7 +324,7 @@
         for fn in ctx.files():
             rename = getrenamed(fn, ctx.rev())
             if rename:
-                copies.append((fn, rename[0]))
+                copies.append((fn, rename))
 
     copies = util.sortdict(copies)
     return compatdict(context, mapping, 'file_copy', copies,
@@ -392,12 +372,19 @@
     return getgraphnode(repo, ctx)
 
 def getgraphnode(repo, ctx):
+    return getgraphnodecurrent(repo, ctx) or getgraphnodesymbol(ctx)
+
+def getgraphnodecurrent(repo, ctx):
     wpnodes = repo.dirstate.parents()
     if wpnodes[1] == nullid:
         wpnodes = wpnodes[:1]
     if ctx.node() in wpnodes:
         return '@'
-    elif ctx.obsolete():
+    else:
+        return ''
+
+def getgraphnodesymbol(ctx):
+    if ctx.obsolete():
         return 'x'
     elif ctx.isunstable():
         return '*'
@@ -481,13 +468,14 @@
     if mnode is None:
         # just avoid crash, we might want to use the 'ff...' hash in future
         return
-    mrev = repo.manifestlog._revlog.rev(mnode)
+    mrev = repo.manifestlog.rev(mnode)
     mhex = hex(mnode)
     mapping = context.overlaymap(mapping, {'rev': mrev, 'node': mhex})
     f = context.process('manifest', mapping)
     # TODO: perhaps 'ctx' should be dropped from mapping because manifest
     # rev and node are completely different from changeset's.
-    return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
+    return templateutil.hybriditem(f, None, f,
+                                   lambda x: {'rev': mrev, 'node': mhex})
 
 @templatekeyword('obsfate', requires={'ui', 'repo', 'ctx'})
 def showobsfate(context, mapping):
@@ -583,7 +571,7 @@
     repo = context.resource(mapping, 'repo')
     ctx = context.resource(mapping, 'ctx')
     predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
-    predecessors = map(hex, predecessors)
+    predecessors = pycompat.maplist(hex, predecessors)
 
     return _hybrid(None, predecessors,
                    lambda x: {'ctx': repo[x]},
--- a/mercurial/templater.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templater.py	Thu Jul 19 13:55:54 2018 -0400
@@ -26,23 +26,23 @@
     values of any printable types, and  will be folded by ``stringify()``
     or ``flatten()``.
 
-    BUG: hgweb overloads this type for mappings (i.e. some hgweb keywords
-    returns a generator of dicts.)
-
 None
     sometimes represents an empty value, which can be stringified to ''.
 
 True, False, int, float
     can be stringified as such.
 
-date tuple
-    a (unixtime, offset) tuple, which produces no meaningful output by itself.
+wrappedbytes, wrappedvalue
+    a wrapper for the above printable types.
+
+date
+    represents a (unixtime, offset) tuple.
 
 hybrid
     represents a list/dict of printable values, which can also be converted
     to mappings by % operator.
 
-mappable
+hybriditem
     represents a scalar printable value, also supports % operator.
 
 mappinggenerator, mappinglist
@@ -253,7 +253,8 @@
     p = parser.parser(elements)
     try:
         while pos < stop:
-            n = min((tmpl.find(c, pos, stop) for c in sepchars),
+            n = min((tmpl.find(c, pos, stop)
+                     for c in pycompat.bytestr(sepchars)),
                     key=lambda n: (n < 0, n))
             if n < 0:
                 yield ('string', unescape(tmpl[pos:stop]), pos)
@@ -596,8 +597,7 @@
     filter uses function to transform value. syntax is
     {key|filter1|filter2|...}.'''
 
-    def __init__(self, loader, filters=None, defaults=None, resources=None,
-                 aliases=()):
+    def __init__(self, loader, filters=None, defaults=None, resources=None):
         self._loader = loader
         if filters is None:
             filters = {}
@@ -609,7 +609,6 @@
             resources = nullresourcemapper()
         self._defaults = defaults
         self._resources = resources
-        self._aliasmap = _aliasrules.buildmap(aliases)
         self._cache = {}  # key: (func, data)
         self._tmplcache = {}  # literal template: (func, data)
 
@@ -664,12 +663,10 @@
     def _load(self, t):
         '''load, parse, and cache a template'''
         if t not in self._cache:
+            x = self._loader(t)
             # put poison to cut recursion while compiling 't'
             self._cache[t] = (_runrecursivesymbol, t)
             try:
-                x = parse(self._loader(t))
-                if self._aliasmap:
-                    x = _aliasrules.expand(self._aliasmap, x)
                 self._cache[t] = compileexp(x, self, methods)
             except: # re-raises
                 del self._cache[t]
@@ -717,8 +714,6 @@
             mapping = extramapping
         return templateutil.flatten(self, mapping, func(self, mapping, data))
 
-engines = {'default': engine}
-
 def stylelist():
     paths = templatepaths()
     if not paths:
@@ -776,13 +771,81 @@
                                        conf.source('templates', key))
             cache[key] = unquotestring(val)
         elif key != '__base__':
-            val = 'default', val
-            if ':' in val[1]:
-                val = val[1].split(':', 1)
-            tmap[key] = val[0], os.path.join(base, val[1])
+            tmap[key] = os.path.join(base, val)
     aliases.extend(conf['templatealias'].items())
     return cache, tmap, aliases
 
+class loader(object):
+    """Load template fragments optionally from a map file"""
+
+    def __init__(self, cache, aliases):
+        if cache is None:
+            cache = {}
+        self.cache = cache.copy()
+        self._map = {}
+        self._aliasmap = _aliasrules.buildmap(aliases)
+
+    def __contains__(self, key):
+        return key in self.cache or key in self._map
+
+    def load(self, t):
+        """Get parsed tree for the given template name. Use a local cache."""
+        if t not in self.cache:
+            try:
+                self.cache[t] = util.readfile(self._map[t])
+            except KeyError as inst:
+                raise templateutil.TemplateNotFound(
+                    _('"%s" not in template map') % inst.args[0])
+            except IOError as inst:
+                reason = (_('template file %s: %s')
+                          % (self._map[t],
+                             stringutil.forcebytestr(inst.args[1])))
+                raise IOError(inst.args[0], encoding.strfromlocal(reason))
+        return self._parse(self.cache[t])
+
+    def _parse(self, tmpl):
+        x = parse(tmpl)
+        if self._aliasmap:
+            x = _aliasrules.expand(self._aliasmap, x)
+        return x
+
+    def _findsymbolsused(self, tree, syms):
+        if not tree:
+            return
+        op = tree[0]
+        if op == 'symbol':
+            s = tree[1]
+            if s in syms[0]:
+                return # avoid recursion: s -> cache[s] -> s
+            syms[0].add(s)
+            if s in self.cache or s in self._map:
+                # s may be a reference for named template
+                self._findsymbolsused(self.load(s), syms)
+            return
+        if op in {'integer', 'string'}:
+            return
+        # '{arg|func}' == '{func(arg)}'
+        if op == '|':
+            syms[1].add(getsymbol(tree[2]))
+            self._findsymbolsused(tree[1], syms)
+            return
+        if op == 'func':
+            syms[1].add(getsymbol(tree[1]))
+            self._findsymbolsused(tree[2], syms)
+            return
+        for x in tree[1:]:
+            self._findsymbolsused(x, syms)
+
+    def symbolsused(self, t):
+        """Look up (keywords, filters/functions) referenced from the name
+        template 't'
+
+        This may load additional templates from the map file.
+        """
+        syms = (set(), set())
+        self._findsymbolsused(self.load(t), syms)
+        return syms
+
 class templater(object):
 
     def __init__(self, filters=None, defaults=None, resources=None,
@@ -800,21 +863,12 @@
         self.cache may be updated later to register additional template
         fragments.
         """
-        if filters is None:
-            filters = {}
-        if defaults is None:
-            defaults = {}
-        if cache is None:
-            cache = {}
-        self.cache = cache.copy()
-        self.map = {}
-        self.filters = templatefilters.filters.copy()
-        self.filters.update(filters)
-        self.defaults = defaults
-        self._resources = resources
-        self._aliases = aliases
-        self.minchunk, self.maxchunk = minchunk, maxchunk
-        self.ecache = {}
+        allfilters = templatefilters.filters.copy()
+        if filters:
+            allfilters.update(filters)
+        self._loader = loader(cache, aliases)
+        self._proc = engine(self._loader.load, allfilters, defaults, resources)
+        self._minchunk, self._maxchunk = minchunk, maxchunk
 
     @classmethod
     def frommapfile(cls, mapfile, filters=None, defaults=None, resources=None,
@@ -822,28 +876,46 @@
         """Create templater from the specified map file"""
         t = cls(filters, defaults, resources, cache, [], minchunk, maxchunk)
         cache, tmap, aliases = _readmapfile(mapfile)
-        t.cache.update(cache)
-        t.map = tmap
-        t._aliases = aliases
+        t._loader.cache.update(cache)
+        t._loader._map = tmap
+        t._loader._aliasmap = _aliasrules.buildmap(aliases)
         return t
 
     def __contains__(self, key):
-        return key in self.cache or key in self.map
+        return key in self._loader
+
+    @property
+    def cache(self):
+        return self._loader.cache
+
+    # for highlight extension to insert one-time 'colorize' filter
+    @property
+    def _filters(self):
+        return self._proc._filters
+
+    @property
+    def defaults(self):
+        return self._proc._defaults
 
     def load(self, t):
-        '''Get the template for the given template name. Use a local cache.'''
-        if t not in self.cache:
-            try:
-                self.cache[t] = util.readfile(self.map[t][1])
-            except KeyError as inst:
-                raise templateutil.TemplateNotFound(
-                    _('"%s" not in template map') % inst.args[0])
-            except IOError as inst:
-                reason = (_('template file %s: %s')
-                          % (self.map[t][1],
-                             stringutil.forcebytestr(inst.args[1])))
-                raise IOError(inst.args[0], encoding.strfromlocal(reason))
-        return self.cache[t]
+        """Get parsed tree for the given template name. Use a local cache."""
+        return self._loader.load(t)
+
+    def symbolsuseddefault(self):
+        """Look up (keywords, filters/functions) referenced from the default
+        unnamed template
+
+        This may load additional templates from the map file.
+        """
+        return self.symbolsused('')
+
+    def symbolsused(self, t):
+        """Look up (keywords, filters/functions) referenced from the name
+        template 't'
+
+        This may load additional templates from the map file.
+        """
+        return self._loader.symbolsused(t)
 
     def renderdefault(self, mapping):
         """Render the default unnamed template and return result as string"""
@@ -856,20 +928,10 @@
     def generate(self, t, mapping):
         """Return a generator that renders the specified named template and
         yields chunks"""
-        ttype = t in self.map and self.map[t][0] or 'default'
-        if ttype not in self.ecache:
-            try:
-                ecls = engines[ttype]
-            except KeyError:
-                raise error.Abort(_('invalid template engine: %s') % ttype)
-            self.ecache[ttype] = ecls(self.load, self.filters, self.defaults,
-                                      self._resources, self._aliases)
-        proc = self.ecache[ttype]
-
-        stream = proc.process(t, mapping)
-        if self.minchunk:
-            stream = util.increasingchunks(stream, min=self.minchunk,
-                                           max=self.maxchunk)
+        stream = self._proc.process(t, mapping)
+        if self._minchunk:
+            stream = util.increasingchunks(stream, min=self._minchunk,
+                                           max=self._maxchunk)
         return stream
 
 def templatepaths():
--- a/mercurial/templates/gitweb/graph.tmpl	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/gitweb/graph.tmpl	Thu Jul 19 13:55:54 2018 -0400
@@ -21,7 +21,7 @@
 <a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a> |
 <a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a> |
 <a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a> |
-<a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a> |
+<a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a>{archives%archiveentry} |
 <a href="{url|urlescape}help{sessionvars%urlparameter}">help</a>
 <br/>
 <a href="{url|urlescape}graph/{symrev}{lessvars%urlparameter}">less</a>
--- a/mercurial/templates/gitweb/manifest.tmpl	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/gitweb/manifest.tmpl	Thu Jul 19 13:55:54 2018 -0400
@@ -30,13 +30,7 @@
 
 <div class="title">{path|escape} {alltags}</div>
 <table cellspacing="0">
-<tr class="parity{upparity}">
-<td style="font-family:monospace">drwxr-xr-x</td>
-<td style="font-family:monospace"></td>
-<td style="font-family:monospace"></td>
-<td><a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
-<td class="link">&nbsp;</td>
-</tr>
+{ifeq(path, up, '', updirentry)}
 {dentries%direntry}
 {fentries%fileentry}
 </table>
--- a/mercurial/templates/gitweb/map	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/gitweb/map	Thu Jul 19 13:55:54 2018 -0400
@@ -59,6 +59,16 @@
 changelogentry = changelogentry.tmpl
 changeset = changeset.tmpl
 manifest = manifest.tmpl
+updirentry = '
+  <tr class="parity{upparity}">
+    <td style="font-family:monospace">drwxr-xr-x</td>
+    <td style="font-family:monospace"></td>
+    <td style="font-family:monospace"></td>
+    <td>
+      <a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a>
+    </td>
+    <td class="link">&nbsp;</td>
+  </tr>'
 direntry = '
   <tr class="parity{parity}">
     <td style="font-family:monospace">drwxr-xr-x</td>
--- a/mercurial/templates/map-cmdline.show	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/map-cmdline.show	Thu Jul 19 13:55:54 2018 -0400
@@ -15,7 +15,11 @@
 # Treat branch and tags specially so we don't display "default" or "tip"
 cset_namespace = '{ifeq(namespace, "branches", names_branches, ifeq(namespace, "tags", names_tags, names_others))}'
 names_branches = '{ifeq(branch, "default", "", " ({label('log.{colorname}', branch)})")}'
-names_tags = '{if(names % "{ifeq(name, 'tip', '', name)}", " ({label('log.{colorname}', join(names % "{ifeq(name, 'tip', '', name)}", ' '))})")}'
+names_tags = '{if(filter_tags(names),
+                  " ({label('log.{colorname}', join(filter_tags(names), ' '))})")}'
 names_others = '{if(names, " ({label('log.{colorname}', join(names, ' '))})")}'
 
 cset_shortdesc = '{label("log.description", desc|firstline)}'
+
+[templatealias]
+filter_tags(names) = filter(names, ifeq(name, 'tip', '', name))
--- a/mercurial/templates/monoblue/graph.tmpl	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/monoblue/graph.tmpl	Thu Jul 19 13:55:54 2018 -0400
@@ -20,6 +20,7 @@
             <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
             <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
             <li><a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">files</a></li>
+            {archives%archiveentry}
             <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
         </ul>
     </div>
--- a/mercurial/templates/monoblue/manifest.tmpl	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/monoblue/manifest.tmpl	Thu Jul 19 13:55:54 2018 -0400
@@ -33,13 +33,7 @@
     <p class="files">{path|escape} {alltags}</p>
 
     <table>
-        <tr class="parity{upparity}">
-            <td>drwxr-xr-x</td>
-            <td></td>
-            <td></td>
-            <td><a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
-            <td class="link">&nbsp;</td>
-        </tr>
+        {ifeq(path, up, '', updirentry)}
         {dentries%direntry}
         {fentries%fileentry}
     </table>
--- a/mercurial/templates/monoblue/map	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/monoblue/map	Thu Jul 19 13:55:54 2018 -0400
@@ -59,6 +59,16 @@
 changelogentry = changelogentry.tmpl
 changeset = changeset.tmpl
 manifest = manifest.tmpl
+updirentry = '
+  <tr class="parity{upparity}">
+    <td>drwxr-xr-x</td>
+    <td></td>
+    <td></td>
+    <td>
+      <a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a>
+    </td>
+    <td class="link">&nbsp;</td>
+  </tr>'
 direntry = '
   <tr class="parity{parity}">
     <td>drwxr-xr-x</td>
--- a/mercurial/templates/paper/graph.tmpl	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/paper/graph.tmpl	Thu Jul 19 13:55:54 2018 -0400
@@ -25,6 +25,9 @@
 <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
 </ul>
 <ul>
+{archives%archiveentry}
+</ul>
+<ul>
  <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
 </ul>
 <div class="atom-logo">
--- a/mercurial/templates/paper/manifest.tmpl	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/paper/manifest.tmpl	Thu Jul 19 13:55:54 2018 -0400
@@ -46,11 +46,7 @@
 </tr>
 </thead>
 <tbody class="stripes2">
-<tr class="fileline">
-  <td class="name"><a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
-  <td class="size"></td>
-  <td class="permissions">drwxr-xr-x</td>
-</tr>
+{ifeq(path, up, '', updirentry)}
 {dentries%direntry}
 {fentries%fileentry}
 </tbody>
--- a/mercurial/templates/paper/map	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/paper/map	Thu Jul 19 13:55:54 2018 -0400
@@ -41,6 +41,15 @@
 navgraph = '{before%navgraphentry}{after%navgraphentry}'
 filenav = '{before%filenaventry}{after%filenaventry}'
 
+updirentry = '
+  <tr class="fileline">
+    <td class="name">
+      <a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a>
+    </td>
+    <td class="size"></td>
+    <td class="permissions">drwxr-xr-x</td>
+  </tr>'
+
 direntry = '
   <tr class="fileline">
     <td class="name">
--- a/mercurial/templates/spartan/manifest.tmpl	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/spartan/manifest.tmpl	Thu Jul 19 13:55:54 2018 -0400
@@ -17,12 +17,7 @@
 <h2><a href="/">Mercurial</a> {pathdef%breadcrumb} / files for changeset <a href="{url|urlescape}rev/{node|short}">{node|short}</a>: {path|escape}</h2>
 
 <table cellpadding="0" cellspacing="0">
-<tr class="parity{upparity}">
-  <td><tt>drwxr-xr-x</tt>&nbsp;
-  <td>&nbsp;
-  <td>&nbsp;
-  <td><a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a>
-</tr>
+{ifeq(path, up, '', updirentry)}
 {dentries%direntry}
 {fentries%fileentry}
 </table>
--- a/mercurial/templates/spartan/map	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/spartan/map	Thu Jul 19 13:55:54 2018 -0400
@@ -25,23 +25,36 @@
 navgraph = '{before%navgraphentry}{after%navgraphentry}'
 filenav = '{before%filenaventry}{after%filenaventry}'
 
+updirentry = '
+  <tr class="parity{upparity}">
+    <td><tt>drwxr-xr-x</tt>&nbsp;</td>
+    <td>&nbsp;</td>
+    <td>&nbsp;</td>
+    <td>
+      <a href="{url|urlescape}file/{symrev}{up|urlescape}{sessionvars%urlparameter}">[up]</a>
+    </td>
+  </tr> '
+
 direntry = '
   <tr class="parity{parity}">
-    <td><tt>drwxr-xr-x</tt>&nbsp;
-    <td>&nbsp;
-    <td>&nbsp;
+    <td><tt>drwxr-xr-x</tt>&nbsp;</td>
+    <td>&nbsp;</td>
+    <td>&nbsp;</td>
     <td>
       <a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">{basename|escape}/</a>
       <a href="{url|urlescape}file/{symrev}{path|urlescape}/{emptydirs|urlescape}{sessionvars%urlparameter}">
         {emptydirs|urlescape}
-      </a>'
+      </a>
+    </td>
+  </tr>'
 
 fileentry = '
   <tr class="parity{parity}">
-    <td><tt>{permissions|permissions}</tt>&nbsp;
-    <td align=right><tt class="date">{date|isodate}</tt>&nbsp;
-    <td align=right><tt>{size}</tt>&nbsp;
-    <td><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a>'
+    <td><tt>{permissions|permissions}</tt>&nbsp;</td>
+    <td align=right><tt class="date">{date|isodate}</tt>&nbsp;</td>
+    <td align=right><tt>{size}</tt>&nbsp;</td>
+    <td><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">{basename|escape}</a></td>
+  </tr>'
 
 filerevision = filerevision.tmpl
 fileannotate = fileannotate.tmpl
--- a/mercurial/templates/static/style-gitweb.css	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/static/style-gitweb.css	Thu Jul 19 13:55:54 2018 -0400
@@ -74,6 +74,7 @@
   background: #ffc;
   border: 1px solid yellow;
   border-radius: 5px;
+  z-index: 15;
 }
 
 #searchform:hover div#hint { display: block; }
--- a/mercurial/templates/static/style-paper.css	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templates/static/style-paper.css	Thu Jul 19 13:55:54 2018 -0400
@@ -90,6 +90,7 @@
   font-size: 70%;
   border: 1px solid yellow;
   border-radius: 5px;
+  z-index: 15;
 }
 
 form.search:hover div#hint { display: block; }
--- a/mercurial/templateutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/templateutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -31,13 +31,46 @@
     """Object requiring extra conversion prior to displaying or processing
     as value
 
-    Use unwrapvalue(), unwrapastype(), or unwraphybrid() to obtain the inner
-    object.
+    Use unwrapvalue() or unwrapastype() to obtain the inner object.
     """
 
     __metaclass__ = abc.ABCMeta
 
     @abc.abstractmethod
+    def contains(self, context, mapping, item):
+        """Test if the specified item is in self
+
+        The item argument may be a wrapped object.
+        """
+
+    @abc.abstractmethod
+    def getmember(self, context, mapping, key):
+        """Return a member item for the specified key
+
+        The key argument may be a wrapped object.
+        A returned object may be either a wrapped object or a pure value
+        depending on the self type.
+        """
+
+    @abc.abstractmethod
+    def getmin(self, context, mapping):
+        """Return the smallest item, which may be either a wrapped or a pure
+        value depending on the self type"""
+
+    @abc.abstractmethod
+    def getmax(self, context, mapping):
+        """Return the largest item, which may be either a wrapped or a pure
+        value depending on the self type"""
+
+    @abc.abstractmethod
+    def filter(self, context, mapping, select):
+        """Return new container of the same type which includes only the
+        selected elements
+
+        select() takes each item as a wrapped object and returns True/False.
+        """
+
+    @abc.abstractmethod
     def itermaps(self, context):
         """Yield each template mapping"""
 
@@ -60,16 +93,153 @@
         """
 
     @abc.abstractmethod
+    def tobool(self, context, mapping):
+        """Return a boolean representation of the inner value"""
+
+    @abc.abstractmethod
     def tovalue(self, context, mapping):
         """Move the inner value object out or create a value representation
 
         A returned value must be serializable by templaterfilters.json().
         """
 
-# stub for representing a date type; may be a real date type that can
-# provide a readable string value
-class date(object):
-    pass
+class mappable(object):
+    """Object which can be converted to a single template mapping"""
+
+    def itermaps(self, context):
+        yield self.tomap(context)
+
+    @abc.abstractmethod
+    def tomap(self, context):
+        """Create a single template mapping representing this"""
+
+class wrappedbytes(wrapped):
+    """Wrapper for byte string"""
+
+    def __init__(self, value):
+        self._value = value
+
+    def contains(self, context, mapping, item):
+        item = stringify(context, mapping, item)
+        return item in self._value
+
+    def getmember(self, context, mapping, key):
+        raise error.ParseError(_('%r is not a dictionary')
+                               % pycompat.bytestr(self._value))
+
+    def getmin(self, context, mapping):
+        return self._getby(context, mapping, min)
+
+    def getmax(self, context, mapping):
+        return self._getby(context, mapping, max)
+
+    def _getby(self, context, mapping, func):
+        if not self._value:
+            raise error.ParseError(_('empty string'))
+        return func(pycompat.iterbytestr(self._value))
+
+    def filter(self, context, mapping, select):
+        raise error.ParseError(_('%r is not filterable')
+                               % pycompat.bytestr(self._value))
+
+    def itermaps(self, context):
+        raise error.ParseError(_('%r is not iterable of mappings')
+                               % pycompat.bytestr(self._value))
+
+    def join(self, context, mapping, sep):
+        return joinitems(pycompat.iterbytestr(self._value), sep)
+
+    def show(self, context, mapping):
+        return self._value
+
+    def tobool(self, context, mapping):
+        return bool(self._value)
+
+    def tovalue(self, context, mapping):
+        return self._value
+
+class wrappedvalue(wrapped):
+    """Generic wrapper for pure non-list/dict/bytes value"""
+
+    def __init__(self, value):
+        self._value = value
+
+    def contains(self, context, mapping, item):
+        raise error.ParseError(_("%r is not iterable") % self._value)
+
+    def getmember(self, context, mapping, key):
+        raise error.ParseError(_('%r is not a dictionary') % self._value)
+
+    def getmin(self, context, mapping):
+        raise error.ParseError(_("%r is not iterable") % self._value)
+
+    def getmax(self, context, mapping):
+        raise error.ParseError(_("%r is not iterable") % self._value)
+
+    def filter(self, context, mapping, select):
+        raise error.ParseError(_("%r is not iterable") % self._value)
+
+    def itermaps(self, context):
+        raise error.ParseError(_('%r is not iterable of mappings')
+                               % self._value)
+
+    def join(self, context, mapping, sep):
+        raise error.ParseError(_('%r is not iterable') % self._value)
+
+    def show(self, context, mapping):
+        if self._value is None:
+            return b''
+        return pycompat.bytestr(self._value)
+
+    def tobool(self, context, mapping):
+        if self._value is None:
+            return False
+        if isinstance(self._value, bool):
+            return self._value
+        # otherwise evaluate as string, which means 0 is True
+        return bool(pycompat.bytestr(self._value))
+
+    def tovalue(self, context, mapping):
+        return self._value
+
+class date(mappable, wrapped):
+    """Wrapper for date tuple"""
+
+    def __init__(self, value, showfmt='%d %d'):
+        # value may be (float, int), but public interface shouldn't support
+        # floating-point timestamp
+        self._unixtime, self._tzoffset = map(int, value)
+        self._showfmt = showfmt
+
+    def contains(self, context, mapping, item):
+        raise error.ParseError(_('date is not iterable'))
+
+    def getmember(self, context, mapping, key):
+        raise error.ParseError(_('date is not a dictionary'))
+
+    def getmin(self, context, mapping):
+        raise error.ParseError(_('date is not iterable'))
+
+    def getmax(self, context, mapping):
+        raise error.ParseError(_('date is not iterable'))
+
+    def filter(self, context, mapping, select):
+        raise error.ParseError(_('date is not iterable'))
+
+    def join(self, context, mapping, sep):
+        raise error.ParseError(_("date is not iterable"))
+
+    def show(self, context, mapping):
+        return self._showfmt % (self._unixtime, self._tzoffset)
+
+    def tomap(self, context):
+        return {'unixtime': self._unixtime, 'tzoffset': self._tzoffset}
+
+    def tobool(self, context, mapping):
+        return True
+
+    def tovalue(self, context, mapping):
+        return (self._unixtime, self._tzoffset)
 
 class hybrid(wrapped):
     """Wrapper for list or dict to support legacy template
@@ -88,7 +258,46 @@
         self._values = values
         self._makemap = makemap
         self._joinfmt = joinfmt
-        self.keytype = keytype  # hint for 'x in y' where type(x) is unresolved
+        self._keytype = keytype  # hint for 'x in y' where type(x) is unresolved
+
+    def contains(self, context, mapping, item):
+        item = unwrapastype(context, mapping, item, self._keytype)
+        return item in self._values
+
+    def getmember(self, context, mapping, key):
+        # TODO: maybe split hybrid list/dict types?
+        if not util.safehasattr(self._values, 'get'):
+            raise error.ParseError(_('not a dictionary'))
+        key = unwrapastype(context, mapping, key, self._keytype)
+        return self._wrapvalue(key, self._values.get(key))
+
+    def getmin(self, context, mapping):
+        return self._getby(context, mapping, min)
+
+    def getmax(self, context, mapping):
+        return self._getby(context, mapping, max)
+
+    def _getby(self, context, mapping, func):
+        if not self._values:
+            raise error.ParseError(_('empty sequence'))
+        val = func(self._values)
+        return self._wrapvalue(val, val)
+
+    def _wrapvalue(self, key, val):
+        if val is None:
+            return
+        if util.safehasattr(val, '_makemap'):
+            # a nested hybrid list/dict, which has its own way of map operation
+            return val
+        return hybriditem(None, key, val, self._makemap)
+
+    def filter(self, context, mapping, select):
+        if util.safehasattr(self._values, 'get'):
+            values = {k: v for k, v in self._values.iteritems()
+                      if select(self._wrapvalue(k, v))}
+        else:
+            values = [v for v in self._values if select(self._wrapvalue(v, v))]
+        return hybrid(None, values, self._makemap, self._joinfmt, self._keytype)
 
     def itermaps(self, context):
         makemap = self._makemap
@@ -108,34 +317,24 @@
             return gen()
         return gen
 
-    def tovalue(self, context, mapping):
-        # TODO: return self._values and get rid of proxy methods
-        return self
+    def tobool(self, context, mapping):
+        return bool(self._values)
 
-    def __contains__(self, x):
-        return x in self._values
-    def __getitem__(self, key):
-        return self._values[key]
-    def __len__(self):
-        return len(self._values)
-    def __iter__(self):
-        return iter(self._values)
-    def __getattr__(self, name):
-        if name not in (r'get', r'items', r'iteritems', r'iterkeys',
-                        r'itervalues', r'keys', r'values'):
-            raise AttributeError(name)
-        return getattr(self._values, name)
+    def tovalue(self, context, mapping):
+        # TODO: make it non-recursive for trivial lists/dicts
+        xs = self._values
+        if util.safehasattr(xs, 'get'):
+            return {k: unwrapvalue(context, mapping, v)
+                    for k, v in xs.iteritems()}
+        return [unwrapvalue(context, mapping, x) for x in xs]
 
-class mappable(wrapped):
+class hybriditem(mappable, wrapped):
     """Wrapper for non-list/dict object to support map operation
 
     This class allows us to handle both:
     - "{manifest}"
     - "{manifest % '{rev}:{node}'}"
     - "{manifest.rev}"
-
-    Unlike a hybrid, this does not simulate the behavior of the underling
-    value.
     """
 
     def __init__(self, gen, key, value, makemap):
@@ -144,19 +343,32 @@
         self._value = value  # may be generator of strings
         self._makemap = makemap
 
-    def tomap(self):
+    def tomap(self, context):
         return self._makemap(self._key)
 
-    def itermaps(self, context):
-        yield self.tomap()
+    def contains(self, context, mapping, item):
+        w = makewrapped(context, mapping, self._value)
+        return w.contains(context, mapping, item)
+
+    def getmember(self, context, mapping, key):
+        w = makewrapped(context, mapping, self._value)
+        return w.getmember(context, mapping, key)
+
+    def getmin(self, context, mapping):
+        w = makewrapped(context, mapping, self._value)
+        return w.getmin(context, mapping)
+
+    def getmax(self, context, mapping):
+        w = makewrapped(context, mapping, self._value)
+        return w.getmax(context, mapping)
+
+    def filter(self, context, mapping, select):
+        w = makewrapped(context, mapping, self._value)
+        return w.filter(context, mapping, select)
 
     def join(self, context, mapping, sep):
-        # TODO: just copies the old behavior where a value was a generator
-        # yielding one item, but reconsider about it. join() over a string
-        # has no consistent result because a string may be a bytes, or a
-        # generator yielding an item, or a generator yielding multiple items.
-        # Preserving all of the current behaviors wouldn't make any sense.
-        return self.show(context, mapping)
+        w = makewrapped(context, mapping, self._value)
+        return w.join(context, mapping, sep)
 
     def show(self, context, mapping):
         # TODO: switch gen to (context, mapping) API?
@@ -167,6 +379,10 @@
             return gen()
         return gen
 
+    def tobool(self, context, mapping):
+        w = makewrapped(context, mapping, self._value)
+        return w.tobool(context, mapping)
+
     def tovalue(self, context, mapping):
         return _unthunk(context, mapping, self._value)
 
@@ -186,6 +402,22 @@
         self._tmpl = tmpl
         self._defaultsep = sep
 
+    def contains(self, context, mapping, item):
+        raise error.ParseError(_('not comparable'))
+
+    def getmember(self, context, mapping, key):
+        raise error.ParseError(_('not a dictionary'))
+
+    def getmin(self, context, mapping):
+        raise error.ParseError(_('not comparable'))
+
+    def getmax(self, context, mapping):
+        raise error.ParseError(_('not comparable'))
+
+    def filter(self, context, mapping, select):
+        # implement if necessary; we'll need a wrapped type for a mapping dict
+        raise error.ParseError(_('not filterable without template'))
+
     def join(self, context, mapping, sep):
         mapsiter = _iteroverlaymaps(context, mapping, self.itermaps(context))
         if self._name:
@@ -224,6 +456,9 @@
     def itermaps(self, context):
         return self._make(context, *self._args)
 
+    def tobool(self, context, mapping):
+        return _nonempty(self.itermaps(context))
+
 class mappinglist(_mappingsequence):
     """Wrapper for list of template mappings"""
 
@@ -234,6 +469,9 @@
     def itermaps(self, context):
         return iter(self._mappings)
 
+    def tobool(self, context, mapping):
+        return bool(self._mappings)
+
 class mappedgenerator(wrapped):
     """Wrapper for generator of strings which acts as a list
 
@@ -246,9 +484,39 @@
         self._make = make
         self._args = args
 
+    def contains(self, context, mapping, item):
+        item = stringify(context, mapping, item)
+        return item in self.tovalue(context, mapping)
+
     def _gen(self, context):
         return self._make(context, *self._args)
 
+    def getmember(self, context, mapping, key):
+        raise error.ParseError(_('not a dictionary'))
+
+    def getmin(self, context, mapping):
+        return self._getby(context, mapping, min)
+
+    def getmax(self, context, mapping):
+        return self._getby(context, mapping, max)
+
+    def _getby(self, context, mapping, func):
+        xs = self.tovalue(context, mapping)
+        if not xs:
+            raise error.ParseError(_('empty sequence'))
+        return func(xs)
+
+    @staticmethod
+    def _filteredgen(context, mapping, make, args, select):
+        for x in make(context, *args):
+            s = stringify(context, mapping, x)
+            if select(wrappedbytes(s)):
+                yield s
+
+    def filter(self, context, mapping, select):
+        args = (mapping, self._make, self._args, select)
+        return mappedgenerator(self._filteredgen, args)
+
     def itermaps(self, context):
         raise error.ParseError(_('list of strings is not mappable'))
 
@@ -258,6 +526,9 @@
     def show(self, context, mapping):
         return self.join(context, mapping, '')
 
+    def tobool(self, context, mapping):
+        return _nonempty(self._gen(context))
+
     def tovalue(self, context, mapping):
         return [stringify(context, mapping, x) for x in self._gen(context)]
 
@@ -278,33 +549,6 @@
         prefmt = pycompat.bytestr
     return hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % prefmt(x))
 
-def unwraphybrid(context, mapping, thing):
-    """Return an object which can be stringified possibly by using a legacy
-    template"""
-    if not isinstance(thing, wrapped):
-        return thing
-    return thing.show(context, mapping)
-
-def unwrapvalue(context, mapping, thing):
-    """Move the inner value object out of the wrapper"""
-    if not isinstance(thing, wrapped):
-        return thing
-    return thing.tovalue(context, mapping)
-
-def wraphybridvalue(container, key, value):
-    """Wrap an element of hybrid container to be mappable
-
-    The key is passed to the makemap function of the given container, which
-    should be an item generated by iter(container).
-    """
-    makemap = getattr(container, '_makemap', None)
-    if makemap is None:
-        return value
-    if util.safehasattr(value, '_makemap'):
-        # a nested hybrid list/dict, which has its own way of map operation
-        return value
-    return mappable(None, key, value, makemap)
-
 def compatdict(context, mapping, name, data, key='key', value='value',
                fmt=None, plural=None, separator=' '):
     """Wrap data like hybriddict(), but also supports old-style list template
@@ -398,7 +642,8 @@
 
 def flatten(context, mapping, thing):
     """Yield a single stream from a possibly nested set of iterators"""
-    thing = unwraphybrid(context, mapping, thing)
+    if isinstance(thing, wrapped):
+        thing = thing.show(context, mapping)
     if isinstance(thing, bytes):
         yield thing
     elif isinstance(thing, str):
@@ -412,7 +657,8 @@
         yield pycompat.bytestr(thing)
     else:
         for i in thing:
-            i = unwraphybrid(context, mapping, i)
+            if isinstance(i, wrapped):
+                i = i.show(context, mapping)
             if isinstance(i, bytes):
                 yield i
             elif i is None:
@@ -441,6 +687,13 @@
         else:
             return None
 
+def _nonempty(xiter):
+    try:
+        next(xiter)
+        return True
+    except StopIteration:
+        return False
+
 def _unthunk(context, mapping, thing):
     """Evaluate a lazy byte string into value"""
     if not isinstance(thing, types.GeneratorType):
@@ -453,15 +706,28 @@
     func, data = arg
     return func(context, mapping, data)
 
+def evalwrapped(context, mapping, arg):
+    """Evaluate given argument to wrapped object"""
+    thing = evalrawexp(context, mapping, arg)
+    return makewrapped(context, mapping, thing)
+
+def makewrapped(context, mapping, thing):
+    """Lift object to a wrapped type"""
+    if isinstance(thing, wrapped):
+        return thing
+    thing = _unthunk(context, mapping, thing)
+    if isinstance(thing, bytes):
+        return wrappedbytes(thing)
+    return wrappedvalue(thing)
+
 def evalfuncarg(context, mapping, arg):
     """Evaluate given argument as value type"""
-    return _unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
+    return unwrapvalue(context, mapping, evalrawexp(context, mapping, arg))
 
-# TODO: unify this with unwrapvalue() once the bug of templatefunc.join()
-# is fixed. we can't do that right now because join() has to take a generator
-# of byte strings as it is, not a lazy byte string.
-def _unwrapvalue(context, mapping, thing):
-    thing = unwrapvalue(context, mapping, thing)
+def unwrapvalue(context, mapping, thing):
+    """Move the inner value object out of the wrapper"""
+    if isinstance(thing, wrapped):
+        return thing.tovalue(context, mapping)
     # evalrawexp() may return string, generator of strings or arbitrary object
     # such as date tuple, but filter does not want generator.
     return _unthunk(context, mapping, thing)
@@ -476,12 +742,7 @@
             thing = stringutil.parsebool(data)
     else:
         thing = func(context, mapping, data)
-    thing = unwrapvalue(context, mapping, thing)
-    if isinstance(thing, bool):
-        return thing
-    # other objects are evaluated as strings, which means 0 is True, but
-    # empty dict/list should be False as they are expected to be ''
-    return bool(stringify(context, mapping, thing))
+    return makewrapped(context, mapping, thing).tobool(context, mapping)
 
 def evaldate(context, mapping, arg, err=None):
     """Evaluate given argument as a date tuple or a date string; returns
@@ -490,7 +751,10 @@
     return unwrapdate(context, mapping, thing, err)
 
 def unwrapdate(context, mapping, thing, err=None):
-    thing = _unwrapvalue(context, mapping, thing)
+    if isinstance(thing, date):
+        return thing.tovalue(context, mapping)
+    # TODO: update hgweb to not return bare tuple; then just stringify 'thing'
+    thing = unwrapvalue(context, mapping, thing)
     try:
         return dateutil.parsedate(thing)
     except AttributeError:
@@ -505,7 +769,7 @@
     return unwrapinteger(context, mapping, thing, err)
 
 def unwrapinteger(context, mapping, thing, err=None):
-    thing = _unwrapvalue(context, mapping, thing)
+    thing = unwrapvalue(context, mapping, thing)
     try:
         return int(thing)
     except (TypeError, ValueError):
@@ -525,7 +789,7 @@
     return stringify(context, mapping, thing)
 
 _unwrapfuncbytype = {
-    None: _unwrapvalue,
+    None: unwrapvalue,
     bytes: stringify,
     date: unwrapdate,
     int: unwrapinteger,
@@ -601,20 +865,6 @@
     return (_("template filter '%s' is not compatible with keyword '%s'")
             % (fn, sym))
 
-def _checkeditermaps(darg, d):
-    try:
-        for v in d:
-            if not isinstance(v, dict):
-                raise TypeError
-            yield v
-    except TypeError:
-        sym = findsymbolicname(darg)
-        if sym:
-            raise error.ParseError(_("keyword '%s' is not iterable of mappings")
-                                   % sym)
-        else:
-            raise error.ParseError(_("%r is not iterable of mappings") % d)
-
 def _iteroverlaymaps(context, origmapping, newmappings):
     """Generate combined mappings from the original mapping and an iterable
     of partial mappings to override the original"""
@@ -623,38 +873,37 @@
         lm['index'] = i
         yield lm
 
-def _applymap(context, mapping, diter, targ):
+def _applymap(context, mapping, d, darg, targ):
+    try:
+        diter = d.itermaps(context)
+    except error.ParseError as err:
+        sym = findsymbolicname(darg)
+        if not sym:
+            raise
+        hint = _("keyword '%s' does not support map operation") % sym
+        raise error.ParseError(bytes(err), hint=hint)
     for lm in _iteroverlaymaps(context, mapping, diter):
         yield evalrawexp(context, lm, targ)
 
 def runmap(context, mapping, data):
     darg, targ = data
-    d = evalrawexp(context, mapping, darg)
-    # TODO: a generator should be rejected because it is a thunk of lazy
-    # string, but we can't because hgweb abuses generator as a keyword
-    # that returns a list of dicts.
-    # TODO: drop _checkeditermaps() and pass 'd' to mappedgenerator so it
-    # can be restarted.
-    if isinstance(d, wrapped):
-        diter = d.itermaps(context)
-    else:
-        diter = _checkeditermaps(darg, d)
-    return mappedgenerator(_applymap, args=(mapping, diter, targ))
+    d = evalwrapped(context, mapping, darg)
+    return mappedgenerator(_applymap, args=(mapping, d, darg, targ))
 
 def runmember(context, mapping, data):
     darg, memb = data
-    d = evalrawexp(context, mapping, darg)
-    if util.safehasattr(d, 'tomap'):
-        lm = context.overlaymap(mapping, d.tomap())
+    d = evalwrapped(context, mapping, darg)
+    if isinstance(d, mappable):
+        lm = context.overlaymap(mapping, d.tomap(context))
         return runsymbol(context, lm, memb)
-    if util.safehasattr(d, 'get'):
-        return getdictitem(d, memb)
-
-    sym = findsymbolicname(darg)
-    if sym:
-        raise error.ParseError(_("keyword '%s' has no member") % sym)
-    else:
-        raise error.ParseError(_("%r has no member") % pycompat.bytestr(d))
+    try:
+        return d.getmember(context, mapping, memb)
+    except error.ParseError as err:
+        sym = findsymbolicname(darg)
+        if not sym:
+            raise
+        hint = _("keyword '%s' does not support member operation") % sym
+        raise error.ParseError(bytes(err), hint=hint)
 
 def runnegate(context, mapping, data):
     data = evalinteger(context, mapping, data,
@@ -672,12 +921,6 @@
     except ZeroDivisionError:
         raise error.Abort(_('division by zero is not defined'))
 
-def getdictitem(dictarg, key):
-    val = dictarg.get(key)
-    if val is None:
-        return
-    return wraphybridvalue(dictarg, key, val)
-
 def joinitems(itemiter, sep):
     """Join items with the separator; Returns generator of bytes"""
     first = True
--- a/mercurial/treediscovery.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/treediscovery.py	Thu Jul 19 13:55:54 2018 -0400
@@ -61,6 +61,7 @@
 
     req = set(unknown)
     reqcnt = 0
+    progress = repo.ui.makeprogress(_('searching'), unit=_('queries'))
 
     # search through remote branches
     # a 'branch' here is a linear segment of history, with four parts:
@@ -107,7 +108,7 @@
 
         if r:
             reqcnt += 1
-            repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
+            progress.increment()
             repo.ui.debug("request %d: %s\n" %
                         (reqcnt, " ".join(map(short, r))))
             for p in xrange(0, len(r), 10):
@@ -125,7 +126,7 @@
     while search:
         newsearch = []
         reqcnt += 1
-        repo.ui.progress(_('searching'), reqcnt, unit=_('queries'))
+        progress.increment()
 
         with remote.commandexecutor() as e:
             between = e.callcommand('between', {'pairs': search}).result()
@@ -166,7 +167,7 @@
     repo.ui.debug("found new changesets starting at " +
                  " ".join([short(f) for f in fetch]) + "\n")
 
-    repo.ui.progress(_('searching'), None)
+    progress.complete()
     repo.ui.debug("%d total queries\n" % reqcnt)
 
     return base, list(fetch), heads
--- a/mercurial/ui.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/ui.py	Thu Jul 19 13:55:54 2018 -0400
@@ -18,7 +18,6 @@
 import socket
 import subprocess
 import sys
-import tempfile
 import traceback
 
 from .i18n import _
@@ -60,18 +59,21 @@
 interface = curses
 
 [commands]
+# Grep working directory by default.
+grep.all-files = True
 # Make `hg status` emit cwd-relative paths by default.
 status.relative = yes
 # Refuse to perform an `hg update` that would cause a file content merge
 update.check = noconflict
 # Show conflicts information in `hg status`
 status.verbose = True
-# Skip the bisect state in conflicts information in `hg status`
-status.skipstates = bisect
+# Collapse entire directories that contain only unknown files
+status.terse = u
 
 [diff]
 git = 1
 showfunc = 1
+word-diff = 1
 """
 
 samplehgrcs = {
@@ -157,10 +159,10 @@
 }
 
 def _maybestrurl(maybebytes):
-    return util.rapply(pycompat.strurl, maybebytes)
+    return pycompat.rapply(pycompat.strurl, maybebytes)
 
 def _maybebytesurl(maybestr):
-    return util.rapply(pycompat.bytesurl, maybestr)
+    return pycompat.rapply(pycompat.bytesurl, maybestr)
 
 class httppasswordmgrdbproxy(object):
     """Delays loading urllib2 until it's needed."""
@@ -225,6 +227,7 @@
         self._colormode = None
         self._terminfoparams = {}
         self._styles = {}
+        self._uninterruptible = False
 
         if src:
             self.fout = src.fout
@@ -335,6 +338,37 @@
             self._blockedtimes[key + '_blocked'] += \
                 (util.timer() - starttime) * 1000
 
+    @contextlib.contextmanager
+    def uninterruptable(self):
+        """Mark an operation as unsafe.
+
+        Most operations on a repository are safe to interrupt, but a
+        few are risky (for example repair.strip). This context manager
+        lets you advise Mercurial that something risky is happening so
+        that control-C etc can be blocked if desired.
+        """
+        enabled = self.configbool('experimental', 'nointerrupt')
+        if (enabled and
+            self.configbool('experimental', 'nointerrupt-interactiveonly')):
+            enabled = self.interactive()
+        if self._uninterruptible or not enabled:
+            # if nointerrupt support is turned off, the process isn't
+            # interactive, or we're already in an uninterruptable
+            # block, do nothing.
+            yield
+            return
+        def warn():
+            self.warn(_("shutting down cleanly\n"))
+            self.warn(
+                _("press ^C again to terminate immediately (dangerous)\n"))
+            return True
+        with procutil.uninterruptable(warn):
+            try:
+                self._uninterruptible = True
+                yield
+            finally:
+                self._uninterruptible = False
+
     def formatter(self, topic, opts):
         return formatter.formatter(self, self, topic, opts)
 
@@ -1180,7 +1214,7 @@
                 "Feature %s does not handle all default interfaces" %
                 feature)
 
-        if self.plain():
+        if self.plain() or encoding.environ.get('TERM') == 'dumb':
             return "text"
 
         # Default interface for all the features
@@ -1446,7 +1480,7 @@
         rdir = None
         if self.configbool('experimental', 'editortmpinhg'):
             rdir = repopath
-        (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
+        (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
                                       suffix=suffix,
                                       dir=rdir)
         try:
@@ -1597,6 +1631,10 @@
         else:
             self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
 
+    def makeprogress(self, topic, unit="", total=None):
+        '''exists only so low-level modules won't need to import scmutil'''
+        return scmutil.progress(self, topic, unit, total)
+
     def log(self, service, *msg, **opts):
         '''hook for logging facility extensions
 
--- a/mercurial/unionrepo.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/unionrepo.py	Thu Jul 19 13:55:54 2018 -0400
@@ -49,7 +49,7 @@
         for rev2 in self.revlog2:
             rev = self.revlog2.index[rev2]
             # rev numbers - in revlog2, very different from self.rev
-            _start, _csize, _rsize, base, linkrev, p1rev, p2rev, node = rev
+            _start, _csize, rsize, base, linkrev, p1rev, p2rev, node = rev
             flags = _start & 0xFFFF
 
             if linkmapper is None: # link is to same revlog
@@ -69,7 +69,9 @@
             p1node = self.revlog2.node(p1rev)
             p2node = self.revlog2.node(p2rev)
 
-            e = (flags, None, None, base,
+            # TODO: it's probably wrong to set compressed length to None, but
+            # I have no idea if csize is valid in the base revlog context.
+            e = (flags, None, rsize, base,
                  link, self.rev(p1node), self.rev(p2node), node)
             self.index.insert(-1, e)
             self.nodemap[node] = n
--- a/mercurial/upgrade.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/upgrade.py	Thu Jul 19 13:55:54 2018 -0400
@@ -8,7 +8,6 @@
 from __future__ import absolute_import
 
 import stat
-import tempfile
 
 from .i18n import _
 from . import (
@@ -18,6 +17,7 @@
     hg,
     localrepo,
     manifest,
+    pycompat,
     revlog,
     scmutil,
     util,
@@ -61,7 +61,9 @@
     the dropped requirement must appear in the returned set for the upgrade
     to be allowed.
     """
-    return set()
+    return {
+        localrepo.SPARSEREVLOG_REQUIREMENT,
+    }
 
 def supporteddestrequirements(repo):
     """Obtain requirements that upgrade supports in the destination.
@@ -77,6 +79,7 @@
         'generaldelta',
         'revlogv1',
         'store',
+        localrepo.SPARSEREVLOG_REQUIREMENT,
     }
 
 def allowednewrequirements(repo):
@@ -93,6 +96,7 @@
         'dotencode',
         'fncache',
         'generaldelta',
+        localrepo.SPARSEREVLOG_REQUIREMENT,
     }
 
 def preservedrequirements(repo):
@@ -259,6 +263,28 @@
                        'faster')
 
 @registerformatvariant
+class sparserevlog(requirementformatvariant):
+    name = 'sparserevlog'
+
+    _requirement = localrepo.SPARSEREVLOG_REQUIREMENT
+
+    default = False
+
+    description = _('in order to limit disk reading and memory usage on older '
+                    'version, the span of a delta chain from its root to its '
+                    'end is limited, whatever the relevant data in this span. '
+                    'This can severly limit Mercurial ability to build good '
+                    'chain of delta resulting is much more storage space being '
+                    'taken and limit reusability of on disk delta during '
+                    'exchange.'
+                   )
+
+    upgrademessage = _('Revlog supports delta chain with more unused data '
+                       'between payload. These gaps will be skipped at read '
+                       'time. This allows for better delta chains, making a '
+                       'better compression and faster exchange with server.')
+
+@registerformatvariant
 class removecldeltachain(formatvariant):
     name = 'plain-cl-delta'
 
@@ -429,7 +455,7 @@
         #reverse of "/".join(("data", path + ".i"))
         return filelog.filelog(repo.svfs, path[5:-2])
 
-def _copyrevlogs(ui, srcrepo, dstrepo, tr, deltareuse, aggressivemergedeltas):
+def _copyrevlogs(ui, srcrepo, dstrepo, tr, deltareuse, deltabothparents):
     """Copy revlogs between 2 repos."""
     revcount = 0
     srcsize = 0
@@ -498,10 +524,9 @@
              (util.bytecount(srcsize), util.bytecount(srcrawsize))))
 
     # Used to keep track of progress.
-    progress = []
+    progress = None
     def oncopiedrevision(rl, rev, node):
-        progress[1] += 1
-        srcrepo.ui.progress(progress[0], progress[1], total=progress[2])
+        progress.increment()
 
     # Do the actual copying.
     # FUTURE this operation can be farmed off to worker processes.
@@ -523,7 +548,8 @@
                      (crevcount, util.bytecount(csrcsize),
                       util.bytecount(crawsize)))
             seen.add('c')
-            progress[:] = [_('changelog revisions'), 0, crevcount]
+            progress = srcrepo.ui.makeprogress(_('changelog revisions'),
+                                               total=crevcount)
         elif isinstance(oldrl, manifest.manifestrevlog) and 'm' not in seen:
             ui.write(_('finished migrating %d filelog revisions across %d '
                        'filelogs; change in size: %s\n') %
@@ -534,21 +560,26 @@
                      (mcount, mrevcount, util.bytecount(msrcsize),
                       util.bytecount(mrawsize)))
             seen.add('m')
-            progress[:] = [_('manifest revisions'), 0, mrevcount]
+            if progress:
+                progress.complete()
+            progress = srcrepo.ui.makeprogress(_('manifest revisions'),
+                                               total=mrevcount)
         elif 'f' not in seen:
             ui.write(_('migrating %d filelogs containing %d revisions '
                        '(%s in store; %s tracked data)\n') %
                      (fcount, frevcount, util.bytecount(fsrcsize),
                       util.bytecount(frawsize)))
             seen.add('f')
-            progress[:] = [_('file revisions'), 0, frevcount]
+            if progress:
+                progress.complete()
+            progress = srcrepo.ui.makeprogress(_('file revisions'),
+                                               total=frevcount)
 
-        ui.progress(progress[0], progress[1], total=progress[2])
 
         ui.note(_('cloning %d revisions from %s\n') % (len(oldrl), unencoded))
         oldrl.clone(tr, newrl, addrevisioncb=oncopiedrevision,
                     deltareuse=deltareuse,
-                    aggressivemergedeltas=aggressivemergedeltas)
+                    deltabothparents=deltabothparents)
 
         datasize = 0
         idx = newrl.index
@@ -564,7 +595,7 @@
         else:
             fdstsize += datasize
 
-    ui.progress(progress[0], None)
+    progress.complete()
 
     ui.write(_('finished migrating %d changelog revisions; change in size: '
                '%s\n') % (crevcount, util.bytecount(cdstsize - csrcsize)))
@@ -657,7 +688,7 @@
 
     ui.write(_('data fully migrated to temporary repository\n'))
 
-    backuppath = tempfile.mkdtemp(prefix='upgradebackup.', dir=srcrepo.path)
+    backuppath = pycompat.mkdtemp(prefix='upgradebackup.', dir=srcrepo.path)
     backupvfs = vfsmod.vfs(backuppath)
 
     # Make a backup of requires file first, as it is the first to be modified.
@@ -842,7 +873,7 @@
         # data. There are less heavyweight ways to do this, but it is easier
         # to create a new repo object than to instantiate all the components
         # (like the store) separately.
-        tmppath = tempfile.mkdtemp(prefix='upgrade.', dir=repo.path)
+        tmppath = pycompat.mkdtemp(prefix='upgrade.', dir=repo.path)
         backuppath = None
         try:
             ui.write(_('creating temporary repository to stage migrated '
--- a/mercurial/util.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/util.py	Thu Jul 19 13:55:54 2018 -0400
@@ -31,7 +31,6 @@
 import socket
 import stat
 import sys
-import tempfile
 import time
 import traceback
 import warnings
@@ -47,7 +46,6 @@
     urllibcompat,
 )
 from .utils import (
-    dateutil,
     procutil,
     stringutil,
 )
@@ -60,10 +58,8 @@
 b85encode = base85.b85encode
 
 cookielib = pycompat.cookielib
-empty = pycompat.empty
 httplib = pycompat.httplib
 pickle = pycompat.pickle
-queue = pycompat.queue
 safehasattr = pycompat.safehasattr
 socketserver = pycompat.socketserver
 bytesio = pycompat.bytesio
@@ -135,39 +131,6 @@
 
 _notset = object()
 
-def _rapply(f, xs):
-    if xs is None:
-        # assume None means non-value of optional data
-        return xs
-    if isinstance(xs, (list, set, tuple)):
-        return type(xs)(_rapply(f, x) for x in xs)
-    if isinstance(xs, dict):
-        return type(xs)((_rapply(f, k), _rapply(f, v)) for k, v in xs.items())
-    return f(xs)
-
-def rapply(f, xs):
-    """Apply function recursively to every item preserving the data structure
-
-    >>> def f(x):
-    ...     return 'f(%s)' % x
-    >>> rapply(f, None) is None
-    True
-    >>> rapply(f, 'a')
-    'f(a)'
-    >>> rapply(f, {'a'}) == {'f(a)'}
-    True
-    >>> rapply(f, ['a', 'b', None, {'c': 'd'}, []])
-    ['f(a)', 'f(b)', None, {'f(c)': 'f(d)'}, []]
-
-    >>> xs = [object()]
-    >>> rapply(pycompat.identity, xs) is xs
-    True
-    """
-    if f is pycompat.identity:
-        # fast path mainly for py2
-        return xs
-    return _rapply(f, xs)
-
 def bitsfrom(container):
     bits = 0
     for bit in container:
@@ -359,6 +322,11 @@
             self._fillbuffer()
         return self._frombuffer(size)
 
+    def unbufferedread(self, size):
+        if not self._eof and self._lenbuf == 0:
+            self._fillbuffer(max(size, _chunksize))
+        return self._frombuffer(min(self._lenbuf, size))
+
     def readline(self, *args, **kwargs):
         if 1 < len(self._buffer):
             # this should not happen because both read and readline end with a
@@ -400,9 +368,9 @@
             self._lenbuf = 0
         return data
 
-    def _fillbuffer(self):
+    def _fillbuffer(self, size=_chunksize):
         """read data to the buffer"""
-        data = os.read(self._input.fileno(), _chunksize)
+        data = os.read(self._input.fileno(), size)
         if not data:
             self._eof = True
         else:
@@ -786,6 +754,13 @@
         if res is None:
             res = ''
 
+        if size == -1 and res == '':
+            # Suppress pointless read(-1) calls that return
+            # nothing. These happen _a lot_ on Python 3, and there
+            # doesn't seem to be a better workaround to have matching
+            # Python 2 and 3 behavior. :(
+            return
+
         if self.logdataapis:
             self.fh.write('%s> read(%d) -> %d' % (self.name, size, len(res)))
 
@@ -1628,31 +1603,30 @@
         except shutil.Error as inst:
             raise error.Abort(str(inst))
 
-def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
+def copyfiles(src, dst, hardlink=None, progress=None):
     """Copy a directory tree using hardlinks if possible."""
     num = 0
 
-    gettopic = lambda: hardlink and _('linking') or _('copying')
+    def settopic():
+        if progress:
+            progress.topic = _('linking') if hardlink else _('copying')
 
     if os.path.isdir(src):
         if hardlink is None:
             hardlink = (os.stat(src).st_dev ==
                         os.stat(os.path.dirname(dst)).st_dev)
-        topic = gettopic()
+        settopic()
         os.mkdir(dst)
         for name, kind in listdir(src):
             srcname = os.path.join(src, name)
             dstname = os.path.join(dst, name)
-            def nprog(t, pos):
-                if pos is not None:
-                    return progress(t, pos + num)
-            hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
+            hardlink, n = copyfiles(srcname, dstname, hardlink, progress)
             num += n
     else:
         if hardlink is None:
             hardlink = (os.stat(os.path.dirname(src)).st_dev ==
                         os.stat(os.path.dirname(dst)).st_dev)
-        topic = gettopic()
+        settopic()
 
         if hardlink:
             try:
@@ -1663,8 +1637,8 @@
         else:
             shutil.copy(src, dst)
         num += 1
-        progress(topic, num)
-    progress(topic, None)
+        if progress:
+            progress.increment()
 
     return hardlink, num
 
@@ -1896,7 +1870,7 @@
     # work around issue2543 (or testfile may get lost on Samba shares)
     f1, f2, fp = None, None, None
     try:
-        fd, f1 = tempfile.mkstemp(prefix='.%s-' % os.path.basename(testfile),
+        fd, f1 = pycompat.mkstemp(prefix='.%s-' % os.path.basename(testfile),
                                   suffix='1~', dir=os.path.dirname(testfile))
         os.close(fd)
         f2 = '%s2~' % f1[:-2]
@@ -1942,7 +1916,7 @@
     Returns the name of the temporary file.
     """
     d, fn = os.path.split(name)
-    fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
+    fd, temp = pycompat.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
     os.close(fd)
     # Temporary files are created with mode 0600, which is usually not
     # what we want.  If the original file already exists, just copy
@@ -2137,17 +2111,18 @@
         else:
             self.close()
 
-def unlinkpath(f, ignoremissing=False):
+def unlinkpath(f, ignoremissing=False, rmdir=True):
     """unlink and remove the directory if it is empty"""
     if ignoremissing:
         tryunlink(f)
     else:
         unlink(f)
-    # try removing directories that might now be empty
-    try:
-        removedirs(os.path.dirname(f))
-    except OSError:
-        pass
+    if rmdir:
+        # try removing directories that might now be empty
+        try:
+            removedirs(os.path.dirname(f))
+        except OSError:
+            pass
 
 def tryunlink(f):
     """Attempt to remove a file, ignoring ENOENT errors."""
@@ -2719,7 +2694,7 @@
                   'query', 'fragment'):
             v = getattr(self, a)
             if v is not None:
-                attrs.append('%s: %r' % (a, v))
+                attrs.append('%s: %r' % (a, pycompat.bytestr(v)))
         return '<url %s>' % ', '.join(attrs)
 
     def __bytes__(self):
@@ -2921,6 +2896,7 @@
         finally:
             elapsed = timer() - start
             _timenesting[0] -= indent
+            stderr = procutil.stderr
             stderr.write('%s%s: %s\n' %
                          (' ' * _timenesting[0], func.__name__,
                           timecount(elapsed)))
@@ -3331,6 +3307,104 @@
         """
         raise NotImplementedError()
 
+class _CompressedStreamReader(object):
+    def __init__(self, fh):
+        if safehasattr(fh, 'unbufferedread'):
+            self._reader = fh.unbufferedread
+        else:
+            self._reader = fh.read
+        self._pending = []
+        self._pos = 0
+        self._eof = False
+
+    def _decompress(self, chunk):
+        raise NotImplementedError()
+
+    def read(self, l):
+        buf = []
+        while True:
+            while self._pending:
+                if len(self._pending[0]) > l + self._pos:
+                    newbuf = self._pending[0]
+                    buf.append(newbuf[self._pos:self._pos + l])
+                    self._pos += l
+                    return ''.join(buf)
+
+                newbuf = self._pending.pop(0)
+                if self._pos:
+                    buf.append(newbuf[self._pos:])
+                    l -= len(newbuf) - self._pos
+                else:
+                    buf.append(newbuf)
+                    l -= len(newbuf)
+                self._pos = 0
+
+            if self._eof:
+                return ''.join(buf)
+            chunk = self._reader(65536)
+            self._decompress(chunk)
+
+class _GzipCompressedStreamReader(_CompressedStreamReader):
+    def __init__(self, fh):
+        super(_GzipCompressedStreamReader, self).__init__(fh)
+        self._decompobj = zlib.decompressobj()
+    def _decompress(self, chunk):
+        newbuf = self._decompobj.decompress(chunk)
+        if newbuf:
+            self._pending.append(newbuf)
+        d = self._decompobj.copy()
+        try:
+            d.decompress('x')
+            d.flush()
+            if d.unused_data == 'x':
+                self._eof = True
+        except zlib.error:
+            pass
+
+class _BZ2CompressedStreamReader(_CompressedStreamReader):
+    def __init__(self, fh):
+        super(_BZ2CompressedStreamReader, self).__init__(fh)
+        self._decompobj = bz2.BZ2Decompressor()
+    def _decompress(self, chunk):
+        newbuf = self._decompobj.decompress(chunk)
+        if newbuf:
+            self._pending.append(newbuf)
+        try:
+            while True:
+                newbuf = self._decompobj.decompress('')
+                if newbuf:
+                    self._pending.append(newbuf)
+                else:
+                    break
+        except EOFError:
+            self._eof = True
+
+class _TruncatedBZ2CompressedStreamReader(_BZ2CompressedStreamReader):
+    def __init__(self, fh):
+        super(_TruncatedBZ2CompressedStreamReader, self).__init__(fh)
+        newbuf = self._decompobj.decompress('BZ')
+        if newbuf:
+            self._pending.append(newbuf)
+
+class _ZstdCompressedStreamReader(_CompressedStreamReader):
+    def __init__(self, fh, zstd):
+        super(_ZstdCompressedStreamReader, self).__init__(fh)
+        self._zstd = zstd
+        self._decompobj = zstd.ZstdDecompressor().decompressobj()
+    def _decompress(self, chunk):
+        newbuf = self._decompobj.decompress(chunk)
+        if newbuf:
+            self._pending.append(newbuf)
+        try:
+            while True:
+                newbuf = self._decompobj.decompress('')
+                if newbuf:
+                    self._pending.append(newbuf)
+                else:
+                    break
+        except self._zstd.ZstdError:
+            self._eof = True
+
 class _zlibengine(compressionengine):
     def name(self):
         return 'zlib'
@@ -3364,15 +3438,7 @@
         yield z.flush()
 
     def decompressorreader(self, fh):
-        def gen():
-            d = zlib.decompressobj()
-            for chunk in filechunkiter(fh):
-                while chunk:
-                    # Limit output size to limit memory.
-                    yield d.decompress(chunk, 2 ** 18)
-                    chunk = d.unconsumed_tail
-
-        return chunkbuffer(gen())
+        return _GzipCompressedStreamReader(fh)
 
     class zlibrevlogcompressor(object):
         def compress(self, data):
@@ -3452,12 +3518,7 @@
         yield z.flush()
 
     def decompressorreader(self, fh):
-        def gen():
-            d = bz2.BZ2Decompressor()
-            for chunk in filechunkiter(fh):
-                yield d.decompress(chunk)
-
-        return chunkbuffer(gen())
+        return _BZ2CompressedStreamReader(fh)
 
 compengines.register(_bz2engine())
 
@@ -3471,14 +3532,7 @@
     # We don't implement compressstream because it is hackily handled elsewhere.
 
     def decompressorreader(self, fh):
-        def gen():
-            # The input stream doesn't have the 'BZ' header. So add it back.
-            d = bz2.BZ2Decompressor()
-            d.decompress('BZ')
-            for chunk in filechunkiter(fh):
-                yield d.decompress(chunk)
-
-        return chunkbuffer(gen())
+        return _TruncatedBZ2CompressedStreamReader(fh)
 
 compengines.register(_truncatedbz2engine())
 
@@ -3573,9 +3627,7 @@
         yield z.flush()
 
     def decompressorreader(self, fh):
-        zstd = self._module
-        dctx = zstd.ZstdDecompressor()
-        return chunkbuffer(dctx.read_from(fh))
+        return _ZstdCompressedStreamReader(fh, self._module)
 
     class zstdrevlogcompressor(object):
         def __init__(self, zstd, level=3):
@@ -3784,93 +3836,3 @@
         if not (byte & 0x80):
             return result
         shift += 7
-
-###
-# Deprecation warnings for util.py splitting
-###
-
-def _deprecatedfunc(func, version, modname=None):
-    def wrapped(*args, **kwargs):
-        fn = pycompat.sysbytes(func.__name__)
-        mn = modname or pycompat.sysbytes(func.__module__)[len('mercurial.'):]
-        msg = "'util.%s' is deprecated, use '%s.%s'" % (fn, mn, fn)
-        nouideprecwarn(msg, version, stacklevel=2)
-        return func(*args, **kwargs)
-    wrapped.__name__ = func.__name__
-    return wrapped
-
-defaultdateformats = dateutil.defaultdateformats
-extendeddateformats = dateutil.extendeddateformats
-makedate = _deprecatedfunc(dateutil.makedate, '4.6')
-datestr = _deprecatedfunc(dateutil.datestr, '4.6')
-shortdate = _deprecatedfunc(dateutil.shortdate, '4.6')
-parsetimezone = _deprecatedfunc(dateutil.parsetimezone, '4.6')
-strdate = _deprecatedfunc(dateutil.strdate, '4.6')
-parsedate = _deprecatedfunc(dateutil.parsedate, '4.6')
-matchdate = _deprecatedfunc(dateutil.matchdate, '4.6')
-
-stderr = procutil.stderr
-stdin = procutil.stdin
-stdout = procutil.stdout
-explainexit = _deprecatedfunc(procutil.explainexit, '4.6',
-                              modname='utils.procutil')
-findexe = _deprecatedfunc(procutil.findexe, '4.6', modname='utils.procutil')
-getuser = _deprecatedfunc(procutil.getuser, '4.6', modname='utils.procutil')
-getpid = _deprecatedfunc(procutil.getpid, '4.6', modname='utils.procutil')
-hidewindow = _deprecatedfunc(procutil.hidewindow, '4.6',
-                             modname='utils.procutil')
-popen = _deprecatedfunc(procutil.popen, '4.6', modname='utils.procutil')
-quotecommand = _deprecatedfunc(procutil.quotecommand, '4.6',
-                               modname='utils.procutil')
-readpipe = _deprecatedfunc(procutil.readpipe, '4.6', modname='utils.procutil')
-setbinary = _deprecatedfunc(procutil.setbinary, '4.6', modname='utils.procutil')
-setsignalhandler = _deprecatedfunc(procutil.setsignalhandler, '4.6',
-                                   modname='utils.procutil')
-shellquote = _deprecatedfunc(procutil.shellquote, '4.6',
-                             modname='utils.procutil')
-shellsplit = _deprecatedfunc(procutil.shellsplit, '4.6',
-                             modname='utils.procutil')
-spawndetached = _deprecatedfunc(procutil.spawndetached, '4.6',
-                                modname='utils.procutil')
-sshargs = _deprecatedfunc(procutil.sshargs, '4.6', modname='utils.procutil')
-testpid = _deprecatedfunc(procutil.testpid, '4.6', modname='utils.procutil')
-try:
-    setprocname = _deprecatedfunc(procutil.setprocname, '4.6',
-                                  modname='utils.procutil')
-except AttributeError:
-    pass
-try:
-    unblocksignal = _deprecatedfunc(procutil.unblocksignal, '4.6',
-                                    modname='utils.procutil')
-except AttributeError:
-    pass
-closefds = procutil.closefds
-isatty = _deprecatedfunc(procutil.isatty, '4.6')
-popen2 = _deprecatedfunc(procutil.popen2, '4.6')
-popen3 = _deprecatedfunc(procutil.popen3, '4.6')
-popen4 = _deprecatedfunc(procutil.popen4, '4.6')
-pipefilter = _deprecatedfunc(procutil.pipefilter, '4.6')
-tempfilter = _deprecatedfunc(procutil.tempfilter, '4.6')
-filter = _deprecatedfunc(procutil.filter, '4.6')
-mainfrozen = _deprecatedfunc(procutil.mainfrozen, '4.6')
-hgexecutable = _deprecatedfunc(procutil.hgexecutable, '4.6')
-isstdin = _deprecatedfunc(procutil.isstdin, '4.6')
-isstdout = _deprecatedfunc(procutil.isstdout, '4.6')
-shellenviron = _deprecatedfunc(procutil.shellenviron, '4.6')
-system = _deprecatedfunc(procutil.system, '4.6')
-gui = _deprecatedfunc(procutil.gui, '4.6')
-hgcmd = _deprecatedfunc(procutil.hgcmd, '4.6')
-rundetached = _deprecatedfunc(procutil.rundetached, '4.6')
-
-binary = _deprecatedfunc(stringutil.binary, '4.6')
-stringmatcher = _deprecatedfunc(stringutil.stringmatcher, '4.6')
-shortuser = _deprecatedfunc(stringutil.shortuser, '4.6')
-emailuser = _deprecatedfunc(stringutil.emailuser, '4.6')
-email = _deprecatedfunc(stringutil.email, '4.6')
-ellipsis = _deprecatedfunc(stringutil.ellipsis, '4.6')
-escapestr = _deprecatedfunc(stringutil.escapestr, '4.6')
-unescapestr = _deprecatedfunc(stringutil.unescapestr, '4.6')
-forcebytestr = _deprecatedfunc(stringutil.forcebytestr, '4.6')
-uirepr = _deprecatedfunc(stringutil.uirepr, '4.6')
-wrap = _deprecatedfunc(stringutil.wrap, '4.6')
-parsebool = _deprecatedfunc(stringutil.parsebool, '4.6')
--- a/mercurial/utils/cborutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/utils/cborutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -140,12 +140,15 @@
 
     yield BREAK
 
+def _mixedtypesortkey(v):
+    return type(v).__name__, v
+
 def streamencodeset(s):
     # https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml defines
     # semantic tag 258 for finite sets.
     yield encodelength(MAJOR_TYPE_SEMANTIC, 258)
 
-    for chunk in streamencodearray(sorted(s)):
+    for chunk in streamencodearray(sorted(s, key=_mixedtypesortkey)):
         yield chunk
 
 def streamencodemap(d):
@@ -155,7 +158,8 @@
     """
     yield encodelength(MAJOR_TYPE_MAP, len(d))
 
-    for key, value in sorted(d.iteritems()):
+    for key, value in sorted(d.iteritems(),
+                             key=lambda x: _mixedtypesortkey(x[0])):
         for chunk in streamencode(key):
             yield chunk
         for chunk in streamencode(value):
--- a/mercurial/utils/procutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/utils/procutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -16,7 +16,6 @@
 import signal
 import subprocess
 import sys
-import tempfile
 import time
 
 from ..i18n import _
@@ -168,11 +167,11 @@
     the temporary files generated.'''
     inname, outname = None, None
     try:
-        infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
+        infd, inname = pycompat.mkstemp(prefix='hg-filter-in-')
         fp = os.fdopen(infd, r'wb')
         fp.write(s)
         fp.close()
-        outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
+        outfd, outname = pycompat.mkstemp(prefix='hg-filter-out-')
         os.close(outfd)
         cmd = cmd.replace('INFILE', inname)
         cmd = cmd.replace('OUTFILE', outname)
@@ -318,6 +317,13 @@
     env['HG'] = hgexecutable()
     return env
 
+if pycompat.iswindows:
+    def shelltonative(cmd, env):
+        return platform.shelltocmdexe(cmd, shellenviron(env))
+else:
+    def shelltonative(cmd, env):
+        return cmd
+
 def system(cmd, environ=None, cwd=None, out=None):
     '''enhanced shell command execution.
     run with environment maybe modified, maybe in different dir.
@@ -409,3 +415,36 @@
     finally:
         if prevhandler is not None:
             signal.signal(signal.SIGCHLD, prevhandler)
+
+@contextlib.contextmanager
+def uninterruptable(warn):
+    """Inhibit SIGINT handling on a region of code.
+
+    Note that if this is called in a non-main thread, it turns into a no-op.
+
+    Args:
+      warn: A callable which takes no arguments, and returns True if the
+            previous signal handling should be restored.
+    """
+
+    oldsiginthandler = [signal.getsignal(signal.SIGINT)]
+    shouldbail = []
+
+    def disabledsiginthandler(*args):
+        if warn():
+            signal.signal(signal.SIGINT, oldsiginthandler[0])
+            del oldsiginthandler[0]
+        shouldbail.append(True)
+
+    try:
+        try:
+            signal.signal(signal.SIGINT, disabledsiginthandler)
+        except ValueError:
+            # wrong thread, oh well, we tried
+            del oldsiginthandler[0]
+        yield
+    finally:
+        if oldsiginthandler:
+            signal.signal(signal.SIGINT, oldsiginthandler[0])
+        if shouldbail:
+            raise KeyboardInterrupt
--- a/mercurial/utils/stringutil.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/utils/stringutil.py	Thu Jul 19 13:55:54 2018 -0400
@@ -23,7 +23,26 @@
     pycompat,
 )
 
-def pprint(o, bprefix=True):
+# regex special chars pulled from https://bugs.python.org/issue29995
+# which was part of Python 3.7.
+_respecial = pycompat.bytestr(b'()[]{}?*+-|^$\\.&~# \t\n\r\v\f')
+_regexescapemap = {ord(i): (b'\\' + i).decode('latin1') for i in _respecial}
+
+def reescape(pat):
+    """Drop-in replacement for re.escape."""
+    # NOTE: it is intentional that this works on unicodes and not
+    # bytes, as it's only possible to do the escaping with
+    # unicode.translate, not bytes.translate. Sigh.
+    wantuni = True
+    if isinstance(pat, bytes):
+        wantuni = False
+        pat = pat.decode('latin1')
+    pat = pat.translate(_regexescapemap)
+    if wantuni:
+        return pat
+    return pat.encode('latin1')
+
+def pprint(o, bprefix=False):
     """Pretty print an object."""
     if isinstance(o, bytes):
         if bprefix:
@@ -40,16 +59,59 @@
             '%s: %s' % (pprint(k, bprefix=bprefix),
                         pprint(v, bprefix=bprefix))
             for k, v in sorted(o.items())))
-    elif isinstance(o, bool):
-        return b'True' if o else b'False'
-    elif isinstance(o, int):
-        return '%d' % o
-    elif isinstance(o, float):
-        return '%f' % o
-    elif o is None:
-        return b'None'
+    elif isinstance(o, tuple):
+        return '(%s)' % (b', '.join(pprint(a, bprefix=bprefix) for a in o))
     else:
-        raise error.ProgrammingError('do not know how to format %r' % o)
+        return pycompat.byterepr(o)
+
+def prettyrepr(o):
+    """Pretty print a representation of a possibly-nested object"""
+    lines = []
+    rs = pycompat.byterepr(o)
+    p0 = p1 = 0
+    while p0 < len(rs):
+        # '... field=<type ... field=<type ...'
+        #      ~~~~~~~~~~~~~~~~
+        #      p0    p1        q0    q1
+        q0 = -1
+        q1 = rs.find('<', p1 + 1)
+        if q1 < 0:
+            q1 = len(rs)
+        elif q1 > p1 + 1 and rs.startswith('=', q1 - 1):
+            # backtrack for ' field=<'
+            q0 = rs.rfind(' ', p1 + 1, q1 - 1)
+        if q0 < 0:
+            q0 = q1
+        else:
+            q0 += 1  # skip ' '
+        l = rs.count('<', 0, p0) - rs.count('>', 0, p0)
+        assert l >= 0
+        lines.append((l, rs[p0:q0].rstrip()))
+        p0, p1 = q0, q1
+    return '\n'.join('  ' * l + s for l, s in lines)
+
+def buildrepr(r):
+    """Format an optional printable representation from unexpanded bits
+
+    ========  =================================
+    type(r)   example
+    ========  =================================
+    tuple     ('<not %r>', other)
+    bytes     '<branch closed>'
+    callable  lambda: '<branch %r>' % sorted(b)
+    object    other
+    ========  =================================
+    """
+    if r is None:
+        return ''
+    elif isinstance(r, tuple):
+        return r[0] % pycompat.rapply(pycompat.maybebytestr, r[1:])
+    elif isinstance(r, bytes):
+        return r
+    elif callable(r):
+        return r()
+    else:
+        return pycompat.byterepr(r)
 
 def binary(s):
     """return true if a string is binary data"""
--- a/mercurial/verify.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/verify.py	Thu Jul 19 13:55:54 2018 -0400
@@ -178,9 +178,10 @@
         filelinkrevs = {}
         seen = {}
         self.checklog(cl, "changelog", 0)
-        total = len(repo)
+        progress = ui.makeprogress(_('checking'), unit=_('changesets'),
+                                   total=len(repo))
         for i in repo:
-            ui.progress(_('checking'), i, total=total, unit=_('changesets'))
+            progress.update(i)
             n = cl.node(i)
             self.checkentry(cl, i, n, seen, [i], "changelog")
 
@@ -195,11 +196,11 @@
             except Exception as inst:
                 self.refersmf = True
                 self.exc(i, _("unpacking changeset %s") % short(n), inst)
-        ui.progress(_('checking'), None)
+        progress.complete()
         return mflinkrevs, filelinkrevs
 
     def _verifymanifest(self, mflinkrevs, dir="", storefiles=None,
-                        progress=None):
+                        subdirprogress=None):
         repo = self.repo
         ui = self.ui
         match = self.match
@@ -217,16 +218,17 @@
             label = dir
             revlogfiles = mf.files()
             storefiles.difference_update(revlogfiles)
-            if progress: # should be true since we're in a subdirectory
-                progress()
+            if subdirprogress: # should be true since we're in a subdirectory
+                subdirprogress.increment()
         if self.refersmf:
             # Do not check manifest if there are only changelog entries with
             # null manifests.
             self.checklog(mf, label, 0)
-        total = len(mf)
+        progress = ui.makeprogress(_('checking'), unit=_('manifests'),
+                                   total=len(mf))
         for i in mf:
             if not dir:
-                ui.progress(_('checking'), i, total=total, unit=_('manifests'))
+                progress.update(i)
             n = mf.node(i)
             lr = self.checkentry(mf, i, n, seen, mflinkrevs.get(n, []), label)
             if n in mflinkrevs:
@@ -257,7 +259,7 @@
             except Exception as inst:
                 self.exc(lr, _("reading delta %s") % short(n), inst, label)
         if not dir:
-            ui.progress(_('checking'), None)
+            progress.complete()
 
         if self.havemf:
             for c, m in sorted([(c, m) for m in mflinkrevs
@@ -280,21 +282,17 @@
                 elif (size > 0 or not revlogv1) and f.startswith('meta/'):
                     storefiles.add(_normpath(f))
                     subdirs.add(os.path.dirname(f))
-            subdircount = len(subdirs)
-            currentsubdir = [0]
-            def progress():
-                currentsubdir[0] += 1
-                ui.progress(_('checking'), currentsubdir[0], total=subdircount,
-                            unit=_('manifests'))
+            subdirprogress = ui.makeprogress(_('checking'), unit=_('manifests'),
+                                             total=len(subdirs))
 
         for subdir, linkrevs in subdirnodes.iteritems():
             subdirfilenodes = self._verifymanifest(linkrevs, subdir, storefiles,
-                                                   progress)
+                                                   subdirprogress)
             for f, onefilenodes in subdirfilenodes.iteritems():
                 filenodes.setdefault(f, {}).update(onefilenodes)
 
         if not dir and subdirnodes:
-            ui.progress(_('checking'), None)
+            subdirprogress.complete()
             if self.warnorphanstorefiles:
                 for f in sorted(storefiles):
                     self.warn(_("warning: orphan data file '%s'") % f)
@@ -307,19 +305,17 @@
         ui.status(_("crosschecking files in changesets and manifests\n"))
 
         total = len(filelinkrevs) + len(filenodes)
-        count = 0
+        progress = ui.makeprogress(_('crosschecking'), total=total)
         if self.havemf:
             for f in sorted(filelinkrevs):
-                count += 1
-                ui.progress(_('crosschecking'), count, total=total)
+                progress.increment()
                 if f not in filenodes:
                     lr = filelinkrevs[f][0]
                     self.err(lr, _("in changeset but not in manifest"), f)
 
         if self.havecl:
             for f in sorted(filenodes):
-                count += 1
-                ui.progress(_('crosschecking'), count, total=total)
+                progress.increment()
                 if f not in filelinkrevs:
                     try:
                         fl = repo.file(f)
@@ -328,7 +324,7 @@
                         lr = None
                     self.err(lr, _("in manifest but not in changeset"), f)
 
-        ui.progress(_('crosschecking'), None)
+        progress.complete()
 
     def _verifyfiles(self, filenodes, filelinkrevs):
         repo = self.repo
@@ -346,10 +342,11 @@
                 storefiles.add(_normpath(f))
 
         files = sorted(set(filenodes) | set(filelinkrevs))
-        total = len(files)
         revisions = 0
+        progress = ui.makeprogress(_('checking'), unit=_('files'),
+                                   total=len(files))
         for i, f in enumerate(files):
-            ui.progress(_('checking'), i, item=f, total=total, unit=_('files'))
+            progress.update(i, item=f)
             try:
                 linkrevs = filelinkrevs[f]
             except KeyError:
@@ -483,7 +480,7 @@
                 for lr, node in sorted(fns):
                     self.err(lr, _("manifest refers to unknown revision %s") %
                              short(node), f)
-        ui.progress(_('checking'), None)
+        progress.complete()
 
         if self.warnorphanstorefiles:
             for f in sorted(storefiles):
--- a/mercurial/vfs.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/vfs.py	Thu Jul 19 13:55:54 2018 -0400
@@ -11,7 +11,6 @@
 import os
 import shutil
 import stat
-import tempfile
 import threading
 
 from .i18n import _
@@ -171,7 +170,7 @@
         return os.mkdir(self.join(path))
 
     def mkstemp(self, suffix='', prefix='tmp', dir=None):
-        fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
+        fd, name = pycompat.mkstemp(suffix=suffix, prefix=prefix,
                                     dir=self.join(dir))
         dname, fname = util.split(name)
         if dir:
@@ -247,8 +246,9 @@
         """Attempt to remove a file, ignoring missing file errors."""
         util.tryunlink(self.join(path))
 
-    def unlinkpath(self, path=None, ignoremissing=False):
-        return util.unlinkpath(self.join(path), ignoremissing=ignoremissing)
+    def unlinkpath(self, path=None, ignoremissing=False, rmdir=True):
+        return util.unlinkpath(self.join(path), ignoremissing=ignoremissing,
+                               rmdir=rmdir)
 
     def utime(self, path=None, t=None):
         return os.utime(self.join(path), t)
@@ -568,7 +568,7 @@
         ui.debug('starting %d threads for background file closing\n' %
                  threadcount)
 
-        self._queue = util.queue(maxsize=maxqueue)
+        self._queue = pycompat.queue.Queue(maxsize=maxqueue)
         self._running = True
 
         for i in range(threadcount):
@@ -600,7 +600,7 @@
                 except Exception as e:
                     # Stash so can re-raise from main thread later.
                     self._threadexception = e
-            except util.empty:
+            except pycompat.queue.Empty:
                 if not self._running:
                     break
 
--- a/mercurial/windows.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/windows.py	Thu Jul 19 13:55:54 2018 -0400
@@ -12,6 +12,7 @@
 import os
 import re
 import stat
+import string
 import sys
 
 from .i18n import _
@@ -253,6 +254,113 @@
 def samestat(s1, s2):
     return False
 
+def shelltocmdexe(path, env):
+    r"""Convert shell variables in the form $var and ${var} inside ``path``
+    to %var% form.  Existing Windows style variables are left unchanged.
+
+    The variables are limited to the given environment.  Unknown variables are
+    left unchanged.
+
+    >>> e = {b'var1': b'v1', b'var2': b'v2', b'var3': b'v3'}
+    >>> # Only valid values are expanded
+    >>> shelltocmdexe(b'cmd $var1 ${var2} %var3% $missing ${missing} %missing%',
+    ...               e)
+    'cmd %var1% %var2% %var3% $missing ${missing} %missing%'
+    >>> # Single quote prevents expansion, as does \$ escaping
+    >>> shelltocmdexe(b"cmd '$var1 ${var2} %var3%' \$var1 \${var2} \\", e)
+    'cmd "$var1 ${var2} %var3%" $var1 ${var2} \\'
+    >>> # $$ is not special. %% is not special either, but can be the end and
+    >>> # start of consecutive variables
+    >>> shelltocmdexe(b"cmd $$ %% %var1%%var2%", e)
+    'cmd $$ %% %var1%%var2%'
+    >>> # No double substitution
+    >>> shelltocmdexe(b"$var1 %var1%", {b'var1': b'%var2%', b'var2': b'boom'})
+    '%var1% %var1%'
+    >>> # Tilde expansion
+    >>> shelltocmdexe(b"~/dir ~\dir2 ~tmpfile \~/", {})
+    '%USERPROFILE%/dir %USERPROFILE%\\dir2 ~tmpfile ~/'
+    """
+    if not any(c in path for c in b"$'~"):
+        return path
+
+    varchars = pycompat.sysbytes(string.ascii_letters + string.digits) + b'_-'
+
+    res = b''
+    index = 0
+    pathlen = len(path)
+    while index < pathlen:
+        c = path[index]
+        if c == b'\'':   # no expansion within single quotes
+            path = path[index + 1:]
+            pathlen = len(path)
+            try:
+                index = path.index(b'\'')
+                res += b'"' + path[:index] + b'"'
+            except ValueError:
+                res += c + path
+                index = pathlen - 1
+        elif c == b'%':  # variable
+            path = path[index + 1:]
+            pathlen = len(path)
+            try:
+                index = path.index(b'%')
+            except ValueError:
+                res += b'%' + path
+                index = pathlen - 1
+            else:
+                var = path[:index]
+                res += b'%' + var + b'%'
+        elif c == b'$':  # variable
+            if path[index + 1:index + 2] == b'{':
+                path = path[index + 2:]
+                pathlen = len(path)
+                try:
+                    index = path.index(b'}')
+                    var = path[:index]
+
+                    # See below for why empty variables are handled specially
+                    if env.get(var, '') != '':
+                        res += b'%' + var + b'%'
+                    else:
+                        res += b'${' + var + b'}'
+                except ValueError:
+                    res += b'${' + path
+                    index = pathlen - 1
+            else:
+                var = b''
+                index += 1
+                c = path[index:index + 1]
+                while c != b'' and c in varchars:
+                    var += c
+                    index += 1
+                    c = path[index:index + 1]
+                # Some variables (like HG_OLDNODE) may be defined, but have an
+                # empty value.  Those need to be skipped because when spawning
+                # cmd.exe to run the hook, it doesn't replace %VAR% for an empty
+                # VAR, and that really confuses things like revset expressions.
+                # OTOH, if it's left in Unix format and the hook runs sh.exe, it
+                # will substitute to an empty string, and everything is happy.
+                if env.get(var, '') != '':
+                    res += b'%' + var + b'%'
+                else:
+                    res += b'$' + var
+
+                if c != '':
+                    index -= 1
+        elif (c == b'~' and index + 1 < pathlen
+              and path[index + 1] in (b'\\', b'/')):
+            res += "%USERPROFILE%"
+        elif (c == b'\\' and index + 1 < pathlen
+              and path[index + 1] in (b'$', b'~')):
+            # Skip '\', but only if it is escaping $ or ~
+            res += path[index + 1]
+            index += 1
+        else:
+            res += c
+
+        index += 1
+    return res
+
 # A sequence of backslashes is special iff it precedes a double quote:
 # - if there's an even number of backslashes, the double quote is not
 #   quoted (i.e. it ends the quoted region)
--- a/mercurial/wireprotoserver.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/wireprotoserver.py	Thu Jul 19 13:55:54 2018 -0400
@@ -18,7 +18,6 @@
 from . import (
     encoding,
     error,
-    hook,
     pycompat,
     util,
     wireprototypes,
@@ -785,8 +784,7 @@
     def __init__(self, ui, repo, logfh=None):
         self._ui = ui
         self._repo = repo
-        self._fin = ui.fin
-        self._fout = ui.fout
+        self._fin, self._fout = procutil.protectstdio(ui.fin, ui.fout)
 
         # Log write I/O to stdout and stderr if configured.
         if logfh:
@@ -795,15 +793,10 @@
             ui.ferr = util.makeloggingfileobject(
                 logfh, ui.ferr, 'e', logdata=True)
 
-        hook.redirect(True)
-        ui.fout = repo.ui.fout = ui.ferr
-
-        # Prevent insertion/deletion of CRs
-        procutil.setbinary(self._fin)
-        procutil.setbinary(self._fout)
-
     def serve_forever(self):
         self.serveuntil(threading.Event())
+        procutil.restorestdio(self._ui.fin, self._ui.fout,
+                              self._fin, self._fout)
         sys.exit(0)
 
     def serveuntil(self, ev):
--- a/mercurial/wireprotov1peer.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/wireprotov1peer.py	Thu Jul 19 13:55:54 2018 -0400
@@ -355,7 +355,7 @@
         yield {'nodes': wireprototypes.encodelist(nodes)}, f
         d = f.value
         try:
-            yield [bool(int(b)) for b in d]
+            yield [bool(int(b)) for b in pycompat.iterbytestr(d)]
         except ValueError:
             self._abort(error.ResponseError(_("unexpected response:"), d))
 
--- a/mercurial/wireprotov1server.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/wireprotov1server.py	Thu Jul 19 13:55:54 2018 -0400
@@ -8,7 +8,6 @@
 from __future__ import absolute_import
 
 import os
-import tempfile
 
 from .i18n import _
 from .node import (
@@ -354,7 +353,9 @@
     common_anc = cl.ancestors([cl.rev(rev) for rev in common], inclusive=True)
     compformats = clientcompressionsupport(proto)
     for entry in res:
-        if 'COMPRESSION' in entry and entry['COMPRESSION'] not in compformats:
+        comp = entry.get('COMPRESSION')
+        altcomp = util.compengines._bundlenames.get(comp)
+        if comp and comp not in compformats and altcomp not in compformats:
             continue
         # No test yet for VERSION, since V2 is supported by any client
         # that advertises partial pulls
@@ -568,7 +569,7 @@
                             fp.close()
                         if tempname:
                             os.unlink(tempname)
-                    fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
+                    fd, tempname = pycompat.mkstemp(prefix='hg-unbundle-')
                     repo.ui.debug('redirecting incoming bundle to %s\n' %
                         tempname)
                     fp = os.fdopen(fd, pycompat.sysstr('wb+'))
--- a/mercurial/worker.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/mercurial/worker.py	Thu Jul 19 13:55:54 2018 -0400
@@ -14,6 +14,12 @@
 import threading
 import time
 
+try:
+    import selectors
+    selectors.BaseSelector
+except ImportError:
+    from .thirdparty import selectors2 as selectors
+
 from .i18n import _
 from . import (
     encoding,
@@ -56,19 +62,28 @@
     return min(max(countcpus(), 4), 32)
 
 if pycompat.isposix or pycompat.iswindows:
-    _startupcost = 0.01
+    _STARTUP_COST = 0.01
+    # The Windows worker is thread based. If tasks are CPU bound, threads
+    # in the presence of the GIL result in excessive context switching and
+    # this overhead can slow down execution.
+    _DISALLOW_THREAD_UNSAFE = pycompat.iswindows
 else:
-    _startupcost = 1e30
+    _STARTUP_COST = 1e30
+    _DISALLOW_THREAD_UNSAFE = False
 
-def worthwhile(ui, costperop, nops):
+def worthwhile(ui, costperop, nops, threadsafe=True):
     '''try to determine whether the benefit of multiple processes can
     outweigh the cost of starting them'''
+
+    if not threadsafe and _DISALLOW_THREAD_UNSAFE:
+        return False
+
     linear = costperop * nops
     workers = _numworkers(ui)
-    benefit = linear - (_startupcost * workers + linear / workers)
+    benefit = linear - (_STARTUP_COST * workers + linear / workers)
     return benefit >= 0.15
 
-def worker(ui, costperarg, func, staticargs, args):
+def worker(ui, costperarg, func, staticargs, args, threadsafe=True):
     '''run a function, possibly in parallel in multiple worker
     processes.
 
@@ -82,14 +97,17 @@
 
     args - arguments to split into chunks, to pass to individual
     workers
+
+    threadsafe - whether work items are thread safe and can be executed using
+    a thread-based worker. Should be disabled for CPU heavy tasks that don't
+    release the GIL.
     '''
     enabled = ui.configbool('worker', 'enabled')
-    if enabled and worthwhile(ui, costperarg, len(args)):
+    if enabled and worthwhile(ui, costperarg, len(args), threadsafe=threadsafe):
         return _platformworker(ui, func, staticargs, args)
     return func(*staticargs + (args,))
 
 def _posixworker(ui, func, staticargs, args):
-    rfd, wfd = os.pipe()
     workers = _numworkers(ui)
     oldhandler = signal.getsignal(signal.SIGINT)
     signal.signal(signal.SIGINT, signal.SIG_IGN)
@@ -138,7 +156,15 @@
     oldchldhandler = signal.signal(signal.SIGCHLD, sigchldhandler)
     ui.flush()
     parentpid = os.getpid()
+    pipes = []
     for pargs in partition(args, workers):
+        # Every worker gets its own pipe to send results on, so we don't have to
+        # implement atomic writes larger than PIPE_BUF. Each forked process has
+        # its own pipe's descriptors in the local variables, and the parent
+        # process has the full list of pipe descriptors (and it doesn't really
+        # care what order they're in).
+        rfd, wfd = os.pipe()
+        pipes.append((rfd, wfd))
         # make sure we use os._exit in all worker code paths. otherwise the
         # worker may do some clean-ups which could cause surprises like
         # deadlock. see sshpeer.cleanup for example.
@@ -154,9 +180,12 @@
                 signal.signal(signal.SIGCHLD, oldchldhandler)
 
                 def workerfunc():
+                    for r, w in pipes[:-1]:
+                        os.close(r)
+                        os.close(w)
                     os.close(rfd)
-                    for i, item in func(*(staticargs + (pargs,))):
-                        os.write(wfd, '%d %s\n' % (i, item))
+                    for result in func(*(staticargs + (pargs,))):
+                        os.write(wfd, util.pickle.dumps(result))
                     return 0
 
                 ret = scmutil.callcatch(ui, workerfunc)
@@ -175,8 +204,10 @@
                 finally:
                     os._exit(ret & 255)
         pids.add(pid)
-    os.close(wfd)
-    fp = os.fdopen(rfd, r'rb', 0)
+    selector = selectors.DefaultSelector()
+    for rfd, wfd in pipes:
+        os.close(wfd)
+        selector.register(os.fdopen(rfd, r'rb', 0), selectors.EVENT_READ)
     def cleanup():
         signal.signal(signal.SIGINT, oldhandler)
         waitforworkers()
@@ -187,9 +218,19 @@
                 os.kill(os.getpid(), -status)
             sys.exit(status)
     try:
-        for line in util.iterfile(fp):
-            l = line.split(' ', 1)
-            yield int(l[0]), l[1][:-1]
+        openpipes = len(pipes)
+        while openpipes > 0:
+            for key, events in selector.select():
+                try:
+                    yield util.pickle.load(key.fileobj)
+                except EOFError:
+                    selector.unregister(key.fileobj)
+                    key.fileobj.close()
+                    openpipes -= 1
+                except IOError as e:
+                    if e.errno == errno.EINTR:
+                        continue
+                    raise
     except: # re-raises
         killworkers()
         cleanup()
@@ -235,7 +276,7 @@
                             # iteration.
                             if self._interrupted:
                                 return
-                    except util.empty:
+                    except pycompat.queue.Empty:
                         break
             except Exception as e:
                 # store the exception such that the main thread can resurface
@@ -262,8 +303,8 @@
                 return
 
     workers = _numworkers(ui)
-    resultqueue = util.queue()
-    taskqueue = util.queue()
+    resultqueue = pycompat.queue.Queue()
+    taskqueue = pycompat.queue.Queue()
     # partition work to more pieces than workers to minimize the chance
     # of uneven distribution of large tasks between the workers
     for pargs in partition(args, workers * 20):
--- a/setup.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/setup.py	Thu Jul 19 13:55:54 2018 -0400
@@ -74,7 +74,7 @@
     badpython = True
 
     # Allow Python 3 from source checkouts.
-    if os.path.isdir('.hg'):
+    if os.path.isdir('.hg') or 'HGPYTHON3' in os.environ:
         badpython = False
 
     if badpython:
@@ -360,7 +360,7 @@
 
     write_if_changed('mercurial/__version__.py', b''.join([
         b'# this file is autogenerated by setup.py\n'
-        b'version = "%s"\n' % versionb,
+        b'version = b"%s"\n' % versionb,
     ]))
 
 try:
@@ -369,7 +369,7 @@
     from mercurial import __version__
     version = __version__.version
 except ImportError:
-    version = 'unknown'
+    version = b'unknown'
 finally:
     if oldpolicy is None:
         del os.environ['HGMODULEPOLICY']
@@ -1066,16 +1066,24 @@
       package_data=packagedata,
       cmdclass=cmdclass,
       distclass=hgdist,
-      options={'py2exe': {'packages': ['hgdemandimport', 'hgext', 'email',
-                                       # implicitly imported per module policy
-                                       # (cffi wouldn't be used as a frozen exe)
-                                       'mercurial.cext',
-                                       #'mercurial.cffi',
-                                       'mercurial.pure']},
-               'bdist_mpkg': {'zipdist': False,
-                              'license': 'COPYING',
-                              'readme': 'contrib/macosx/Readme.html',
-                              'welcome': 'contrib/macosx/Welcome.html',
-                              },
-               },
+      options={
+          'py2exe': {
+              'packages': [
+                  'hgdemandimport',
+                  'hgext',
+                  'email',
+                  # implicitly imported per module policy
+                  # (cffi wouldn't be used as a frozen exe)
+                  'mercurial.cext',
+                  #'mercurial.cffi',
+                  'mercurial.pure',
+              ],
+          },
+          'bdist_mpkg': {
+              'zipdist': False,
+              'license': 'COPYING',
+              'readme': 'contrib/packaging/macosx/Readme.html',
+              'welcome': 'contrib/packaging/macosx/Welcome.html',
+          },
+      },
       **extra)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/basic_test_result.py	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,52 @@
+from __future__ import absolute_import, print_function
+
+import unittest
+
+class TestResult(unittest._TextTestResult):
+
+    def __init__(self, options, *args, **kwargs):
+        super(TestResult, self).__init__(*args, **kwargs)
+        self._options = options
+
+        # unittest.TestResult didn't have skipped until 2.7. We need to
+        # polyfill it.
+        self.skipped = []
+
+        # We have a custom "ignored" result that isn't present in any Python
+        # unittest implementation. It is very similar to skipped. It may make
+        # sense to map it into skip some day.
+        self.ignored = []
+
+        self.times = []
+        self._firststarttime = None
+        # Data stored for the benefit of generating xunit reports.
+        self.successes = []
+        self.faildata = {}
+
+    def addFailure(self, test, reason):
+        print("FAILURE!", test, reason)
+
+    def addSuccess(self, test):
+        print("SUCCESS!", test)
+
+    def addError(self, test, err):
+        print("ERR!", test, err)
+
+    # Polyfill.
+    def addSkip(self, test, reason):
+        print("SKIP!", test, reason)
+
+    def addIgnore(self, test, reason):
+        print("IGNORE!", test, reason)
+
+    def onStart(self, test):
+        print("ON_START!", test)
+
+    def onEnd(self):
+        print("ON_END!")
+
+    def addOutputMismatch(self, test, ret, got, expected):
+        return False
+
+    def stopTest(self, test, interrupted=False):
+        super(TestResult, self).stopTest(test)
--- a/tests/common-pattern.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/common-pattern.py	Thu Jul 19 13:55:54 2018 -0400
@@ -71,6 +71,20 @@
      # (replacement patterns)
      br'$USUAL_BUNDLE2_CAPS_SERVER$'
      ),
+    (
+     br'bundle2=HG20%0A'
+     br'bookmarks%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'pushkey%0A'
+     br'remote-changegroup%3Dhttp%2Chttps%0A'
+     br'rev-branch-cache',
+     # (replacement patterns)
+     br'$USUAL_BUNDLE2_CAPS_NO_PHASES$'
+    ),
     # HTTP access log dates
     (br' - - \[\d\d/.../2\d\d\d \d\d:\d\d:\d\d] "(GET|PUT|POST)',
      lambda m: br' - - [$LOGDATE$] "' + m.group(1)
--- a/tests/dumbhttp.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/dumbhttp.py	Thu Jul 19 13:55:54 2018 -0400
@@ -13,6 +13,7 @@
 import sys
 
 from mercurial import (
+    encoding,
     pycompat,
     server,
     util,
@@ -66,7 +67,8 @@
 
     opts = {b'pid_file': options.pid,
             b'daemon': not options.foreground,
-            b'daemon_postexec': options.daemon_postexec}
+            b'daemon_postexec': pycompat.rapply(encoding.strtolocal,
+                                                options.daemon_postexec)}
     service = simplehttpservice(options.host, options.port)
     runargs = [sys.executable, __file__] + sys.argv[1:]
     runargs = [pycompat.fsencode(a) for a in runargs]
--- a/tests/f	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/f	Thu Jul 19 13:55:54 2018 -0400
@@ -84,7 +84,7 @@
             if opts.mode and not islink:
                 facts.append(b'mode=%o' % (stat.st_mode & 0o777))
             if opts.links:
-                facts.append(b'links=%s' % stat.st_nlink)
+                facts.append(b'links=%d' % stat.st_nlink)
             if opts.newer:
                 # mtime might be in whole seconds so newer file might be same
                 if stat.st_mtime >= os.stat(opts.newer).st_mtime:
--- a/tests/hghave.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/hghave.py	Thu Jul 19 13:55:54 2018 -0400
@@ -452,8 +452,9 @@
 
 @check("clang-format", "clang-format C code formatter")
 def has_clang_format():
-    return matchoutput("clang-format --help",
-                       br"^OVERVIEW: A tool to format C/C\+\+[^ ]+ code.")
+    m = matchoutput('clang-format --version', br'clang-format version (\d)')
+    # style changed somewhere between 4.x and 6.x
+    return m and int(m.group(1)) >= 6
 
 @check("jshint", "JSHint static code analysis tool")
 def has_jshint():
@@ -616,7 +617,7 @@
        "debian build dependencies (run dpkg-checkbuilddeps in contrib/)")
 def has_debdeps():
     # just check exit status (ignoring output)
-    path = '%s/../contrib/debian/control' % os.environ['TESTDIR']
+    path = '%s/../contrib/packaging/debian/control' % os.environ['TESTDIR']
     return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
 
 @check("demandimport", "demandimport enabled")
@@ -709,6 +710,10 @@
         return int(mat.group(1)) > 5
     return False
 
+@check("clang-6.0", "clang 6.0 with version suffix (libfuzzer included)")
+def has_clang60():
+    return matchoutput('clang-6.0 --version', b'clang version 6\.')
+
 @check("xdiff", "xdiff algorithm")
 def has_xdiff():
     try:
--- a/tests/killdaemons.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/killdaemons.py	Thu Jul 19 13:55:54 2018 -0400
@@ -124,4 +124,4 @@
     else:
         path = os.environ["DAEMON_PIDS"]
 
-    killdaemons(path)
+    killdaemons(path, remove=True)
--- a/tests/printenv.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/printenv.py	Thu Jul 19 13:55:54 2018 -0400
@@ -26,6 +26,7 @@
 
 exitcode = 0
 out = sys.stdout
+out = getattr(out, 'buffer', out)
 
 name = sys.argv[1]
 if len(sys.argv) > 2:
@@ -39,14 +40,15 @@
        if k.startswith("HG_") and v]
 env.sort()
 
-out.write("%s hook: " % name)
+out.write(b"%s hook: " % name.encode('ascii'))
 if os.name == 'nt':
     filter = lambda x: x.replace('\\', '/')
 else:
     filter = lambda x: x
-vars = ["%s=%s" % (k, filter(v)) for k, v in env]
-out.write(" ".join(vars))
-out.write("\n")
+vars = [b"%s=%s" % (k.encode('ascii'), filter(v).encode('ascii'))
+        for k, v in env]
+out.write(b" ".join(vars))
+out.write(b"\n")
 out.close()
 
 sys.exit(exitcode)
--- a/tests/run-tests.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/run-tests.py	Thu Jul 19 13:55:54 2018 -0400
@@ -120,7 +120,7 @@
         }
 
     class TestRunnerLexer(lexer.RegexLexer):
-        testpattern = r'[\w-]+\.(t|py)( \(case [\w-]+\))?'
+        testpattern = r'[\w-]+\.(t|py)(#[a-zA-Z0-9_\-\.]+)?'
         tokens = {
             'root': [
                 (r'^Skipped', token.Generic.Skipped, 'skipped'),
@@ -1247,7 +1247,7 @@
         self._allcases = parsettestcases(path)
         super(TTest, self).__init__(path, *args, **kwds)
         if case:
-            self.name = '%s (case %s)' % (self.name, _strpath(case))
+            self.name = '%s#%s' % (self.name, _strpath(case))
             self.errpath = b'%s.%s.err' % (self.errpath[:-4], case)
             self._tmpname += b'-%s' % case
         self._have = {}
@@ -1480,20 +1480,17 @@
                 if expected.get(pos, None):
                     els = expected[pos]
 
-                i = 0
                 optional = []
-                while i < len(els):
-                    el = els[i]
-
-                    r = self.linematch(el, lout)
+                for i, el in enumerate(els):
+                    r = False
+                    if el:
+                        r, exact = self.linematch(el, lout)
                     if isinstance(r, str):
                         if r == '-glob':
                             lout = ''.join(el.rsplit(' (glob)', 1))
                             r = '' # Warn only this line.
                         elif r == "retry":
                             postout.append(b'  ' + el)
-                            els.pop(i)
-                            break
                         else:
                             log('\ninfo, unknown linematch result: %r\n' % r)
                             r = False
@@ -1511,8 +1508,11 @@
 
                                 if not self._iftest(conditions):
                                     optional.append(i)
-
-                    i += 1
+                        if exact:
+                            # Don't allow line to be matches against a later
+                            # line in the output
+                            els.pop(i)
+                            break
 
                 if r:
                     if r == "retry":
@@ -1612,42 +1612,41 @@
         return TTest.rematch(res, l)
 
     def linematch(self, el, l):
+        if el == l: # perfect match (fast)
+            return True, True
         retry = False
-        if el == l: # perfect match (fast)
-            return True
-        if el:
-            if el.endswith(b" (?)\n"):
-                retry = "retry"
-                el = el[:-5] + b"\n"
+        if el.endswith(b" (?)\n"):
+            retry = "retry"
+            el = el[:-5] + b"\n"
+        else:
+            m = optline.match(el)
+            if m:
+                conditions = [c for c in m.group(2).split(b' ')]
+
+                el = m.group(1) + b"\n"
+                if not self._iftest(conditions):
+                    retry = "retry"    # Not required by listed features
+
+        if el.endswith(b" (esc)\n"):
+            if PYTHON3:
+                el = el[:-7].decode('unicode_escape') + '\n'
+                el = el.encode('utf-8')
             else:
-                m = optline.match(el)
-                if m:
-                    conditions = [c for c in m.group(2).split(b' ')]
-
-                    el = m.group(1) + b"\n"
-                    if not self._iftest(conditions):
-                        retry = "retry"    # Not required by listed features
-
-            if el.endswith(b" (esc)\n"):
-                if PYTHON3:
-                    el = el[:-7].decode('unicode_escape') + '\n'
-                    el = el.encode('utf-8')
-                else:
-                    el = el[:-7].decode('string-escape') + '\n'
-            if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
-                return True
-            if el.endswith(b" (re)\n"):
-                return TTest.rematch(el[:-6], l) or retry
-            if el.endswith(b" (glob)\n"):
-                # ignore '(glob)' added to l by 'replacements'
-                if l.endswith(b" (glob)\n"):
-                    l = l[:-8] + b"\n"
-                return TTest.globmatch(el[:-8], l) or retry
-            if os.altsep:
-                _l = l.replace(b'\\', b'/')
-                if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
-                    return True
-        return retry
+                el = el[:-7].decode('string-escape') + '\n'
+        if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
+            return True, True
+        if el.endswith(b" (re)\n"):
+            return (TTest.rematch(el[:-6], l) or retry), False
+        if el.endswith(b" (glob)\n"):
+            # ignore '(glob)' added to l by 'replacements'
+            if l.endswith(b" (glob)\n"):
+                l = l[:-8] + b"\n"
+            return (TTest.globmatch(el[:-8], l) or retry), False
+        if os.altsep:
+            _l = l.replace(b'\\', b'/')
+            if el == _l or os.name == 'nt' and el[:-1] + b'\r\n' == _l:
+                return True, True
+        return retry, True
 
     @staticmethod
     def parsehghaveoutput(lines):
@@ -1711,6 +1710,14 @@
         else: # 'always', for testing purposes
             self.color = pygmentspresent
 
+    def onStart(self, test):
+        """ Can be overriden by custom TestResult
+        """
+
+    def onEnd(self):
+        """ Can be overriden by custom TestResult
+        """
+
     def addFailure(self, test, reason):
         self.failures.append((test, reason))
 
@@ -1852,6 +1859,16 @@
                 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
                     test.name, self.times[-1][3]))
 
+def getTestResult():
+    """
+    Returns the relevant test result
+    """
+    if "CUSTOM_TEST_RESULT" in os.environ:
+        testresultmodule = __import__(os.environ["CUSTOM_TEST_RESULT"])
+        return testresultmodule.TestResult
+    else:
+        return TestResult
+
 class TestSuite(unittest.TestSuite):
     """Custom unittest TestSuite that knows how to execute Mercurial tests."""
 
@@ -2090,71 +2107,74 @@
 
         self._runner = runner
 
+        self._result = getTestResult()(self._runner.options, self.stream,
+                                       self.descriptions, self.verbosity)
+
     def listtests(self, test):
-        result = TestResult(self._runner.options, self.stream,
-                            self.descriptions, 0)
         test = sorted(test, key=lambda t: t.name)
+
+        self._result.onStart(test)
+
         for t in test:
             print(t.name)
-            result.addSuccess(t)
+            self._result.addSuccess(t)
 
         if self._runner.options.xunit:
             with open(self._runner.options.xunit, "wb") as xuf:
-                self._writexunit(result, xuf)
+                self._writexunit(self._result, xuf)
 
         if self._runner.options.json:
             jsonpath = os.path.join(self._runner._outputdir, b'report.json')
             with open(jsonpath, 'w') as fp:
-                self._writejson(result, fp)
-
-        return result
+                self._writejson(self._result, fp)
+
+        return self._result
 
     def run(self, test):
-        result = TestResult(self._runner.options, self.stream,
-                            self.descriptions, self.verbosity)
-
-        test(result)
-
-        failed = len(result.failures)
-        skipped = len(result.skipped)
-        ignored = len(result.ignored)
+        self._result.onStart(test)
+        test(self._result)
+
+        failed = len(self._result.failures)
+        skipped = len(self._result.skipped)
+        ignored = len(self._result.ignored)
 
         with iolock:
             self.stream.writeln('')
 
             if not self._runner.options.noskips:
-                for test, msg in result.skipped:
+                for test, msg in self._result.skipped:
                     formatted = 'Skipped %s: %s\n' % (test.name, msg)
-                    self.stream.write(highlightmsg(formatted, result.color))
-            for test, msg in result.failures:
+                    msg = highlightmsg(formatted, self._result.color)
+                    self.stream.write(msg)
+            for test, msg in self._result.failures:
                 formatted = 'Failed %s: %s\n' % (test.name, msg)
-                self.stream.write(highlightmsg(formatted, result.color))
-            for test, msg in result.errors:
+                self.stream.write(highlightmsg(formatted, self._result.color))
+            for test, msg in self._result.errors:
                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
             if self._runner.options.xunit:
                 with open(self._runner.options.xunit, "wb") as xuf:
-                    self._writexunit(result, xuf)
+                    self._writexunit(self._result, xuf)
 
             if self._runner.options.json:
                 jsonpath = os.path.join(self._runner._outputdir, b'report.json')
                 with open(jsonpath, 'w') as fp:
-                    self._writejson(result, fp)
+                    self._writejson(self._result, fp)
 
             self._runner._checkhglib('Tested')
 
-            savetimes(self._runner._outputdir, result)
+            savetimes(self._runner._outputdir, self._result)
 
             if failed and self._runner.options.known_good_rev:
-                self._bisecttests(t for t, m in result.failures)
+                self._bisecttests(t for t, m in self._result.failures)
             self.stream.writeln(
                 '# Ran %d tests, %d skipped, %d failed.'
-                % (result.testsRun, skipped + ignored, failed))
+                % (self._result.testsRun, skipped + ignored, failed))
             if failed:
                 self.stream.writeln('python hash seed: %s' %
                     os.environ['PYTHONHASHSEED'])
             if self._runner.options.time:
-                self.printtimes(result.times)
+                self.printtimes(self._result.times)
 
             if self._runner.options.exceptions:
                 exceptions = aggregateexceptions(
@@ -2177,7 +2197,7 @@
 
             self.stream.flush()
 
-        return result
+        return self._result
 
     def _bisecttests(self, tests):
         bisectcmd = ['hg', 'bisect']
@@ -2646,16 +2666,31 @@
                 expanded_args.append(arg)
         args = expanded_args
 
+        testcasepattern = re.compile(br'([\w-]+\.t|py)(#([a-zA-Z0-9_\-\.]+))')
         tests = []
         for t in args:
+            case = None
+
             if not (os.path.basename(t).startswith(b'test-')
                     and (t.endswith(b'.py') or t.endswith(b'.t'))):
-                continue
+
+                m = testcasepattern.match(t)
+                if m is not None:
+                    t, _, case = m.groups()
+                else:
+                    continue
+
             if t.endswith(b'.t'):
                 # .t file may contain multiple test cases
                 cases = sorted(parsettestcases(t))
                 if cases:
-                    tests += [{'path': t, 'case': c} for c in sorted(cases)]
+                    if case is not None and case in cases:
+                        tests += [{'path': t, 'case': case}]
+                    elif case is not None and case not in cases:
+                        # Ignore invalid cases
+                        pass
+                    else:
+                        tests += [{'path': t, 'case': c} for c in sorted(cases)]
                 else:
                     tests.append({'path': t})
             else:
@@ -2707,7 +2742,9 @@
                               showchannels=self.options.showchannels,
                               tests=tests, loadtest=_reloadtest)
             verbosity = 1
-            if self.options.verbose:
+            if self.options.list_tests:
+                verbosity = 0
+            elif self.options.verbose:
                 verbosity = 2
             runner = TextTestRunner(self, verbosity=verbosity)
 
@@ -2728,6 +2765,8 @@
             if result.failures:
                 failed = True
 
+            result.onEnd()
+
             if self.options.anycoverage:
                 self._outputcoverage()
         except KeyboardInterrupt:
--- a/tests/simplestorerepo.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/simplestorerepo.py	Thu Jul 19 13:55:54 2018 -0400
@@ -24,9 +24,6 @@
 from mercurial.thirdparty import (
     cbor,
 )
-from mercurial.thirdparty.zope import (
-    interface as zi,
-)
 from mercurial import (
     ancestor,
     bundlerepo,
@@ -40,6 +37,9 @@
     store,
     verify,
 )
+from mercurial.utils import (
+    interfaceutil,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -60,7 +60,7 @@
     if not isinstance(rev, int):
         raise ValueError('expected int')
 
-@zi.implementer(repository.ifilestorage)
+@interfaceutil.implementer(repository.ifilestorage)
 class filestorage(object):
     """Implements storage for a tracked path.
 
--- a/tests/test-acl.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-acl.t	Thu Jul 19 13:55:54 2018 -0400
@@ -15,7 +15,7 @@
   >     #  LOGNAME=$user hg --cws a --debug push ../b
   >     # fails with "This variable is read only."
   >     # Use env to work around this.
-  >     env LOGNAME=$user hg --cwd a --debug push ../b
+  >     env LOGNAME=$user hg --cwd a --debug push ../b $*
   >     hg --cwd b rollback
   >     hg --cwd b --quiet tip
   >     echo
@@ -47,6 +47,7 @@
   >     cat > $config <<EOF
   > [hooks]
   > pretxnchangegroup.acl = python:hgext.acl.hook
+  > prepushkey.acl = python:hgext.acl.hook
   > [acl]
   > sources = push
   > [extensions]
@@ -148,6 +149,7 @@
 
   $ echo '[hooks]' >> $config
   $ echo 'pretxnchangegroup.acl = python:hgext.acl.hook' >> $config
+  $ echo 'prepushkey.acl = python:hgext.acl.hook' >> $config
 
 Extension disabled for lack of acl.sources
 
@@ -156,6 +158,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   """
   pushing to ../b
   query 1; heads
@@ -220,6 +223,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   """
@@ -295,6 +299,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -362,6 +367,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -434,6 +440,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -503,6 +510,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -577,6 +585,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -649,6 +658,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -712,6 +722,178 @@
   0:6675d58eff77
   
 
+fred is not blocked from moving bookmarks
+
+  $ hg -R a book -q moving-bookmark -r 1
+  $ hg -R b book -q moving-bookmark -r 0
+  $ cp $config normalconfig
+  $ do_push fred -r 1
+  Pushing as user fred
+  hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
+  [acl]
+  sources = push
+  [acl.allow]
+  foo/** = fred
+  [acl.deny]
+  foo/bar/** = fred
+  foo/Bar/** = fred
+  """
+  pushing to ../b
+  query 1; heads
+  searching for changes
+  all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
+  listing keys for "bookmarks"
+  1 changesets found
+  list of changesets:
+  ef1ea85a6374b77d6da9dcda9541f498f2d17df7
+  bundle2-output-bundle: "HG20", 7 parts total
+  bundle2-output-part: "replycaps" 205 bytes payload
+  bundle2-output-part: "check:bookmarks" 37 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
+  bundle2-output-part: "check:heads" streamed payload
+  bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
+  bundle2-output-part: "bookmarks" 37 bytes payload
+  bundle2-input-bundle: with-transaction
+  bundle2-input-part: "replycaps" supported
+  bundle2-input-part: total payload size 205
+  bundle2-input-part: "check:bookmarks" supported
+  bundle2-input-part: total payload size 37
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-part: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
+  adding changesets
+  add changeset ef1ea85a6374
+  adding manifests
+  adding file changes
+  adding foo/file.txt revisions
+  added 1 changesets with 1 changes to 1 files
+  calling hook pretxnchangegroup.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  acl: acl.allow.branches not enabled
+  acl: acl.deny.branches not enabled
+  acl: acl.allow enabled, 1 entries for user fred
+  acl: acl.deny enabled, 2 entries for user fred
+  acl: branch access granted: "ef1ea85a6374" on branch "default"
+  acl: path access granted: "ef1ea85a6374"
+  bundle2-input-part: total payload size 520
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-part: "bookmarks" supported
+  bundle2-input-part: total payload size 37
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  acl: acl.allow.bookmarks not enabled
+  acl: acl.deny.bookmarks not enabled
+  acl: bookmark access granted: "ef1ea85a6374b77d6da9dcda9541f498f2d17df7" on bookmark "moving-bookmark"
+  bundle2-input-bundle: 6 parts total
+  updating the branch cache
+  bundle2-output-bundle: "HG20", 1 parts total
+  bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
+  bundle2-input-bundle: no-transaction
+  bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
+  bundle2-input-bundle: 0 parts total
+  updating bookmark moving-bookmark
+  listing keys for "phases"
+  repository tip rolled back to revision 0 (undo push)
+  0:6675d58eff77
+  
+
+fred is not allowed to move bookmarks
+
+  $ echo '[acl.deny.bookmarks]' >> $config
+  $ echo '* = fred' >> $config
+  $ do_push fred -r 1
+  Pushing as user fred
+  hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
+  [acl]
+  sources = push
+  [acl.allow]
+  foo/** = fred
+  [acl.deny]
+  foo/bar/** = fred
+  foo/Bar/** = fred
+  [acl.deny.bookmarks]
+  * = fred
+  """
+  pushing to ../b
+  query 1; heads
+  searching for changes
+  all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
+  listing keys for "bookmarks"
+  1 changesets found
+  list of changesets:
+  ef1ea85a6374b77d6da9dcda9541f498f2d17df7
+  bundle2-output-bundle: "HG20", 7 parts total
+  bundle2-output-part: "replycaps" 205 bytes payload
+  bundle2-output-part: "check:bookmarks" 37 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
+  bundle2-output-part: "check:heads" streamed payload
+  bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
+  bundle2-output-part: "bookmarks" 37 bytes payload
+  bundle2-input-bundle: with-transaction
+  bundle2-input-part: "replycaps" supported
+  bundle2-input-part: total payload size 205
+  bundle2-input-part: "check:bookmarks" supported
+  bundle2-input-part: total payload size 37
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-part: "check:heads" supported
+  bundle2-input-part: total payload size 20
+  bundle2-input-part: "changegroup" (params: 1 mandatory) supported
+  adding changesets
+  add changeset ef1ea85a6374
+  adding manifests
+  adding file changes
+  adding foo/file.txt revisions
+  added 1 changesets with 1 changes to 1 files
+  calling hook pretxnchangegroup.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  acl: acl.allow.branches not enabled
+  acl: acl.deny.branches not enabled
+  acl: acl.allow enabled, 1 entries for user fred
+  acl: acl.deny enabled, 2 entries for user fred
+  acl: branch access granted: "ef1ea85a6374" on branch "default"
+  acl: path access granted: "ef1ea85a6374"
+  bundle2-input-part: total payload size 520
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-part: "bookmarks" supported
+  bundle2-input-part: total payload size 37
+  calling hook prepushkey.acl: hgext.acl.hook
+  acl: checking access for user "fred"
+  acl: acl.allow.bookmarks not enabled
+  acl: acl.deny.bookmarks enabled, 1 entries for user fred
+  error: prepushkey.acl hook failed: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
+  bundle2-input-bundle: 6 parts total
+  transaction abort!
+  rollback completed
+  abort: acl: user "fred" denied on bookmark "moving-bookmark" (changeset "ef1ea85a6374b77d6da9dcda9541f498f2d17df7")
+  no rollback information available
+  0:6675d58eff77
+  
+
+cleanup bookmark stuff
+
+  $ hg book -R a -d moving-bookmark
+  $ hg book -R b -d moving-bookmark
+  $ cp normalconfig $config
+
 barney is allowed everywhere
 
   $ echo '[acl.allow]' >> $config
@@ -721,6 +903,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -803,6 +986,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -882,6 +1066,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -954,6 +1139,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -1039,6 +1225,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [acl.allow]
@@ -1138,6 +1325,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1219,6 +1407,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1301,6 +1490,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1383,6 +1573,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1507,6 +1698,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1590,6 +1782,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1669,6 +1862,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1742,6 +1936,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1810,6 +2005,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1899,6 +2095,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1987,6 +2184,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -2062,6 +2260,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -2145,6 +2344,7 @@
   hgrc = """
   [hooks]
   pretxnchangegroup.acl = python:hgext.acl.hook
+  prepushkey.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
--- a/tests/test-annotate.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-annotate.t	Thu Jul 19 13:55:54 2018 -0400
@@ -76,6 +76,14 @@
   $ hg annotate -T'{lines % "{rev} {node|shortest}: {line}"}' a
   0 8435: a
 
+'{line_number}' field should be populated as necessary
+
+  $ hg annotate -T'{lines % "{rev}:{line_number}: {line}"}' a
+  0:1: a
+  $ hg annotate -Ta a \
+  > --config templates.a='"{lines % "{rev}:{line_number}: {line}"}"'
+  0:1: a
+
   $ cat <<EOF >>a
   > a
   > a
--- a/tests/test-archive.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-archive.t	Thu Jul 19 13:55:54 2018 -0400
@@ -94,7 +94,7 @@
   $ echo "archivesubrepos = True" >> .hg/hgrc
   $ cp .hg/hgrc .hg/hgrc-base
   > test_archtype() {
-  >     echo "allow_archive = $1" >> .hg/hgrc
+  >     echo "allow-archive = $1" >> .hg/hgrc
   >     test_archtype_run "$@"
   > }
   > test_archtype_deprecated() {
@@ -293,7 +293,7 @@
   
   body: size=1451, sha1=4c5cf0f574446c44feb7f88f4e0e2a56bd92c352
 
-  $ echo "allow_archive = gz bz2 zip" >> .hg/hgrc
+  $ echo "allow-archive = gz bz2 zip" >> .hg/hgrc
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
--- a/tests/test-bad-extension.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bad-extension.t	Thu Jul 19 13:55:54 2018 -0400
@@ -72,10 +72,9 @@
   $ hg --config extensions.badexts=showbadexts.py showbadexts 2>&1 | grep '^BADEXTS'
   BADEXTS: badext badext2
 
-show traceback for ImportError of hgext.name if debug is set
-(note that --debug option isn't applied yet when loading extensions)
+show traceback for ImportError of hgext.name if devel.debug.extensions is set
 
-  $ (hg -q help help --traceback --config ui.debug=True 2>&1) \
+  $ (hg help help --traceback --debug --config devel.debug.extensions=yes 2>&1) \
   > | grep -v '^ ' \
   > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|not import'
   *** failed to import extension badext from $TESTTMP/badext.py: bit bucket overflow
--- a/tests/test-bad-pull.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bad-pull.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require serve killdaemons
+#require serve
 
   $ hg clone http://localhost:$HGPORT/ copy
   abort: * (glob)
--- a/tests/test-basic.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-basic.t	Thu Jul 19 13:55:54 2018 -0400
@@ -64,7 +64,7 @@
   > from mercurial import ui, hg, commands
   > myui = ui.ui.load()
   > repo = hg.repository(myui, path=b'.')
-  > commands.update(myui, repo, rev=0)
+  > commands.update(myui, repo, rev=b"0")
   > EOF
   $ hg up null
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-blackbox.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-blackbox.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,13 +1,29 @@
 setup
+
+  $ cat > myextension.py <<EOF
+  > from mercurial import error, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command(b'crash', [], b'hg crash')
+  > def crash(ui, *args, **kwargs):
+  >     raise Exception("oops")
+  > @command(b'abort', [], b'hg abort')
+  > def abort(ui, *args, **kwargs):
+  >     raise error.Abort(b"oops")
+  > EOF
+  $ abspath=`pwd`/myextension.py
+
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
   > blackbox=
   > mock=$TESTDIR/mockblackbox.py
   > mq=
+  > myextension=$TESTTMP/myextension.py
   > [alias]
   > confuse = log --limit 3
   > so-confusing = confuse --style compact
   > EOF
+
   $ hg init blackboxtest
   $ cd blackboxtest
 
@@ -21,6 +37,32 @@
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* (glob)
 
+failure exit code
+  $ rm ./.hg/blackbox.log
+  $ hg add non-existent
+  non-existent: $ENOENT$
+  [1]
+  $ hg blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add non-existent
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add non-existent exited 1 after * seconds (glob)
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
+
+abort exit code
+  $ rm ./.hg/blackbox.log
+  $ hg abort 2> /dev/null
+  [255]
+  $ hg blackbox -l 2
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> abort exited 255 after * seconds (glob)
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox -l 2
+
+unhandled exception
+  $ rm ./.hg/blackbox.log
+  $ hg crash 2> /dev/null
+  [1]
+  $ hg blackbox -l 2
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> crash exited 1 after * seconds (glob)
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox -l 2
+
 alias expansion is logged
   $ rm ./.hg/blackbox.log
   $ hg confuse
@@ -206,7 +248,7 @@
   committing changelog
   updating the branch cache
   committed changeset 0:0e46349438790c460c5c9f7546bfcd39b267bbd2
-  result: None
+  result: 0
   running: --debug commit -m commit2 -d 2000-01-02 foo
   committing files:
   foo
@@ -214,7 +256,7 @@
   committing changelog
   updating the branch cache
   committed changeset 1:45589e459b2edfbf3dbde7e01f611d2c1e7453d7
-  result: None
+  result: 0
   running: --debug log -r 0
   changeset:   0:0e46349438790c460c5c9f7546bfcd39b267bbd2
   phase:       draft
@@ -229,7 +271,7 @@
   commit1
   
   
-  result: None
+  result: 0
   running: --debug log -r tip
   changeset:   1:45589e459b2edfbf3dbde7e01f611d2c1e7453d7
   tag:         tip
@@ -245,7 +287,7 @@
   commit2
   
   
-  result: None
+  result: 0
   $ hg blackbox
   1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updating the branch cache
   1970/01/01 00:00:00 bob @45589e459b2edfbf3dbde7e01f611d2c1e7453d7 (5000)> updated served branch cache in * seconds (glob)
--- a/tests/test-bookmarks-pushpull.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bookmarks-pushpull.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1034,7 +1034,8 @@
 ===============================================
 
 #if b2-binary
-  >>> open('longname', 'w').write('wat' * 100)
+  >>> with open('longname', 'w') as f:
+  ...     f.write('wat' * 100) and None
   $ hg book `cat longname`
   $ hg push -B `cat longname` ../unchanged-b
   pushing to ../unchanged-b
--- a/tests/test-bookmarks.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bookmarks.t	Thu Jul 19 13:55:54 2018 -0400
@@ -68,12 +68,20 @@
      X                         0:f7b1eb17ad24
    * X2                        0:f7b1eb17ad24
      Y                         -1:000000000000
+  $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
+  0 X
+  0 X2
 
   $ echo b > b
   $ hg add b
   $ hg commit -m 1 --config "$TESTHOOK"
   test-hook-bookmark: X2:  f7b1eb17ad24730a1651fccd46c43826d1bbc2ac -> 925d80f479bb026b0fb3deb27503780b13f74123
 
+  $ hg bookmarks -T '{rev}:{node|shortest} {bookmark} {desc|firstline}\n'
+  0:f7b1 X 0
+  1:925d X2 1
+  -1:0000 Y 
+
   $ hg bookmarks -Tjson
   [
    {
@@ -299,6 +307,11 @@
      Y                         2:db815d6d32e6
      Z                         0:f7b1eb17ad24
    * x  y                      2:db815d6d32e6
+  $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
+  2 Y
+  2 x  y
+  1 X2
+  0 Z
 
 look up stripped bookmark name
 
@@ -445,6 +458,11 @@
      Y                         2:db815d6d32e6
    * Z                         2:db815d6d32e6
      x  y                      2:db815d6d32e6
+  $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
+  2 Y
+  2 Z
+  2 x  y
+  1 X2
 
 revision but no bookmark name
 
--- a/tests/test-bugzilla.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bugzilla.t	Thu Jul 19 13:55:54 2018 -0400
@@ -8,15 +8,15 @@
   > configtable = {}
   > configitem = registrar.configitem(configtable)
   > 
-  > configitem('bugzilla', 'mocklog',
+  > configitem(b'bugzilla', b'mocklog',
   >     default=None,
   > )
   > def extsetup(ui):
-  >     bugzilla = extensions.find('bugzilla')
+  >     bugzilla = extensions.find(b'bugzilla')
   >     class bzmock(bugzilla.bzaccess):
   >         def __init__(self, ui):
   >             super(bzmock, self).__init__(ui)
-  >             self._logfile = ui.config('bugzilla', 'mocklog')
+  >             self._logfile = ui.config(b'bugzilla', b'mocklog')
   >         def updatebug(self, bugid, newstate, text, committer):
   >             with open(self._logfile, 'a') as f:
   >                 f.write('update bugid=%r, newstate=%r, committer=%r\n'
@@ -26,7 +26,7 @@
   >             with open(self._logfile, 'a') as f:
   >                 f.write('notify bugs=%r, committer=%r\n'
   >                         % (bugs, committer))
-  >     bugzilla.bugzilla._versions['mock'] = bzmock
+  >     bugzilla.bugzilla._versions[b'mock'] = bzmock
   > EOF
 
 set up mock repository:
--- a/tests/test-bundle-r.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bundle-r.t	Thu Jul 19 13:55:54 2018 -0400
@@ -156,6 +156,7 @@
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
   new changesets c70afb1ee985:faa2e4234c7a
+  1 local changesets published
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
--- a/tests/test-bundle.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bundle.t	Thu Jul 19 13:55:54 2018 -0400
@@ -339,20 +339,20 @@
   > from __future__ import absolute_import
   > 
   > def showtip(ui, repo, hooktype, **kwargs):
-  >     ui.warn('%s: %s\n' % (hooktype, repo['tip'].hex()[:12]))
+  >     ui.warn(b'%s: %s\n' % (hooktype, repo[b'tip'].hex()[:12]))
   > 
   > def reposetup(ui, repo):
   >     # this confirms (and ensures) that (empty) 00changelog.i
   >     # before streamclone is already cached as repo.changelog
-  >     ui.setconfig('hooks', 'pretxnopen.showtip', showtip)
+  >     ui.setconfig(b'hooks', b'pretxnopen.showtip', showtip)
   > 
   >     # this confirms that streamclone-ed changes are visible to
   >     # in-process procedures before closing transaction
-  >     ui.setconfig('hooks', 'pretxnclose.showtip', showtip)
+  >     ui.setconfig(b'hooks', b'pretxnclose.showtip', showtip)
   > 
   >     # this confirms that streamclone-ed changes are still visible
   >     # after closing transaction
-  >     ui.setconfig('hooks', 'txnclose.showtip', showtip)
+  >     ui.setconfig(b'hooks', b'txnclose.showtip', showtip)
   > EOF
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-bundle2-exchange.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bundle2-exchange.t	Thu Jul 19 13:55:54 2018 -0400
@@ -150,6 +150,7 @@
   pulling from $TESTTMP/main
   no changes found
   pre-close-tip:24b6387c8c8c public 
+  1 local changesets published
   postclose-tip:24b6387c8c8c public 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -935,11 +936,11 @@
   > import os
   > from mercurial import extensions, bundle2, error
   > def checklock(orig, repo, *args, **kwargs):
-  >     if repo.svfs.lexists("lock"):
-  >         raise error.Abort("Lock should not be taken")
+  >     if repo.svfs.lexists(b"lock"):
+  >         raise error.Abort(b"Lock should not be taken")
   >     return orig(repo, *args, **kwargs)
   > def extsetup(ui):
-  >    extensions.wrapfunction(bundle2, 'processbundle', checklock)
+  >    extensions.wrapfunction(bundle2, b'processbundle', checklock)
   > EOF
 
   $ hg init lazylock
--- a/tests/test-bundle2-format.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bundle2-format.t	Thu Jul 19 13:55:54 2018 -0400
@@ -91,8 +91,8 @@
   >         p = p.split(b'=', 1)
   >         try:
   >             bundler.addparam(*p)
-  >         except ValueError as exc:
-  >             raise error.Abort('%s' % exc)
+  >         except error.ProgrammingError as exc:
+  >             raise error.Abort(b'%s' % exc)
   > 
   >     if opts['compress']:
   >         bundler.setcompression(opts['compress'])
@@ -184,9 +184,9 @@
   >             op = bundle2.processbundle(repo, unbundler, lambda: tr)
   >             tr.close()
   >         except error.BundleValueError as exc:
-  >             raise error.Abort('missing support for %s' % exc)
+  >             raise error.Abort(b'missing support for %s' % exc)
   >         except error.PushRaced as exc:
-  >             raise error.Abort('push race: %s' % exc)
+  >             raise error.Abort(b'push race: %s' % exc)
   >     finally:
   >         if tr is not None:
   >             tr.release()
--- a/tests/test-bundle2-pushback.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bundle2-pushback.t	Thu Jul 19 13:55:54 2018 -0400
@@ -20,18 +20,18 @@
   >     It issues an additional pushkey part to send a new
   >     bookmark back to the client"""
   >     result = bundle2.handlechangegroup(op, inpart)
-  >     if 'pushback' in op.reply.capabilities:
-  >         params = {'namespace': 'bookmarks',
-  >                   'key': 'new-server-mark',
-  >                   'old': '',
-  >                   'new': 'tip'}
+  >     if b'pushback' in op.reply.capabilities:
+  >         params = {b'namespace': b'bookmarks',
+  >                   b'key': b'new-server-mark',
+  >                   b'old': b'',
+  >                   b'new': b'tip'}
   >         encodedparams = [(k, pushkey.encode(v)) for (k,v) in params.items()]
-  >         op.reply.newpart('pushkey', mandatoryparams=encodedparams)
+  >         op.reply.newpart(b'pushkey', mandatoryparams=encodedparams)
   >     else:
-  >         op.reply.newpart('output', data='pushback not enabled')
+  >         op.reply.newpart(b'output', data=b'pushback not enabled')
   >     return result
   > _newhandlechangegroup.params = bundle2.handlechangegroup.params
-  > bundle2.parthandlermapping['changegroup'] = _newhandlechangegroup
+  > bundle2.parthandlermapping[b'changegroup'] = _newhandlechangegroup
   > EOF
 
   $ cat >> $HGRCPATH <<EOF
--- a/tests/test-bundle2-remote-changegroup.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-bundle2-remote-changegroup.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 #testcases sshv1 sshv2
 
 #if sshv2
@@ -18,7 +16,14 @@
   > Current bundle2 implementation doesn't provide a way to generate those
   > parts, so they must be created by extensions.
   > """
-  > from mercurial import bundle2, changegroup, discovery, exchange, util
+  > from mercurial import (
+  >     bundle2,
+  >     changegroup,
+  >     discovery,
+  >     exchange,
+  >     pycompat,
+  >     util,
+  > )
   > 
   > def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
   >                               b2caps=None, heads=None, common=None,
@@ -42,45 +47,45 @@
   >           Python expression as parameters. The Python expression is
   >           evaluated with eval, and is expected to be a dict.
   >     """
-  >     def newpart(name, data=''):
+  >     def newpart(name, data=b''):
   >         """wrapper around bundler.newpart adding an extra part making the
   >         client output information about each processed part"""
-  >         bundler.newpart('output', data=name)
+  >         bundler.newpart(b'output', data=name)
   >         part = bundler.newpart(name, data=data)
   >         return part
   > 
-  >     for line in open(repo.vfs.join('bundle2maker'), 'r'):
+  >     for line in open(repo.vfs.join(b'bundle2maker'), 'rb'):
   >         line = line.strip()
   >         try:
   >             verb, args = line.split(None, 1)
   >         except ValueError:
-  >             verb, args = line, ''
-  >         if verb == 'remote-changegroup':
+  >             verb, args = line, b''
+  >         if verb == b'remote-changegroup':
   >            url, file = args.split()
   >            bundledata = open(file, 'rb').read()
-  >            digest = util.digester.preferred(b2caps['digests'])
+  >            digest = util.digester.preferred(b2caps[b'digests'])
   >            d = util.digester([digest], bundledata)
-  >            part = newpart('remote-changegroup')
-  >            part.addparam('url', url)
-  >            part.addparam('size', str(len(bundledata)))
-  >            part.addparam('digests', digest)
-  >            part.addparam('digest:%s' % digest, d[digest])
-  >         elif verb == 'raw-remote-changegroup':
-  >            part = newpart('remote-changegroup')
+  >            part = newpart(b'remote-changegroup')
+  >            part.addparam(b'url', url)
+  >            part.addparam(b'size', b'%d' % len(bundledata))
+  >            part.addparam(b'digests', digest)
+  >            part.addparam(b'digest:%s' % digest, d[digest])
+  >         elif verb == b'raw-remote-changegroup':
+  >            part = newpart(b'remote-changegroup')
   >            for k, v in eval(args).items():
-  >                part.addparam(k, str(v))
-  >         elif verb == 'changegroup':
+  >                part.addparam(pycompat.sysbytes(k), pycompat.bytestr(v))
+  >         elif verb == b'changegroup':
   >             _common, heads = args.split()
   >             common.extend(repo[r].node() for r in repo.revs(_common))
   >             heads = [repo[r].node() for r in repo.revs(heads)]
   >             outgoing = discovery.outgoing(repo, common, heads)
-  >             cg = changegroup.makechangegroup(repo, outgoing, '01',
-  >                                              'changegroup')
-  >             newpart('changegroup', cg.getchunks())
+  >             cg = changegroup.makechangegroup(repo, outgoing, b'01',
+  >                                              b'changegroup')
+  >             newpart(b'changegroup', cg.getchunks())
   >         else:
   >             raise Exception('unknown verb')
   > 
-  > exchange.getbundle2partsmapping['changegroup'] = _getbundlechangegrouppart
+  > exchange.getbundle2partsmapping[b'changegroup'] = _getbundlechangegrouppart
   > EOF
 
 Start a simple HTTP server to serve bundles
--- a/tests/test-cat.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-cat.t	Thu Jul 19 13:55:54 2018 -0400
@@ -65,10 +65,10 @@
 
 Test template output
 
-  $ hg --cwd tmp cat ../b ../c -T '== {path} ({abspath}) ==\n{data}'
-  == ../b (b) ==
+  $ hg --cwd tmp cat ../b ../c -T '== {path} ({abspath}) r{rev} ==\n{data}'
+  == ../b (b) r2 ==
   1
-  == ../c (c) ==
+  == ../c (c) r2 ==
   3
 
   $ hg cat b c -Tjson --output -
--- a/tests/test-cbor.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-cbor.py	Thu Jul 19 13:55:54 2018 -0400
@@ -69,7 +69,7 @@
 
         dest = b''.join(cborutil.streamencodeindefinitebytestring(
             source, chunksize=42))
-        self.assertEqual(cbor.loads(dest), b''.join(source))
+        self.assertEqual(cbor.loads(dest), source)
 
     def testreadtoiter(self):
         source = io.BytesIO(b'\x5f\x44\xaa\xbb\xcc\xdd\x43\xee\xff\x99\xff')
--- a/tests/test-check-interfaces.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-check-interfaces.py	Thu Jul 19 13:55:54 2018 -0400
@@ -25,6 +25,8 @@
     filelog,
     httppeer,
     localrepo,
+    manifest,
+    pycompat,
     repository,
     sshpeer,
     statichttprepo,
@@ -37,7 +39,8 @@
     wireprotov2server,
 )
 
-rootdir = os.path.normpath(os.path.join(os.path.dirname(__file__), '..'))
+rootdir = pycompat.fsencode(
+    os.path.normpath(os.path.join(os.path.dirname(__file__), '..')))
 
 def checkzobject(o, allowextra=False):
     """Verify an object with a zope interface."""
@@ -106,7 +109,7 @@
                          httppeer.httpv2peer)
     ziverify.verifyClass(repository.ipeercapabilities,
                          httppeer.httpv2peer)
-    checkzobject(httppeer.httpv2peer(None, '', None, None, None, None))
+    checkzobject(httppeer.httpv2peer(None, b'', b'', None, None, None))
 
     ziverify.verifyClass(repository.ipeerbase,
                          localrepo.localpeer)
@@ -121,11 +124,11 @@
     checkzobject(wireprotov1peer.peerexecutor(None))
 
     ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv1peer)
-    checkzobject(sshpeer.sshv1peer(ui, 'ssh://localhost/foo', None, dummypipe(),
+    checkzobject(sshpeer.sshv1peer(ui, b'ssh://localhost/foo', b'', dummypipe(),
                                    dummypipe(), None, None))
 
     ziverify.verifyClass(repository.ipeerbase, sshpeer.sshv2peer)
-    checkzobject(sshpeer.sshv2peer(ui, 'ssh://localhost/foo', None, dummypipe(),
+    checkzobject(sshpeer.sshv2peer(ui, b'ssh://localhost/foo', b'', dummypipe(),
                                    dummypipe(), None, None))
 
     ziverify.verifyClass(repository.ipeerbase, bundlerepo.bundlepeer)
@@ -162,9 +165,35 @@
     checkzobject(httpv2)
 
     ziverify.verifyClass(repository.ifilestorage, filelog.filelog)
+    ziverify.verifyClass(repository.imanifestdict, manifest.manifestdict)
+    ziverify.verifyClass(repository.imanifestrevisionstored,
+                         manifest.manifestctx)
+    ziverify.verifyClass(repository.imanifestrevisionwritable,
+                         manifest.memmanifestctx)
+    ziverify.verifyClass(repository.imanifestrevisionstored,
+                         manifest.treemanifestctx)
+    ziverify.verifyClass(repository.imanifestrevisionwritable,
+                         manifest.memtreemanifestctx)
+    ziverify.verifyClass(repository.imanifestlog, manifest.manifestlog)
 
-    vfs = vfsmod.vfs('.')
-    fl = filelog.filelog(vfs, 'dummy.i')
+    vfs = vfsmod.vfs(b'.')
+    fl = filelog.filelog(vfs, b'dummy.i')
     checkzobject(fl, allowextra=True)
 
+    # Conforms to imanifestlog.
+    ml = manifest.manifestlog(vfs, repo)
+    checkzobject(ml)
+    checkzobject(repo.manifestlog)
+
+    # Conforms to imanifestrevision.
+    mctx = ml[repo[0].manifestnode()]
+    checkzobject(mctx)
+
+    # Conforms to imanifestrevisionwritable.
+    checkzobject(mctx.new())
+    checkzobject(mctx.copy())
+
+    # Conforms to imanifestdict.
+    checkzobject(mctx.read())
+
 main()
--- a/tests/test-check-module-imports.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-check-module-imports.t	Thu Jul 19 13:55:54 2018 -0400
@@ -20,6 +20,7 @@
   > -X setup.py \
   > -X contrib/debugshell.py \
   > -X contrib/hgweb.fcgi \
+  > -X contrib/packaging/hg-docker \
   > -X contrib/python-zstandard/ \
   > -X contrib/win32/hgwebdir_wsgi.py \
   > -X doc/gendoc.py \
--- a/tests/test-clone-uncompressed.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-clone-uncompressed.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require serve no-reposimplestore
+#require serve no-reposimplestore no-chg
 
 #testcases stream-legacy stream-bundle2
 
--- a/tests/test-clonebundles.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-clonebundles.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require no-reposimplestore
+#require no-reposimplestore no-chg
 
 Set up a server
 
@@ -129,6 +129,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   new changesets aaff8d2ffbbf
+  1 local changesets published
 
 Incremental pull doesn't fetch bundle
 
@@ -201,6 +202,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Feature works over SSH
 
@@ -213,6 +215,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Entry with unknown BUNDLESPEC is filtered and not used
 
@@ -232,6 +235,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Automatic fallback when all entries are filtered
 
@@ -269,6 +273,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 #else
 Python <2.7.9 will filter SNI URLs
 
@@ -373,6 +378,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Preferring bz2 type will download first entry of that type
 
@@ -385,6 +391,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Preferring multiple values of an option works
 
@@ -397,6 +404,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Sorting multiple values should get us back to original first entry
 
@@ -409,6 +417,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Preferring multiple attributes has correct order
 
@@ -421,6 +430,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Test where attribute is missing from some entries
 
@@ -440,6 +450,7 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
 
 Test interaction between clone bundles and --stream
 
@@ -545,3 +556,4 @@
   finished applying clone bundle
   searching for changes
   no changes found
+  2 local changesets published
--- a/tests/test-command-template.t	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,4884 +0,0 @@
-  $ hg init a
-  $ cd a
-  $ echo a > a
-  $ hg add a
-  $ echo line 1 > b
-  $ echo line 2 >> b
-  $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
-
-  $ hg add b
-  $ echo other 1 > c
-  $ echo other 2 >> c
-  $ echo >> c
-  $ echo other 3 >> c
-  $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
-
-  $ hg add c
-  $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
-  $ echo c >> c
-  $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
-
-  $ echo foo > .hg/branch
-  $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
-
-  $ hg co -q 3
-  $ echo other 4 >> d
-  $ hg add d
-  $ hg commit -m 'new head' -d '1500000 0' -u 'person'
-
-  $ hg merge -q foo
-  $ hg commit -m 'merge' -d '1500001 0' -u 'person'
-
-Test arithmetic operators have the right precedence:
-
-  $ hg log -l 1 -T '{date(date, "%Y") + 5 * 10} {date(date, "%Y") - 2 * 3}\n'
-  2020 1964
-  $ hg log -l 1 -T '{date(date, "%Y") * 5 + 10} {date(date, "%Y") * 3 - 2}\n'
-  9860 5908
-
-Test division:
-
-  $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
-  (template
-    (/
-      (integer '5')
-      (integer '2'))
-    (string ' ')
-    (func
-      (symbol 'mod')
-      (list
-        (integer '5')
-        (integer '2')))
-    (string '\n'))
-  2 1
-  $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
-  (template
-    (/
-      (integer '5')
-      (negate
-        (integer '2')))
-    (string ' ')
-    (func
-      (symbol 'mod')
-      (list
-        (integer '5')
-        (negate
-          (integer '2'))))
-    (string '\n'))
-  -3 -1
-  $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
-  (template
-    (/
-      (negate
-        (integer '5'))
-      (integer '2'))
-    (string ' ')
-    (func
-      (symbol 'mod')
-      (list
-        (negate
-          (integer '5'))
-        (integer '2')))
-    (string '\n'))
-  -3 1
-  $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
-  (template
-    (/
-      (negate
-        (integer '5'))
-      (negate
-        (integer '2')))
-    (string ' ')
-    (func
-      (symbol 'mod')
-      (list
-        (negate
-          (integer '5'))
-        (negate
-          (integer '2'))))
-    (string '\n'))
-  2 -1
-
-Filters bind closer than arithmetic:
-
-  $ hg debugtemplate -r0 -v '{revset(".")|count - 1}\n'
-  (template
-    (-
-      (|
-        (func
-          (symbol 'revset')
-          (string '.'))
-        (symbol 'count'))
-      (integer '1'))
-    (string '\n'))
-  0
-
-But negate binds closer still:
-
-  $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
-  (template
-    (-
-      (integer '1')
-      (|
-        (integer '3')
-        (symbol 'stringify')))
-    (string '\n'))
-  hg: parse error: arithmetic only defined on integers
-  [255]
-  $ hg debugtemplate -r0 -v '{-3|stringify}\n'
-  (template
-    (|
-      (negate
-        (integer '3'))
-      (symbol 'stringify'))
-    (string '\n'))
-  -3
-
-Filters bind as close as map operator:
-
-  $ hg debugtemplate -r0 -v '{desc|splitlines % "{line}\n"}'
-  (template
-    (%
-      (|
-        (symbol 'desc')
-        (symbol 'splitlines'))
-      (template
-        (symbol 'line')
-        (string '\n'))))
-  line 1
-  line 2
-
-Keyword arguments:
-
-  $ hg debugtemplate -r0 -v '{foo=bar|baz}'
-  (template
-    (keyvalue
-      (symbol 'foo')
-      (|
-        (symbol 'bar')
-        (symbol 'baz'))))
-  hg: parse error: can't use a key-value pair in this context
-  [255]
-
-  $ hg debugtemplate '{pad("foo", width=10, left=true)}\n'
-         foo
-
-Call function which takes named arguments by filter syntax:
-
-  $ hg debugtemplate '{" "|separate}'
-  $ hg debugtemplate '{("not", "an", "argument", "list")|separate}'
-  hg: parse error: unknown method 'list'
-  [255]
-
-Second branch starting at nullrev:
-
-  $ hg update null
-  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
-  $ echo second > second
-  $ hg add second
-  $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
-  created new head
-
-  $ echo third > third
-  $ hg add third
-  $ hg mv second fourth
-  $ hg commit -m third -d "2020-01-01 10:01"
-
-  $ hg log --template '{join(file_copies, ",\n")}\n' -r .
-  fourth (second)
-  $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
-  second -> fourth
-  $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
-  8 t
-  7 f
-
-Working-directory revision has special identifiers, though they are still
-experimental:
-
-  $ hg log -r 'wdir()' -T '{rev}:{node}\n'
-  2147483647:ffffffffffffffffffffffffffffffffffffffff
-
-Some keywords are invalid for working-directory revision, but they should
-never cause crash:
-
-  $ hg log -r 'wdir()' -T '{manifest}\n'
-  
-
-Internal resources shouldn't be exposed (issue5699):
-
-  $ hg log -r. -T '{cache}{ctx}{repo}{revcache}{templ}{ui}'
-
-Never crash on internal resource not available:
-
-  $ hg --cwd .. debugtemplate '{"c0bebeef"|shortest}\n'
-  abort: template resource not available: ctx
-  [255]
-
-  $ hg config -T '{author}'
-
-Quoting for ui.logtemplate
-
-  $ hg tip --config "ui.logtemplate={rev}\n"
-  8
-  $ hg tip --config "ui.logtemplate='{rev}\n'"
-  8
-  $ hg tip --config 'ui.logtemplate="{rev}\n"'
-  8
-  $ hg tip --config 'ui.logtemplate=n{rev}\n'
-  n8
-
-Make sure user/global hgrc does not affect tests
-
-  $ echo '[ui]' > .hg/hgrc
-  $ echo 'logtemplate =' >> .hg/hgrc
-  $ echo 'style =' >> .hg/hgrc
-
-Add some simple styles to settings
-
-  $ cat <<'EOF' >> .hg/hgrc
-  > [templates]
-  > simple = "{rev}\n"
-  > simple2 = {rev}\n
-  > rev = "should not precede {rev} keyword\n"
-  > EOF
-
-  $ hg log -l1 -Tsimple
-  8
-  $ hg log -l1 -Tsimple2
-  8
-  $ hg log -l1 -Trev
-  should not precede 8 keyword
-  $ hg log -l1 -T '{simple}'
-  8
-
-Map file shouldn't see user templates:
-
-  $ cat <<EOF > tmpl
-  > changeset = 'nothing expanded:{simple}\n'
-  > EOF
-  $ hg log -l1 --style ./tmpl
-  nothing expanded:
-
-Test templates and style maps in files:
-
-  $ echo "{rev}" > tmpl
-  $ hg log -l1 -T./tmpl
-  8
-  $ hg log -l1 -Tblah/blah
-  blah/blah (no-eol)
-
-  $ printf 'changeset = "{rev}\\n"\n' > map-simple
-  $ hg log -l1 -T./map-simple
-  8
-
- a map file may have [templates] and [templatealias] sections:
-
-  $ cat <<'EOF' > map-simple
-  > [templates]
-  > changeset = "{a}\n"
-  > [templatealias]
-  > a = rev
-  > EOF
-  $ hg log -l1 -T./map-simple
-  8
-
- so it can be included in hgrc
-
-  $ cat <<EOF > myhgrc
-  > %include $HGRCPATH
-  > %include map-simple
-  > [templates]
-  > foo = "{changeset}"
-  > EOF
-  $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
-  8
-  $ HGRCPATH=./myhgrc hg log -l1 -T'{a}\n'
-  8
-
-Test template map inheritance
-
-  $ echo "__base__ = map-cmdline.default" > map-simple
-  $ printf 'cset = "changeset: ***{rev}***\\n"\n' >> map-simple
-  $ hg log -l1 -T./map-simple
-  changeset: ***8***
-  tag:         tip
-  user:        test
-  date:        Wed Jan 01 10:01:00 2020 +0000
-  summary:     third
-  
-
-Test docheader, docfooter and separator in template map
-
-  $ cat <<'EOF' > map-myjson
-  > docheader = '\{\n'
-  > docfooter = '\n}\n'
-  > separator = ',\n'
-  > changeset = ' {dict(rev, node|short)|json}'
-  > EOF
-  $ hg log -l2 -T./map-myjson
-  {
-   {"node": "95c24699272e", "rev": 8},
-   {"node": "29114dbae42b", "rev": 7}
-  }
-
-Test docheader, docfooter and separator in [templates] section
-
-  $ cat <<'EOF' >> .hg/hgrc
-  > [templates]
-  > myjson = ' {dict(rev, node|short)|json}'
-  > myjson:docheader = '\{\n'
-  > myjson:docfooter = '\n}\n'
-  > myjson:separator = ',\n'
-  > :docheader = 'should not be selected as a docheader for literal templates\n'
-  > EOF
-  $ hg log -l2 -Tmyjson
-  {
-   {"node": "95c24699272e", "rev": 8},
-   {"node": "29114dbae42b", "rev": 7}
-  }
-  $ hg log -l1 -T'{rev}\n'
-  8
-
-Template should precede style option
-
-  $ hg log -l1 --style default -T '{rev}\n'
-  8
-
-Add a commit with empty description, to ensure that the templates
-below will omit the description line.
-
-  $ echo c >> c
-  $ hg add c
-  $ hg commit -qm ' '
-
-Default style is like normal output. Phases style should be the same
-as default style, except for extra phase lines.
-
-  $ hg log > log.out
-  $ hg log --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-  $ hg log -T phases > phases.out
-  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-
-  $ hg log -v > log.out
-  $ hg log -v --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-  $ hg log -v -T phases > phases.out
-  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-  +phase:       draft
-
-  $ hg log -q > log.out
-  $ hg log -q --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-  $ hg log -q -T phases > phases.out
-  $ cmp log.out phases.out || diff -u log.out phases.out
-
-  $ hg log --debug > log.out
-  $ hg log --debug --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-  $ hg log --debug -T phases > phases.out
-  $ cmp log.out phases.out || diff -u log.out phases.out
-
-Default style of working-directory revision should also be the same (but
-date may change while running tests):
-
-  $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
-  $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-
-  $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
-  $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-
-  $ hg log -r 'wdir()' -q > log.out
-  $ hg log -r 'wdir()' -q --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-
-  $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
-  $ hg log -r 'wdir()' --debug --style default \
-  > | sed 's|^date:.*|date:|' > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-
-Default style should also preserve color information (issue2866):
-
-  $ cp $HGRCPATH $HGRCPATH-bak
-  $ cat <<EOF >> $HGRCPATH
-  > [extensions]
-  > color=
-  > EOF
-
-  $ hg --color=debug log > log.out
-  $ hg --color=debug log --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-  $ hg --color=debug log -T phases > phases.out
-  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-
-  $ hg --color=debug -v log > log.out
-  $ hg --color=debug -v log --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-  $ hg --color=debug -v log -T phases > phases.out
-  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-  +[log.phase|phase:       draft]
-
-  $ hg --color=debug -q log > log.out
-  $ hg --color=debug -q log --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-  $ hg --color=debug -q log -T phases > phases.out
-  $ cmp log.out phases.out || diff -u log.out phases.out
-
-  $ hg --color=debug --debug log > log.out
-  $ hg --color=debug --debug log --style default > style.out
-  $ cmp log.out style.out || diff -u log.out style.out
-  $ hg --color=debug --debug log -T phases > phases.out
-  $ cmp log.out phases.out || diff -u log.out phases.out
-
-  $ mv $HGRCPATH-bak $HGRCPATH
-
-Remove commit with empty commit message, so as to not pollute further
-tests.
-
-  $ hg --config extensions.strip= strip -q .
-
-Revision with no copies (used to print a traceback):
-
-  $ hg tip -v --template '\n'
-  
-
-Compact style works:
-
-  $ hg log -Tcompact
-  8[tip]   95c24699272e   2020-01-01 10:01 +0000   test
-    third
-  
-  7:-1   29114dbae42b   1970-01-12 13:46 +0000   user
-    second
-  
-  6:5,4   d41e714fe50d   1970-01-18 08:40 +0000   person
-    merge
-  
-  5:3   13207e5a10d9   1970-01-18 08:40 +0000   person
-    new head
-  
-  4   bbe44766e73d   1970-01-17 04:53 +0000   person
-    new branch
-  
-  3   10e46f2dcbf4   1970-01-16 01:06 +0000   person
-    no user, no domain
-  
-  2   97054abb4ab8   1970-01-14 21:20 +0000   other
-    no person
-  
-  1   b608e9d1a3f0   1970-01-13 17:33 +0000   other
-    other 1
-  
-  0   1e4e1b8f71e0   1970-01-12 13:46 +0000   user
-    line 1
-  
-
-  $ hg log -v --style compact
-  8[tip]   95c24699272e   2020-01-01 10:01 +0000   test
-    third
-  
-  7:-1   29114dbae42b   1970-01-12 13:46 +0000   User Name <user@hostname>
-    second
-  
-  6:5,4   d41e714fe50d   1970-01-18 08:40 +0000   person
-    merge
-  
-  5:3   13207e5a10d9   1970-01-18 08:40 +0000   person
-    new head
-  
-  4   bbe44766e73d   1970-01-17 04:53 +0000   person
-    new branch
-  
-  3   10e46f2dcbf4   1970-01-16 01:06 +0000   person
-    no user, no domain
-  
-  2   97054abb4ab8   1970-01-14 21:20 +0000   other@place
-    no person
-  
-  1   b608e9d1a3f0   1970-01-13 17:33 +0000   A. N. Other <other@place>
-    other 1
-  other 2
-  
-  other 3
-  
-  0   1e4e1b8f71e0   1970-01-12 13:46 +0000   User Name <user@hostname>
-    line 1
-  line 2
-  
-
-  $ hg log --debug --style compact
-  8[tip]:7,-1   95c24699272e   2020-01-01 10:01 +0000   test
-    third
-  
-  7:-1,-1   29114dbae42b   1970-01-12 13:46 +0000   User Name <user@hostname>
-    second
-  
-  6:5,4   d41e714fe50d   1970-01-18 08:40 +0000   person
-    merge
-  
-  5:3,-1   13207e5a10d9   1970-01-18 08:40 +0000   person
-    new head
-  
-  4:3,-1   bbe44766e73d   1970-01-17 04:53 +0000   person
-    new branch
-  
-  3:2,-1   10e46f2dcbf4   1970-01-16 01:06 +0000   person
-    no user, no domain
-  
-  2:1,-1   97054abb4ab8   1970-01-14 21:20 +0000   other@place
-    no person
-  
-  1:0,-1   b608e9d1a3f0   1970-01-13 17:33 +0000   A. N. Other <other@place>
-    other 1
-  other 2
-  
-  other 3
-  
-  0:-1,-1   1e4e1b8f71e0   1970-01-12 13:46 +0000   User Name <user@hostname>
-    line 1
-  line 2
-  
-
-Test xml styles:
-
-  $ hg log --style xml -r 'not all()'
-  <?xml version="1.0"?>
-  <log>
-  </log>
-
-  $ hg log --style xml
-  <?xml version="1.0"?>
-  <log>
-  <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
-  <tag>tip</tag>
-  <author email="test">test</author>
-  <date>2020-01-01T10:01:00+00:00</date>
-  <msg xml:space="preserve">third</msg>
-  </logentry>
-  <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="user@hostname">User Name</author>
-  <date>1970-01-12T13:46:40+00:00</date>
-  <msg xml:space="preserve">second</msg>
-  </logentry>
-  <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
-  <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
-  <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
-  <author email="person">person</author>
-  <date>1970-01-18T08:40:01+00:00</date>
-  <msg xml:space="preserve">merge</msg>
-  </logentry>
-  <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
-  <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
-  <author email="person">person</author>
-  <date>1970-01-18T08:40:00+00:00</date>
-  <msg xml:space="preserve">new head</msg>
-  </logentry>
-  <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
-  <branch>foo</branch>
-  <author email="person">person</author>
-  <date>1970-01-17T04:53:20+00:00</date>
-  <msg xml:space="preserve">new branch</msg>
-  </logentry>
-  <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
-  <author email="person">person</author>
-  <date>1970-01-16T01:06:40+00:00</date>
-  <msg xml:space="preserve">no user, no domain</msg>
-  </logentry>
-  <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
-  <author email="other@place">other</author>
-  <date>1970-01-14T21:20:00+00:00</date>
-  <msg xml:space="preserve">no person</msg>
-  </logentry>
-  <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
-  <author email="other@place">A. N. Other</author>
-  <date>1970-01-13T17:33:20+00:00</date>
-  <msg xml:space="preserve">other 1
-  other 2
-  
-  other 3</msg>
-  </logentry>
-  <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
-  <author email="user@hostname">User Name</author>
-  <date>1970-01-12T13:46:40+00:00</date>
-  <msg xml:space="preserve">line 1
-  line 2</msg>
-  </logentry>
-  </log>
-
-  $ hg log -v --style xml
-  <?xml version="1.0"?>
-  <log>
-  <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
-  <tag>tip</tag>
-  <author email="test">test</author>
-  <date>2020-01-01T10:01:00+00:00</date>
-  <msg xml:space="preserve">third</msg>
-  <paths>
-  <path action="A">fourth</path>
-  <path action="A">third</path>
-  <path action="R">second</path>
-  </paths>
-  <copies>
-  <copy source="second">fourth</copy>
-  </copies>
-  </logentry>
-  <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="user@hostname">User Name</author>
-  <date>1970-01-12T13:46:40+00:00</date>
-  <msg xml:space="preserve">second</msg>
-  <paths>
-  <path action="A">second</path>
-  </paths>
-  </logentry>
-  <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
-  <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
-  <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
-  <author email="person">person</author>
-  <date>1970-01-18T08:40:01+00:00</date>
-  <msg xml:space="preserve">merge</msg>
-  <paths>
-  </paths>
-  </logentry>
-  <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
-  <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
-  <author email="person">person</author>
-  <date>1970-01-18T08:40:00+00:00</date>
-  <msg xml:space="preserve">new head</msg>
-  <paths>
-  <path action="A">d</path>
-  </paths>
-  </logentry>
-  <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
-  <branch>foo</branch>
-  <author email="person">person</author>
-  <date>1970-01-17T04:53:20+00:00</date>
-  <msg xml:space="preserve">new branch</msg>
-  <paths>
-  </paths>
-  </logentry>
-  <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
-  <author email="person">person</author>
-  <date>1970-01-16T01:06:40+00:00</date>
-  <msg xml:space="preserve">no user, no domain</msg>
-  <paths>
-  <path action="M">c</path>
-  </paths>
-  </logentry>
-  <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
-  <author email="other@place">other</author>
-  <date>1970-01-14T21:20:00+00:00</date>
-  <msg xml:space="preserve">no person</msg>
-  <paths>
-  <path action="A">c</path>
-  </paths>
-  </logentry>
-  <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
-  <author email="other@place">A. N. Other</author>
-  <date>1970-01-13T17:33:20+00:00</date>
-  <msg xml:space="preserve">other 1
-  other 2
-  
-  other 3</msg>
-  <paths>
-  <path action="A">b</path>
-  </paths>
-  </logentry>
-  <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
-  <author email="user@hostname">User Name</author>
-  <date>1970-01-12T13:46:40+00:00</date>
-  <msg xml:space="preserve">line 1
-  line 2</msg>
-  <paths>
-  <path action="A">a</path>
-  </paths>
-  </logentry>
-  </log>
-
-  $ hg log --debug --style xml
-  <?xml version="1.0"?>
-  <log>
-  <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
-  <tag>tip</tag>
-  <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="test">test</author>
-  <date>2020-01-01T10:01:00+00:00</date>
-  <msg xml:space="preserve">third</msg>
-  <paths>
-  <path action="A">fourth</path>
-  <path action="A">third</path>
-  <path action="R">second</path>
-  </paths>
-  <copies>
-  <copy source="second">fourth</copy>
-  </copies>
-  <extra key="branch">default</extra>
-  </logentry>
-  <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="user@hostname">User Name</author>
-  <date>1970-01-12T13:46:40+00:00</date>
-  <msg xml:space="preserve">second</msg>
-  <paths>
-  <path action="A">second</path>
-  </paths>
-  <extra key="branch">default</extra>
-  </logentry>
-  <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
-  <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
-  <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
-  <author email="person">person</author>
-  <date>1970-01-18T08:40:01+00:00</date>
-  <msg xml:space="preserve">merge</msg>
-  <paths>
-  </paths>
-  <extra key="branch">default</extra>
-  </logentry>
-  <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
-  <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="person">person</author>
-  <date>1970-01-18T08:40:00+00:00</date>
-  <msg xml:space="preserve">new head</msg>
-  <paths>
-  <path action="A">d</path>
-  </paths>
-  <extra key="branch">default</extra>
-  </logentry>
-  <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
-  <branch>foo</branch>
-  <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="person">person</author>
-  <date>1970-01-17T04:53:20+00:00</date>
-  <msg xml:space="preserve">new branch</msg>
-  <paths>
-  </paths>
-  <extra key="branch">foo</extra>
-  </logentry>
-  <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
-  <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="person">person</author>
-  <date>1970-01-16T01:06:40+00:00</date>
-  <msg xml:space="preserve">no user, no domain</msg>
-  <paths>
-  <path action="M">c</path>
-  </paths>
-  <extra key="branch">default</extra>
-  </logentry>
-  <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
-  <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="other@place">other</author>
-  <date>1970-01-14T21:20:00+00:00</date>
-  <msg xml:space="preserve">no person</msg>
-  <paths>
-  <path action="A">c</path>
-  </paths>
-  <extra key="branch">default</extra>
-  </logentry>
-  <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
-  <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="other@place">A. N. Other</author>
-  <date>1970-01-13T17:33:20+00:00</date>
-  <msg xml:space="preserve">other 1
-  other 2
-  
-  other 3</msg>
-  <paths>
-  <path action="A">b</path>
-  </paths>
-  <extra key="branch">default</extra>
-  </logentry>
-  <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <parent revision="-1" node="0000000000000000000000000000000000000000" />
-  <author email="user@hostname">User Name</author>
-  <date>1970-01-12T13:46:40+00:00</date>
-  <msg xml:space="preserve">line 1
-  line 2</msg>
-  <paths>
-  <path action="A">a</path>
-  </paths>
-  <extra key="branch">default</extra>
-  </logentry>
-  </log>
-
-
-Test JSON style:
-
-  $ hg log -k nosuch -Tjson
-  [
-  ]
-
-  $ hg log -qr . -Tjson
-  [
-   {
-    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
-    "rev": 8
-   }
-  ]
-
-  $ hg log -vpr . -Tjson --stat
-  [
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1577872860, 0],
-    "desc": "third",
-    "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n",
-    "diffstat": " fourth |  1 +\n second |  1 -\n third  |  1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
-    "files": ["fourth", "second", "third"],
-    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
-    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
-    "phase": "draft",
-    "rev": 8,
-    "tags": ["tip"],
-    "user": "test"
-   }
-  ]
-
-honor --git but not format-breaking diffopts
-  $ hg --config diff.noprefix=True log --git -vpr . -Tjson
-  [
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1577872860, 0],
-    "desc": "third",
-    "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n",
-    "files": ["fourth", "second", "third"],
-    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
-    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
-    "phase": "draft",
-    "rev": 8,
-    "tags": ["tip"],
-    "user": "test"
-   }
-  ]
-
-  $ hg log -T json
-  [
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1577872860, 0],
-    "desc": "third",
-    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
-    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
-    "phase": "draft",
-    "rev": 8,
-    "tags": ["tip"],
-    "user": "test"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1000000, 0],
-    "desc": "second",
-    "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
-    "parents": ["0000000000000000000000000000000000000000"],
-    "phase": "draft",
-    "rev": 7,
-    "tags": [],
-    "user": "User Name <user@hostname>"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1500001, 0],
-    "desc": "merge",
-    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
-    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
-    "phase": "draft",
-    "rev": 6,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1500000, 0],
-    "desc": "new head",
-    "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
-    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
-    "phase": "draft",
-    "rev": 5,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "bookmarks": [],
-    "branch": "foo",
-    "date": [1400000, 0],
-    "desc": "new branch",
-    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
-    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
-    "phase": "draft",
-    "rev": 4,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1300000, 0],
-    "desc": "no user, no domain",
-    "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
-    "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
-    "phase": "draft",
-    "rev": 3,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1200000, 0],
-    "desc": "no person",
-    "node": "97054abb4ab824450e9164180baf491ae0078465",
-    "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
-    "phase": "draft",
-    "rev": 2,
-    "tags": [],
-    "user": "other@place"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1100000, 0],
-    "desc": "other 1\nother 2\n\nother 3",
-    "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
-    "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
-    "phase": "draft",
-    "rev": 1,
-    "tags": [],
-    "user": "A. N. Other <other@place>"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1000000, 0],
-    "desc": "line 1\nline 2",
-    "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
-    "parents": ["0000000000000000000000000000000000000000"],
-    "phase": "draft",
-    "rev": 0,
-    "tags": [],
-    "user": "User Name <user@hostname>"
-   }
-  ]
-
-  $ hg heads -v -Tjson
-  [
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1577872860, 0],
-    "desc": "third",
-    "files": ["fourth", "second", "third"],
-    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
-    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
-    "phase": "draft",
-    "rev": 8,
-    "tags": ["tip"],
-    "user": "test"
-   },
-   {
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1500001, 0],
-    "desc": "merge",
-    "files": [],
-    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
-    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
-    "phase": "draft",
-    "rev": 6,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "bookmarks": [],
-    "branch": "foo",
-    "date": [1400000, 0],
-    "desc": "new branch",
-    "files": [],
-    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
-    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
-    "phase": "draft",
-    "rev": 4,
-    "tags": [],
-    "user": "person"
-   }
-  ]
-
-  $ hg log --debug -Tjson
-  [
-   {
-    "added": ["fourth", "third"],
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1577872860, 0],
-    "desc": "third",
-    "extra": {"branch": "default"},
-    "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
-    "modified": [],
-    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
-    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
-    "phase": "draft",
-    "removed": ["second"],
-    "rev": 8,
-    "tags": ["tip"],
-    "user": "test"
-   },
-   {
-    "added": ["second"],
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1000000, 0],
-    "desc": "second",
-    "extra": {"branch": "default"},
-    "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
-    "modified": [],
-    "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
-    "parents": ["0000000000000000000000000000000000000000"],
-    "phase": "draft",
-    "removed": [],
-    "rev": 7,
-    "tags": [],
-    "user": "User Name <user@hostname>"
-   },
-   {
-    "added": [],
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1500001, 0],
-    "desc": "merge",
-    "extra": {"branch": "default"},
-    "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
-    "modified": [],
-    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
-    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
-    "phase": "draft",
-    "removed": [],
-    "rev": 6,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "added": ["d"],
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1500000, 0],
-    "desc": "new head",
-    "extra": {"branch": "default"},
-    "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
-    "modified": [],
-    "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
-    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
-    "phase": "draft",
-    "removed": [],
-    "rev": 5,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "added": [],
-    "bookmarks": [],
-    "branch": "foo",
-    "date": [1400000, 0],
-    "desc": "new branch",
-    "extra": {"branch": "foo"},
-    "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
-    "modified": [],
-    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
-    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
-    "phase": "draft",
-    "removed": [],
-    "rev": 4,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "added": [],
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1300000, 0],
-    "desc": "no user, no domain",
-    "extra": {"branch": "default"},
-    "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
-    "modified": ["c"],
-    "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
-    "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
-    "phase": "draft",
-    "removed": [],
-    "rev": 3,
-    "tags": [],
-    "user": "person"
-   },
-   {
-    "added": ["c"],
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1200000, 0],
-    "desc": "no person",
-    "extra": {"branch": "default"},
-    "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
-    "modified": [],
-    "node": "97054abb4ab824450e9164180baf491ae0078465",
-    "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
-    "phase": "draft",
-    "removed": [],
-    "rev": 2,
-    "tags": [],
-    "user": "other@place"
-   },
-   {
-    "added": ["b"],
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1100000, 0],
-    "desc": "other 1\nother 2\n\nother 3",
-    "extra": {"branch": "default"},
-    "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
-    "modified": [],
-    "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
-    "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
-    "phase": "draft",
-    "removed": [],
-    "rev": 1,
-    "tags": [],
-    "user": "A. N. Other <other@place>"
-   },
-   {
-    "added": ["a"],
-    "bookmarks": [],
-    "branch": "default",
-    "date": [1000000, 0],
-    "desc": "line 1\nline 2",
-    "extra": {"branch": "default"},
-    "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
-    "modified": [],
-    "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
-    "parents": ["0000000000000000000000000000000000000000"],
-    "phase": "draft",
-    "removed": [],
-    "rev": 0,
-    "tags": [],
-    "user": "User Name <user@hostname>"
-   }
-  ]
-
-Error if style not readable:
-
-#if unix-permissions no-root
-  $ touch q
-  $ chmod 0 q
-  $ hg log --style ./q
-  abort: Permission denied: ./q
-  [255]
-#endif
-
-Error if no style:
-
-  $ hg log --style notexist
-  abort: style 'notexist' not found
-  (available styles: bisect, changelog, compact, default, phases, show, status, xml)
-  [255]
-
-  $ hg log -T list
-  available styles: bisect, changelog, compact, default, phases, show, status, xml
-  abort: specify a template
-  [255]
-
-Error if style missing key:
-
-  $ echo 'q = q' > t
-  $ hg log --style ./t
-  abort: "changeset" not in template map
-  [255]
-
-Error if style missing value:
-
-  $ echo 'changeset =' > t
-  $ hg log --style t
-  hg: parse error at t:1: missing value
-  [255]
-
-Error if include fails:
-
-  $ echo 'changeset = q' >> t
-#if unix-permissions no-root
-  $ hg log --style ./t
-  abort: template file ./q: Permission denied
-  [255]
-  $ rm -f q
-#endif
-
-Include works:
-
-  $ echo '{rev}' > q
-  $ hg log --style ./t
-  8
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-
-Check that recursive reference does not fall into RuntimeError (issue4758):
-
- common mistake:
-
-  $ cat << EOF > issue4758
-  > changeset = '{changeset}\n'
-  > EOF
-  $ hg log --style ./issue4758
-  abort: recursive reference 'changeset' in template
-  [255]
-
- circular reference:
-
-  $ cat << EOF > issue4758
-  > changeset = '{foo}'
-  > foo = '{changeset}'
-  > EOF
-  $ hg log --style ./issue4758
-  abort: recursive reference 'foo' in template
-  [255]
-
- buildmap() -> gettemplate(), where no thunk was made:
-
-  $ cat << EOF > issue4758
-  > changeset = '{files % changeset}\n'
-  > EOF
-  $ hg log --style ./issue4758
-  abort: recursive reference 'changeset' in template
-  [255]
-
- not a recursion if a keyword of the same name exists:
-
-  $ cat << EOF > issue4758
-  > changeset = '{tags % rev}'
-  > rev = '{rev} {tag}\n'
-  > EOF
-  $ hg log --style ./issue4758 -r tip
-  8 tip
-
-Check that {phase} works correctly on parents:
-
-  $ cat << EOF > parentphase
-  > changeset_debug = '{rev} ({phase}):{parents}\n'
-  > parent = ' {rev} ({phase})'
-  > EOF
-  $ hg phase -r 5 --public
-  $ hg phase -r 7 --secret --force
-  $ hg log --debug -G --style ./parentphase
-  @  8 (secret): 7 (secret) -1 (public)
-  |
-  o  7 (secret): -1 (public) -1 (public)
-  
-  o    6 (draft): 5 (public) 4 (draft)
-  |\
-  | o  5 (public): 3 (public) -1 (public)
-  | |
-  o |  4 (draft): 3 (public) -1 (public)
-  |/
-  o  3 (public): 2 (public) -1 (public)
-  |
-  o  2 (public): 1 (public) -1 (public)
-  |
-  o  1 (public): 0 (public) -1 (public)
-  |
-  o  0 (public): -1 (public) -1 (public)
-  
-
-Missing non-standard names give no error (backward compatibility):
-
-  $ echo "changeset = '{c}'" > t
-  $ hg log --style ./t
-
-Defining non-standard name works:
-
-  $ cat <<EOF > t
-  > changeset = '{c}'
-  > c = q
-  > EOF
-  $ hg log --style ./t
-  8
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-
-ui.style works:
-
-  $ echo '[ui]' > .hg/hgrc
-  $ echo 'style = t' >> .hg/hgrc
-  $ hg log
-  8
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-
-
-Issue338:
-
-  $ hg log --style=changelog > changelog
-
-  $ cat changelog
-  2020-01-01  test  <test>
-  
-  	* fourth, second, third:
-  	third
-  	[95c24699272e] [tip]
-  
-  1970-01-12  User Name  <user@hostname>
-  
-  	* second:
-  	second
-  	[29114dbae42b]
-  
-  1970-01-18  person  <person>
-  
-  	* merge
-  	[d41e714fe50d]
-  
-  	* d:
-  	new head
-  	[13207e5a10d9]
-  
-  1970-01-17  person  <person>
-  
-  	* new branch
-  	[bbe44766e73d] <foo>
-  
-  1970-01-16  person  <person>
-  
-  	* c:
-  	no user, no domain
-  	[10e46f2dcbf4]
-  
-  1970-01-14  other  <other@place>
-  
-  	* c:
-  	no person
-  	[97054abb4ab8]
-  
-  1970-01-13  A. N. Other  <other@place>
-  
-  	* b:
-  	other 1 other 2
-  
-  	other 3
-  	[b608e9d1a3f0]
-  
-  1970-01-12  User Name  <user@hostname>
-  
-  	* a:
-  	line 1 line 2
-  	[1e4e1b8f71e0]
-  
-
-Issue2130: xml output for 'hg heads' is malformed
-
-  $ hg heads --style changelog
-  2020-01-01  test  <test>
-  
-  	* fourth, second, third:
-  	third
-  	[95c24699272e] [tip]
-  
-  1970-01-18  person  <person>
-  
-  	* merge
-  	[d41e714fe50d]
-  
-  1970-01-17  person  <person>
-  
-  	* new branch
-  	[bbe44766e73d] <foo>
-  
-
-Keys work:
-
-  $ for key in author branch branches date desc file_adds file_dels file_mods \
-  >         file_copies file_copies_switch files \
-  >         manifest node parents rev tags diffstat extras \
-  >         p1rev p2rev p1node p2node; do
-  >     for mode in '' --verbose --debug; do
-  >         hg log $mode --template "$key$mode: {$key}\n"
-  >     done
-  > done
-  author: test
-  author: User Name <user@hostname>
-  author: person
-  author: person
-  author: person
-  author: person
-  author: other@place
-  author: A. N. Other <other@place>
-  author: User Name <user@hostname>
-  author--verbose: test
-  author--verbose: User Name <user@hostname>
-  author--verbose: person
-  author--verbose: person
-  author--verbose: person
-  author--verbose: person
-  author--verbose: other@place
-  author--verbose: A. N. Other <other@place>
-  author--verbose: User Name <user@hostname>
-  author--debug: test
-  author--debug: User Name <user@hostname>
-  author--debug: person
-  author--debug: person
-  author--debug: person
-  author--debug: person
-  author--debug: other@place
-  author--debug: A. N. Other <other@place>
-  author--debug: User Name <user@hostname>
-  branch: default
-  branch: default
-  branch: default
-  branch: default
-  branch: foo
-  branch: default
-  branch: default
-  branch: default
-  branch: default
-  branch--verbose: default
-  branch--verbose: default
-  branch--verbose: default
-  branch--verbose: default
-  branch--verbose: foo
-  branch--verbose: default
-  branch--verbose: default
-  branch--verbose: default
-  branch--verbose: default
-  branch--debug: default
-  branch--debug: default
-  branch--debug: default
-  branch--debug: default
-  branch--debug: foo
-  branch--debug: default
-  branch--debug: default
-  branch--debug: default
-  branch--debug: default
-  branches: 
-  branches: 
-  branches: 
-  branches: 
-  branches: foo
-  branches: 
-  branches: 
-  branches: 
-  branches: 
-  branches--verbose: 
-  branches--verbose: 
-  branches--verbose: 
-  branches--verbose: 
-  branches--verbose: foo
-  branches--verbose: 
-  branches--verbose: 
-  branches--verbose: 
-  branches--verbose: 
-  branches--debug: 
-  branches--debug: 
-  branches--debug: 
-  branches--debug: 
-  branches--debug: foo
-  branches--debug: 
-  branches--debug: 
-  branches--debug: 
-  branches--debug: 
-  date: 1577872860.00
-  date: 1000000.00
-  date: 1500001.00
-  date: 1500000.00
-  date: 1400000.00
-  date: 1300000.00
-  date: 1200000.00
-  date: 1100000.00
-  date: 1000000.00
-  date--verbose: 1577872860.00
-  date--verbose: 1000000.00
-  date--verbose: 1500001.00
-  date--verbose: 1500000.00
-  date--verbose: 1400000.00
-  date--verbose: 1300000.00
-  date--verbose: 1200000.00
-  date--verbose: 1100000.00
-  date--verbose: 1000000.00
-  date--debug: 1577872860.00
-  date--debug: 1000000.00
-  date--debug: 1500001.00
-  date--debug: 1500000.00
-  date--debug: 1400000.00
-  date--debug: 1300000.00
-  date--debug: 1200000.00
-  date--debug: 1100000.00
-  date--debug: 1000000.00
-  desc: third
-  desc: second
-  desc: merge
-  desc: new head
-  desc: new branch
-  desc: no user, no domain
-  desc: no person
-  desc: other 1
-  other 2
-  
-  other 3
-  desc: line 1
-  line 2
-  desc--verbose: third
-  desc--verbose: second
-  desc--verbose: merge
-  desc--verbose: new head
-  desc--verbose: new branch
-  desc--verbose: no user, no domain
-  desc--verbose: no person
-  desc--verbose: other 1
-  other 2
-  
-  other 3
-  desc--verbose: line 1
-  line 2
-  desc--debug: third
-  desc--debug: second
-  desc--debug: merge
-  desc--debug: new head
-  desc--debug: new branch
-  desc--debug: no user, no domain
-  desc--debug: no person
-  desc--debug: other 1
-  other 2
-  
-  other 3
-  desc--debug: line 1
-  line 2
-  file_adds: fourth third
-  file_adds: second
-  file_adds: 
-  file_adds: d
-  file_adds: 
-  file_adds: 
-  file_adds: c
-  file_adds: b
-  file_adds: a
-  file_adds--verbose: fourth third
-  file_adds--verbose: second
-  file_adds--verbose: 
-  file_adds--verbose: d
-  file_adds--verbose: 
-  file_adds--verbose: 
-  file_adds--verbose: c
-  file_adds--verbose: b
-  file_adds--verbose: a
-  file_adds--debug: fourth third
-  file_adds--debug: second
-  file_adds--debug: 
-  file_adds--debug: d
-  file_adds--debug: 
-  file_adds--debug: 
-  file_adds--debug: c
-  file_adds--debug: b
-  file_adds--debug: a
-  file_dels: second
-  file_dels: 
-  file_dels: 
-  file_dels: 
-  file_dels: 
-  file_dels: 
-  file_dels: 
-  file_dels: 
-  file_dels: 
-  file_dels--verbose: second
-  file_dels--verbose: 
-  file_dels--verbose: 
-  file_dels--verbose: 
-  file_dels--verbose: 
-  file_dels--verbose: 
-  file_dels--verbose: 
-  file_dels--verbose: 
-  file_dels--verbose: 
-  file_dels--debug: second
-  file_dels--debug: 
-  file_dels--debug: 
-  file_dels--debug: 
-  file_dels--debug: 
-  file_dels--debug: 
-  file_dels--debug: 
-  file_dels--debug: 
-  file_dels--debug: 
-  file_mods: 
-  file_mods: 
-  file_mods: 
-  file_mods: 
-  file_mods: 
-  file_mods: c
-  file_mods: 
-  file_mods: 
-  file_mods: 
-  file_mods--verbose: 
-  file_mods--verbose: 
-  file_mods--verbose: 
-  file_mods--verbose: 
-  file_mods--verbose: 
-  file_mods--verbose: c
-  file_mods--verbose: 
-  file_mods--verbose: 
-  file_mods--verbose: 
-  file_mods--debug: 
-  file_mods--debug: 
-  file_mods--debug: 
-  file_mods--debug: 
-  file_mods--debug: 
-  file_mods--debug: c
-  file_mods--debug: 
-  file_mods--debug: 
-  file_mods--debug: 
-  file_copies: fourth (second)
-  file_copies: 
-  file_copies: 
-  file_copies: 
-  file_copies: 
-  file_copies: 
-  file_copies: 
-  file_copies: 
-  file_copies: 
-  file_copies--verbose: fourth (second)
-  file_copies--verbose: 
-  file_copies--verbose: 
-  file_copies--verbose: 
-  file_copies--verbose: 
-  file_copies--verbose: 
-  file_copies--verbose: 
-  file_copies--verbose: 
-  file_copies--verbose: 
-  file_copies--debug: fourth (second)
-  file_copies--debug: 
-  file_copies--debug: 
-  file_copies--debug: 
-  file_copies--debug: 
-  file_copies--debug: 
-  file_copies--debug: 
-  file_copies--debug: 
-  file_copies--debug: 
-  file_copies_switch: 
-  file_copies_switch: 
-  file_copies_switch: 
-  file_copies_switch: 
-  file_copies_switch: 
-  file_copies_switch: 
-  file_copies_switch: 
-  file_copies_switch: 
-  file_copies_switch: 
-  file_copies_switch--verbose: 
-  file_copies_switch--verbose: 
-  file_copies_switch--verbose: 
-  file_copies_switch--verbose: 
-  file_copies_switch--verbose: 
-  file_copies_switch--verbose: 
-  file_copies_switch--verbose: 
-  file_copies_switch--verbose: 
-  file_copies_switch--verbose: 
-  file_copies_switch--debug: 
-  file_copies_switch--debug: 
-  file_copies_switch--debug: 
-  file_copies_switch--debug: 
-  file_copies_switch--debug: 
-  file_copies_switch--debug: 
-  file_copies_switch--debug: 
-  file_copies_switch--debug: 
-  file_copies_switch--debug: 
-  files: fourth second third
-  files: second
-  files: 
-  files: d
-  files: 
-  files: c
-  files: c
-  files: b
-  files: a
-  files--verbose: fourth second third
-  files--verbose: second
-  files--verbose: 
-  files--verbose: d
-  files--verbose: 
-  files--verbose: c
-  files--verbose: c
-  files--verbose: b
-  files--verbose: a
-  files--debug: fourth second third
-  files--debug: second
-  files--debug: 
-  files--debug: d
-  files--debug: 
-  files--debug: c
-  files--debug: c
-  files--debug: b
-  files--debug: a
-  manifest: 6:94961b75a2da
-  manifest: 5:f2dbc354b94e
-  manifest: 4:4dc3def4f9b4
-  manifest: 4:4dc3def4f9b4
-  manifest: 3:cb5a1327723b
-  manifest: 3:cb5a1327723b
-  manifest: 2:6e0e82995c35
-  manifest: 1:4e8d705b1e53
-  manifest: 0:a0c8bcbbb45c
-  manifest--verbose: 6:94961b75a2da
-  manifest--verbose: 5:f2dbc354b94e
-  manifest--verbose: 4:4dc3def4f9b4
-  manifest--verbose: 4:4dc3def4f9b4
-  manifest--verbose: 3:cb5a1327723b
-  manifest--verbose: 3:cb5a1327723b
-  manifest--verbose: 2:6e0e82995c35
-  manifest--verbose: 1:4e8d705b1e53
-  manifest--verbose: 0:a0c8bcbbb45c
-  manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
-  manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
-  manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
-  manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
-  manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
-  manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
-  manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
-  manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
-  manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
-  node: 95c24699272ef57d062b8bccc32c878bf841784a
-  node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
-  node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
-  node: 13207e5a10d9fd28ec424934298e176197f2c67f
-  node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
-  node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  node: 97054abb4ab824450e9164180baf491ae0078465
-  node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
-  node: 1e4e1b8f71e05681d422154f5421e385fec3454f
-  node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
-  node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
-  node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
-  node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
-  node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
-  node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  node--verbose: 97054abb4ab824450e9164180baf491ae0078465
-  node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
-  node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
-  node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
-  node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
-  node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
-  node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
-  node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
-  node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  node--debug: 97054abb4ab824450e9164180baf491ae0078465
-  node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
-  node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
-  parents: 
-  parents: -1:000000000000 
-  parents: 5:13207e5a10d9 4:bbe44766e73d 
-  parents: 3:10e46f2dcbf4 
-  parents: 
-  parents: 
-  parents: 
-  parents: 
-  parents: 
-  parents--verbose: 
-  parents--verbose: -1:000000000000 
-  parents--verbose: 5:13207e5a10d9 4:bbe44766e73d 
-  parents--verbose: 3:10e46f2dcbf4 
-  parents--verbose: 
-  parents--verbose: 
-  parents--verbose: 
-  parents--verbose: 
-  parents--verbose: 
-  parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000 
-  parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000 
-  parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74 
-  parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000 
-  parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000 
-  parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000 
-  parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000 
-  parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000 
-  parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000 
-  rev: 8
-  rev: 7
-  rev: 6
-  rev: 5
-  rev: 4
-  rev: 3
-  rev: 2
-  rev: 1
-  rev: 0
-  rev--verbose: 8
-  rev--verbose: 7
-  rev--verbose: 6
-  rev--verbose: 5
-  rev--verbose: 4
-  rev--verbose: 3
-  rev--verbose: 2
-  rev--verbose: 1
-  rev--verbose: 0
-  rev--debug: 8
-  rev--debug: 7
-  rev--debug: 6
-  rev--debug: 5
-  rev--debug: 4
-  rev--debug: 3
-  rev--debug: 2
-  rev--debug: 1
-  rev--debug: 0
-  tags: tip
-  tags: 
-  tags: 
-  tags: 
-  tags: 
-  tags: 
-  tags: 
-  tags: 
-  tags: 
-  tags--verbose: tip
-  tags--verbose: 
-  tags--verbose: 
-  tags--verbose: 
-  tags--verbose: 
-  tags--verbose: 
-  tags--verbose: 
-  tags--verbose: 
-  tags--verbose: 
-  tags--debug: tip
-  tags--debug: 
-  tags--debug: 
-  tags--debug: 
-  tags--debug: 
-  tags--debug: 
-  tags--debug: 
-  tags--debug: 
-  tags--debug: 
-  diffstat: 3: +2/-1
-  diffstat: 1: +1/-0
-  diffstat: 0: +0/-0
-  diffstat: 1: +1/-0
-  diffstat: 0: +0/-0
-  diffstat: 1: +1/-0
-  diffstat: 1: +4/-0
-  diffstat: 1: +2/-0
-  diffstat: 1: +1/-0
-  diffstat--verbose: 3: +2/-1
-  diffstat--verbose: 1: +1/-0
-  diffstat--verbose: 0: +0/-0
-  diffstat--verbose: 1: +1/-0
-  diffstat--verbose: 0: +0/-0
-  diffstat--verbose: 1: +1/-0
-  diffstat--verbose: 1: +4/-0
-  diffstat--verbose: 1: +2/-0
-  diffstat--verbose: 1: +1/-0
-  diffstat--debug: 3: +2/-1
-  diffstat--debug: 1: +1/-0
-  diffstat--debug: 0: +0/-0
-  diffstat--debug: 1: +1/-0
-  diffstat--debug: 0: +0/-0
-  diffstat--debug: 1: +1/-0
-  diffstat--debug: 1: +4/-0
-  diffstat--debug: 1: +2/-0
-  diffstat--debug: 1: +1/-0
-  extras: branch=default
-  extras: branch=default
-  extras: branch=default
-  extras: branch=default
-  extras: branch=foo
-  extras: branch=default
-  extras: branch=default
-  extras: branch=default
-  extras: branch=default
-  extras--verbose: branch=default
-  extras--verbose: branch=default
-  extras--verbose: branch=default
-  extras--verbose: branch=default
-  extras--verbose: branch=foo
-  extras--verbose: branch=default
-  extras--verbose: branch=default
-  extras--verbose: branch=default
-  extras--verbose: branch=default
-  extras--debug: branch=default
-  extras--debug: branch=default
-  extras--debug: branch=default
-  extras--debug: branch=default
-  extras--debug: branch=foo
-  extras--debug: branch=default
-  extras--debug: branch=default
-  extras--debug: branch=default
-  extras--debug: branch=default
-  p1rev: 7
-  p1rev: -1
-  p1rev: 5
-  p1rev: 3
-  p1rev: 3
-  p1rev: 2
-  p1rev: 1
-  p1rev: 0
-  p1rev: -1
-  p1rev--verbose: 7
-  p1rev--verbose: -1
-  p1rev--verbose: 5
-  p1rev--verbose: 3
-  p1rev--verbose: 3
-  p1rev--verbose: 2
-  p1rev--verbose: 1
-  p1rev--verbose: 0
-  p1rev--verbose: -1
-  p1rev--debug: 7
-  p1rev--debug: -1
-  p1rev--debug: 5
-  p1rev--debug: 3
-  p1rev--debug: 3
-  p1rev--debug: 2
-  p1rev--debug: 1
-  p1rev--debug: 0
-  p1rev--debug: -1
-  p2rev: -1
-  p2rev: -1
-  p2rev: 4
-  p2rev: -1
-  p2rev: -1
-  p2rev: -1
-  p2rev: -1
-  p2rev: -1
-  p2rev: -1
-  p2rev--verbose: -1
-  p2rev--verbose: -1
-  p2rev--verbose: 4
-  p2rev--verbose: -1
-  p2rev--verbose: -1
-  p2rev--verbose: -1
-  p2rev--verbose: -1
-  p2rev--verbose: -1
-  p2rev--verbose: -1
-  p2rev--debug: -1
-  p2rev--debug: -1
-  p2rev--debug: 4
-  p2rev--debug: -1
-  p2rev--debug: -1
-  p2rev--debug: -1
-  p2rev--debug: -1
-  p2rev--debug: -1
-  p2rev--debug: -1
-  p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
-  p1node: 0000000000000000000000000000000000000000
-  p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
-  p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  p1node: 97054abb4ab824450e9164180baf491ae0078465
-  p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
-  p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
-  p1node: 0000000000000000000000000000000000000000
-  p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
-  p1node--verbose: 0000000000000000000000000000000000000000
-  p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
-  p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
-  p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
-  p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
-  p1node--verbose: 0000000000000000000000000000000000000000
-  p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
-  p1node--debug: 0000000000000000000000000000000000000000
-  p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
-  p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
-  p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
-  p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
-  p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
-  p1node--debug: 0000000000000000000000000000000000000000
-  p2node: 0000000000000000000000000000000000000000
-  p2node: 0000000000000000000000000000000000000000
-  p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
-  p2node: 0000000000000000000000000000000000000000
-  p2node: 0000000000000000000000000000000000000000
-  p2node: 0000000000000000000000000000000000000000
-  p2node: 0000000000000000000000000000000000000000
-  p2node: 0000000000000000000000000000000000000000
-  p2node: 0000000000000000000000000000000000000000
-  p2node--verbose: 0000000000000000000000000000000000000000
-  p2node--verbose: 0000000000000000000000000000000000000000
-  p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
-  p2node--verbose: 0000000000000000000000000000000000000000
-  p2node--verbose: 0000000000000000000000000000000000000000
-  p2node--verbose: 0000000000000000000000000000000000000000
-  p2node--verbose: 0000000000000000000000000000000000000000
-  p2node--verbose: 0000000000000000000000000000000000000000
-  p2node--verbose: 0000000000000000000000000000000000000000
-  p2node--debug: 0000000000000000000000000000000000000000
-  p2node--debug: 0000000000000000000000000000000000000000
-  p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
-  p2node--debug: 0000000000000000000000000000000000000000
-  p2node--debug: 0000000000000000000000000000000000000000
-  p2node--debug: 0000000000000000000000000000000000000000
-  p2node--debug: 0000000000000000000000000000000000000000
-  p2node--debug: 0000000000000000000000000000000000000000
-  p2node--debug: 0000000000000000000000000000000000000000
-
-Filters work:
-
-  $ hg log --template '{author|domain}\n'
-  
-  hostname
-  
-  
-  
-  
-  place
-  place
-  hostname
-
-  $ hg log --template '{author|person}\n'
-  test
-  User Name
-  person
-  person
-  person
-  person
-  other
-  A. N. Other
-  User Name
-
-  $ hg log --template '{author|user}\n'
-  test
-  user
-  person
-  person
-  person
-  person
-  other
-  other
-  user
-
-  $ hg log --template '{date|date}\n'
-  Wed Jan 01 10:01:00 2020 +0000
-  Mon Jan 12 13:46:40 1970 +0000
-  Sun Jan 18 08:40:01 1970 +0000
-  Sun Jan 18 08:40:00 1970 +0000
-  Sat Jan 17 04:53:20 1970 +0000
-  Fri Jan 16 01:06:40 1970 +0000
-  Wed Jan 14 21:20:00 1970 +0000
-  Tue Jan 13 17:33:20 1970 +0000
-  Mon Jan 12 13:46:40 1970 +0000
-
-  $ hg log --template '{date|isodate}\n'
-  2020-01-01 10:01 +0000
-  1970-01-12 13:46 +0000
-  1970-01-18 08:40 +0000
-  1970-01-18 08:40 +0000
-  1970-01-17 04:53 +0000
-  1970-01-16 01:06 +0000
-  1970-01-14 21:20 +0000
-  1970-01-13 17:33 +0000
-  1970-01-12 13:46 +0000
-
-  $ hg log --template '{date|isodatesec}\n'
-  2020-01-01 10:01:00 +0000
-  1970-01-12 13:46:40 +0000
-  1970-01-18 08:40:01 +0000
-  1970-01-18 08:40:00 +0000
-  1970-01-17 04:53:20 +0000
-  1970-01-16 01:06:40 +0000
-  1970-01-14 21:20:00 +0000
-  1970-01-13 17:33:20 +0000
-  1970-01-12 13:46:40 +0000
-
-  $ hg log --template '{date|rfc822date}\n'
-  Wed, 01 Jan 2020 10:01:00 +0000
-  Mon, 12 Jan 1970 13:46:40 +0000
-  Sun, 18 Jan 1970 08:40:01 +0000
-  Sun, 18 Jan 1970 08:40:00 +0000
-  Sat, 17 Jan 1970 04:53:20 +0000
-  Fri, 16 Jan 1970 01:06:40 +0000
-  Wed, 14 Jan 1970 21:20:00 +0000
-  Tue, 13 Jan 1970 17:33:20 +0000
-  Mon, 12 Jan 1970 13:46:40 +0000
-
-  $ hg log --template '{desc|firstline}\n'
-  third
-  second
-  merge
-  new head
-  new branch
-  no user, no domain
-  no person
-  other 1
-  line 1
-
-  $ hg log --template '{node|short}\n'
-  95c24699272e
-  29114dbae42b
-  d41e714fe50d
-  13207e5a10d9
-  bbe44766e73d
-  10e46f2dcbf4
-  97054abb4ab8
-  b608e9d1a3f0
-  1e4e1b8f71e0
-
-  $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
-  <changeset author="test"/>
-  <changeset author="User Name &lt;user@hostname&gt;"/>
-  <changeset author="person"/>
-  <changeset author="person"/>
-  <changeset author="person"/>
-  <changeset author="person"/>
-  <changeset author="other@place"/>
-  <changeset author="A. N. Other &lt;other@place&gt;"/>
-  <changeset author="User Name &lt;user@hostname&gt;"/>
-
-  $ hg log --template '{rev}: {children}\n'
-  8: 
-  7: 8:95c24699272e
-  6: 
-  5: 6:d41e714fe50d
-  4: 6:d41e714fe50d
-  3: 4:bbe44766e73d 5:13207e5a10d9
-  2: 3:10e46f2dcbf4
-  1: 2:97054abb4ab8
-  0: 1:b608e9d1a3f0
-
-Formatnode filter works:
-
-  $ hg -q log -r 0 --template '{node|formatnode}\n'
-  1e4e1b8f71e0
-
-  $ hg log -r 0 --template '{node|formatnode}\n'
-  1e4e1b8f71e0
-
-  $ hg -v log -r 0 --template '{node|formatnode}\n'
-  1e4e1b8f71e0
-
-  $ hg --debug log -r 0 --template '{node|formatnode}\n'
-  1e4e1b8f71e05681d422154f5421e385fec3454f
-
-Age filter:
-
-  $ hg init unstable-hash
-  $ cd unstable-hash
-  $ hg log --template '{date|age}\n' > /dev/null || exit 1
-
-  >>> from __future__ import absolute_import
-  >>> import datetime
-  >>> fp = open('a', 'wb')
-  >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
-  >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
-  >>> fp.close()
-  $ hg add a
-  $ hg commit -m future -d "`cat a`"
-
-  $ hg log -l1 --template '{date|age}\n'
-  7 years from now
-
-  $ cd ..
-  $ rm -rf unstable-hash
-
-Filename filters:
-
-  $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
-  bar||foo|
-  $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
-  foo|foo||
-  $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
-  foo|foo|foo|
-
-Add a dummy commit to make up for the instability of the above:
-
-  $ echo a > a
-  $ hg add a
-  $ hg ci -m future
-
-Count filter:
-
-  $ hg log -l1 --template '{node|count} {node|short|count}\n'
-  40 12
-
-  $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
-  0 1 4
-
-  $ hg log -G --template '{rev}: children: {children|count}, \
-  > tags: {tags|count}, file_adds: {file_adds|count}, \
-  > ancestors: {revset("ancestors(%s)", rev)|count}'
-  @  9: children: 0, tags: 1, file_adds: 1, ancestors: 3
-  |
-  o  8: children: 1, tags: 0, file_adds: 2, ancestors: 2
-  |
-  o  7: children: 1, tags: 0, file_adds: 1, ancestors: 1
-  
-  o    6: children: 0, tags: 0, file_adds: 0, ancestors: 7
-  |\
-  | o  5: children: 1, tags: 0, file_adds: 1, ancestors: 5
-  | |
-  o |  4: children: 1, tags: 0, file_adds: 0, ancestors: 5
-  |/
-  o  3: children: 2, tags: 0, file_adds: 0, ancestors: 4
-  |
-  o  2: children: 1, tags: 0, file_adds: 1, ancestors: 3
-  |
-  o  1: children: 1, tags: 0, file_adds: 1, ancestors: 2
-  |
-  o  0: children: 1, tags: 0, file_adds: 1, ancestors: 1
-  
-
-  $ hg log -l1 -T '{termwidth|count}\n'
-  hg: parse error: not countable
-  (template filter 'count' is not compatible with keyword 'termwidth')
-  [255]
-
-Upper/lower filters:
-
-  $ hg log -r0 --template '{branch|upper}\n'
-  DEFAULT
-  $ hg log -r0 --template '{author|lower}\n'
-  user name <user@hostname>
-  $ hg log -r0 --template '{date|upper}\n'
-  1000000.00
-
-Add a commit that does all possible modifications at once
-
-  $ echo modify >> third
-  $ touch b
-  $ hg add b
-  $ hg mv fourth fifth
-  $ hg rm a
-  $ hg ci -m "Modify, add, remove, rename"
-
-Check the status template
-
-  $ cat <<EOF >> $HGRCPATH
-  > [extensions]
-  > color=
-  > EOF
-
-  $ hg log -T status -r 10
-  changeset:   10:0f9759ec227a
-  tag:         tip
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     Modify, add, remove, rename
-  files:
-  M third
-  A b
-  A fifth
-  R a
-  R fourth
-  
-  $ hg log -T status -C -r 10
-  changeset:   10:0f9759ec227a
-  tag:         tip
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     Modify, add, remove, rename
-  files:
-  M third
-  A b
-  A fifth
-    fourth
-  R a
-  R fourth
-  
-  $ hg log -T status -C -r 10 -v
-  changeset:   10:0f9759ec227a
-  tag:         tip
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  description:
-  Modify, add, remove, rename
-  
-  files:
-  M third
-  A b
-  A fifth
-    fourth
-  R a
-  R fourth
-  
-  $ hg log -T status -C -r 10 --debug
-  changeset:   10:0f9759ec227a4859c2014a345cd8a859022b7c6c
-  tag:         tip
-  phase:       secret
-  parent:      9:bf9dfba36635106d6a73ccc01e28b762da60e066
-  parent:      -1:0000000000000000000000000000000000000000
-  manifest:    8:89dd546f2de0a9d6d664f58d86097eb97baba567
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  extra:       branch=default
-  description:
-  Modify, add, remove, rename
-  
-  files:
-  M third
-  A b
-  A fifth
-    fourth
-  R a
-  R fourth
-  
-  $ hg log -T status -C -r 10 --quiet
-  10:0f9759ec227a
-  $ hg --color=debug log -T status -r 10
-  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
-  [log.tag|tag:         tip]
-  [log.user|user:        test]
-  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
-  [log.summary|summary:     Modify, add, remove, rename]
-  [ui.note log.files|files:]
-  [status.modified|M third]
-  [status.added|A b]
-  [status.added|A fifth]
-  [status.removed|R a]
-  [status.removed|R fourth]
-  
-  $ hg --color=debug log -T status -C -r 10
-  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
-  [log.tag|tag:         tip]
-  [log.user|user:        test]
-  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
-  [log.summary|summary:     Modify, add, remove, rename]
-  [ui.note log.files|files:]
-  [status.modified|M third]
-  [status.added|A b]
-  [status.added|A fifth]
-  [status.copied|  fourth]
-  [status.removed|R a]
-  [status.removed|R fourth]
-  
-  $ hg --color=debug log -T status -C -r 10 -v
-  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
-  [log.tag|tag:         tip]
-  [log.user|user:        test]
-  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
-  [ui.note log.description|description:]
-  [ui.note log.description|Modify, add, remove, rename]
-  
-  [ui.note log.files|files:]
-  [status.modified|M third]
-  [status.added|A b]
-  [status.added|A fifth]
-  [status.copied|  fourth]
-  [status.removed|R a]
-  [status.removed|R fourth]
-  
-  $ hg --color=debug log -T status -C -r 10 --debug
-  [log.changeset changeset.secret|changeset:   10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
-  [log.tag|tag:         tip]
-  [log.phase|phase:       secret]
-  [log.parent changeset.secret|parent:      9:bf9dfba36635106d6a73ccc01e28b762da60e066]
-  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
-  [ui.debug log.manifest|manifest:    8:89dd546f2de0a9d6d664f58d86097eb97baba567]
-  [log.user|user:        test]
-  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
-  [ui.debug log.extra|extra:       branch=default]
-  [ui.note log.description|description:]
-  [ui.note log.description|Modify, add, remove, rename]
-  
-  [ui.note log.files|files:]
-  [status.modified|M third]
-  [status.added|A b]
-  [status.added|A fifth]
-  [status.copied|  fourth]
-  [status.removed|R a]
-  [status.removed|R fourth]
-  
-  $ hg --color=debug log -T status -C -r 10 --quiet
-  [log.node|10:0f9759ec227a]
-
-Check the bisect template
-
-  $ hg bisect -g 1
-  $ hg bisect -b 3 --noupdate
-  Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
-  $ hg log -T bisect -r 0:4
-  changeset:   0:1e4e1b8f71e0
-  bisect:      good (implicit)
-  user:        User Name <user@hostname>
-  date:        Mon Jan 12 13:46:40 1970 +0000
-  summary:     line 1
-  
-  changeset:   1:b608e9d1a3f0
-  bisect:      good
-  user:        A. N. Other <other@place>
-  date:        Tue Jan 13 17:33:20 1970 +0000
-  summary:     other 1
-  
-  changeset:   2:97054abb4ab8
-  bisect:      untested
-  user:        other@place
-  date:        Wed Jan 14 21:20:00 1970 +0000
-  summary:     no person
-  
-  changeset:   3:10e46f2dcbf4
-  bisect:      bad
-  user:        person
-  date:        Fri Jan 16 01:06:40 1970 +0000
-  summary:     no user, no domain
-  
-  changeset:   4:bbe44766e73d
-  bisect:      bad (implicit)
-  branch:      foo
-  user:        person
-  date:        Sat Jan 17 04:53:20 1970 +0000
-  summary:     new branch
-  
-  $ hg log --debug -T bisect -r 0:4
-  changeset:   0:1e4e1b8f71e05681d422154f5421e385fec3454f
-  bisect:      good (implicit)
-  phase:       public
-  parent:      -1:0000000000000000000000000000000000000000
-  parent:      -1:0000000000000000000000000000000000000000
-  manifest:    0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
-  user:        User Name <user@hostname>
-  date:        Mon Jan 12 13:46:40 1970 +0000
-  files+:      a
-  extra:       branch=default
-  description:
-  line 1
-  line 2
-  
-  
-  changeset:   1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
-  bisect:      good
-  phase:       public
-  parent:      0:1e4e1b8f71e05681d422154f5421e385fec3454f
-  parent:      -1:0000000000000000000000000000000000000000
-  manifest:    1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
-  user:        A. N. Other <other@place>
-  date:        Tue Jan 13 17:33:20 1970 +0000
-  files+:      b
-  extra:       branch=default
-  description:
-  other 1
-  other 2
-  
-  other 3
-  
-  
-  changeset:   2:97054abb4ab824450e9164180baf491ae0078465
-  bisect:      untested
-  phase:       public
-  parent:      1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
-  parent:      -1:0000000000000000000000000000000000000000
-  manifest:    2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
-  user:        other@place
-  date:        Wed Jan 14 21:20:00 1970 +0000
-  files+:      c
-  extra:       branch=default
-  description:
-  no person
-  
-  
-  changeset:   3:10e46f2dcbf4823578cf180f33ecf0b957964c47
-  bisect:      bad
-  phase:       public
-  parent:      2:97054abb4ab824450e9164180baf491ae0078465
-  parent:      -1:0000000000000000000000000000000000000000
-  manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc
-  user:        person
-  date:        Fri Jan 16 01:06:40 1970 +0000
-  files:       c
-  extra:       branch=default
-  description:
-  no user, no domain
-  
-  
-  changeset:   4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
-  bisect:      bad (implicit)
-  branch:      foo
-  phase:       draft
-  parent:      3:10e46f2dcbf4823578cf180f33ecf0b957964c47
-  parent:      -1:0000000000000000000000000000000000000000
-  manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc
-  user:        person
-  date:        Sat Jan 17 04:53:20 1970 +0000
-  extra:       branch=foo
-  description:
-  new branch
-  
-  
-  $ hg log -v -T bisect -r 0:4
-  changeset:   0:1e4e1b8f71e0
-  bisect:      good (implicit)
-  user:        User Name <user@hostname>
-  date:        Mon Jan 12 13:46:40 1970 +0000
-  files:       a
-  description:
-  line 1
-  line 2
-  
-  
-  changeset:   1:b608e9d1a3f0
-  bisect:      good
-  user:        A. N. Other <other@place>
-  date:        Tue Jan 13 17:33:20 1970 +0000
-  files:       b
-  description:
-  other 1
-  other 2
-  
-  other 3
-  
-  
-  changeset:   2:97054abb4ab8
-  bisect:      untested
-  user:        other@place
-  date:        Wed Jan 14 21:20:00 1970 +0000
-  files:       c
-  description:
-  no person
-  
-  
-  changeset:   3:10e46f2dcbf4
-  bisect:      bad
-  user:        person
-  date:        Fri Jan 16 01:06:40 1970 +0000
-  files:       c
-  description:
-  no user, no domain
-  
-  
-  changeset:   4:bbe44766e73d
-  bisect:      bad (implicit)
-  branch:      foo
-  user:        person
-  date:        Sat Jan 17 04:53:20 1970 +0000
-  description:
-  new branch
-  
-  
-  $ hg --color=debug log -T bisect -r 0:4
-  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e0]
-  [log.bisect bisect.good|bisect:      good (implicit)]
-  [log.user|user:        User Name <user@hostname>]
-  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
-  [log.summary|summary:     line 1]
-  
-  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0]
-  [log.bisect bisect.good|bisect:      good]
-  [log.user|user:        A. N. Other <other@place>]
-  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
-  [log.summary|summary:     other 1]
-  
-  [log.changeset changeset.public|changeset:   2:97054abb4ab8]
-  [log.bisect bisect.untested|bisect:      untested]
-  [log.user|user:        other@place]
-  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
-  [log.summary|summary:     no person]
-  
-  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4]
-  [log.bisect bisect.bad|bisect:      bad]
-  [log.user|user:        person]
-  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
-  [log.summary|summary:     no user, no domain]
-  
-  [log.changeset changeset.draft|changeset:   4:bbe44766e73d]
-  [log.bisect bisect.bad|bisect:      bad (implicit)]
-  [log.branch|branch:      foo]
-  [log.user|user:        person]
-  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
-  [log.summary|summary:     new branch]
-  
-  $ hg --color=debug log --debug -T bisect -r 0:4
-  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e05681d422154f5421e385fec3454f]
-  [log.bisect bisect.good|bisect:      good (implicit)]
-  [log.phase|phase:       public]
-  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
-  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
-  [ui.debug log.manifest|manifest:    0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
-  [log.user|user:        User Name <user@hostname>]
-  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
-  [ui.debug log.files|files+:      a]
-  [ui.debug log.extra|extra:       branch=default]
-  [ui.note log.description|description:]
-  [ui.note log.description|line 1
-  line 2]
-  
-  
-  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
-  [log.bisect bisect.good|bisect:      good]
-  [log.phase|phase:       public]
-  [log.parent changeset.public|parent:      0:1e4e1b8f71e05681d422154f5421e385fec3454f]
-  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
-  [ui.debug log.manifest|manifest:    1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
-  [log.user|user:        A. N. Other <other@place>]
-  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
-  [ui.debug log.files|files+:      b]
-  [ui.debug log.extra|extra:       branch=default]
-  [ui.note log.description|description:]
-  [ui.note log.description|other 1
-  other 2
-  
-  other 3]
-  
-  
-  [log.changeset changeset.public|changeset:   2:97054abb4ab824450e9164180baf491ae0078465]
-  [log.bisect bisect.untested|bisect:      untested]
-  [log.phase|phase:       public]
-  [log.parent changeset.public|parent:      1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
-  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
-  [ui.debug log.manifest|manifest:    2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
-  [log.user|user:        other@place]
-  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
-  [ui.debug log.files|files+:      c]
-  [ui.debug log.extra|extra:       branch=default]
-  [ui.note log.description|description:]
-  [ui.note log.description|no person]
-  
-  
-  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
-  [log.bisect bisect.bad|bisect:      bad]
-  [log.phase|phase:       public]
-  [log.parent changeset.public|parent:      2:97054abb4ab824450e9164180baf491ae0078465]
-  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
-  [ui.debug log.manifest|manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
-  [log.user|user:        person]
-  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
-  [ui.debug log.files|files:       c]
-  [ui.debug log.extra|extra:       branch=default]
-  [ui.note log.description|description:]
-  [ui.note log.description|no user, no domain]
-  
-  
-  [log.changeset changeset.draft|changeset:   4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
-  [log.bisect bisect.bad|bisect:      bad (implicit)]
-  [log.branch|branch:      foo]
-  [log.phase|phase:       draft]
-  [log.parent changeset.public|parent:      3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
-  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
-  [ui.debug log.manifest|manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
-  [log.user|user:        person]
-  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
-  [ui.debug log.extra|extra:       branch=foo]
-  [ui.note log.description|description:]
-  [ui.note log.description|new branch]
-  
-  
-  $ hg --color=debug log -v -T bisect -r 0:4
-  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e0]
-  [log.bisect bisect.good|bisect:      good (implicit)]
-  [log.user|user:        User Name <user@hostname>]
-  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
-  [ui.note log.files|files:       a]
-  [ui.note log.description|description:]
-  [ui.note log.description|line 1
-  line 2]
-  
-  
-  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0]
-  [log.bisect bisect.good|bisect:      good]
-  [log.user|user:        A. N. Other <other@place>]
-  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
-  [ui.note log.files|files:       b]
-  [ui.note log.description|description:]
-  [ui.note log.description|other 1
-  other 2
-  
-  other 3]
-  
-  
-  [log.changeset changeset.public|changeset:   2:97054abb4ab8]
-  [log.bisect bisect.untested|bisect:      untested]
-  [log.user|user:        other@place]
-  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
-  [ui.note log.files|files:       c]
-  [ui.note log.description|description:]
-  [ui.note log.description|no person]
-  
-  
-  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4]
-  [log.bisect bisect.bad|bisect:      bad]
-  [log.user|user:        person]
-  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
-  [ui.note log.files|files:       c]
-  [ui.note log.description|description:]
-  [ui.note log.description|no user, no domain]
-  
-  
-  [log.changeset changeset.draft|changeset:   4:bbe44766e73d]
-  [log.bisect bisect.bad|bisect:      bad (implicit)]
-  [log.branch|branch:      foo]
-  [log.user|user:        person]
-  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
-  [ui.note log.description|description:]
-  [ui.note log.description|new branch]
-  
-  
-  $ hg bisect --reset
-
-Error on syntax:
-
-  $ echo 'x = "f' >> t
-  $ hg log
-  hg: parse error at t:3: unmatched quotes
-  [255]
-
-  $ hg log -T '{date'
-  hg: parse error at 1: unterminated template expansion
-  ({date
-    ^ here)
-  [255]
-  $ hg log -T '{date(}'
-  hg: parse error at 6: not a prefix: end
-  ({date(}
-         ^ here)
-  [255]
-  $ hg log -T '{date)}'
-  hg: parse error at 5: invalid token
-  ({date)}
-        ^ here)
-  [255]
-  $ hg log -T '{date date}'
-  hg: parse error at 6: invalid token
-  ({date date}
-         ^ here)
-  [255]
-
-  $ hg log -T '{}'
-  hg: parse error at 1: not a prefix: end
-  ({}
-    ^ here)
-  [255]
-  $ hg debugtemplate -v '{()}'
-  (template
-    (group
-      None))
-  hg: parse error: missing argument
-  [255]
-
-Behind the scenes, this would throw TypeError without intype=bytes
-
-  $ hg log -l 3 --template '{date|obfuscate}\n'
-  &#48;&#46;&#48;&#48;
-  &#48;&#46;&#48;&#48;
-  &#49;&#53;&#55;&#55;&#56;&#55;&#50;&#56;&#54;&#48;&#46;&#48;&#48;
-
-Behind the scenes, this will throw a ValueError
-
-  $ hg log -l 3 --template 'line: {desc|shortdate}\n'
-  hg: parse error: invalid date: 'Modify, add, remove, rename'
-  (template filter 'shortdate' is not compatible with keyword 'desc')
-  [255]
-
-Behind the scenes, this would throw AttributeError without intype=bytes
-
-  $ hg log -l 3 --template 'line: {date|escape}\n'
-  line: 0.00
-  line: 0.00
-  line: 1577872860.00
-
-  $ hg log -l 3 --template 'line: {extras|localdate}\n'
-  hg: parse error: localdate expects a date information
-  [255]
-
-Behind the scenes, this will throw ValueError
-
-  $ hg tip --template '{author|email|date}\n'
-  hg: parse error: date expects a date information
-  [255]
-
-  $ hg tip -T '{author|email|shortdate}\n'
-  hg: parse error: invalid date: 'test'
-  (template filter 'shortdate' is not compatible with keyword 'author')
-  [255]
-
-  $ hg tip -T '{get(extras, "branch")|shortdate}\n'
-  hg: parse error: invalid date: 'default'
-  (incompatible use of template filter 'shortdate')
-  [255]
-
-Error in nested template:
-
-  $ hg log -T '{"date'
-  hg: parse error at 2: unterminated string
-  ({"date
-     ^ here)
-  [255]
-
-  $ hg log -T '{"foo{date|?}"}'
-  hg: parse error at 11: syntax error
-  ({"foo{date|?}"}
-              ^ here)
-  [255]
-
-Thrown an error if a template function doesn't exist
-
-  $ hg tip --template '{foo()}\n'
-  hg: parse error: unknown function 'foo'
-  [255]
-
-Pass generator object created by template function to filter
-
-  $ hg log -l 1 --template '{if(author, author)|user}\n'
-  test
-
-Test index keyword:
-
-  $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
-  10 0:a 1:b 2:fifth 3:fourth 4:third
-  11 0:a
-
-  $ hg branches -T '{index} {branch}\n'
-  0 default
-  1 foo
-
-Test diff function:
-
-  $ hg diff -c 8
-  diff -r 29114dbae42b -r 95c24699272e fourth
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
-  @@ -0,0 +1,1 @@
-  +second
-  diff -r 29114dbae42b -r 95c24699272e second
-  --- a/second	Mon Jan 12 13:46:40 1970 +0000
-  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +0,0 @@
-  -second
-  diff -r 29114dbae42b -r 95c24699272e third
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
-  @@ -0,0 +1,1 @@
-  +third
-
-  $ hg log -r 8 -T "{diff()}"
-  diff -r 29114dbae42b -r 95c24699272e fourth
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
-  @@ -0,0 +1,1 @@
-  +second
-  diff -r 29114dbae42b -r 95c24699272e second
-  --- a/second	Mon Jan 12 13:46:40 1970 +0000
-  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +0,0 @@
-  -second
-  diff -r 29114dbae42b -r 95c24699272e third
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
-  @@ -0,0 +1,1 @@
-  +third
-
-  $ hg log -r 8 -T "{diff('glob:f*')}"
-  diff -r 29114dbae42b -r 95c24699272e fourth
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
-  @@ -0,0 +1,1 @@
-  +second
-
-  $ hg log -r 8 -T "{diff('', 'glob:f*')}"
-  diff -r 29114dbae42b -r 95c24699272e second
-  --- a/second	Mon Jan 12 13:46:40 1970 +0000
-  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +0,0 @@
-  -second
-  diff -r 29114dbae42b -r 95c24699272e third
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
-  @@ -0,0 +1,1 @@
-  +third
-
-  $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
-  diff -r 29114dbae42b -r 95c24699272e fourth
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
-  @@ -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 ..
-
-
-latesttag:
-
-  $ hg init latesttag
-  $ cd latesttag
-
-  $ echo a > file
-  $ hg ci -Am a -d '0 0'
-  adding file
-
-  $ echo b >> file
-  $ hg ci -m b -d '1 0'
-
-  $ echo c >> head1
-  $ hg ci -Am h1c -d '2 0'
-  adding head1
-
-  $ hg update -q 1
-  $ echo d >> head2
-  $ hg ci -Am h2d -d '3 0'
-  adding head2
-  created new head
-
-  $ echo e >> head2
-  $ hg ci -m h2e -d '4 0'
-
-  $ hg merge -q
-  $ hg ci -m merge -d '5 -3600'
-
-No tag set:
-
-  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  @    5: null+5
-  |\
-  | o  4: null+4
-  | |
-  | o  3: null+3
-  | |
-  o |  2: null+3
-  |/
-  o  1: null+2
-  |
-  o  0: null+1
-  
-
-One common tag: longest path wins for {latesttagdistance}:
-
-  $ hg tag -r 1 -m t1 -d '6 0' t1
-  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  @  6: t1+4
-  |
-  o    5: t1+3
-  |\
-  | o  4: t1+2
-  | |
-  | o  3: t1+1
-  | |
-  o |  2: t1+1
-  |/
-  o  1: t1+0
-  |
-  o  0: null+1
-  
-
-One ancestor tag: closest wins:
-
-  $ hg tag -r 2 -m t2 -d '7 0' t2
-  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  @  7: t2+3
-  |
-  o  6: t2+2
-  |
-  o    5: t2+1
-  |\
-  | o  4: t1+2
-  | |
-  | o  3: t1+1
-  | |
-  o |  2: t2+0
-  |/
-  o  1: t1+0
-  |
-  o  0: null+1
-  
-
-Two branch tags: more recent wins if same number of changes:
-
-  $ hg tag -r 3 -m t3 -d '8 0' t3
-  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  @  8: t3+5
-  |
-  o  7: t3+4
-  |
-  o  6: t3+3
-  |
-  o    5: t3+2
-  |\
-  | o  4: t3+1
-  | |
-  | o  3: t3+0
-  | |
-  o |  2: t2+0
-  |/
-  o  1: t1+0
-  |
-  o  0: null+1
-  
-
-Two branch tags: fewest changes wins:
-
-  $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
-  $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
-  @  9: t4+5,6
-  |
-  o  8: t4+4,5
-  |
-  o  7: t4+3,4
-  |
-  o  6: t4+2,3
-  |
-  o    5: t4+1,2
-  |\
-  | o  4: t4+0,0
-  | |
-  | o  3: t3+0,0
-  | |
-  o |  2: t2+0,0
-  |/
-  o  1: t1+0,0
-  |
-  o  0: null+1,1
-  
-
-Merged tag overrides:
-
-  $ hg tag -r 5 -m t5 -d '9 0' t5
-  $ hg tag -r 3 -m at3 -d '10 0' at3
-  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
-  @  11: t5+6
-  |
-  o  10: t5+5
-  |
-  o  9: t5+4
-  |
-  o  8: t5+3
-  |
-  o  7: t5+2
-  |
-  o  6: t5+1
-  |
-  o    5: t5+0
-  |\
-  | o  4: t4+0
-  | |
-  | o  3: at3:t3+0
-  | |
-  o |  2: t2+0
-  |/
-  o  1: t1+0
-  |
-  o  0: null+1
-  
-
-  $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
-  @  11: t5+6,6
-  |
-  o  10: t5+5,5
-  |
-  o  9: t5+4,4
-  |
-  o  8: t5+3,3
-  |
-  o  7: t5+2,2
-  |
-  o  6: t5+1,1
-  |
-  o    5: t5+0,0
-  |\
-  | o  4: t4+0,0
-  | |
-  | o  3: at3+0,0 t3+0,0
-  | |
-  o |  2: t2+0,0
-  |/
-  o  1: t1+0,0
-  |
-  o  0: null+1,1
-  
-
-  $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
-  @  11: t3, C: 9, D: 8
-  |
-  o  10: t3, C: 8, D: 7
-  |
-  o  9: t3, C: 7, D: 6
-  |
-  o  8: t3, C: 6, D: 5
-  |
-  o  7: t3, C: 5, D: 4
-  |
-  o  6: t3, C: 4, D: 3
-  |
-  o    5: t3, C: 3, D: 2
-  |\
-  | o  4: t3, C: 1, D: 1
-  | |
-  | o  3: t3, C: 0, D: 0
-  | |
-  o |  2: t1, C: 1, D: 1
-  |/
-  o  1: t1, C: 0, D: 0
-  |
-  o  0: null, C: 1, D: 1
-  
-
-  $ cd ..
-
-
-Style path expansion: issue1948 - ui.style option doesn't work on OSX
-if it is a relative path
-
-  $ mkdir -p home/styles
-
-  $ cat > home/styles/teststyle <<EOF
-  > changeset = 'test {rev}:{node|short}\n'
-  > EOF
-
-  $ HOME=`pwd`/home; export HOME
-
-  $ cat > latesttag/.hg/hgrc <<EOF
-  > [ui]
-  > style = ~/styles/teststyle
-  > EOF
-
-  $ hg -R latesttag tip
-  test 11:97e5943b523a
-
-Test recursive showlist template (issue1989):
-
-  $ cat > style1989 <<EOF
-  > changeset = '{file_mods}{manifest}{extras}'
-  > file_mod  = 'M|{author|person}\n'
-  > manifest = '{rev},{author}\n'
-  > extra = '{key}: {author}\n'
-  > EOF
-
-  $ hg -R latesttag log -r tip --style=style1989
-  M|test
-  11,test
-  branch: test
-
-Test new-style inline templating:
-
-  $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
-  modified files:  .hgtags
-  
-
-  $ hg log -R latesttag -r tip -T '{rev % "a"}\n'
-  hg: parse error: keyword 'rev' is not iterable of mappings
-  [255]
-  $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "a"}\n'
-  hg: parse error: None is not iterable of mappings
-  [255]
-  $ hg log -R latesttag -r tip -T '{extras % "{key}\n" % "{key}\n"}'
-  hg: parse error: list of strings is not mappable
-  [255]
-
-Test new-style inline templating of non-list/dict type:
-
-  $ hg log -R latesttag -r tip -T '{manifest}\n'
-  11:2bc6e9006ce2
-  $ hg log -R latesttag -r tip -T 'string length: {manifest|count}\n'
-  string length: 15
-  $ hg log -R latesttag -r tip -T '{manifest % "{rev}:{node}"}\n'
-  11:2bc6e9006ce29882383a22d39fd1f4e66dd3e2fc
-
-  $ hg log -R latesttag -r tip -T '{get(extras, "branch") % "{key}: {value}\n"}'
-  branch: default
-  $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "{key}\n"}'
-  hg: parse error: None is not iterable of mappings
-  [255]
-  $ hg log -R latesttag -r tip -T '{min(extras) % "{key}: {value}\n"}'
-  branch: default
-  $ hg log -R latesttag -l1 -T '{min(revset("0:9")) % "{rev}:{node|short}\n"}'
-  0:ce3cec86e6c2
-  $ hg log -R latesttag -l1 -T '{max(revset("0:9")) % "{rev}:{node|short}\n"}'
-  9:fbc7cd862e9c
-
-Test manifest/get() can be join()-ed as before, though it's silly:
-
-  $ hg log -R latesttag -r tip -T '{join(manifest, "")}\n'
-  11:2bc6e9006ce2
-  $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), "")}\n'
-  default
-
-Test min/max of integers
-
-  $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
-  9
-  $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
-  10
-
-Test min/max over map operation:
-
-  $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
-  at3
-  $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
-  t3
-
-Test min/max of if() result
-
-  $ cd latesttag
-  $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
-  9
-  $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
-  10
-  $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
-  9
-  $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
-  10
-  $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
-  9
-  $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
-  10
-  $ cd ..
-
-Test laziness of if() then/else clause
-
-  $ hg debugtemplate '{count(0)}'
-  hg: parse error: not countable
-  (incompatible use of template filter 'count')
-  [255]
-  $ hg debugtemplate '{if(true, "", count(0))}'
-  $ hg debugtemplate '{if(false, count(0), "")}'
-  $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
-  $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
-  $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
-  $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
-
-Test dot operator precedence:
-
-  $ hg debugtemplate -R latesttag -r0 -v '{manifest.node|short}\n'
-  (template
-    (|
-      (.
-        (symbol 'manifest')
-        (symbol 'node'))
-      (symbol 'short'))
-    (string '\n'))
-  89f4071fec70
-
- (the following examples are invalid, but seem natural in parsing POV)
-
-  $ hg debugtemplate -R latesttag -r0 -v '{foo|bar.baz}\n' 2> /dev/null
-  (template
-    (|
-      (symbol 'foo')
-      (.
-        (symbol 'bar')
-        (symbol 'baz')))
-    (string '\n'))
-  [255]
-  $ hg debugtemplate -R latesttag -r0 -v '{foo.bar()}\n' 2> /dev/null
-  (template
-    (.
-      (symbol 'foo')
-      (func
-        (symbol 'bar')
-        None))
-    (string '\n'))
-  [255]
-
-Test evaluation of dot operator:
-
-  $ hg log -R latesttag -l1 -T '{min(revset("0:9")).node}\n'
-  ce3cec86e6c26bd9bdfc590a6b92abc9680f1796
-  $ hg log -R latesttag -r0 -T '{extras.branch}\n'
-  default
-
-  $ hg log -R latesttag -l1 -T '{author.invalid}\n'
-  hg: parse error: keyword 'author' has no member
-  [255]
-  $ hg log -R latesttag -l1 -T '{min("abc").invalid}\n'
-  hg: parse error: 'a' has no member
-  [255]
-
-Test the sub function of templating for expansion:
-
-  $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
-  xx
-
-  $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
-  hg: parse error: sub got an invalid pattern: [
-  [255]
-  $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
-  hg: parse error: sub got an invalid replacement: \1
-  [255]
-
-Test the strip function with chars specified:
-
-  $ hg log -R latesttag --template '{desc}\n'
-  at3
-  t5
-  t4
-  t3
-  t2
-  t1
-  merge
-  h2e
-  h2d
-  h1c
-  b
-  a
-
-  $ hg log -R latesttag --template '{strip(desc, "te")}\n'
-  at3
-  5
-  4
-  3
-  2
-  1
-  merg
-  h2
-  h2d
-  h1c
-  b
-  a
-
-Test date format:
-
-  $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
-  date: 70 01 01 10 +0000
-  date: 70 01 01 09 +0000
-  date: 70 01 01 04 +0000
-  date: 70 01 01 08 +0000
-  date: 70 01 01 07 +0000
-  date: 70 01 01 06 +0000
-  date: 70 01 01 05 +0100
-  date: 70 01 01 04 +0000
-  date: 70 01 01 03 +0000
-  date: 70 01 01 02 +0000
-  date: 70 01 01 01 +0000
-  date: 70 01 01 00 +0000
-
-Test invalid date:
-
-  $ hg log -R latesttag -T '{date(rev)}\n'
-  hg: parse error: date expects a date information
-  [255]
-
-Test integer literal:
-
-  $ hg debugtemplate -v '{(0)}\n'
-  (template
-    (group
-      (integer '0'))
-    (string '\n'))
-  0
-  $ hg debugtemplate -v '{(123)}\n'
-  (template
-    (group
-      (integer '123'))
-    (string '\n'))
-  123
-  $ hg debugtemplate -v '{(-4)}\n'
-  (template
-    (group
-      (negate
-        (integer '4')))
-    (string '\n'))
-  -4
-  $ hg debugtemplate '{(-)}\n'
-  hg: parse error at 3: not a prefix: )
-  ({(-)}\n
-      ^ here)
-  [255]
-  $ hg debugtemplate '{(-a)}\n'
-  hg: parse error: negation needs an integer argument
-  [255]
-
-top-level integer literal is interpreted as symbol (i.e. variable name):
-
-  $ hg debugtemplate -D 1=one -v '{1}\n'
-  (template
-    (integer '1')
-    (string '\n'))
-  one
-  $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
-  (template
-    (func
-      (symbol 'if')
-      (list
-        (string 't')
-        (template
-          (integer '1'))))
-    (string '\n'))
-  one
-  $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
-  (template
-    (|
-      (integer '1')
-      (symbol 'stringify'))
-    (string '\n'))
-  one
-
-unless explicit symbol is expected:
-
-  $ hg log -Ra -r0 -T '{desc|1}\n'
-  hg: parse error: expected a symbol, got 'integer'
-  [255]
-  $ hg log -Ra -r0 -T '{1()}\n'
-  hg: parse error: expected a symbol, got 'integer'
-  [255]
-
-Test string literal:
-
-  $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
-  (template
-    (string 'string with no template fragment')
-    (string '\n'))
-  string with no template fragment
-  $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
-  (template
-    (template
-      (string 'template: ')
-      (symbol 'rev'))
-    (string '\n'))
-  template: 0
-  $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
-  (template
-    (string 'rawstring: {rev}')
-    (string '\n'))
-  rawstring: {rev}
-  $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
-  (template
-    (%
-      (symbol 'files')
-      (string 'rawstring: {file}'))
-    (string '\n'))
-  rawstring: {file}
-
-Test string escaping:
-
-  $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
-  >
-  <>\n<[>
-  <>\n<]>
-  <>\n<
-
-  $ hg log -R latesttag -r 0 \
-  > --config ui.logtemplate='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
-  >
-  <>\n<[>
-  <>\n<]>
-  <>\n<
-
-  $ hg log -R latesttag -r 0 -T esc \
-  > --config templates.esc='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
-  >
-  <>\n<[>
-  <>\n<]>
-  <>\n<
-
-  $ cat <<'EOF' > esctmpl
-  > changeset = '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
-  > EOF
-  $ hg log -R latesttag -r 0 --style ./esctmpl
-  >
-  <>\n<[>
-  <>\n<]>
-  <>\n<
-
-Test string escaping of quotes:
-
-  $ hg log -Ra -r0 -T '{"\""}\n'
-  "
-  $ hg log -Ra -r0 -T '{"\\\""}\n'
-  \"
-  $ hg log -Ra -r0 -T '{r"\""}\n'
-  \"
-  $ hg log -Ra -r0 -T '{r"\\\""}\n'
-  \\\"
-
-
-  $ hg log -Ra -r0 -T '{"\""}\n'
-  "
-  $ hg log -Ra -r0 -T '{"\\\""}\n'
-  \"
-  $ hg log -Ra -r0 -T '{r"\""}\n'
-  \"
-  $ hg log -Ra -r0 -T '{r"\\\""}\n'
-  \\\"
-
-Test exception in quoted template. single backslash before quotation mark is
-stripped before parsing:
-
-  $ cat <<'EOF' > escquotetmpl
-  > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
-  > EOF
-  $ cd latesttag
-  $ hg log -r 2 --style ../escquotetmpl
-  " \" \" \\" head1
-
-  $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
-  valid
-  $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
-  valid
-
-Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
-_evalifliteral() templates (issue4733):
-
-  $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
-  "2
-  $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
-  "2
-  $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\"{rev}\\\")}\")}")}\n'
-  "2
-
-  $ hg log -r 2 -T '{if(rev, "\\\"")}\n'
-  \"
-  $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\\\\\"\")}")}\n'
-  \"
-  $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
-  \"
-
-  $ hg log -r 2 -T '{if(rev, r"\\\"")}\n'
-  \\\"
-  $ hg log -r 2 -T '{if(rev, "{if(rev, r\"\\\\\\\"\")}")}\n'
-  \\\"
-  $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, r\\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
-  \\\"
-
-escaped single quotes and errors:
-
-  $ hg log -r 2 -T "{if(rev, '{if(rev, \'foo\')}')}"'\n'
-  foo
-  $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
-  foo
-  $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
-  hg: parse error at 21: unterminated string
-  ({if(rev, "{if(rev, \")}")}\n
-                        ^ here)
-  [255]
-  $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
-  hg: parse error: trailing \ in string
-  [255]
-  $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
-  hg: parse error: trailing \ in string
-  [255]
-
-  $ cd ..
-
-Test leading backslashes:
-
-  $ cd latesttag
-  $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
-  {rev} {file}
-  $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
-  \2 \head1
-  $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
-  \{rev} \{file}
-  $ cd ..
-
-Test leading backslashes in "if" expression (issue4714):
-
-  $ cd latesttag
-  $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
-  {rev} \{rev}
-  $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
-  \2 \\{rev}
-  $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
-  \{rev} \\\{rev}
-  $ cd ..
-
-"string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
-
-  $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
-  \x6e
-  $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
-  \x5c\x786e
-  $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
-  \x6e
-  $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
-  \x5c\x786e
-
-  $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
-  \x6e
-  $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
-  \x5c\x786e
-  $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
-  \x6e
-  $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
-  \x5c\x786e
-
-  $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
-  fourth
-  second
-  third
-  $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
-  fourth\nsecond\nthird
-
-  $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
-  <p>
-  1st
-  </p>
-  <p>
-  2nd
-  </p>
-  $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
-  <p>
-  1st\n\n2nd
-  </p>
-  $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
-  1st
-  
-  2nd
-
-  $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
-  o perso
-  $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
-  no person
-  $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
-  o perso
-  $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
-  no perso
-
-  $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
-  -o perso-
-  $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
-  no person
-  $ hg log -R a -r 2 --template '{sub("n", r"\x2d", desc)}\n'
-  \x2do perso\x2d
-  $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
-  -o perso-
-  $ hg log -R a -r 2 --template '{sub("n", r"\x2d", r"no perso\x6e")}\n'
-  \x2do perso\x6e
-
-  $ hg log -R a -r 8 --template '{files % "{file}\n"}'
-  fourth
-  second
-  third
-
-Test string escaping in nested expression:
-
-  $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
-  fourth\x6esecond\x6ethird
-  $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
-  fourth\x6esecond\x6ethird
-
-  $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
-  fourth\x6esecond\x6ethird
-  $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
-  fourth\x5c\x786esecond\x5c\x786ethird
-
-  $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
-  3:\x6eo user, \x6eo domai\x6e
-  4:\x5c\x786eew bra\x5c\x786ech
-
-Test quotes in nested expression are evaluated just like a $(command)
-substitution in POSIX shells:
-
-  $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
-  8:95c24699272e
-  $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
-  {8} "95c24699272e"
-
-Test recursive evaluation:
-
-  $ hg init r
-  $ cd r
-  $ echo a > a
-  $ hg ci -Am '{rev}'
-  adding a
-  $ hg log -r 0 --template '{if(rev, desc)}\n'
-  {rev}
-  $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
-  test 0
-
-  $ hg branch -q 'text.{rev}'
-  $ echo aa >> aa
-  $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
-
-  $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
-  {node|short}desc to
-  text.{rev}be wrapped
-  text.{rev}desc to be
-  text.{rev}wrapped (no-eol)
-  $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
-  bcc7ff960b8e:desc to
-  text.1:be wrapped
-  text.1:desc to be
-  text.1:wrapped (no-eol)
-  $ hg log -l1 -T '{fill(desc, date, "", "")}\n'
-  hg: parse error: fill expects an integer width
-  [255]
-
-  $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
-  bcc7ff960b8e:desc to be
-  termwidth.1:wrapped desc
-  termwidth.1:to be wrapped (no-eol)
-
-  $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
-  {node|short} (no-eol)
-  $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
-  bcc-ff---b-e (no-eol)
-
-  $ cat >> .hg/hgrc <<EOF
-  > [extensions]
-  > color=
-  > [color]
-  > mode=ansi
-  > text.{rev} = red
-  > text.1 = green
-  > EOF
-  $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
-  \x1b[0;31mtext\x1b[0m (esc)
-  $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
-  \x1b[0;32mtext\x1b[0m (esc)
-
-color effect can be specified without quoting:
-
-  $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
-  \x1b[0;31mtext\x1b[0m (esc)
-
-color effects can be nested (issue5413)
-
-  $ hg debugtemplate --color=always \
-  > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
-  \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
-
-pad() should interact well with color codes (issue5416)
-
-  $ hg debugtemplate --color=always \
-  > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
-  \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
-
-label should be no-op if color is disabled:
-
-  $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
-  text
-  $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
-  text
-
-Test branches inside if statement:
-
-  $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
-  no
-
-Test dict constructor:
-
-  $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
-  y=f7769ec2ab97 x=0
-  $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
-  x=0
-  y=f7769ec2ab97
-  $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
-  {"x": 0, "y": "f7769ec2ab97"}
-  $ hg log -r 0 -T '{dict()|json}\n'
-  {}
-
-  $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
-  rev=0 node=f7769ec2ab97
-  $ hg log -r 0 -T '{dict(rev, node|short)}\n'
-  rev=0 node=f7769ec2ab97
-
-  $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
-  hg: parse error: duplicated dict key 'rev' inferred
-  [255]
-  $ hg log -r 0 -T '{dict(node, node|short)}\n'
-  hg: parse error: duplicated dict key 'node' inferred
-  [255]
-  $ hg log -r 0 -T '{dict(1 + 2)}'
-  hg: parse error: dict key cannot be inferred
-  [255]
-
-  $ hg log -r 0 -T '{dict(x=rev, x=node)}'
-  hg: parse error: dict got multiple values for keyword argument 'x'
-  [255]
-
-Test get function:
-
-  $ hg log -r 0 --template '{get(extras, "branch")}\n'
-  default
-  $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
-  default
-  $ hg log -r 0 --template '{get(files, "should_fail")}\n'
-  hg: parse error: get() expects a dict as first argument
-  [255]
-
-Test json filter applied to hybrid object:
-
-  $ hg log -r0 -T '{files|json}\n'
-  ["a"]
-  $ hg log -r0 -T '{extras|json}\n'
-  {"branch": "default"}
-
-Test json filter applied to map result:
-
-  $ hg log -r0 -T '{json(extras % "{key}")}\n'
-  ["branch"]
-
-Test localdate(date, tz) function:
-
-  $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
-  1970-01-01 09:00 +0900
-  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
-  1970-01-01 00:00 +0000
-  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
-  hg: parse error: localdate expects a timezone
-  [255]
-  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
-  1970-01-01 02:00 +0200
-  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
-  1970-01-01 00:00 +0000
-  $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
-  1970-01-01 00:00 +0000
-  $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
-  hg: parse error: localdate expects a timezone
-  [255]
-  $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
-  hg: parse error: localdate expects a timezone
-  [255]
-
-Test shortest(node) function:
-
-  $ echo b > b
-  $ hg ci -qAm b
-  $ hg log --template '{shortest(node)}\n'
-  e777
-  bcc7
-  f776
-  $ hg log --template '{shortest(node, 10)}\n'
-  e777603221
-  bcc7ff960b
-  f7769ec2ab
-  $ hg log --template '{node|shortest}\n' -l1
-  e777
-
-  $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
-  f7769ec2ab
-  $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
-  hg: parse error: shortest() expects an integer minlength
-  [255]
-
-  $ hg log -r 'wdir()' -T '{node|shortest}\n'
-  ffff
-
-  $ hg log --template '{shortest("f")}\n' -l1
-  f
-
-  $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
-  0123456789012345678901234567890123456789
-
-  $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
-  01234567890123456789012345678901234567890123456789
-
-  $ hg log --template '{shortest("not a hex string")}\n' -l1
-  not a hex string
-
-  $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
-  not a hex string, but it's 40 bytes long
-
-  $ cd ..
-
-Test shortest(node) with the repo having short hash collision:
-
-  $ hg init hashcollision
-  $ cd hashcollision
-  $ cat <<EOF >> .hg/hgrc
-  > [experimental]
-  > evolution.createmarkers=True
-  > EOF
-  $ echo 0 > a
-  $ hg ci -qAm 0
-  $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
-  >   hg up -q 0
-  >   echo $i > a
-  >   hg ci -qm $i
-  > done
-  $ hg up -q null
-  $ hg log -r0: -T '{rev}:{node}\n'
-  0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
-  1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
-  2:11407b3f1b9c3e76a79c1ec5373924df096f0499
-  3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
-  4:10776689e627b465361ad5c296a20a487e153ca4
-  5:a00be79088084cb3aff086ab799f8790e01a976b
-  6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
-  7:a0457b3450b8e1b778f1163b31a435802987fe5d
-  8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
-  9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
-  10:c562ddd9c94164376c20b86b0b4991636a3bf84f
-  $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
-  obsoleted 1 changesets
-  $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
-  obsoleted 1 changesets
-  $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
-  obsoleted 1 changesets
-
- nodes starting with '11' (we don't have the revision number '11' though)
-
-  $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
-  1:1142
-  2:1140
-  3:11d
-
- '5:a00' is hidden, but still we have two nodes starting with 'a0'
-
-  $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
-  6:a0b
-  7:a04
-
- node '10' conflicts with the revision number '10' even if it is hidden
- (we could exclude hidden revision numbers, but currently we don't)
-
-  $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
-  4:107
-  $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
-  4:107
-
- node 'c562' should be unique if the other 'c562' nodes are hidden
- (but we don't try the slow path to filter out hidden nodes for now)
-
-  $ hg log -r 8 -T '{rev}:{node|shortest}\n'
-  8:c5625
-  $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
-  8:c5625
-  9:c5623
-  10:c562d
-
-  $ cd ..
-
-Test pad function
-
-  $ cd r
-
-  $ hg log --template '{pad(rev, 20)} {author|user}\n'
-  2                    test
-  1                    {node|short}
-  0                    test
-
-  $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
-                     2 test
-                     1 {node|short}
-                     0 test
-
-  $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
-  2------------------- test
-  1------------------- {node|short}
-  0------------------- test
-
-Test template string in pad function
-
-  $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
-  {0}        test
-
-  $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
-  \{rev}     test
-
-Test width argument passed to pad function
-
-  $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
-  0          test
-  $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
-  hg: parse error: pad() expects an integer width
-  [255]
-
-Test invalid fillchar passed to pad function
-
-  $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
-  hg: parse error: pad() expects a single fill character
-  [255]
-  $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
-  hg: parse error: pad() expects a single fill character
-  [255]
-
-Test boolean argument passed to pad function
-
- no crash
-
-  $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
-  ---------0
-
- string/literal
-
-  $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
-  ---------0
-  $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
-  0---------
-  $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
-  0---------
-
- unknown keyword is evaluated to ''
-
-  $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
-  0---------
-
-Test separate function
-
-  $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
-  a-b-c
-  $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
-  0:f7769ec2ab97 test default
-  $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
-  a \x1b[0;31mb\x1b[0m c d (esc)
-
-Test boolean expression/literal passed to if function
-
-  $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
-  rev 0 is True
-  $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
-  literal 0 is True as well
-  $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
-  empty string is False
-  $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
-  empty list is False
-  $ hg log -r 0 -T '{if(true, "true is True")}\n'
-  true is True
-  $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
-  false is False
-  $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
-  non-empty string is True
-
-Test ifcontains function
-
-  $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
-  2 is in the string
-  1 is not
-  0 is in the string
-
-  $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
-  2 is in the string
-  1 is not
-  0 is in the string
-
-  $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
-  2 did not add a
-  1 did not add a
-  0 added a
-
-  $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
-  2 is parent of 1
-  1
-  0
-
-Test revset function
-
-  $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
-  2 current rev
-  1 not current rev
-  0 not current rev
-
-  $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
-  2 match rev
-  1 match rev
-  0 not match rev
-
-  $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
-  type not match
-
-  $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
-  2 Parents: 1
-  1 Parents: 0
-  0 Parents: 
-
-  $ cat >> .hg/hgrc <<EOF
-  > [revsetalias]
-  > myparents(\$1) = parents(\$1)
-  > EOF
-  $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
-  2 Parents: 1
-  1 Parents: 0
-  0 Parents: 
-
-  $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
-  Rev: 2
-  Ancestor: 0
-  Ancestor: 1
-  Ancestor: 2
-  
-  Rev: 1
-  Ancestor: 0
-  Ancestor: 1
-  
-  Rev: 0
-  Ancestor: 0
-  
-  $ hg log --template '{revset("TIP"|lower)}\n' -l1
-  2
-
-  $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
-  2
-
- a list template is evaluated for each item of revset/parents
-
-  $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
-  2 p: 1:bcc7ff960b8e
-  1 p: 0:f7769ec2ab97
-  0 p: 
-
-  $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
-  2 p: 1:bcc7ff960b8e -1:000000000000
-  1 p: 0:f7769ec2ab97 -1:000000000000
-  0 p: -1:000000000000 -1:000000000000
-
- therefore, 'revcache' should be recreated for each rev
-
-  $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
-  2 aa b
-  p 
-  1 
-  p a
-  0 a
-  p 
-
-  $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
-  2 aa b
-  p 
-  1 
-  p a
-  0 a
-  p 
-
-a revset item must be evaluated as an integer revision, not an offset from tip
-
-  $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
-  -1:000000000000
-  $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
-  -1:000000000000
-
-join() should pick '{rev}' from revset items:
-
-  $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
-  4, 5
-
-on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
-default. join() should agree with the default formatting:
-
-  $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
-  5:13207e5a10d9, 4:bbe44766e73d
-
-  $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
-  5:13207e5a10d9fd28ec424934298e176197f2c67f,
-  4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
-
-Invalid arguments passed to revset()
-
-  $ hg log -T '{revset("%whatever", 0)}\n'
-  hg: parse error: unexpected revspec format character w
-  [255]
-  $ hg log -T '{revset("%lwhatever", files)}\n'
-  hg: parse error: unexpected revspec format character w
-  [255]
-  $ hg log -T '{revset("%s %s", 0)}\n'
-  hg: parse error: missing argument for revspec
-  [255]
-  $ hg log -T '{revset("", 0)}\n'
-  hg: parse error: too many revspec arguments specified
-  [255]
-  $ hg log -T '{revset("%s", 0, 1)}\n'
-  hg: parse error: too many revspec arguments specified
-  [255]
-  $ hg log -T '{revset("%", 0)}\n'
-  hg: parse error: incomplete revspec format character
-  [255]
-  $ hg log -T '{revset("%l", 0)}\n'
-  hg: parse error: incomplete revspec format character
-  [255]
-  $ hg log -T '{revset("%d", 'foo')}\n'
-  hg: parse error: invalid argument for revspec
-  [255]
-  $ hg log -T '{revset("%ld", files)}\n'
-  hg: parse error: invalid argument for revspec
-  [255]
-  $ hg log -T '{revset("%ls", 0)}\n'
-  hg: parse error: invalid argument for revspec
-  [255]
-  $ hg log -T '{revset("%b", 'foo')}\n'
-  hg: parse error: invalid argument for revspec
-  [255]
-  $ hg log -T '{revset("%lb", files)}\n'
-  hg: parse error: invalid argument for revspec
-  [255]
-  $ hg log -T '{revset("%r", 0)}\n'
-  hg: parse error: invalid argument for revspec
-  [255]
-
-Test 'originalnode'
-
-  $ hg log -r 1 -T '{revset("null") % "{node|short} {originalnode|short}"}\n'
-  000000000000 bcc7ff960b8e
-  $ hg log -r 0 -T '{manifest % "{node} {originalnode}"}\n'
-  a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 f7769ec2ab975ad19684098ad1ffd9b81ecc71a1
-
-Test files function
-
-  $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
-  2
-  a
-  aa
-  b
-  1
-  a
-  0
-  a
-
-  $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
-  2
-  aa
-  1
-  
-  0
-  
-
-Test relpath function
-
-  $ hg log -r0 -T '{files % "{file|relpath}\n"}'
-  a
-  $ cd ..
-  $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
-  r/a
-  $ cd r
-
-Test active bookmark templating
-
-  $ hg book foo
-  $ hg book bar
-  $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
-  2 bar* foo 
-  1 
-  0 
-  $ hg log --template "{rev} {activebookmark}\n"
-  2 bar
-  1 
-  0 
-  $ hg bookmarks --inactive bar
-  $ hg log --template "{rev} {activebookmark}\n"
-  2 
-  1 
-  0 
-  $ hg book -r1 baz
-  $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
-  2 bar foo
-  1 baz
-  0 
-  $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
-  2 t
-  1 f
-  0 f
-
-Test namespaces dict
-
-  $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n  {join(names, ",")}\n"}\n'
-  2
-   bookmarks color=bookmark builtin=True
-    bar,foo
-   tags color=tag builtin=True
-    tip
-   branches color=branch builtin=True
-    text.{rev}
-   revnames color=revname builtin=False
-    r2
-  
-  1
-   bookmarks color=bookmark builtin=True
-    baz
-   tags color=tag builtin=True
-    
-   branches color=branch builtin=True
-    text.{rev}
-   revnames color=revname builtin=False
-    r1
-  
-  0
-   bookmarks color=bookmark builtin=True
-    
-   tags color=tag builtin=True
-    
-   branches color=branch builtin=True
-    default
-   revnames color=revname builtin=False
-    r0
-  
-  $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
-  bookmarks: bar foo
-  tags: tip
-  branches: text.{rev}
-  $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
-  bookmarks:
-   bar
-   foo
-  tags:
-   tip
-  branches:
-   text.{rev}
-  $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
-  bar
-  foo
-  $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
-  bar
-  foo
-
-Test stringify on sub expressions
-
-  $ cd ..
-  $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
-  fourth, second, third
-  $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
-  abc
-
-Test splitlines
-
-  $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
-  @  foo Modify, add, remove, rename
-  |
-  o  foo future
-  |
-  o  foo third
-  |
-  o  foo second
-  
-  o    foo merge
-  |\
-  | o  foo new head
-  | |
-  o |  foo new branch
-  |/
-  o  foo no user, no domain
-  |
-  o  foo no person
-  |
-  o  foo other 1
-  |  foo other 2
-  |  foo
-  |  foo other 3
-  o  foo line 1
-     foo line 2
-
-  $ hg log -R a -r0 -T '{desc|splitlines}\n'
-  line 1 line 2
-  $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
-  line 1|line 2
-
-Test startswith
-  $ hg log -Gv -R a --template "{startswith(desc)}"
-  hg: parse error: startswith expects two arguments
-  [255]
-
-  $ hg log -Gv -R a --template "{startswith('line', desc)}"
-  @
-  |
-  o
-  |
-  o
-  |
-  o
-  
-  o
-  |\
-  | o
-  | |
-  o |
-  |/
-  o
-  |
-  o
-  |
-  o
-  |
-  o  line 1
-     line 2
-
-Test bad template with better error message
-
-  $ hg log -Gv -R a --template '{desc|user()}'
-  hg: parse error: expected a symbol, got 'func'
-  [255]
-
-Test word function (including index out of bounds graceful failure)
-
-  $ hg log -Gv -R a --template "{word('1', desc)}"
-  @  add,
-  |
-  o
-  |
-  o
-  |
-  o
-  
-  o
-  |\
-  | o  head
-  | |
-  o |  branch
-  |/
-  o  user,
-  |
-  o  person
-  |
-  o  1
-  |
-  o  1
-  
-
-Test word third parameter used as splitter
-
-  $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
-  @  M
-  |
-  o  future
-  |
-  o  third
-  |
-  o  sec
-  
-  o    merge
-  |\
-  | o  new head
-  | |
-  o |  new branch
-  |/
-  o  n
-  |
-  o  n
-  |
-  o
-  |
-  o  line 1
-     line 2
-
-Test word error messages for not enough and too many arguments
-
-  $ hg log -Gv -R a --template "{word('0')}"
-  hg: parse error: word expects two or three arguments, got 1
-  [255]
-
-  $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
-  hg: parse error: word expects two or three arguments, got 7
-  [255]
-
-Test word for integer literal
-
-  $ hg log -R a --template "{word(2, desc)}\n" -r0
-  line
-
-Test word for invalid numbers
-
-  $ hg log -Gv -R a --template "{word('a', desc)}"
-  hg: parse error: word expects an integer index
-  [255]
-
-Test word for out of range
-
-  $ hg log -R a --template "{word(10000, desc)}"
-  $ hg log -R a --template "{word(-10000, desc)}"
-
-Test indent and not adding to empty lines
-
-  $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
-  -----
-   > line 1
-  >> line 2
-  -----
-   > other 1
-  >> other 2
-  
-  >> other 3
-
-Test with non-strings like dates
-
-  $ hg log -T "{indent(date, '   ')}\n" -r 2:3 -R a
-     1200000.00
-     1300000.00
-
-Test broken string escapes:
-
-  $ hg log -T "bogus\\" -R a
-  hg: parse error: trailing \ in string
-  [255]
-  $ hg log -T "\\xy" -R a
-  hg: parse error: invalid \x escape* (glob)
-  [255]
-
-json filter should escape HTML tags so that the output can be embedded in hgweb:
-
-  $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
-  "\u003cfoo@example.org\u003e"
-
-Templater supports aliases of symbol and func() styles:
-
-  $ hg clone -q a aliases
-  $ cd aliases
-  $ cat <<EOF >> .hg/hgrc
-  > [templatealias]
-  > r = rev
-  > rn = "{r}:{node|short}"
-  > status(c, files) = files % "{c} {file}\n"
-  > utcdate(d) = localdate(d, "UTC")
-  > EOF
-
-  $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
-  (template
-    (symbol 'rn')
-    (string ' ')
-    (|
-      (func
-        (symbol 'utcdate')
-        (symbol 'date'))
-      (symbol 'isodate'))
-    (string '\n'))
-  * expanded:
-  (template
-    (template
-      (symbol 'rev')
-      (string ':')
-      (|
-        (symbol 'node')
-        (symbol 'short')))
-    (string ' ')
-    (|
-      (func
-        (symbol 'localdate')
-        (list
-          (symbol 'date')
-          (string 'UTC')))
-      (symbol 'isodate'))
-    (string '\n'))
-  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
-
-  $ hg debugtemplate -vr0 '{status("A", file_adds)}'
-  (template
-    (func
-      (symbol 'status')
-      (list
-        (string 'A')
-        (symbol 'file_adds'))))
-  * expanded:
-  (template
-    (%
-      (symbol 'file_adds')
-      (template
-        (string 'A')
-        (string ' ')
-        (symbol 'file')
-        (string '\n'))))
-  A a
-
-A unary function alias can be called as a filter:
-
-  $ hg debugtemplate -vr0 '{date|utcdate|isodate}\n'
-  (template
-    (|
-      (|
-        (symbol 'date')
-        (symbol 'utcdate'))
-      (symbol 'isodate'))
-    (string '\n'))
-  * expanded:
-  (template
-    (|
-      (func
-        (symbol 'localdate')
-        (list
-          (symbol 'date')
-          (string 'UTC')))
-      (symbol 'isodate'))
-    (string '\n'))
-  1970-01-12 13:46 +0000
-
-Aliases should be applied only to command arguments and templates in hgrc.
-Otherwise, our stock styles and web templates could be corrupted:
-
-  $ hg log -r0 -T '{rn} {utcdate(date)|isodate}\n'
-  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
-
-  $ hg log -r0 --config ui.logtemplate='"{rn} {utcdate(date)|isodate}\n"'
-  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
-
-  $ cat <<EOF > tmpl
-  > changeset = 'nothing expanded:{rn}\n'
-  > EOF
-  $ hg log -r0 --style ./tmpl
-  nothing expanded:
-
-Aliases in formatter:
-
-  $ hg branches -T '{pad(branch, 7)} {rn}\n'
-  default 6:d41e714fe50d
-  foo     4:bbe44766e73d
-
-Aliases should honor HGPLAIN:
-
-  $ HGPLAIN= hg log -r0 -T 'nothing expanded:{rn}\n'
-  nothing expanded:
-  $ HGPLAINEXCEPT=templatealias hg log -r0 -T '{rn}\n'
-  0:1e4e1b8f71e0
-
-Unparsable alias:
-
-  $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
-  (template
-    (symbol 'bad'))
-  abort: bad definition of template alias "bad": at 2: not a prefix: end
-  [255]
-  $ hg log --config templatealias.bad='x(' -T '{bad}'
-  abort: bad definition of template alias "bad": at 2: not a prefix: end
-  [255]
-
-  $ cd ..
-
-Set up repository for non-ascii encoding tests:
-
-  $ hg init nonascii
-  $ cd nonascii
-  $ $PYTHON <<EOF
-  > open('latin1', 'wb').write(b'\xe9')
-  > open('utf-8', 'wb').write(b'\xc3\xa9')
-  > EOF
-  $ HGENCODING=utf-8 hg branch -q `cat utf-8`
-  $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
-
-json filter should try round-trip conversion to utf-8:
-
-  $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
-  "\u00e9"
-  $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
-  "non-ascii branch: \u00e9"
-
-json filter takes input as utf-8b:
-
-  $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
-  "\u00e9"
-  $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
-  "\udce9"
-
-utf8 filter:
-
-  $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
-  round-trip: c3a9
-  $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
-  decoded: c3a9
-  $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
-  abort: decoding near * (glob)
-  [255]
-  $ hg log -T "coerced to string: {rev|utf8}\n" -r0
-  coerced to string: 0
-
-pad width:
-
-  $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
-  \xc3\xa9- (esc)
-
-  $ cd ..
-
-Test that template function in extension is registered as expected
-
-  $ cd a
-
-  $ cat <<EOF > $TESTTMP/customfunc.py
-  > from mercurial import registrar
-  > 
-  > templatefunc = registrar.templatefunc()
-  > 
-  > @templatefunc(b'custom()')
-  > def custom(context, mapping, args):
-  >     return b'custom'
-  > EOF
-  $ cat <<EOF > .hg/hgrc
-  > [extensions]
-  > customfunc = $TESTTMP/customfunc.py
-  > EOF
-
-  $ hg log -r . -T "{custom()}\n" --config customfunc.enabled=true
-  custom
-
-  $ cd ..
-
-Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
-printed graphwidths 3, 5, 7, etc. should all line up in their respective
-columns. We don't care about other aspects of the graph rendering here.
-
-  $ hg init graphwidth
-  $ cd graphwidth
-
-  $ wrappabletext="a a a a a a a a a a a a"
-
-  $ printf "first\n" > file
-  $ hg add file
-  $ hg commit -m "$wrappabletext"
-
-  $ printf "first\nsecond\n" > file
-  $ hg commit -m "$wrappabletext"
-
-  $ hg checkout 0
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ printf "third\nfirst\n" > file
-  $ hg commit -m "$wrappabletext"
-  created new head
-
-  $ hg merge
-  merging file
-  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-
-  $ hg log --graph -T "{graphwidth}"
-  @  3
-  |
-  | @  5
-  |/
-  o  3
-  
-  $ hg commit -m "$wrappabletext"
-
-  $ hg log --graph -T "{graphwidth}"
-  @    5
-  |\
-  | o  5
-  | |
-  o |  5
-  |/
-  o  3
-  
-
-  $ hg checkout 0
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ printf "third\nfirst\nsecond\n" > file
-  $ hg commit -m "$wrappabletext"
-  created new head
-
-  $ hg log --graph -T "{graphwidth}"
-  @  3
-  |
-  | o    7
-  | |\
-  +---o  7
-  | |
-  | o  5
-  |/
-  o  3
-  
-
-  $ hg log --graph -T "{graphwidth}" -r 3
-  o    5
-  |\
-  ~ ~
-
-  $ hg log --graph -T "{graphwidth}" -r 1
-  o  3
-  |
-  ~
-
-  $ hg merge
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
-  $ hg commit -m "$wrappabletext"
-
-  $ printf "seventh\n" >> file
-  $ hg commit -m "$wrappabletext"
-
-  $ hg log --graph -T "{graphwidth}"
-  @  3
-  |
-  o    5
-  |\
-  | o  5
-  | |
-  o |    7
-  |\ \
-  | o |  7
-  | |/
-  o /  5
-  |/
-  o  3
-  
-
-The point of graphwidth is to allow wrapping that accounts for the space taken
-by the graph.
-
-  $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
-  @  a a a a
-  |  a a a a
-  |  a a a a
-  o    a a a
-  |\   a a a
-  | |  a a a
-  | |  a a a
-  | o  a a a
-  | |  a a a
-  | |  a a a
-  | |  a a a
-  o |    a a
-  |\ \   a a
-  | | |  a a
-  | | |  a a
-  | | |  a a
-  | | |  a a
-  | o |  a a
-  | |/   a a
-  | |    a a
-  | |    a a
-  | |    a a
-  | |    a a
-  o |  a a a
-  |/   a a a
-  |    a a a
-  |    a a a
-  o  a a a a
-     a a a a
-     a a a a
-
-Something tricky happens when there are elided nodes; the next drawn row of
-edges can be more than one column wider, but the graph width only increases by
-one column. The remaining columns are added in between the nodes.
-
-  $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
-  o    5
-  |\
-  | \
-  | :\
-  o : :  7
-  :/ /
-  : o  5
-  :/
-  o  3
-  
-
-  $ cd ..
-
--- a/tests/test-commit-interactive-curses.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-commit-interactive-curses.t	Thu Jul 19 13:55:54 2018 -0400
@@ -68,7 +68,7 @@
 Committing only one file
 
   $ echo "a" >> a
-  >>> open('b', 'wb').write("1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n")
+  >>> open('b', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
   $ hg add b
   $ cat <<EOF >testModeCommands
   > TOGGLE
@@ -368,6 +368,16 @@
   $ chunkselectorinterface
   curses
 
+If TERM=dumb, we use text, even if the config says curses
+  $ chunkselectorinterface
+  curses
+  $ TERM=dumb chunkselectorinterface
+  text
+(Something is keeping TERM=dumb in the environment unless I do this, it's not
+scoped to just that previous command like in many shells)
+  $ TERM=xterm chunkselectorinterface
+  curses
+
 It is possible to override the default interface with a feature specific
 interface
   $ cp $HGRCPATH.pretest $HGRCPATH
--- a/tests/test-completion.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-completion.t	Thu Jul 19 13:55:54 2018 -0400
@@ -191,6 +191,7 @@
   --pid-file
   --port
   --prefix
+  --print-url
   --profile
   --quiet
   --repository
@@ -231,7 +232,7 @@
   clone: noupdate, updaterev, rev, branch, pull, uncompressed, stream, ssh, remotecmd, insecure
   commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
   diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, unified, stat, root, include, exclude, subrepos
-  export: output, switch-parent, rev, text, git, binary, nodates, template
+  export: bookmark, output, switch-parent, rev, text, git, binary, nodates, template
   forget: interactive, include, exclude, dry-run
   init: ssh, remotecmd, insecure
   log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
@@ -239,7 +240,7 @@
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
   push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude, dry-run
-  serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
+  serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, print-url, subrepos
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
   summary: remote
   update: clean, check, merge, date, rev, tool
@@ -272,7 +273,7 @@
   debugdiscovery: old, nonheads, rev, ssh, remotecmd, insecure
   debugdownload: output
   debugextensions: template
-  debugfileset: rev
+  debugfileset: rev, all-files
   debugformat: template
   debugfsinfo: 
   debuggetbundle: head, common, type
@@ -311,8 +312,8 @@
   debugwireargs: three, four, five, ssh, remotecmd, insecure
   debugwireproto: localssh, peer, noreadstderr, nologhandshake, ssh, remotecmd, insecure
   files: rev, print0, include, exclude, template, subrepos
-  graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run
-  grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, template, include, exclude
+  graft: rev, continue, stop, abort, edit, log, no-commit, force, currentdate, currentuser, date, user, tool, dry-run
+  grep: print0, all, diff, text, follow, ignore-case, files-with-matches, line-number, rev, all-files, user, date, template, include, exclude
   heads: rev, topo, active, closed, style, template
   help: extension, command, keyword, system
   identify: rev, num, id, branch, tags, bookmarks, ssh, remotecmd, insecure, template
--- a/tests/test-conflict.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-conflict.t	Thu Jul 19 13:55:54 2018 -0400
@@ -57,8 +57,8 @@
   # 
   # To mark files as resolved:  hg resolve --mark FILE
   
-  # To continue:                hg commit
-  # To abort:                   hg update --clean .    (warning: this will discard uncommitted changes)
+  # To continue:    hg commit
+  # To abort:       hg update --clean . (warning: this will discard uncommitted changes)
   
 
   $ cat a
--- a/tests/test-context-metadata.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-context-metadata.t	Thu Jul 19 13:55:54 2018 -0400
@@ -23,7 +23,7 @@
   >         old = repo[b'.']
   >         kwargs = dict(s.split(b'=', 1) for s in arg.split(b';'))
   >         if 'parents' in kwargs:
-  >             kwargs[b'parents'] = kwargs[b'parents'].split(b',')
+  >             kwargs[b'parents'] = map(int, kwargs[b'parents'].split(b','))
   >         new = context.metadataonlyctx(repo, old,
   >                                       **pycompat.strkwargs(kwargs))
   >         new.commit()
--- a/tests/test-context.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-context.py	Thu Jul 19 13:55:54 2018 -0400
@@ -1,18 +1,35 @@
 from __future__ import absolute_import, print_function
 import os
 import stat
+import sys
 from mercurial.node import hex
 from mercurial import (
     context,
+    diffutil,
     encoding,
     hg,
     scmutil,
     ui as uimod,
 )
 
+print_ = print
+def print(*args, **kwargs):
+    """print() wrapper that flushes stdout buffers to avoid py3 buffer issues
+
+    We could also just write directly to sys.stdout.buffer the way the
+    ui object will, but this was easier for porting the test.
+    """
+    print_(*args, **kwargs)
+    sys.stdout.flush()
+
+def printb(data, end=b'\n'):
+    out = getattr(sys.stdout, 'buffer', sys.stdout)
+    out.write(data + end)
+    out.flush()
+
 u = uimod.ui.load()
 
-repo = hg.repository(u, 'test1', create=1)
+repo = hg.repository(u, b'test1', create=1)
 os.chdir('test1')
 
 # create 'foo' with fixed time stamp
@@ -22,10 +39,10 @@
 os.utime('foo', (1000, 1000))
 
 # add+commit 'foo'
-repo[None].add(['foo'])
-repo.commit(text='commit1', date="0 0")
+repo[None].add([b'foo'])
+repo.commit(text=b'commit1', date=b"0 0")
 
-d = repo[None]['foo'].date()
+d = repo[None][b'foo'].date()
 if os.name == 'nt':
     d = d[:2]
 print("workingfilectx.date = (%d, %d)" % d)
@@ -33,54 +50,55 @@
 # test memctx with non-ASCII commit message
 
 def filectxfn(repo, memctx, path):
-    return context.memfilectx(repo, memctx, "foo", "")
+    return context.memfilectx(repo, memctx, b"foo", b"")
 
-ctx = context.memctx(repo, ['tip', None],
-                     encoding.tolocal("Gr\xc3\xbcezi!"),
-                     ["foo"], filectxfn)
+ctx = context.memctx(repo, [b'tip', None],
+                     encoding.tolocal(b"Gr\xc3\xbcezi!"),
+                     [b"foo"], filectxfn)
 ctx.commit()
 for enc in "ASCII", "Latin-1", "UTF-8":
     encoding.encoding = enc
-    print("%-8s: %s" % (enc, repo["tip"].description()))
+    printb(b"%-8s: %s" % (enc.encode('ascii'), repo[b"tip"].description()))
 
 # test performing a status
 
 def getfilectx(repo, memctx, f):
     fctx = memctx.parents()[0][f]
     data, flags = fctx.data(), fctx.flags()
-    if f == 'foo':
-        data += 'bar\n'
-    return context.memfilectx(repo, memctx, f, data, 'l' in flags, 'x' in flags)
+    if f == b'foo':
+        data += b'bar\n'
+    return context.memfilectx(
+        repo, memctx, f, data, b'l' in flags, b'x' in flags)
 
 ctxa = repo[0]
-ctxb = context.memctx(repo, [ctxa.node(), None], "test diff", ["foo"],
+ctxb = context.memctx(repo, [ctxa.node(), None], b"test diff", [b"foo"],
                       getfilectx, ctxa.user(), ctxa.date())
 
 print(ctxb.status(ctxa))
 
 # test performing a diff on a memctx
-
-for d in ctxb.diff(ctxa, git=True):
-    print(d, end='')
+diffopts = diffutil.diffallopts(repo.ui, {b'git': True})
+for d in ctxb.diff(ctxa, opts=diffopts):
+    printb(d, end=b'')
 
 # test safeness and correctness of "ctx.status()"
 print('= checking context.status():')
 
 # ancestor "wcctx ~ 2"
-actx2 = repo['.']
+actx2 = repo[b'.']
 
-repo.wwrite('bar-m', 'bar-m\n', '')
-repo.wwrite('bar-r', 'bar-r\n', '')
-repo[None].add(['bar-m', 'bar-r'])
-repo.commit(text='add bar-m, bar-r', date="0 0")
+repo.wwrite(b'bar-m', b'bar-m\n', b'')
+repo.wwrite(b'bar-r', b'bar-r\n', b'')
+repo[None].add([b'bar-m', b'bar-r'])
+repo.commit(text=b'add bar-m, bar-r', date=b"0 0")
 
 # ancestor "wcctx ~ 1"
-actx1 = repo['.']
+actx1 = repo[b'.']
 
-repo.wwrite('bar-m', 'bar-m bar-m\n', '')
-repo.wwrite('bar-a', 'bar-a\n', '')
-repo[None].add(['bar-a'])
-repo[None].forget(['bar-r'])
+repo.wwrite(b'bar-m', b'bar-m bar-m\n', b'')
+repo.wwrite(b'bar-a', b'bar-a\n', b'')
+repo[None].add([b'bar-a'])
+repo[None].forget([b'bar-r'])
 
 # status at this point:
 #   M bar-m
@@ -97,10 +115,10 @@
 
 print('=== with "pattern match":')
 print(actx1.status(other=wctx,
-                   match=scmutil.matchfiles(repo, ['bar-m', 'foo'])))
+                   match=scmutil.matchfiles(repo, [b'bar-m', b'foo'])))
 print('wctx._status=%s' % (str(wctx._status)))
 print(actx2.status(other=wctx,
-                   match=scmutil.matchfiles(repo, ['bar-m', 'foo'])))
+                   match=scmutil.matchfiles(repo, [b'bar-m', b'foo'])))
 print('wctx._status=%s' % (str(wctx._status)))
 
 print('=== with "always match" and "listclean=True":')
@@ -112,11 +130,11 @@
 print("== checking workingcommitctx.status:")
 
 wcctx = context.workingcommitctx(repo,
-                                 scmutil.status(['bar-m'],
-                                                ['bar-a'],
+                                 scmutil.status([b'bar-m'],
+                                                [b'bar-a'],
                                                 [],
                                                 [], [], [], []),
-                                 text='', date='0 0')
+                                 text=b'', date=b'0 0')
 print('wcctx._status=%s' % (str(wcctx._status)))
 
 print('=== with "always match":')
@@ -133,19 +151,19 @@
 
 print('=== with "pattern match":')
 print(actx1.status(other=wcctx,
-                   match=scmutil.matchfiles(repo, ['bar-m', 'foo'])))
+                   match=scmutil.matchfiles(repo, [b'bar-m', b'foo'])))
 print('wcctx._status=%s' % (str(wcctx._status)))
 print(actx2.status(other=wcctx,
-                   match=scmutil.matchfiles(repo, ['bar-m', 'foo'])))
+                   match=scmutil.matchfiles(repo, [b'bar-m', b'foo'])))
 print('wcctx._status=%s' % (str(wcctx._status)))
 
 print('=== with "pattern match" and "listclean=True":')
 print(actx1.status(other=wcctx,
-                   match=scmutil.matchfiles(repo, ['bar-r', 'foo']),
+                   match=scmutil.matchfiles(repo, [b'bar-r', b'foo']),
                    listclean=True))
 print('wcctx._status=%s' % (str(wcctx._status)))
 print(actx2.status(other=wcctx,
-                   match=scmutil.matchfiles(repo, ['bar-r', 'foo']),
+                   match=scmutil.matchfiles(repo, [b'bar-r', b'foo']),
                    listclean=True))
 print('wcctx._status=%s' % (str(wcctx._status)))
 
@@ -154,7 +172,7 @@
 # test manifestlog being changed
 print('== commit with manifestlog invalidated')
 
-repo = hg.repository(u, 'test2', create=1)
+repo = hg.repository(u, b'test2', create=1)
 os.chdir('test2')
 
 # make some commits
@@ -166,12 +184,12 @@
                                    date=(0, 0))
     ctx.p1().manifest() # side effect: cache manifestctx
     n = repo.commitctx(ctx)
-    print('commit %s: %s' % (i, hex(n)))
+    printb(b'commit %s: %s' % (i, hex(n)))
 
     # touch 00manifest.i mtime so storecache could expire.
     # repo.__dict__['manifestlog'] is deleted by transaction releasefn.
-    st = repo.svfs.stat('00manifest.i')
-    repo.svfs.utime('00manifest.i',
+    st = repo.svfs.stat(b'00manifest.i')
+    repo.svfs.utime(b'00manifest.i',
                     (st[stat.ST_MTIME] + 1, st[stat.ST_MTIME] + 1))
 
     # read the file just committed
@@ -181,11 +199,11 @@
     except Exception as ex:
         print('cannot read data: %r' % ex)
 
-with repo.wlock(), repo.lock(), repo.transaction('test'):
+with repo.wlock(), repo.lock(), repo.transaction(b'test'):
     with open(b'4', 'wb') as f:
         f.write(b'4')
-    repo.dirstate.normal('4')
-    repo.commit('4')
+    repo.dirstate.normal(b'4')
+    repo.commit(b'4')
     revsbefore = len(repo.changelog)
     repo.invalidate(clearfilecache=True)
     revsafter = len(repo.changelog)
--- a/tests/test-contrib-perf.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-contrib-perf.t	Thu Jul 19 13:55:54 2018 -0400
@@ -88,7 +88,8 @@
    perflookup    (no help text available)
    perflrucachedict
                  (no help text available)
-   perfmanifest  (no help text available)
+   perfmanifest  benchmark the time to read a manifest from disk and return a
+                 usable
    perfmergecalculate
                  (no help text available)
    perfmoonwalk  benchmark walking the changelog backwards
@@ -113,7 +114,7 @@
    perfstatus    (no help text available)
    perftags      (no help text available)
    perftemplating
-                 (no help text available)
+                 test the rendering time of a given template
    perfunidiff   benchmark a unified diff between revisions
    perfvolatilesets
                  benchmark the computation of various volatile set
@@ -145,7 +146,11 @@
 #if repofncache
   $ hg perffncacheencode
   $ hg perffncacheload
+  $ hg debugrebuildfncache
+  fncache already up to date
   $ hg perffncachewrite
+  $ hg debugrebuildfncache
+  fncache already up to date
 #endif
   $ hg perfheads
   $ hg perfindex
@@ -175,7 +180,24 @@
   $ hg perfwalk
   $ hg perfparents
 
+test actual output
+------------------
+
+normal output:
+
+  $ hg perfheads --config perf.stub=no
+  ! wall * comb * user * sys * (best of *) (glob)
+
+detailed output:
+
+  $ hg perfheads --config perf.all-timing=yes --config perf.stub=no
+  ! wall * comb * user * sys * (best of *) (glob)
+  ! wall * comb * user * sys * (max of *) (glob)
+  ! wall * comb * user * sys * (avg of *) (glob)
+  ! wall * comb * user * sys * (median of *) (glob)
+
 Check perf.py for historical portability
+----------------------------------------
 
   $ cd "$TESTDIR/.."
 
--- a/tests/test-convert-bzr-merges.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-convert-bzr-merges.t	Thu Jul 19 13:55:54 2018 -0400
@@ -14,32 +14,32 @@
   $ cd source
   $ echo content > file
   $ bzr add -q file
-  $ bzr commit -q -m 'Initial add'
+  $ bzr commit -q -m 'Initial add' '--commit-time=2009-10-10 08:00:00 +0100'
   $ cd ..
   $ bzr branch -q source source-branch1
   $ cd source-branch1
   $ echo morecontent >> file
   $ echo evenmorecontent > file-branch1
   $ bzr add -q file-branch1
-  $ bzr commit -q -m 'Added branch1 file'
+  $ bzr commit -q -m 'Added branch1 file' '--commit-time=2009-10-10 08:00:01 +0100'
   $ cd ../source
   $ sleep 1
   $ echo content > file-parent
   $ bzr add -q file-parent
-  $ bzr commit -q -m 'Added parent file'
+  $ bzr commit -q -m 'Added parent file' '--commit-time=2009-10-10 08:00:02 +0100'
   $ cd ..
   $ bzr branch -q source source-branch2
   $ cd source-branch2
   $ echo somecontent > file-branch2
   $ bzr add -q file-branch2
-  $ bzr commit -q -m 'Added brach2 file'
+  $ bzr commit -q -m 'Added brach2 file' '--commit-time=2009-10-10 08:00:03 +0100'
   $ sleep 1
   $ cd ../source
   $ bzr merge -q ../source-branch1
   $ bzr merge -q --force ../source-branch2
-  $ bzr commit -q -m 'Merged branches'
+  $ bzr commit -q -m 'Merged branches' '--commit-time=2009-10-10 08:00:04 +0100'
   $ cd ..
-  $ hg convert --datesort source source-hg
+  $ hg convert --datesort --config convert.bzr.saverev=False source source-hg
   initializing destination source-hg repository
   scanning source...
   sorting...
@@ -69,4 +69,76 @@
   644   file-branch2
   644   file-parent
 
+  $ hg convert source-hg hg2hg
+  initializing destination hg2hg repository
+  scanning source...
+  sorting...
+  converting...
+  5 Initial add
+  4 Added branch1 file
+  3 Added parent file
+  2 Added brach2 file
+  1 Merged branches
+  0 (octopus merge fixup)
+  $ hg -R hg2hg out source-hg -T compact
+  comparing with source-hg
+  searching for changes
+  5[tip]:4,3   6bd55e826939   2009-10-10 08:00 +0100   foo
+    (octopus merge fixup)
+  
+XXX: The manifest lines should probably agree, to avoid changing the hash when
+converting hg -> hg
+
+  $ hg -R source-hg log --debug -r tip
+  changeset:   5:b209510f11b2c987f920749cd8e352aa4b3230f2
+  branch:      source
+  tag:         tip
+  phase:       draft
+  parent:      4:1dc38c377bb35eeea4fa955056fbe4440d54a743
+  parent:      3:4aaba1bfb426b8941bbf63f9dd52301152695164
+  manifest:    5:1109e42bdcbd1f51baa69bc91079011d77057dbb
+  user:        Foo Bar <foo.bar@example.com>
+  date:        Sat Oct 10 08:00:04 2009 +0100
+  extra:       branch=source
+  description:
+  (octopus merge fixup)
+  
+  
+  $ hg -R hg2hg log --debug -r tip
+  changeset:   5:6bd55e8269392769783345686faf7ff7b3b0215d
+  branch:      source
+  tag:         tip
+  phase:       draft
+  parent:      4:1dc38c377bb35eeea4fa955056fbe4440d54a743
+  parent:      3:4aaba1bfb426b8941bbf63f9dd52301152695164
+  manifest:    4:daa315d56a98ba20811fdd0d9d575861f65cfa8c
+  user:        Foo Bar <foo.bar@example.com>
+  date:        Sat Oct 10 08:00:04 2009 +0100
+  extra:       branch=source
+  description:
+  (octopus merge fixup)
+  
+  
+  $ hg -R source-hg manifest --debug -r tip
+  cdf31ed9242b209cd94697112160e2c5b37a667d 644   file
+  5108144f585149b29779d7c7e51d61dd22303ffe 644   file-branch1
+  80753c4a9ac3806858405b96b24a907b309e3616 644   file-branch2
+  7108421418404a937c684d2479a34a24d2ce4757 644   file-parent
+  $ hg -R source-hg manifest --debug -r 'tip^'
+  cdf31ed9242b209cd94697112160e2c5b37a667d 644   file
+  5108144f585149b29779d7c7e51d61dd22303ffe 644   file-branch1
+  80753c4a9ac3806858405b96b24a907b309e3616 644   file-branch2
+  7108421418404a937c684d2479a34a24d2ce4757 644   file-parent
+
+  $ hg -R hg2hg manifest --debug -r tip
+  cdf31ed9242b209cd94697112160e2c5b37a667d 644   file
+  5108144f585149b29779d7c7e51d61dd22303ffe 644   file-branch1
+  80753c4a9ac3806858405b96b24a907b309e3616 644   file-branch2
+  7108421418404a937c684d2479a34a24d2ce4757 644   file-parent
+  $ hg -R hg2hg manifest --debug -r 'tip^'
+  cdf31ed9242b209cd94697112160e2c5b37a667d 644   file
+  5108144f585149b29779d7c7e51d61dd22303ffe 644   file-branch1
+  80753c4a9ac3806858405b96b24a907b309e3616 644   file-branch2
+  7108421418404a937c684d2479a34a24d2ce4757 644   file-parent
+
   $ cd ..
--- a/tests/test-convert-cvs.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-convert-cvs.t	Thu Jul 19 13:55:54 2018 -0400
@@ -12,10 +12,10 @@
   $ echo "convert = " >> $HGRCPATH
   $ cat > cvshooks.py <<EOF
   > def cvslog(ui,repo,hooktype,log):
-  >     ui.write('%s hook: %d entries\n' % (hooktype,len(log)))
+  >     ui.write(b'%s hook: %d entries\n' % (hooktype,len(log)))
   > 
   > def cvschangesets(ui,repo,hooktype,changesets):
-  >     ui.write('%s hook: %d changesets\n' % (hooktype,len(changesets)))
+  >     ui.write(b'%s hook: %d changesets\n' % (hooktype,len(changesets)))
   > EOF
   $ hookpath=`pwd`
   $ cat <<EOF >> $HGRCPATH
@@ -522,8 +522,8 @@
 
   $ mkdir -p cvsrepo/transcoding
   $ python <<EOF
-  > fp = open('cvsrepo/transcoding/file,v', 'w')
-  > fp.write(('''
+  > fp = open('cvsrepo/transcoding/file,v', 'wb')
+  > fp.write((b'''
   > head	1.4;
   > access;
   > symbols
@@ -570,7 +570,7 @@
   > 
   > 1.4
   > log
-  > @''' + u'\u3042'.encode('cp932') + ''' (cp932)
+  > @''' + u'\u3042'.encode('cp932') + b''' (cp932)
   > @
   > text
   > @1
@@ -582,7 +582,7 @@
   > 
   > 1.3
   > log
-  > @''' + u'\u3042'.encode('euc-jp') + ''' (euc-jp)
+  > @''' + u'\u3042'.encode('euc-jp') + b''' (euc-jp)
   > @
   > text
   > @d4 1
@@ -591,7 +591,7 @@
   > 
   > 1.2
   > log
-  > @''' + u'\u3042'.encode('utf-8') +  ''' (utf-8)
+  > @''' + u'\u3042'.encode('utf-8') +  b''' (utf-8)
   > @
   > text
   > @d3 1
--- a/tests/test-convert.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-convert.t	Thu Jul 19 13:55:54 2018 -0400
@@ -148,6 +148,15 @@
       convert.hg.revs
                     revset specifying the source revisions to convert.
   
+      Bazaar Source
+      #############
+  
+      The following options can be used with "--config":
+  
+      convert.bzr.saverev
+                    whether to store the original Bazaar commit ID in the
+                    metadata of the destination commit. The default is True.
+  
       CVS Source
       ##########
   
@@ -419,6 +428,7 @@
   pulling from ../a
   searching for changes
   no changes found
+  5 local changesets published
 
 conversion to existing file should fail
 
--- a/tests/test-copytrace-heuristics.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-copytrace-heuristics.t	Thu Jul 19 13:55:54 2018 -0400
@@ -583,7 +583,6 @@
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 2:45f63161acea "changes to: initial" (tip)
   merging b and a to b
   $ ls
   b
--- a/tests/test-custom-filters.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-custom-filters.t	Thu Jul 19 13:55:54 2018 -0400
@@ -12,15 +12,15 @@
   $ cat > prefix.py <<EOF
   > from mercurial import error
   > def stripprefix(s, cmd, filename, **kwargs):
-  >     header = '%s\n' % cmd
+  >     header = b'%s\n' % cmd
   >     if s[:len(header)] != header:
-  >         raise error.Abort('missing header "%s" in %s' % (cmd, filename))
+  >         raise error.Abort(b'missing header "%s" in %s' % (cmd, filename))
   >     return s[len(header):]
   > def insertprefix(s, cmd):
-  >     return '%s\n%s' % (cmd, s)
+  >     return b'%s\n%s' % (cmd, s)
   > def reposetup(ui, repo):
-  >     repo.adddatafilter('stripprefix:', stripprefix)
-  >     repo.adddatafilter('insertprefix:', insertprefix)
+  >     repo.adddatafilter(b'stripprefix:', stripprefix)
+  >     repo.adddatafilter(b'insertprefix:', insertprefix)
   > EOF
 
   $ cat > .hgignore <<EOF
--- a/tests/test-debugcommands.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-debugcommands.t	Thu Jul 19 13:55:54 2018 -0400
@@ -8,33 +8,41 @@
   $ echo a > a
   $ hg ci -Am adda
   adding a
+  $ hg rm .
+  removing a
+  $ hg ci -Am make-it-empty
+  $ hg revert --all -r 0
+  adding a
+  $ hg ci -Am make-it-full
 #if reporevlogstore
   $ hg debugrevlog -m
   format : 1
   flags  : inline, generaldelta
   
-  revisions     :  1
+  revisions     :  3
       merges    :  0 ( 0.00%)
-      normal    :  1 (100.00%)
-  revisions     :  1
-      full      :  1 (100.00%)
+      normal    :  3 (100.00%)
+  revisions     :  3
+      full      :  3 (100.00%)
       deltas    :  0 ( 0.00%)
-  revision size : 44
-      full      : 44 (100.00%)
+  revision size : 88
+      full      : 88 (100.00%)
       deltas    :  0 ( 0.00%)
   
-  chunks        :  1
-      0x75 (u)  :  1 (100.00%)
-  chunks size   : 44
-      0x75 (u)  : 44 (100.00%)
+  chunks        :  3
+      empty     :  1 (33.33%)
+      0x75 (u)  :  2 (66.67%)
+  chunks size   : 88
+      empty     :  0 ( 0.00%)
+      0x75 (u)  : 88 (100.00%)
   
   avg chain length  :  0
   max chain length  :  0
   max chain reach   : 44
   compression ratio :  0
   
-  uncompressed data size (min/max/avg) : 43 / 43 / 43
-  full revision size (min/max/avg)     : 44 / 44 / 44
+  uncompressed data size (min/max/avg) : 0 / 43 / 28
+  full revision size (min/max/avg)     : 0 / 44 / 29
   delta size (min/max/avg)             : 0 / 0 / 0
 #endif
 
@@ -73,9 +81,13 @@
   $ hg debugdeltachain -m
       rev  chain# chainlen     prev   delta       size    rawsize  chainsize     ratio   lindist extradist extraratio
         0       1        1       -1    base         44         43         44   1.02326        44         0    0.00000
+        1       2        1       -1    base          0          0          0   0.00000         0         0    0.00000
+        2       3        1       -1    base         44         43         44   1.02326        44         0    0.00000
 
   $ hg debugdeltachain -m -T '{rev} {chainid} {chainlen}\n'
   0 1 1
+  1 2 1
+  2 3 1
 
   $ hg debugdeltachain -m -Tjson
   [
@@ -92,6 +104,34 @@
     "prevrev": -1,
     "rev": 0,
     "uncompsize": 43
+   },
+   {
+    "chainid": 2,
+    "chainlen": 1,
+    "chainratio": 0,
+    "chainsize": 0,
+    "compsize": 0,
+    "deltatype": "base",
+    "extradist": 0,
+    "extraratio": 0,
+    "lindist": 0,
+    "prevrev": -1,
+    "rev": 1,
+    "uncompsize": 0
+   },
+   {
+    "chainid": 3,
+    "chainlen": 1,
+    "chainratio": 1.02325581395,
+    "chainsize": 44,
+    "compsize": 44,
+    "deltatype": "base",
+    "extradist": 0,
+    "extraratio": 0.0,
+    "lindist": 44,
+    "prevrev": -1,
+    "rev": 2,
+    "uncompsize": 43
    }
   ]
 
@@ -104,9 +144,13 @@
   $ hg debugdeltachain -m
       rev  chain# chainlen     prev   delta       size    rawsize  chainsize     ratio   lindist extradist extraratio   readsize largestblk rddensity srchunks
         0       1        1       -1    base         44         43         44   1.02326        44         0    0.00000         44         44   1.00000        1
+        1       2        1       -1    base          0          0          0   0.00000         0         0    0.00000          0          0   1.00000        1
+        2       3        1       -1    base         44         43         44   1.02326        44         0    0.00000         44         44   1.00000        1
 
   $ hg debugdeltachain -m -T '{rev} {chainid} {chainlen} {readsize} {largestblock} {readdensity}\n'
   0 1 1 44 44 1.0
+  1 2 1 0 0 1
+  2 3 1 44 44 1.0
 
   $ hg debugdeltachain -m -Tjson
   [
@@ -127,6 +171,42 @@
     "rev": 0,
     "srchunks": 1,
     "uncompsize": 43
+   },
+   {
+    "chainid": 2,
+    "chainlen": 1,
+    "chainratio": 0,
+    "chainsize": 0,
+    "compsize": 0,
+    "deltatype": "base",
+    "extradist": 0,
+    "extraratio": 0,
+    "largestblock": 0,
+    "lindist": 0,
+    "prevrev": -1,
+    "readdensity": 1,
+    "readsize": 0,
+    "rev": 1,
+    "srchunks": 1,
+    "uncompsize": 0
+   },
+   {
+    "chainid": 3,
+    "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": 2,
+    "srchunks": 1,
+    "uncompsize": 43
    }
   ]
 
@@ -337,7 +417,7 @@
 
 #if no-windows
   $ hg debugcolor --style --color always | egrep 'mode|style|log\.'
-  color mode: ansi
+  color mode: 'ansi'
   available style:
   \x1b[0;33mlog.changeset\x1b[0m:                      \x1b[0;33myellow\x1b[0m (esc)
 #endif
--- a/tests/test-demandimport.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-demandimport.py	Thu Jul 19 13:55:54 2018 -0400
@@ -58,12 +58,12 @@
 print("os.system =", f(os.system))
 print("os =", f(os))
 
-from mercurial import util
+from mercurial.utils import procutil
 
-print("util =", f(util))
-print("util.system =", f(util.system))
-print("util =", f(util))
-print("util.system =", f(util.system))
+print("procutil =", f(procutil))
+print("procutil.system =", f(procutil.system))
+print("procutil =", f(procutil))
+print("procutil.system =", f(procutil.system))
 
 from mercurial import hgweb
 print("hgweb =", f(hgweb))
@@ -100,6 +100,8 @@
     print('contextlib.unknownattr = ImportError: %s'
           % rsub(r"'", '', str(inst)))
 
+from mercurial import util
+
 # Unlike the import statement, __import__() function should not raise
 # ImportError even if fromlist has an unknown item
 # (see Python/import.c:import_module_level() and ensure_fromlist())
--- a/tests/test-demandimport.py.out	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-demandimport.py.out	Thu Jul 19 13:55:54 2018 -0400
@@ -7,10 +7,10 @@
 os = <unloaded module 'os'>
 os.system = <built-in function system>
 os = <module 'os' from '?'>
-util = <unloaded module 'util'>
-util.system = <function system at 0x?>
-util = <module 'mercurial.util' from '?'>
-util.system = <function system at 0x?>
+procutil = <unloaded module 'procutil'>
+procutil.system = <function system at 0x?>
+procutil = <module 'mercurial.utils.procutil' from '?'>
+procutil.system = <function system at 0x?>
 hgweb = <unloaded module 'hgweb'>
 hgweb_mod = <unloaded module 'hgweb_mod'>
 hgweb = <module 'mercurial.hgweb' from '?'>
--- a/tests/test-diff-antipatience.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-diff-antipatience.t	Thu Jul 19 13:55:54 2018 -0400
@@ -11,9 +11,9 @@
 Test case that makes use of the weakness of patience diff algorithm
 
   $ hg init
-  >>> open('a', 'wb').write(b'\n'.join(list(b'a' + b'x' * 10 + b'u' + b'x' * 30 + b'a\n')))
+  >>> open('a', 'wb').write(('\n'.join(list('a' + 'x' * 10 + 'u' + 'x' * 30 + 'a\n'))).encode('ascii')) and None
   $ hg commit -m 1 -A a
-  >>> open('a', 'wb').write(b'\n'.join(list(b'b' + b'x' * 30 + b'u' + b'x' * 10 + b'b\n')))
+  >>> open('a', 'wb').write(('\n'.join(list('b' + 'x' * 30 + 'u' + 'x' * 10 + 'b\n'))).encode('ascii')) and None
 #if xdiff
   $ hg diff
   diff -r f0aeecb49805 a
--- a/tests/test-diff-color.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-diff-color.t	Thu Jul 19 13:55:54 2018 -0400
@@ -51,6 +51,27 @@
    a
    c
 
+trailing whitespace
+
+  $ cp a a.orig
+  $ sed 's/^dd$/dd \r/' a >a.new
+  $ mv a.new a
+  $ hg diff --nodates
+  \x1b[0;1mdiff -r cf9f4ba66af2 a\x1b[0m (esc)
+  \x1b[0;31;1m--- a/a\x1b[0m (esc)
+  \x1b[0;32;1m+++ b/a\x1b[0m (esc)
+  \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
+   c
+   a
+   a
+  \x1b[0;31m-b\x1b[0m (esc)
+  \x1b[0;32m+dd\x1b[0m\x1b[0;1;41m \x1b[0m\r (esc)
+   a
+   a
+   c
+
+  $ mv a.orig a
+
 (check that 'ui.color=yes' match '--color=auto')
 
   $ hg diff --nodates --config ui.formatted=no
@@ -304,7 +325,7 @@
   > three of those lines have
   > collapsed onto one
   > EOF
-  $ hg diff --config experimental.worddiff=False --color=debug
+  $ hg diff --config diff.word-diff=False --color=debug
   [diff.diffline|diff --git a/file1 b/file1]
   [diff.file_a|--- a/file1]
   [diff.file_b|+++ b/file1]
@@ -337,7 +358,7 @@
   [diff.deleted|-(to see if it works)]
   [diff.inserted|+three of those lines have]
   [diff.inserted|+collapsed onto one]
-  $ hg diff --config experimental.worddiff=True --color=debug
+  $ hg diff --config diff.word-diff=True --color=debug
   [diff.diffline|diff --git a/file1 b/file1]
   [diff.file_a|--- a/file1]
   [diff.file_b|+++ b/file1]
@@ -384,7 +405,7 @@
   > EOF
   $ hg ci -m 'slightly change utf8 char' utf8
 
-  $ hg diff --config experimental.worddiff=True --color=debug -c.
+  $ hg diff --config diff.word-diff=True --color=debug -c.
   [diff.diffline|diff --git a/utf8 b/utf8]
   [diff.file_a|--- a/utf8]
   [diff.file_b|+++ b/utf8]
--- a/tests/test-directaccess.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-directaccess.t	Thu Jul 19 13:55:54 2018 -0400
@@ -156,9 +156,9 @@
 `hg update`
 
   $ hg up 28ad74
-  updating to a hidden changeset 28ad74487de9
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 28ad74487de9
   (hidden revision '28ad74487de9' was rewritten as: 2443a0e66469)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg up 3
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-dispatch.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-dispatch.py	Thu Jul 19 13:55:54 2018 -0400
@@ -1,18 +1,24 @@
 from __future__ import absolute_import, print_function
 import os
+import sys
 from mercurial import (
     dispatch,
 )
 
+def printb(data, end=b'\n'):
+    out = getattr(sys.stdout, 'buffer', sys.stdout)
+    out.write(data + end)
+    out.flush()
+
 def testdispatch(cmd):
     """Simple wrapper around dispatch.dispatch()
 
     Prints command and result value, but does not handle quoting.
     """
-    print(b"running: %s" % (cmd,))
+    printb(b"running: %s" % (cmd,))
     req = dispatch.request(cmd.split())
     result = dispatch.dispatch(req)
-    print(b"result: %r" % (result,))
+    printb(b"result: %r" % (result,))
 
 testdispatch(b"init test1")
 os.chdir('test1')
--- a/tests/test-dispatch.py.out	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-dispatch.py.out	Thu Jul 19 13:55:54 2018 -0400
@@ -1,18 +1,18 @@
 running: init test1
-result: None
+result: 0
 running: add foo
 result: 0
 running: commit -m commit1 -d 2000-01-01 foo
-result: None
+result: 0
 running: commit -m commit2 -d 2000-01-02 foo
-result: None
+result: 0
 running: log -r 0
 changeset:   0:0e4634943879
 user:        test
 date:        Sat Jan 01 00:00:00 2000 +0000
 summary:     commit1
 
-result: None
+result: 0
 running: log -r tip
 changeset:   1:45589e459b2e
 tag:         tip
@@ -20,4 +20,4 @@
 date:        Sun Jan 02 00:00:00 2000 +0000
 summary:     commit2
 
-result: None
+result: 0
--- a/tests/test-doctest.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-doctest.py	Thu Jul 19 13:55:54 2018 -0400
@@ -60,6 +60,7 @@
 testmod('mercurial.pathutil')
 testmod('mercurial.parser')
 testmod('mercurial.pycompat')
+testmod('mercurial.revlog')
 testmod('mercurial.revsetlang')
 testmod('mercurial.smartset')
 testmod('mercurial.store')
--- a/tests/test-duplicateoptions.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-duplicateoptions.py	Thu Jul 19 13:55:54 2018 -0400
@@ -6,7 +6,7 @@
     ui as uimod,
 )
 
-ignore = {b'highlight', b'win32text', b'factotum'}
+ignore = {b'highlight', b'win32text', b'factotum', b'beautifygraph'}
 
 if os.name != 'nt':
     ignore.add(b'win32mbcs')
--- a/tests/test-encoding-func.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-encoding-func.py	Thu Jul 19 13:55:54 2018 -0400
@@ -35,11 +35,46 @@
         self.assertTrue(s is encoding.fromlocal(s))
 
 class Utf8bEncodingTest(unittest.TestCase):
+    def setUp(self):
+        self.origencoding = encoding.encoding
+
+    def tearDown(self):
+        encoding.encoding = self.origencoding
+
     def testasciifastpath(self):
         s = b'\0' * 100
         self.assertTrue(s is encoding.toutf8b(s))
         self.assertTrue(s is encoding.fromutf8b(s))
 
+    def testlossylatin(self):
+        encoding.encoding = b'ascii'
+        s = u'\xc0'.encode('utf-8')
+        l = encoding.tolocal(s)
+        self.assertEqual(l, b'?')  # lossy
+        self.assertEqual(s, encoding.toutf8b(l))  # utf8 sequence preserved
+
+    def testlosslesslatin(self):
+        encoding.encoding = b'latin-1'
+        s = u'\xc0'.encode('utf-8')
+        l = encoding.tolocal(s)
+        self.assertEqual(l, b'\xc0')  # lossless
+        self.assertEqual(s, encoding.toutf8b(l))  # convert back to utf-8
+
+    def testlossy0xed(self):
+        encoding.encoding = b'euc-kr'  # U+Dxxx Hangul
+        s = u'\ud1bc\xc0'.encode('utf-8')
+        l = encoding.tolocal(s)
+        self.assertIn(b'\xed', l)
+        self.assertTrue(l.endswith(b'?'))  # lossy
+        self.assertEqual(s, encoding.toutf8b(l))  # utf8 sequence preserved
+
+    def testlossless0xed(self):
+        encoding.encoding = b'euc-kr'  # U+Dxxx Hangul
+        s = u'\ud1bc'.encode('utf-8')
+        l = encoding.tolocal(s)
+        self.assertEqual(l, b'\xc5\xed')  # lossless
+        self.assertEqual(s, encoding.toutf8b(l))  # convert back to utf-8
+
 if __name__ == '__main__':
     import silenttestrunner
     silenttestrunner.main(__name__)
--- a/tests/test-eol.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-eol.t	Thu Jul 19 13:55:54 2018 -0400
@@ -7,7 +7,7 @@
 
 Set up helpers
 
-  $ cat > switch-eol.py <<EOF
+  $ cat > switch-eol.py <<'EOF'
   > from __future__ import absolute_import
   > import os
   > import sys
@@ -17,8 +17,10 @@
   >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
   > except ImportError:
   >     pass
+  > eolmap = {b'\n': '\\n', b'\r\n': '\\r\\n'}
   > (old, new) = sys.argv[1] == 'LF' and (b'\n', b'\r\n') or (b'\r\n', b'\n')
-  > print("%% switching encoding from %r to %r" % (old, new))
+  > print("%% switching encoding from '%s' to '%s'"
+  >       % (eolmap[old], eolmap[new]))
   > for path in sys.argv[2:]:
   >     data = open(path, 'rb').read()
   >     data = data.replace(old, new)
--- a/tests/test-eolfilename.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-eolfilename.t	Thu Jul 19 13:55:54 2018 -0400
@@ -32,8 +32,9 @@
   abort: '\n' and '\r' disallowed in filenames: 'hell\no'
   [255]
   $ echo foo > "$A"
-  $ hg debugwalk
-  matcher: <alwaysmatcher>
+  $ hg debugwalk -v
+  * matcher:
+  <alwaysmatcher>
   f  he\r (no-eol) (esc)
   llo  he\r (no-eol) (esc)
   llo
--- a/tests/test-export.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-export.t	Thu Jul 19 13:55:54 2018 -0400
@@ -101,6 +101,44 @@
   $ grep HG foo-foo_3.patch | wc -l
   \s*1 (re)
 
+Using bookmarks:
+
+  $ hg book -f -r 9 @
+  $ hg book -f -r 11 test
+  $ hg export -B test
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 5f17a83f5fbd9414006a5e563eab4c8a00729efd
+  # Parent  747d3c68f8ec44bb35816bfcd59aeb50b9654c2f
+  foo-10
+  
+  diff -r 747d3c68f8ec -r 5f17a83f5fbd foo
+  --- a/foo	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/foo	Thu Jan 01 00:00:00 1970 +0000
+  @@ -8,3 +8,4 @@
+   foo-7
+   foo-8
+   foo-9
+  +foo-10
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID f3acbafac161ec68f1598af38f794f28847ca5d3
+  # Parent  5f17a83f5fbd9414006a5e563eab4c8a00729efd
+  foo-11
+  
+  diff -r 5f17a83f5fbd -r f3acbafac161 foo
+  --- a/foo	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/foo	Thu Jan 01 00:00:00 1970 +0000
+  @@ -9,3 +9,4 @@
+   foo-8
+   foo-9
+   foo-10
+  +foo-11
+
 Exporting 4 changesets to a file:
 
   $ hg export -o export_internal 1 2 3 4
--- a/tests/test-extdata.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-extdata.t	Thu Jul 19 13:55:54 2018 -0400
@@ -82,6 +82,13 @@
   $ hg log -T "{extdata('unknown')}\n"
   abort: unknown extdata source 'unknown'
   [255]
+  $ hg log -T "{extdata(unknown)}\n"
+  hg: parse error: empty data source specified
+  (did you mean extdata('unknown')?)
+  [255]
+  $ hg log -T "{extdata('{unknown}')}\n"
+  hg: parse error: empty data source specified
+  [255]
 
 we don't fix up relative file URLs, but we do run shell commands in repo root
 
--- a/tests/test-extension.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-extension.t	Thu Jul 19 13:55:54 2018 -0400
@@ -9,7 +9,9 @@
   > configitem = registrar.configitem(configtable)
   > configitem(b'tests', b'foo', default=b"Foo")
   > def uisetup(ui):
+  >     ui.debug(b"uisetup called [debug]\\n")
   >     ui.write(b"uisetup called\\n")
+  >     ui.status(b"uisetup called [status]\\n")
   >     ui.flush()
   > def reposetup(ui, repo):
   >     ui.write(b"reposetup called for %s\\n" % os.path.basename(repo.root))
@@ -40,15 +42,29 @@
   $ echo "foobar = $abspath" >> $HGRCPATH
   $ hg foo
   uisetup called
+  uisetup called [status]
   reposetup called for a
   ui == repo.ui
   reposetup called for a (chg !)
   ui == repo.ui (chg !)
   Foo
+  $ hg foo --quiet
+  uisetup called (no-chg !)
+  reposetup called for a (chg !)
+  ui == repo.ui
+  Foo
+  $ hg foo --debug
+  uisetup called [debug] (no-chg !)
+  uisetup called (no-chg !)
+  uisetup called [status] (no-chg !)
+  reposetup called for a (chg !)
+  ui == repo.ui
+  Foo
 
   $ cd ..
   $ hg clone a b
   uisetup called (no-chg !)
+  uisetup called [status] (no-chg !)
   reposetup called for a
   ui == repo.ui
   reposetup called for b
@@ -58,6 +74,7 @@
 
   $ hg bar
   uisetup called (no-chg !)
+  uisetup called [status] (no-chg !)
   Bar
   $ echo 'foobar = !' >> $HGRCPATH
 
@@ -67,6 +84,7 @@
   $ cd a
   $ hg foo
   uisetup called
+  uisetup called [status]
   reposetup called for a
   ui == repo.ui
   reposetup called for a (chg !)
@@ -460,7 +478,7 @@
   >     result.append(absdetail)
   >     result.append(legacydetail)
   >     result.append(proxied.detail)
-  >     ui.write('LIB: %s\n' % '\nLIB: '.join(result))
+  >     ui.write(b'LIB: %s\n' % '\nLIB: '.join(result))
   > EOF
 
 Examine module importing.
@@ -1229,9 +1247,14 @@
 
   $ cat > hgext/forest.py <<EOF
   > cmdtable = None
+  > @command()
+  > def f():
+  >     pass
+  > @command(123)
+  > def g():
+  >     pass
   > EOF
   $ hg --config extensions.path=./path.py help foo > /dev/null
-  warning: error finding commands in $TESTTMP/hgext/forest.py
   abort: no such help topic: foo
   (try 'hg help --keyword foo')
   [255]
@@ -1283,7 +1306,7 @@
 
 If the extensions declare outdated versions, accuse the older extension first:
   $ echo "from mercurial import util" >> older.py
-  $ echo "util.version = lambda:'2.2'" >> older.py
+  $ echo "util.version = lambda:b'2.2'" >> older.py
   $ echo "testedwith = b'1.9.3'" >> older.py
   $ echo "testedwith = b'2.1.1'" >> throw.py
   $ rm -f throw.pyc throw.pyo
@@ -1388,7 +1411,7 @@
   Enabled extensions:
   
     throw  external  1.2.3
-  $ echo 'getversion = lambda: "1.twentythree"' >> throw.py
+  $ echo 'getversion = lambda: b"1.twentythree"' >> throw.py
   $ rm -f throw.pyc throw.pyo
   $ rm -Rf __pycache__
   $ hg version -v --config extensions.throw=throw.py --config extensions.strip=
@@ -1517,6 +1540,14 @@
   reposetup() for $TESTTMP/reposetup-test/src
   reposetup() for $TESTTMP/reposetup-test/src (chg !)
 
+  $ hg --cwd src debugextensions
+  reposetup() for $TESTTMP/reposetup-test/src
+  dodo (untested!)
+  dudu (untested!)
+  mq
+  reposetuptest (untested!)
+  strip
+
   $ hg clone -U src clone-dst1
   reposetup() for $TESTTMP/reposetup-test/src
   $ hg init push-dst1
@@ -1670,7 +1701,7 @@
   > def exbookmarks(orig, *args, **opts):
   >     return orig(*args, **opts)
   > def uisetup(ui):
-  >     synopsis = ' GREPME [--foo] [-x]'
+  >     synopsis = b' GREPME [--foo] [-x]'
   >     docstring = '''
   >     GREPME make sure that this is in the help!
   >     '''
@@ -1697,10 +1728,6 @@
   >     pass
   > EOF
 
-  $ hg --config extensions.nonregistrar=`pwd`/nonregistrar.py version > /dev/null
-  devel-warn: cmdutil.command is deprecated, use registrar.command to register 'foo'
-  (compatibility will be dropped after Mercurial-4.6, update your code.) * (glob)
-
 Prohibit the use of unicode strings as the default value of options
 
   $ hg init $TESTTMP/opt-unicode-default
@@ -1709,9 +1736,9 @@
   > from mercurial import registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
-  > @command(b'dummy', [('', 'opt', u'value', u'help')], 'ext [OPTIONS]')
+  > @command(b'dummy', [(b'', b'opt', u'value', u'help')], 'ext [OPTIONS]')
   > def ext(*args, **opts):
-  >     print(opts['opt'])
+  >     print(opts[b'opt'])
   > EOF
   $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
   > [extensions]
--- a/tests/test-extensions-afterloaded.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-extensions-afterloaded.t	Thu Jul 19 13:55:54 2018 -0400
@@ -3,16 +3,16 @@
   $ cat > foo.py <<EOF
   > from mercurial import extensions
   > def uisetup(ui):
-  >     ui.write("foo.uisetup\\n")
+  >     ui.write(b"foo.uisetup\\n")
   >     ui.flush()
   >     def bar_loaded(loaded):
-  >         ui.write("foo: bar loaded: %r\\n" % (loaded,))
+  >         ui.write(b"foo: bar loaded: %r\\n" % (loaded,))
   >         ui.flush()
-  >     extensions.afterloaded('bar', bar_loaded)
+  >     extensions.afterloaded(b'bar', bar_loaded)
   > EOF
   $ cat > bar.py <<EOF
   > def uisetup(ui):
-  >     ui.write("bar.uisetup\\n")
+  >     ui.write(b"bar.uisetup\\n")
   >     ui.flush()
   > EOF
   $ basepath=`pwd`
@@ -72,9 +72,9 @@
 
   $ cd ..
   $ cat > minvers.py <<EOF
-  > minimumhgversion = '9999.9999'
+  > minimumhgversion = b'9999.9999'
   > def uisetup(ui):
-  >     ui.write("minvers.uisetup\\n")
+  >     ui.write(b"minvers.uisetup\\n")
   >     ui.flush()
   > EOF
   $ hg init minversion
--- a/tests/test-extensions-wrapfunction.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-extensions-wrapfunction.py	Thu Jul 19 13:55:54 2018 -0400
@@ -35,7 +35,7 @@
         print('unwrap %s: %s: %s' % (getid(w), getid(result), msg))
 
 batchwrap(wrappers + [wrappers[0]])
-batchunwrap([(wrappers[i] if i >= 0 else None)
+batchunwrap([(wrappers[i] if i is not None and i >= 0 else None)
              for i in [3, None, 0, 4, 0, 2, 1, None]])
 
 wrap0 = extensions.wrappedfunction(dummy, 'getstack', wrappers[0])
--- a/tests/test-filecache.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-filecache.py	Thu Jul 19 13:55:54 2018 -0400
@@ -8,6 +8,16 @@
                     'cacheable']):
     sys.exit(80)
 
+print_ = print
+def print(*args, **kwargs):
+    """print() wrapper that flushes stdout buffers to avoid py3 buffer issues
+
+    We could also just write directly to sys.stdout.buffer the way the
+    ui object will, but this was easier for porting the test.
+    """
+    print_(*args, **kwargs)
+    sys.stdout.flush()
+
 from mercurial import (
     extensions,
     hg,
@@ -46,7 +56,7 @@
     def invalidate(self):
         for k in self._filecache:
             try:
-                delattr(self, k)
+                delattr(self, pycompat.sysstr(k))
             except AttributeError:
                 pass
 
@@ -84,8 +94,8 @@
     # atomic replace file, size doesn't change
     # hopefully st_mtime doesn't change as well so this doesn't use the cache
     # because of inode change
-    f = vfsmod.vfs('.')('x', 'w', atomictemp=True)
-    f.write('b')
+    f = vfsmod.vfs(b'.')(b'x', b'w', atomictemp=True)
+    f.write(b'b')
     f.close()
 
     repo.invalidate()
@@ -108,19 +118,19 @@
     # should recreate the object
     repo.cached
 
-    f = vfsmod.vfs('.')('y', 'w', atomictemp=True)
-    f.write('B')
+    f = vfsmod.vfs(b'.')(b'y', b'w', atomictemp=True)
+    f.write(b'B')
     f.close()
 
     repo.invalidate()
     print("* file y changed inode")
     repo.cached
 
-    f = vfsmod.vfs('.')('x', 'w', atomictemp=True)
-    f.write('c')
+    f = vfsmod.vfs(b'.')(b'x', b'w', atomictemp=True)
+    f.write(b'c')
     f.close()
-    f = vfsmod.vfs('.')('y', 'w', atomictemp=True)
-    f.write('C')
+    f = vfsmod.vfs(b'.')(b'y', b'w', atomictemp=True)
+    f.write(b'C')
     f.close()
 
     repo.invalidate()
@@ -155,14 +165,14 @@
     repo = hg.repository(uimod.ui.load())
     # first rollback clears the filecache, but changelog to stays in __dict__
     repo.rollback()
-    repo.commit('.')
+    repo.commit(b'.')
     # second rollback comes along and touches the changelog externally
     # (file is moved)
     repo.rollback()
     # but since changelog isn't under the filecache control anymore, we don't
     # see that it changed, and return the old changelog without reconstructing
     # it
-    repo.commit('.')
+    repo.commit(b'.')
 
 def setbeforeget(repo):
     os.remove('x')
--- a/tests/test-filelog.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-filelog.py	Thu Jul 19 13:55:54 2018 -0400
@@ -14,21 +14,21 @@
 )
 
 myui = uimod.ui.load()
-repo = hg.repository(myui, path='.', create=True)
+repo = hg.repository(myui, path=b'.', create=True)
 
-fl = repo.file('foobar')
+fl = repo.file(b'foobar')
 
 def addrev(text, renamed=False):
     if renamed:
         # data doesn't matter. Just make sure filelog.renamed() returns True
-        meta = {'copyrev': hex(nullid), 'copy': 'bar'}
+        meta = {b'copyrev': hex(nullid), b'copy': b'bar'}
     else:
         meta = {}
 
     lock = t = None
     try:
         lock = repo.lock()
-        t = repo.transaction('commit')
+        t = repo.transaction(b'commit')
         node = fl.add(text, meta, t, 0, nullid, nullid)
         return node
     finally:
@@ -40,8 +40,8 @@
 def error(text):
     print('ERROR: ' + text)
 
-textwith = '\1\nfoo'
-without = 'foo'
+textwith = b'\1\nfoo'
+without = b'foo'
 
 node = addrev(textwith)
 if not textwith == fl.read(node):
--- a/tests/test-fileset.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-fileset.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,5 @@
   $ fileset() {
-  >   hg debugfileset "$@"
+  >   hg debugfileset --all-files "$@"
   > }
 
   $ hg init repo
@@ -68,7 +68,7 @@
   a2
   $ fileset 'a_b'
   $ fileset '"\xy"'
-  hg: parse error: invalid \x escape
+  hg: parse error: invalid \x escape* (glob)
   [255]
 
 Test invalid syntax
@@ -142,8 +142,10 @@
   .hgignore
   c2
   $ fileset 'hgignore()'
+  .hgignore
   a2
   b2
+  c2
   $ fileset 'clean()'
   b1
   $ fileset 'copied()'
@@ -169,8 +171,8 @@
   R a2
   ? c3
   $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
+  a2
   b2
-  a2
   $ fileset -r0 'added() or revs("wdir()", added())'
   a1
   a2
@@ -180,8 +182,9 @@
 
 Test files properties
 
-  >>> open('bin', 'wb').write(b'\0a')
+  >>> open('bin', 'wb').write(b'\0a') and None
   $ fileset 'binary()'
+  bin
   $ fileset 'binary() and unknown()'
   bin
   $ echo '^bin$' >> .hgignore
@@ -192,11 +195,12 @@
   bin
 
   $ fileset 'grep("b{1}")'
+  .hgignore
+  b1
   b2
   c1
-  b1
   $ fileset 'grep("missingparens(")'
-  hg: parse error: invalid match pattern: unbalanced parenthesis
+  hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
   [255]
 
 #if execbit
@@ -219,8 +223,8 @@
   $ hg --config ui.portablefilenames=ignore add con.xml
 #endif
 
-  >>> open('1k', 'wb').write(b' '*1024)
-  >>> open('2k', 'wb').write(b' '*2048)
+  >>> open('1k', 'wb').write(b' '*1024) and None
+  >>> open('2k', 'wb').write(b' '*2048) and None
   $ hg add 1k 2k
   $ fileset 'size("bar")'
   hg: parse error: couldn't parse size: bar
@@ -354,8 +358,12 @@
   $ fileset -r1 'unknown()'
   $ fileset -r1 'ignored()'
   $ fileset -r1 'hgignore()'
+  .hgignore
+  a2
   b2
   bin
+  c2
+  sub2
   $ fileset -r1 'binary()'
   bin
   $ fileset -r1 'size(1k)'
@@ -391,9 +399,9 @@
   b2
   c1
 
-  >>> open('dos', 'wb').write("dos\r\n")
-  >>> open('mixed', 'wb').write("dos\r\nunix\n")
-  >>> open('mac', 'wb').write("mac\r")
+  >>> open('dos', 'wb').write(b"dos\r\n") and None
+  >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
+  >>> open('mac', 'wb').write(b"mac\r") and None
   $ hg add dos mixed mac
 
 (remove a1, to examine safety of 'eol' on removed files)
@@ -403,50 +411,42 @@
   dos
   mixed
   $ fileset 'eol(unix)'
-  mixed
+  .hgignore
   .hgsub
   .hgsubstate
   b1
   b2
+  b2.orig
   c1
+  c2
+  c3
+  con.xml
+  mixed
+  unknown
   $ fileset 'eol(mac)'
   mac
 
 Test safety of 'encoding' on removed files
 
   $ fileset 'encoding("ascii")'
-  dos
-  mac
-  mixed
+  .hgignore
   .hgsub
   .hgsubstate
   1k
   2k
   b1
   b2
+  b2.orig
   b2link (symlink !)
   bin
   c1
-
-Test detection of unintentional 'matchctx.existing()' invocation
-
-  $ cat > $TESTTMP/existingcaller.py <<EOF
-  > from mercurial import registrar
-  > 
-  > filesetpredicate = registrar.filesetpredicate()
-  > @filesetpredicate('existingcaller()', callexisting=False)
-  > def existingcaller(mctx, x):
-  >     # this 'mctx.existing()' invocation is unintentional
-  >     return [f for f in mctx.existing()]
-  > EOF
-
-  $ cat >> .hg/hgrc <<EOF
-  > [extensions]
-  > existingcaller = $TESTTMP/existingcaller.py
-  > EOF
-
-  $ fileset 'existingcaller()' 2>&1 | tail -1
-  AssertionError: unexpected existing() invocation
+  c2
+  c3
+  con.xml
+  dos
+  mac
+  mixed
+  unknown
 
 Test 'revs(...)'
 ================
@@ -524,7 +524,7 @@
 Test files at -r0 should be filtered by files at wdir
 -----------------------------------------------------
 
-  $ fileset -r0 '* and revs("wdir()", *)'
+  $ fileset -r0 'tracked() and revs("wdir()", tracked())'
   a1
   b1
   b2
@@ -561,12 +561,12 @@
 ---------------------------------------
 
   $ fileset "revs('0+4', added())"
+  .hgsub
+  .hgsubstate
   a1
   a2
   b1
   b2
-  .hgsub
-  .hgsubstate
 
 overlapping set
 
@@ -590,12 +590,12 @@
   R a2
   $ fileset "status(0, 1, removed())"
   a2
-  $ fileset "* and status(0, 1, removed())"
+  $ fileset "tracked() and status(0, 1, removed())"
   $ fileset -r 4 "status(0, 1, removed())"
   a2
-  $ fileset -r 4 "* and status(0, 1, removed())"
-  $ fileset "revs('4', * and status(0, 1, removed()))"
-  $ fileset "revs('0', * and status(0, 1, removed()))"
+  $ fileset -r 4 "tracked() and status(0, 1, removed())"
+  $ fileset "revs('4', tracked() and status(0, 1, removed()))"
+  $ fileset "revs('0', tracked() and status(0, 1, removed()))"
   a2
 
 check wdir()
--- a/tests/test-fix-topology.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-fix-topology.t	Thu Jul 19 13:55:54 2018 -0400
@@ -322,7 +322,6 @@
   
 
   $ hg fix --all
-  1 new orphan changesets
 
   $ hg log --graph --template '{rev} {desc}\n' -r 'sort(all(), topo)' --hidden
   o  11 fifth
--- a/tests/test-fix.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-fix.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1027,3 +1027,29 @@
 
   $ cd ..
 
+The --base flag affects the set of files being fixed. So while the --whole flag
+makes the base irrelevant for changed line ranges, it still changes the
+meaning and effect of the command. In this example, no files or lines are fixed
+until we specify the base, but then we do fix unchanged lines.
+
+  $ hg init basewhole
+  $ cd basewhole
+  $ printf "foo1\n" > foo.changed
+  $ hg commit -Aqm "first"
+  $ printf "foo2\n" >> foo.changed
+  $ printf "bar\n" > bar.changed
+  $ hg commit -Aqm "second"
+
+  $ hg fix --working-dir --whole
+  $ cat *.changed
+  bar
+  foo1
+  foo2
+
+  $ hg fix --working-dir --base 0 --whole
+  $ cat *.changed
+  BAR
+  FOO1
+  FOO2
+
+  $ cd ..
--- a/tests/test-flags.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-flags.t	Thu Jul 19 13:55:54 2018 -0400
@@ -46,6 +46,7 @@
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
   new changesets 7f4313b42a34
+  1 local changesets published
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg heads
   changeset:   2:7f4313b42a34
--- a/tests/test-fncache.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-fncache.t	Thu Jul 19 13:55:54 2018 -0400
@@ -436,3 +436,73 @@
   $ cat .hg/store/fncache | sort
   data/.bar.i
   data/foo.i
+
+  $ cd ..
+
+In repositories that have accumulated a large number of files over time, the
+fncache file is going to be large. If we possibly can avoid loading it, so much the better.
+The cache should not loaded when committing changes to existing files, or when unbundling
+changesets that only contain changes to existing files:
+
+  $ cat > fncacheloadwarn.py << EOF
+  > from __future__ import absolute_import
+  > from mercurial import extensions, store
+  > 
+  > def extsetup(ui):
+  >     def wrapstore(orig, requirements, *args):
+  >         store = orig(requirements, *args)
+  >         if 'store' in requirements and 'fncache' in requirements:
+  >             instrumentfncachestore(store, ui)
+  >         return store
+  >     extensions.wrapfunction(store, 'store', wrapstore)
+  > 
+  > def instrumentfncachestore(fncachestore, ui):
+  >     class instrumentedfncache(type(fncachestore.fncache)):
+  >         def _load(self):
+  >             ui.warn('fncache load triggered!\n')
+  >             super(instrumentedfncache, self)._load()
+  >     fncachestore.fncache.__class__ = instrumentedfncache
+  > EOF
+
+  $ fncachextpath=`pwd`/fncacheloadwarn.py
+  $ hg init nofncacheload
+  $ cd nofncacheload
+  $ printf "[extensions]\nfncacheloadwarn=$fncachextpath\n" >> .hg/hgrc
+
+A new file should trigger a load, as we'd want to update the fncache set in that case:
+
+  $ touch foo
+  $ hg ci -qAm foo
+  fncache load triggered!
+
+But modifying that file should not:
+
+  $ echo bar >> foo
+  $ hg ci -qm foo
+
+If a transaction has been aborted, the zero-size truncated index file will
+not prevent the fncache from being loaded; rather than actually abort
+a transaction, we simulate the situation by creating a zero-size index file:
+
+  $ touch .hg/store/data/bar.i
+  $ touch bar
+  $ hg ci -qAm bar
+  fncache load triggered!
+
+Unbundling should follow the same rules; existing files should not cause a load:
+
+  $ hg clone -q . tobundle
+  $ echo 'new line' > tobundle/bar
+  $ hg -R tobundle ci -qm bar
+  $ hg -R tobundle bundle -q barupdated.hg
+  $ hg unbundle -q barupdated.hg
+
+but adding new files should:
+
+  $ touch tobundle/newfile
+  $ hg -R tobundle ci -qAm newfile
+  $ hg -R tobundle bundle -q newfile.hg
+  $ hg unbundle -q newfile.hg
+  fncache load triggered!
+
+  $ cd ..
--- a/tests/test-fuzz-targets.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-fuzz-targets.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,18 @@
-#require clang-libfuzzer test-repo
+#require test-repo
+
   $ cd $TESTDIR/../contrib/fuzz
-  $ make
-Just run the fuzzer for five seconds to verify it works at all.
+
+#if clang-libfuzzer
+  $ make -s clean all
+#endif
+#if no-clang-libfuzzer clang-6.0
+  $ make -s clean all CC=clang-6.0 CXX=clang++-6.0
+#endif
+#if no-clang-libfuzzer no-clang-6.0
+  $ exit 80
+#endif
+
+Just run the fuzzers for five seconds each to verify it works at all.
   $ ./bdiff -max_total_time 5
+  $ ./mpatch -max_total_time 5
+  $ ./xdiff -max_total_time 5
--- a/tests/test-generaldelta.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-generaldelta.t	Thu Jul 19 13:55:54 2018 -0400
@@ -118,7 +118,7 @@
         2       1        2        0      p1         57        135        161   1.19259       218        57    0.35404
         3       1        2        0      p1         57        135        161   1.19259       275       114    0.70807
 
-Test format.aggressivemergedeltas
+Test revlog.optimize-delta-parent-choice
 
   $ hg init --config format.generaldelta=1 aggressive
   $ cd aggressive
@@ -139,14 +139,14 @@
       rev  chain# chainlen     prev   delta       size    rawsize  chainsize     ratio   lindist extradist extraratio
         0       1        1       -1    base         59        215         59   0.27442        59         0    0.00000
         1       1        2        0    prev         61         86        120   1.39535       120         0    0.00000
-        2       1        3        1      p1         65        301        185   0.61462       185         0    0.00000
+        2       1        2        0      p2         62        301        121   0.40199       182        61    0.50413
 
   $ hg strip -q -r . --config extensions.strip=
 
 - Verify aggressive merge uses p2 (commit 0) as delta parent
   $ hg up -q -C 1
   $ hg merge -q 0
-  $ hg commit -q -m merge --config format.aggressivemergedeltas=True
+  $ hg commit -q -m merge --config revlog.optimize-delta-parent-choice=yes
   $ hg debugdeltachain -m
       rev  chain# chainlen     prev   delta       size    rawsize  chainsize     ratio   lindist extradist extraratio
         0       1        1       -1    base         59        215         59   0.27442        59         0    0.00000
@@ -172,6 +172,44 @@
   $ hg init source-repo
   $ cd source-repo
   $ hg debugbuilddag --new-file '.+5:brancha$.+11:branchb$.+30:branchc<brancha+2<branchb+2'
+# add an empty revision somewhere
+  $ hg up tip
+  14 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg rm .
+  removing nf10
+  removing nf11
+  removing nf12
+  removing nf13
+  removing nf14
+  removing nf15
+  removing nf16
+  removing nf17
+  removing nf51
+  removing nf52
+  removing nf6
+  removing nf7
+  removing nf8
+  removing nf9
+  $ hg commit -m 'empty all'
+  $ hg revert --all --rev 'p1(.)'
+  adding nf10
+  adding nf11
+  adding nf12
+  adding nf13
+  adding nf14
+  adding nf15
+  adding nf16
+  adding nf17
+  adding nf51
+  adding nf52
+  adding nf6
+  adding nf7
+  adding nf8
+  adding nf9
+  $ hg commit -m 'restore all'
+  $ hg up null
+  0 files updated, 0 files merged, 14 files removed, 0 files unresolved
+  $ 
   $ cd ..
   $ hg -R source-repo debugdeltachain -m
       rev  chain# chainlen     prev   delta       size    rawsize  chainsize     ratio   lindist extradist extraratio
@@ -228,13 +266,15 @@
        50       4        2       49      p1         58        362        255   0.70442       255         0    0.00000
        51       4        3       50    prev        356        594        611   1.02862       611         0    0.00000
        52       4        4       51      p1         58        640        669   1.04531       669         0    0.00000
+       53       5        1       -1    base          0          0          0   0.00000         0         0    0.00000
+       54       5        2       53      p1        376        640        376   0.58750       376         0    0.00000
   $ hg clone --pull source-repo --config experimental.maxdeltachainspan=2800 relax-chain --config format.generaldelta=yes
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
-  added 53 changesets with 53 changes to 53 files (+2 heads)
-  new changesets 61246295ee1e:99cae3713489
+  added 55 changesets with 53 changes to 53 files (+2 heads)
+  new changesets 61246295ee1e:c930ac4a5b32
   updating to branch default
   14 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R relax-chain debugdeltachain -m
@@ -292,13 +332,15 @@
        50       4        2       49      p1         58        362        255   0.70442       255         0    0.00000
        51       2       13       17      p1         58        594        739   1.24411      2781      2042    2.76319
        52       5        1       -1    base        369        640        369   0.57656       369         0    0.00000
+       53       6        1       -1    base          0          0          0   0.00000         0         0    0.00000
+       54       6        2       53      p1        376        640        376   0.58750       376         0    0.00000
   $ hg clone --pull source-repo --config experimental.maxdeltachainspan=0 noconst-chain --config format.generaldelta=yes
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
-  added 53 changesets with 53 changes to 53 files (+2 heads)
-  new changesets 61246295ee1e:99cae3713489
+  added 55 changesets with 53 changes to 53 files (+2 heads)
+  new changesets 61246295ee1e:c930ac4a5b32
   updating to branch default
   14 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R noconst-chain debugdeltachain -m
@@ -356,3 +398,5 @@
        50       1        8       49      p1         58        362        447   1.23481      2915      2468    5.52125
        51       2       13       17      p1         58        594        739   1.24411      2642      1903    2.57510
        52       2       14       51      p1         58        640        797   1.24531      2700      1903    2.38770
+       53       4        1       -1    base          0          0          0   0.00000         0         0    0.00000
+       54       4        2       53      p1        376        640        376   0.58750       376         0    0.00000
--- a/tests/test-githelp.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-githelp.t	Thu Jul 19 13:55:54 2018 -0400
@@ -43,8 +43,8 @@
 
 githelp on a command with unrecognized option packed with other options should fail with error
   $ hg githelp -- commit -pv
-  abort: unknown option v packed with other options
-  Please try passing the option as it's own flag: -v
+  abort: unknown option 'v' packed with other options
+  (please try passing the option as its own flag: -v)
   [255]
 
 githelp for git rebase --skip
@@ -165,11 +165,11 @@
   hg update .~3
 
   $ hg githelp -- reset --mixed HEAD
-  NOTE: --mixed has no meaning since Mercurial has no staging area
+  note: --mixed has no meaning since Mercurial has no staging area
   
   hg update .
   $ hg githelp -- reset --soft HEAD
-  NOTE: --soft has no meaning since Mercurial has no staging area
+  note: --soft has no meaning since Mercurial has no staging area
   
   hg update .
   $ hg githelp -- reset --hard HEAD
@@ -221,7 +221,7 @@
 
 githelp for whatchanged should show deprecated message
   $ hg githelp -- whatchanged -p
-  This command has been deprecated in the git project, thus isn't supported by this tool.
+  this command has been deprecated in the git project, thus isn't supported by this tool
   
 
 githelp for git branch -m renaming
@@ -259,8 +259,8 @@
 git merge-base
   $ hg githelp -- git merge-base --is-ancestor
   ignoring unknown option --is-ancestor
-  NOTE: ancestors() is part of the revset language.
-  Learn more about revsets with 'hg help revsets'
+  note: ancestors() is part of the revset language
+  (learn more about revsets with 'hg help revsets')
   
   hg log -T '{node}\n' -r 'ancestor(A,B)'
 
@@ -279,7 +279,7 @@
   hg commit --interactive
 
   $ hg githelp -- git add --all
-  note: use hg addremove to remove files that have been deleted.
+  note: use hg addremove to remove files that have been deleted
   
   hg add
 
@@ -288,9 +288,9 @@
   $ hg githelp -- git reflog
   hg journal
   
-  note: in hg commits can be deleted from repo but we always have backups.
+  note: in hg commits can be deleted from repo but we always have backups
 
   $ hg githelp -- git reflog --all
   hg journal --all
   
-  note: in hg commits can be deleted from repo but we always have backups.
+  note: in hg commits can be deleted from repo but we always have backups
--- a/tests/test-globalopts.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-globalopts.t	Thu Jul 19 13:55:54 2018 -0400
@@ -353,6 +353,7 @@
    color         Colorizing Outputs
    config        Configuration Files
    dates         Date Formats
+   deprecated    Deprecated Features
    diffs         Diff Formats
    environment   Environment Variables
    extensions    Using Additional Features
@@ -436,6 +437,7 @@
    color         Colorizing Outputs
    config        Configuration Files
    dates         Date Formats
+   deprecated    Deprecated Features
    diffs         Diff Formats
    environment   Environment Variables
    extensions    Using Additional Features
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-glog-beautifygraph.t	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,3158 @@
+@  (34) head
+|
+| o  (33) head
+| |
+o |    (32) expand
+|\ \
+| o \    (31) expand
+| |\ \
+| | o \    (30) expand
+| | |\ \
+| | | o |  (29) regular commit
+| | | | |
+| | o | |    (28) merge zero known
+| | |\ \ \
+o | | | | |  (27) collapse
+|/ / / / /
+| | o---+  (26) merge one known; far right
+| | | | |
++---o | |  (25) merge one known; far left
+| | | | |
+| | o | |  (24) merge one known; immediate right
+| | |\| |
+| | o | |  (23) merge one known; immediate left
+| |/| | |
++---o---+  (22) merge two known; one far left, one far right
+| |  / /
+o | | |    (21) expand
+|\ \ \ \
+| o---+-+  (20) merge two known; two far right
+|  / / /
+o | | |    (19) expand
+|\ \ \ \
++---+---o  (18) merge two known; two far left
+| | | |
+| o | |    (17) expand
+| |\ \ \
+| | o---+  (16) merge two known; one immediate right, one near right
+| | |/ /
+o | | |    (15) expand
+|\ \ \ \
+| o-----+  (14) merge two known; one immediate right, one far right
+| |/ / /
+o | | |    (13) expand
+|\ \ \ \
++---o | |  (12) merge two known; one immediate right, one far left
+| | |/ /
+| o | |    (11) expand
+| |\ \ \
+| | o---+  (10) merge two known; one immediate left, one near right
+| |/ / /
+o | | |    (9) expand
+|\ \ \ \
+| o-----+  (8) merge two known; one immediate left, one far right
+|/ / / /
+o | | |    (7) expand
+|\ \ \ \
++---o | |  (6) merge two known; one immediate left, one far left
+| |/ / /
+| o | |    (5) expand
+| |\ \ \
+| | o | |  (4) merge two known; one immediate left, one immediate right
+| |/|/ /
+| o / /  (3) collapse
+|/ / /
+o / /  (2) collapse
+|/ /
+o /  (1) collapse
+|/
+o  (0) root
+
+  $ commit()
+  > {
+  >   rev=$1
+  >   msg=$2
+  >   shift 2
+  >   if [ "$#" -gt 0 ]; then
+  >       hg debugsetparents "$@"
+  >   fi
+  >   echo $rev > a
+  >   hg commit -Aqd "$rev 0" -m "($rev) $msg"
+  > }
+
+  $ cat > printrevset.py <<EOF
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   cmdutil,
+  >   commands,
+  >   extensions,
+  >   logcmdutil,
+  >   revsetlang,
+  >   smartset,
+  > )
+  > 
+  > from mercurial.utils import (
+  >   stringutil,
+  > )
+  > 
+  > def logrevset(repo, pats, opts):
+  >     revs = logcmdutil._initialrevs(repo, opts)
+  >     if not revs:
+  >         return None
+  >     match, pats, slowpath = logcmdutil._makematcher(repo, revs, pats, opts)
+  >     return logcmdutil._makerevset(repo, match, pats, slowpath, opts)
+  > 
+  > def uisetup(ui):
+  >     def printrevset(orig, repo, pats, opts):
+  >         revs, filematcher = orig(repo, pats, opts)
+  >         if opts.get(b'print_revset'):
+  >             expr = logrevset(repo, pats, opts)
+  >             if expr:
+  >                 tree = revsetlang.parse(expr)
+  >                 tree = revsetlang.analyze(tree)
+  >             else:
+  >                 tree = []
+  >             ui = repo.ui
+  >             ui.write(b'%r\n' % (opts.get(b'rev', []),))
+  >             ui.write(revsetlang.prettyformat(tree) + b'\n')
+  >             ui.write(stringutil.prettyrepr(revs) + b'\n')
+  >             revs = smartset.baseset()  # display no revisions
+  >         return revs, filematcher
+  >     extensions.wrapfunction(logcmdutil, 'getrevs', printrevset)
+  >     aliases, entry = cmdutil.findcmd(b'log', commands.table)
+  >     entry[1].append((b'', b'print-revset', False,
+  >                      b'print generated revset and exit (DEPRECATED)'))
+  > EOF
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "printrevset=`pwd`/printrevset.py" >> $HGRCPATH
+  $ echo "beautifygraph=" >> $HGRCPATH
+
+Set a default of narrow-text UTF-8.
+
+  $ HGENCODING=UTF-8; export HGENCODING
+  $ HGENCODINGAMBIGUOUS=narrow; export HGENCODINGAMBIGUOUS
+
+Empty repo:
+
+  $ hg init repo
+  $ cd repo
+  $ hg log -G
+
+Building DAG:
+
+  $ commit 0 "root"
+  $ commit 1 "collapse" 0
+  $ commit 2 "collapse" 1
+  $ commit 3 "collapse" 2
+  $ commit 4 "merge two known; one immediate left, one immediate right" 1 3
+  $ commit 5 "expand" 3 4
+  $ commit 6 "merge two known; one immediate left, one far left" 2 5
+  $ commit 7 "expand" 2 5
+  $ commit 8 "merge two known; one immediate left, one far right" 0 7
+  $ commit 9 "expand" 7 8
+  $ commit 10 "merge two known; one immediate left, one near right" 0 6
+  $ commit 11 "expand" 6 10
+  $ commit 12 "merge two known; one immediate right, one far left" 1 9
+  $ commit 13 "expand" 9 11
+  $ commit 14 "merge two known; one immediate right, one far right" 0 12
+  $ commit 15 "expand" 13 14
+  $ commit 16 "merge two known; one immediate right, one near right" 0 1
+  $ commit 17 "expand" 12 16
+  $ commit 18 "merge two known; two far left" 1 15
+  $ commit 19 "expand" 15 17
+  $ commit 20 "merge two known; two far right" 0 18
+  $ commit 21 "expand" 19 20
+  $ commit 22 "merge two known; one far left, one far right" 18 21
+  $ commit 23 "merge one known; immediate left" 1 22
+  $ commit 24 "merge one known; immediate right" 0 23
+  $ commit 25 "merge one known; far left" 21 24
+  $ commit 26 "merge one known; far right" 18 25
+  $ commit 27 "collapse" 21
+  $ commit 28 "merge zero known" 1 26
+  $ commit 29 "regular commit" 0
+  $ commit 30 "expand" 28 29
+  $ commit 31 "expand" 21 30
+  $ commit 32 "expand" 27 31
+  $ commit 33 "head" 18
+  $ commit 34 "head" 32
+
+The extension should not turn on unless we're in UTF-8.
+
+  $ HGENCODING=latin1 hg log -G -q
+  beautifygraph: unsupported encoding, UTF-8 required
+  @  34:fea3ac5810e0
+  |
+  | o  33:68608f5145f9
+  | |
+  o |    32:d06dffa21a31
+  |\ \
+  | o \    31:621d83e11f67
+  | |\ \
+  | | o \    30:6e11cd4b648f
+  | | |\ \
+  | | | o |  29:cd9bb2be7593
+  | | | | |
+  | | o | |    28:44ecd0b9ae99
+  | | |\ \ \
+  o | | | | |  27:886ed638191b
+  |/ / / / /
+  | | o---+  26:7f25b6c2f0b9
+  | | | | |
+  +---o | |  25:91da8ed57247
+  | | | | |
+  | | o | |  24:a9c19a3d96b7
+  | | |\| |
+  | | o | |  23:a01cddf0766d
+  | |/| | |
+  +---o---+  22:e0d9cccacb5d
+  | |  / /
+  o | | |    21:d42a756af44d
+  |\ \ \ \
+  | o---+-+  20:d30ed6450e32
+  |  / / /
+  o | | |    19:31ddc2c1573b
+  |\ \ \ \
+  +---+---o  18:1aa84d96232a
+  | | | |
+  | o | |    17:44765d7c06e0
+  | |\ \ \
+  | | o---+  16:3677d192927d
+  | | |/ /
+  o | | |    15:1dda3f72782d
+  |\ \ \ \
+  | o-----+  14:8eac370358ef
+  | |/ / /
+  o | | |    13:22d8966a97e3
+  |\ \ \ \
+  +---o | |  12:86b91144a6e9
+  | | |/ /
+  | o | |    11:832d76e6bdf2
+  | |\ \ \
+  | | o---+  10:74c64d036d72
+  | |/ / /
+  o | | |    9:7010c0af0a35
+  |\ \ \ \
+  | o-----+  8:7a0b11f71937
+  |/ / / /
+  o | | |    7:b632bb1b1224
+  |\ \ \ \
+  +---o | |  6:b105a072e251
+  | |/ / /
+  | o | |    5:4409d547b708
+  | |\ \ \
+  | | o | |  4:26a8bac39d9f
+  | |/|/ /
+  | o / /  3:27eef8ed80b4
+  |/ / /
+  o / /  2:3d9a33b8d1e1
+  |/ /
+  o /  1:6db2ef61d156
+  |/
+  o  0:e6eb3150255d
+  
+
+The extension should not turn on if we're using wide text.
+
+  $ HGENCODINGAMBIGUOUS=wide hg log -G -q
+  beautifygraph: unsupported terminal settings, monospace narrow text required
+  @  34:fea3ac5810e0
+  |
+  | o  33:68608f5145f9
+  | |
+  o |    32:d06dffa21a31
+  |\ \
+  | o \    31:621d83e11f67
+  | |\ \
+  | | o \    30:6e11cd4b648f
+  | | |\ \
+  | | | o |  29:cd9bb2be7593
+  | | | | |
+  | | o | |    28:44ecd0b9ae99
+  | | |\ \ \
+  o | | | | |  27:886ed638191b
+  |/ / / / /
+  | | o---+  26:7f25b6c2f0b9
+  | | | | |
+  +---o | |  25:91da8ed57247
+  | | | | |
+  | | o | |  24:a9c19a3d96b7
+  | | |\| |
+  | | o | |  23:a01cddf0766d
+  | |/| | |
+  +---o---+  22:e0d9cccacb5d
+  | |  / /
+  o | | |    21:d42a756af44d
+  |\ \ \ \
+  | o---+-+  20:d30ed6450e32
+  |  / / /
+  o | | |    19:31ddc2c1573b
+  |\ \ \ \
+  +---+---o  18:1aa84d96232a
+  | | | |
+  | o | |    17:44765d7c06e0
+  | |\ \ \
+  | | o---+  16:3677d192927d
+  | | |/ /
+  o | | |    15:1dda3f72782d
+  |\ \ \ \
+  | o-----+  14:8eac370358ef
+  | |/ / /
+  o | | |    13:22d8966a97e3
+  |\ \ \ \
+  +---o | |  12:86b91144a6e9
+  | | |/ /
+  | o | |    11:832d76e6bdf2
+  | |\ \ \
+  | | o---+  10:74c64d036d72
+  | |/ / /
+  o | | |    9:7010c0af0a35
+  |\ \ \ \
+  | o-----+  8:7a0b11f71937
+  |/ / / /
+  o | | |    7:b632bb1b1224
+  |\ \ \ \
+  +---o | |  6:b105a072e251
+  | |/ / /
+  | o | |    5:4409d547b708
+  | |\ \ \
+  | | o | |  4:26a8bac39d9f
+  | |/|/ /
+  | o / /  3:27eef8ed80b4
+  |/ / /
+  o / /  2:3d9a33b8d1e1
+  |/ /
+  o /  1:6db2ef61d156
+  |/
+  o  0:e6eb3150255d
+  
+
+The rest of our tests will use the default narrow text UTF-8.
+
+  $ hg log -G -q
+  \xe2\x97\x8d  34:fea3ac5810e0 (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  33:68608f5145f9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x95\xb2  31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x95\xb2  30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  29:cd9bb2be7593 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  27:886ed638191b (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82  \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  21:d42a756af44d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\xa4  20:d30ed6450e32 (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  19:31ddc2c1573b (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b  18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  15:1dda3f72782d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  13:22d8966a97e3 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  9:7010c0af0a35 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  8:7a0b11f71937 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  7:b632bb1b1224 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  4:26a8bac39d9f (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x95\xb1 \xe2\x95\xb1  3:27eef8ed80b4 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x95\xb1 \xe2\x95\xb1  2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x95\xb1  1:6db2ef61d156 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 (esc)
+  \xe2\x97\x8b  0:e6eb3150255d (esc)
+  
+
+  $ hg log -G
+  \xe2\x97\x8d  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:34 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (34) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:33 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82  summary:     (33) head (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      27:886ed638191b (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:32 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (32) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:31 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (31) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      29:cd9bb2be7593 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:30 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (30) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   29:cd9bb2be7593 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:29 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (29) regular commit (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:28 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (28) merge zero known (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   27:886ed638191b (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:27 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (27) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:26 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (26) merge one known; far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:25 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (25) merge one known; far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:24 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (24) merge one known; immediate right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:23 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (23) merge one known; immediate left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82  \xe2\x95\xb1 \xe2\x95\xb1   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:22 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (22) merge two known; one far left, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   21:d42a756af44d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      19:31ddc2c1573b (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      20:d30ed6450e32 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:21 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (21) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\xa4  changeset:   20:d30ed6450e32 (esc)
+  \xe2\x94\x82   \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:20 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (20) merge two known; two far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   19:31ddc2c1573b (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      15:1dda3f72782d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:19 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (19) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b  changeset:   18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      15:1dda3f72782d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:18 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (18) merge two known; two far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:17 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (17) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:16 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (16) merge two known; one immediate right, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   15:1dda3f72782d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      13:22d8966a97e3 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:15 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (15) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:14 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (14) merge two known; one immediate right, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   13:22d8966a97e3 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:13 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (13) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:12 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (12) merge two known; one immediate right, one far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:11 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (11) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:10 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (10) merge two known; one immediate left, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   9:7010c0af0a35 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      8:7a0b11f71937 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:09 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (9) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   8:7a0b11f71937 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:08 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (8) merge two known; one immediate left, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   7:b632bb1b1224 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:07 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (7) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:06 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (6) merge two known; one immediate left, one far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      3:27eef8ed80b4 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      4:26a8bac39d9f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:05 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (5) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   4:26a8bac39d9f (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      3:27eef8ed80b4 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:04 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (4) merge two known; one immediate left, one immediate right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   3:27eef8ed80b4 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:03 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (3) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:02 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (2) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   1:6db2ef61d156 (esc)
+  \xe2\x94\x82\xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:01 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (1) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   0:e6eb3150255d (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     (0) root
+  
+File glog:
+  $ hg log -G a
+  \xe2\x97\x8d  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:34 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (34) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:33 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82  summary:     (33) head (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      27:886ed638191b (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:32 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (32) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:31 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (31) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      29:cd9bb2be7593 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:30 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (30) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   29:cd9bb2be7593 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:29 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (29) regular commit (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:28 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (28) merge zero known (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   27:886ed638191b (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:27 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (27) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:26 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (26) merge one known; far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:25 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (25) merge one known; far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:24 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (24) merge one known; immediate right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:23 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (23) merge one known; immediate left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82  \xe2\x95\xb1 \xe2\x95\xb1   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:22 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (22) merge two known; one far left, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   21:d42a756af44d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      19:31ddc2c1573b (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      20:d30ed6450e32 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:21 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (21) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\xa4  changeset:   20:d30ed6450e32 (esc)
+  \xe2\x94\x82   \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:20 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (20) merge two known; two far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   19:31ddc2c1573b (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      15:1dda3f72782d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:19 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (19) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b  changeset:   18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      15:1dda3f72782d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:18 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (18) merge two known; two far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:17 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (17) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:16 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (16) merge two known; one immediate right, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   15:1dda3f72782d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      13:22d8966a97e3 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:15 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (15) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:14 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (14) merge two known; one immediate right, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   13:22d8966a97e3 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:13 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (13) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:12 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (12) merge two known; one immediate right, one far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:11 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (11) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:10 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (10) merge two known; one immediate left, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   9:7010c0af0a35 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      8:7a0b11f71937 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:09 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (9) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   8:7a0b11f71937 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:08 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (8) merge two known; one immediate left, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   7:b632bb1b1224 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:07 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (7) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:06 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (6) merge two known; one immediate left, one far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      3:27eef8ed80b4 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      4:26a8bac39d9f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:05 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (5) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   4:26a8bac39d9f (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      3:27eef8ed80b4 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:04 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (4) merge two known; one immediate left, one immediate right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   3:27eef8ed80b4 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:03 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (3) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:02 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (2) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   1:6db2ef61d156 (esc)
+  \xe2\x94\x82\xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:01 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (1) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   0:e6eb3150255d (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     (0) root
+  
+File glog per revset:
+
+  $ hg log -G -r 'file("a")'
+  \xe2\x97\x8d  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:34 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (34) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:33 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82  summary:     (33) head (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      27:886ed638191b (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:32 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (32) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:31 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (31) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      29:cd9bb2be7593 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:30 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (30) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   29:cd9bb2be7593 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:29 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (29) regular commit (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:28 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (28) merge zero known (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   27:886ed638191b (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:27 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (27) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:26 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (26) merge one known; far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:25 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (25) merge one known; far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:24 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (24) merge one known; immediate right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:23 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (23) merge one known; immediate left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82  \xe2\x95\xb1 \xe2\x95\xb1   parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:22 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (22) merge two known; one far left, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   21:d42a756af44d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      19:31ddc2c1573b (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      20:d30ed6450e32 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:21 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (21) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\xa4  changeset:   20:d30ed6450e32 (esc)
+  \xe2\x94\x82   \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:20 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (20) merge two known; two far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   19:31ddc2c1573b (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      15:1dda3f72782d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:19 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (19) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xbc\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b  changeset:   18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      15:1dda3f72782d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:18 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (18) merge two known; two far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:17 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (17) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:16 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (16) merge two known; one immediate right, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   15:1dda3f72782d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      13:22d8966a97e3 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:15 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (15) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:14 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (14) merge two known; one immediate right, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   13:22d8966a97e3 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:13 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (13) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:12 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (12) merge two known; one immediate right, one far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:11 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (11) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:10 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (10) merge two known; one immediate left, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   9:7010c0af0a35 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      8:7a0b11f71937 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:09 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (9) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x94\xa4  changeset:   8:7a0b11f71937 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:08 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (8) merge two known; one immediate left, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  changeset:   7:b632bb1b1224 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:07 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (7) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:06 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (6) merge two known; one immediate left, one far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   5:4409d547b708 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2 \xe2\x95\xb2   parent:      3:27eef8ed80b4 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      4:26a8bac39d9f (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:05 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (5) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   4:26a8bac39d9f (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    parent:      3:27eef8ed80b4 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:04 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (4) merge two known; one immediate left, one immediate right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   3:27eef8ed80b4 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1 \xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:03 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82    summary:     (3) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82\xe2\x95\xb1 \xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:02 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (2) collapse (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   1:6db2ef61d156 (esc)
+  \xe2\x94\x82\xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:01 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (1) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   0:e6eb3150255d (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     (0) root
+  
+
+File glog per revset (only merges):
+
+  $ hg log -G -r 'file("a")' -m
+  \xe2\x97\x8b  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      27:886ed638191b (esc)
+  \xe2\x94\x82 \xe2\x94\x86  parent:      31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x86  date:        Thu Jan 01 00:00:32 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x86  summary:     (32) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x97\x8b \xe2\x94\x86  changeset:   31:621d83e11f67 (esc)
+  \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x86  parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x86  parent:      30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x86  date:        Thu Jan 01 00:00:31 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x86  summary:     (31) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x97\x8b \xe2\x94\x86  changeset:   30:6e11cd4b648f (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x86  parent:      29:cd9bb2be7593 (esc)
+  \xe2\x94\x82   \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x86  date:        Thu Jan 01 00:00:30 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x86  summary:     (30) expand (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x86  changeset:   28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x86  parent:      26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82   \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x86  date:        Thu Jan 01 00:00:28 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x86  summary:     (28) merge zero known (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x86  changeset:   26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  parent:      25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  date:        Thu Jan 01 00:00:26 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  summary:     (26) merge one known; far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x86  changeset:   25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x86  parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  parent:      24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  date:        Thu Jan 01 00:00:25 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  summary:     (25) merge one known; far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x86  changeset:   24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x86  parent:      23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  date:        Thu Jan 01 00:00:24 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  summary:     (24) merge one known; immediate right (esc)
+  \xe2\x94\x82 \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x86  changeset:   23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x86  parent:      22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  date:        Thu Jan 01 00:00:23 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  summary:     (23) merge one known; immediate left (esc)
+  \xe2\x94\x82 \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x86  changeset:   22:e0d9cccacb5d (esc)
+  \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x86\xe2\x95\xb1   parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x86    parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x86    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x86    date:        Thu Jan 01 00:00:22 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x86    summary:     (22) merge two known; one far left, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      19:31ddc2c1573b (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      20:d30ed6450e32 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:21 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (21) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b  changeset:   20:d30ed6450e32 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:20 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (20) merge two known; two far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   19:31ddc2c1573b (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      15:1dda3f72782d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:19 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (19) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   18:1aa84d96232a (esc)
+  \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x82 \xe2\x94\x82  parent:      1:6db2ef61d156 (esc)
+  \xe2\x95\xa7 \xe2\x94\x82 \xe2\x94\x82  parent:      15:1dda3f72782d (esc)
+    \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+    \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:18 1970 +0000 (esc)
+    \xe2\x94\x82 \xe2\x94\x82  summary:     (18) merge two known; two far left (esc)
+   \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:17 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (17) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b  changeset:   16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7 \xe2\x95\xa7  parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82      user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82      date:        Thu Jan 01 00:00:16 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82      summary:     (16) merge two known; one immediate right, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   15:1dda3f72782d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      13:22d8966a97e3 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:15 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (15) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x82  parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x82  date:        Thu Jan 01 00:00:14 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  summary:     (14) merge two known; one immediate right, one far right (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   13:22d8966a97e3 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:13 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (13) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b  changeset:   12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7  parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:12 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (12) merge two known; one immediate right, one far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:11 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (11) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b  changeset:   10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7  parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:10 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (10) merge two known; one immediate left, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   9:7010c0af0a35 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      8:7a0b11f71937 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:09 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (9) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   8:7a0b11f71937 (esc)
+  \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x82  parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x82  date:        Thu Jan 01 00:00:08 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  summary:     (8) merge two known; one immediate left, one far right (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   7:b632bb1b1224 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x82  parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x82  date:        Thu Jan 01 00:00:07 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  summary:     (7) expand (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   6:b105a072e251 (esc)
+  \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82  parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7  parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:06 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (6) merge two known; one immediate left, one far left (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   5:4409d547b708 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      3:27eef8ed80b4 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7  parent:      4:26a8bac39d9f (esc)
+  \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:05 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (5) expand (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   4:26a8bac39d9f (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x95\xa7 \xe2\x95\xa7  parent:      3:27eef8ed80b4 (esc)
+       user:        test
+       date:        Thu Jan 01 00:00:04 1970 +0000
+       summary:     (4) merge two known; one immediate left, one immediate right
+  
+
+Empty revision range - display nothing:
+  $ hg log -G -r 1..0
+
+  $ cd ..
+
+#if no-outer-repo
+
+From outer space:
+  $ hg log -G -l1 repo
+  \xe2\x97\x8d  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x95\xa7  parent:      32:d06dffa21a31 (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:34 1970 +0000
+     summary:     (34) head
+  
+  $ hg log -G -l1 repo/a
+  \xe2\x97\x8d  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x95\xa7  parent:      32:d06dffa21a31 (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:34 1970 +0000
+     summary:     (34) head
+  
+  $ hg log -G -l1 repo/missing
+
+#endif
+
+File log with revs != cset revs:
+  $ hg init flog
+  $ cd flog
+  $ echo one >one
+  $ hg add one
+  $ hg commit -mone
+  $ echo two >two
+  $ hg add two
+  $ hg commit -mtwo
+  $ echo more >two
+  $ hg commit -mmore
+  $ hg log -G two
+  \xe2\x97\x8d  changeset:   2:12c28321755b (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     more (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   1:5ac72c0599bf (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x95\xa7  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+     summary:     two
+  
+
+Issue1896: File log with explicit style
+  $ hg log -G --style=default one
+  \xe2\x97\x8b  changeset:   0:3d578b4a1f53 (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     one
+  
+Issue2395: glog --style header and footer
+  $ hg log -G --style=xml one
+  <?xml version="1.0"?>
+  <log>
+  \xe2\x97\x8b  <logentry revision="0" node="3d578b4a1f537d5fcf7301bfa9c0b97adfaa6fb1"> (esc)
+     <author email="test">test</author>
+     <date>1970-01-01T00:00:00+00:00</date>
+     <msg xml:space="preserve">one</msg>
+     </logentry>
+  </log>
+
+  $ cd ..
+
+Incoming and outgoing:
+
+  $ hg clone -U -r31 repo repo2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 31 changesets with 31 changes to 1 files
+  new changesets e6eb3150255d:621d83e11f67
+  $ cd repo2
+
+  $ hg incoming --graph ../repo
+  comparing with ../repo
+  searching for changes
+  \xe2\x97\x8b  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:34 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (34) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82    parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:33 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (33) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82  parent:      27:886ed638191b (esc)
+  \xe2\x94\x82  parent:      31:621d83e11f67 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:32 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (32) expand (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   27:886ed638191b (esc)
+     parent:      21:d42a756af44d
+     user:        test
+     date:        Thu Jan 01 00:00:27 1970 +0000
+     summary:     (27) collapse
+  
+  $ cd ..
+
+  $ hg -R repo outgoing --graph repo2
+  comparing with repo2
+  searching for changes
+  \xe2\x97\x8d  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:34 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (34) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82    parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:33 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (33) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82  parent:      27:886ed638191b (esc)
+  \xe2\x94\x82  parent:      31:621d83e11f67 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:32 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (32) expand (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   27:886ed638191b (esc)
+     parent:      21:d42a756af44d
+     user:        test
+     date:        Thu Jan 01 00:00:27 1970 +0000
+     summary:     (27) collapse
+  
+
+File + limit with revs != cset revs:
+  $ cd repo
+  $ touch b
+  $ hg ci -Aqm0
+  $ hg log -G -l2 a
+  \xe2\x97\x8b  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x95\xa7  user:        test (esc)
+     date:        Thu Jan 01 00:00:34 1970 +0000
+     summary:     (34) head
+  
+  \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x95\xa7  user:        test (esc)
+     date:        Thu Jan 01 00:00:33 1970 +0000
+     summary:     (33) head
+  
+
+File + limit + -ra:b, (b - a) < limit:
+  $ hg log -G -l3000 -r32:tip a
+  \xe2\x97\x8b  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:34 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (34) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x95\xa7  user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:33 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (33) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      27:886ed638191b (esc)
+  \xe2\x95\xa7 \xe2\x95\xa7  parent:      31:621d83e11f67 (esc)
+       user:        test
+       date:        Thu Jan 01 00:00:32 1970 +0000
+       summary:     (32) expand
+  
+
+Point out a common and an uncommon unshown parent
+
+  $ hg log -G -r 'rev(8) or rev(9)'
+  \xe2\x97\x8b  changeset:   9:7010c0af0a35 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7  parent:      8:7a0b11f71937 (esc)
+  \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:09 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (9) expand (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   8:7a0b11f71937 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      0:e6eb3150255d (esc)
+  \xe2\x95\xa7 \xe2\x95\xa7  parent:      7:b632bb1b1224 (esc)
+       user:        test
+       date:        Thu Jan 01 00:00:08 1970 +0000
+       summary:     (8) merge two known; one immediate left, one far right
+  
+
+File + limit + -ra:b, b < tip:
+
+  $ hg log -G -l1 -r32:34 a
+  \xe2\x97\x8b  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x95\xa7  user:        test (esc)
+     date:        Thu Jan 01 00:00:34 1970 +0000
+     summary:     (34) head
+  
+
+file(File) + limit + -ra:b, b < tip:
+
+  $ hg log -G -l1 -r32:34 -r 'file("a")'
+  \xe2\x97\x8b  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x95\xa7  user:        test (esc)
+     date:        Thu Jan 01 00:00:34 1970 +0000
+     summary:     (34) head
+  
+
+limit(file(File) and a::b), b < tip:
+
+  $ hg log -G -r 'limit(file("a") and 32::34, 1)'
+  \xe2\x97\x8b  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      27:886ed638191b (esc)
+  \xe2\x95\xa7 \xe2\x95\xa7  parent:      31:621d83e11f67 (esc)
+       user:        test
+       date:        Thu Jan 01 00:00:32 1970 +0000
+       summary:     (32) expand
+  
+
+File + limit + -ra:b, b < tip:
+
+  $ hg log -G -r 'limit(file("a") and 34::32, 1)'
+
+File + limit + -ra:b, b < tip, (b - a) < limit:
+
+  $ hg log -G -l10 -r33:34 a
+  \xe2\x97\x8b  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x95\xa7  user:        test (esc)
+     date:        Thu Jan 01 00:00:34 1970 +0000
+     summary:     (34) head
+  
+  \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x95\xa7  user:        test (esc)
+     date:        Thu Jan 01 00:00:33 1970 +0000
+     summary:     (33) head
+  
+
+Do not crash or produce strange graphs if history is buggy
+
+  $ hg branch branch
+  marked working directory as branch branch
+  (branches are permanent and global, did you want a bookmark?)
+  $ commit 36 "buggy merge: identical parents" 35 35
+  $ hg log -G -l5
+  \xe2\x97\x8d  changeset:   36:08a19a744424 (esc)
+  \xe2\x94\x82  branch:      branch (esc)
+  \xe2\x94\x82  tag:         tip (esc)
+  \xe2\x94\x82  parent:      35:9159c3644c5e (esc)
+  \xe2\x94\x82  parent:      35:9159c3644c5e (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:36 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (36) buggy merge: identical parents (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   35:9159c3644c5e (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     0 (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   34:fea3ac5810e0 (esc)
+  \xe2\x94\x82  parent:      32:d06dffa21a31 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:34 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     (34) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   33:68608f5145f9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x95\xa7  user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:33 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (33) head (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      27:886ed638191b (esc)
+  \xe2\x95\xa7 \xe2\x95\xa7  parent:      31:621d83e11f67 (esc)
+       user:        test
+       date:        Thu Jan 01 00:00:32 1970 +0000
+       summary:     (32) expand
+  
+
+Test log -G options
+
+  $ testlog() {
+  >   hg log -G --print-revset "$@"
+  >   hg log --template 'nodetag {rev}\n' "$@" | grep nodetag \
+  >     | sed 's/.*nodetag/nodetag/' > log.nodes
+  >   hg log -G --template 'nodetag {rev}\n' "$@" | grep nodetag \
+  >     | sed 's/.*nodetag/nodetag/' > glog.nodes
+  >   (cmp log.nodes glog.nodes || diff -u log.nodes glog.nodes) \
+  >     | grep '^[-+@ ]' || :
+  > }
+
+glog always reorders nodes which explains the difference with log
+
+  $ testlog -r 27 -r 25 -r 21 -r 34 -r 32 -r 31
+  ['27', '25', '21', '34', '32', '31']
+  []
+  <baseset- [21, 25, 27, 31, 32, 34]>
+  --- log.nodes	* (glob)
+  +++ glog.nodes	* (glob)
+  @@ -1,6 +1,6 @@
+  -nodetag 27
+  -nodetag 25
+  -nodetag 21
+   nodetag 34
+   nodetag 32
+   nodetag 31
+  +nodetag 27
+  +nodetag 25
+  +nodetag 21
+  $ testlog -u test -u not-a-user
+  []
+  (or
+    (list
+      (func
+        (symbol 'user')
+        (string 'test'))
+      (func
+        (symbol 'user')
+        (string 'not-a-user'))))
+  <filteredset
+    <spanset- 0:37>,
+    <addset
+      <filteredset
+        <fullreposet+ 0:37>,
+        <user 'test'>>,
+      <filteredset
+        <fullreposet+ 0:37>,
+        <user 'not-a-user'>>>>
+  $ testlog -b not-a-branch
+  abort: unknown revision 'not-a-branch'!
+  abort: unknown revision 'not-a-branch'!
+  abort: unknown revision 'not-a-branch'!
+  $ testlog -b 35 -b 36 --only-branch branch
+  []
+  (or
+    (list
+      (func
+        (symbol 'branch')
+        (string 'default'))
+      (or
+        (list
+          (func
+            (symbol 'branch')
+            (string 'branch'))
+          (func
+            (symbol 'branch')
+            (string 'branch'))))))
+  <filteredset
+    <spanset- 0:37>,
+    <addset
+      <filteredset
+        <fullreposet+ 0:37>,
+        <branch 'default'>>,
+      <addset
+        <filteredset
+          <fullreposet+ 0:37>,
+          <branch 'branch'>>,
+        <filteredset
+          <fullreposet+ 0:37>,
+          <branch 'branch'>>>>>
+  $ testlog -k expand -k merge
+  []
+  (or
+    (list
+      (func
+        (symbol 'keyword')
+        (string 'expand'))
+      (func
+        (symbol 'keyword')
+        (string 'merge'))))
+  <filteredset
+    <spanset- 0:37>,
+    <addset
+      <filteredset
+        <fullreposet+ 0:37>,
+        <keyword 'expand'>>,
+      <filteredset
+        <fullreposet+ 0:37>,
+        <keyword 'merge'>>>>
+  $ testlog --only-merges
+  []
+  (func
+    (symbol 'merge')
+    None)
+  <filteredset
+    <spanset- 0:37>,
+    <merge>>
+  $ testlog --no-merges
+  []
+  (not
+    (func
+      (symbol 'merge')
+      None))
+  <filteredset
+    <spanset- 0:37>,
+    <not
+      <filteredset
+        <spanset- 0:37>,
+        <merge>>>>
+  $ testlog --date '2 0 to 4 0'
+  []
+  (func
+    (symbol 'date')
+    (string '2 0 to 4 0'))
+  <filteredset
+    <spanset- 0:37>,
+    <date '2 0 to 4 0'>>
+  $ hg log -G -d 'brace ) in a date'
+  hg: parse error: invalid date: 'brace ) in a date'
+  [255]
+  $ testlog --prune 31 --prune 32
+  []
+  (not
+    (or
+      (list
+        (func
+          (symbol 'ancestors')
+          (string '31'))
+        (func
+          (symbol 'ancestors')
+          (string '32')))))
+  <filteredset
+    <spanset- 0:37>,
+    <not
+      <addset
+        <filteredset
+          <spanset- 0:37>,
+          <generatorsetdesc+>>,
+        <filteredset
+          <spanset- 0:37>,
+          <generatorsetdesc+>>>>>
+
+Dedicated repo for --follow and paths filtering. The g is crafted to
+have 2 filelog topological heads in a linear changeset graph.
+
+  $ cd ..
+  $ hg init follow
+  $ cd follow
+  $ testlog --follow
+  []
+  []
+  <baseset []>
+  $ testlog -rnull
+  ['null']
+  []
+  <baseset [-1]>
+  $ echo a > a
+  $ echo aa > aa
+  $ echo f > f
+  $ hg ci -Am "add a" a aa f
+  $ hg cp a b
+  $ hg cp f g
+  $ hg ci -m "copy a b"
+  $ mkdir dir
+  $ hg mv b dir
+  $ echo g >> g
+  $ echo f >> f
+  $ hg ci -m "mv b dir/b"
+  $ hg mv a b
+  $ hg cp -f f g
+  $ echo a > d
+  $ hg add d
+  $ hg ci -m "mv a b; add d"
+  $ hg mv dir/b e
+  $ hg ci -m "mv dir/b e"
+  $ hg log -G --template '({rev}) {desc|firstline}\n'
+  \xe2\x97\x8d  (4) mv dir/b e (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  (3) mv a b; add d (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  (2) mv b dir/b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  (1) copy a b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  (0) add a (esc)
+  
+
+  $ testlog a
+  []
+  (func
+    (symbol 'filelog')
+    (string 'a'))
+  <filteredset
+    <spanset- 0:5>, set([0])>
+  $ testlog a b
+  []
+  (or
+    (list
+      (func
+        (symbol 'filelog')
+        (string 'a'))
+      (func
+        (symbol 'filelog')
+        (string 'b'))))
+  <filteredset
+    <spanset- 0:5>,
+    <addset
+      <baseset+ [0]>,
+      <baseset+ [1]>>>
+
+Test falling back to slow path for non-existing files
+
+  $ testlog a c
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'p:a')
+      (string 'p:c')))
+  <filteredset
+    <spanset- 0:5>,
+    <matchfiles patterns=['a', 'c'], include=[] exclude=[], default='relpath', rev=2147483647>>
+
+Test multiple --include/--exclude/paths
+
+  $ testlog --include a --include e --exclude b --exclude e a e
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'p:a')
+      (string 'p:e')
+      (string 'i:a')
+      (string 'i:e')
+      (string 'x:b')
+      (string 'x:e')))
+  <filteredset
+    <spanset- 0:5>,
+    <matchfiles patterns=['a', 'e'], include=['a', 'e'] exclude=['b', 'e'], default='relpath', rev=2147483647>>
+
+Test glob expansion of pats
+
+  $ expandglobs=`$PYTHON -c "import mercurial.util; \
+  >   print(mercurial.util.expandglobs and 'true' or 'false')"`
+  $ if [ $expandglobs = "true" ]; then
+  >    testlog 'a*';
+  > else
+  >    testlog a*;
+  > fi;
+  []
+  (func
+    (symbol 'filelog')
+    (string 'aa'))
+  <filteredset
+    <spanset- 0:5>, set([0])>
+
+Test --follow on a non-existent directory
+
+  $ testlog -f dir
+  abort: cannot follow file not in parent revision: "dir"
+  abort: cannot follow file not in parent revision: "dir"
+  abort: cannot follow file not in parent revision: "dir"
+
+Test --follow on a directory
+
+  $ hg up -q '.^'
+  $ testlog -f dir
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'p:dir')))
+  <filteredset
+    <generatorsetdesc->,
+    <matchfiles patterns=['dir'], include=[] exclude=[], default='relpath', rev=2147483647>>
+  $ hg up -q tip
+
+Test --follow on file not in parent revision
+
+  $ testlog -f a
+  abort: cannot follow file not in parent revision: "a"
+  abort: cannot follow file not in parent revision: "a"
+  abort: cannot follow file not in parent revision: "a"
+
+Test --follow and patterns
+
+  $ testlog -f 'glob:*'
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'p:glob:*')))
+  <filteredset
+    <generatorsetdesc->,
+    <matchfiles patterns=['glob:*'], include=[] exclude=[], default='relpath', rev=2147483647>>
+
+Test --follow on a single rename
+
+  $ hg up -q 2
+  $ testlog -f a
+  []
+  []
+  <generatorsetdesc->
+
+Test --follow and multiple renames
+
+  $ hg up -q tip
+  $ testlog -f e
+  []
+  []
+  <generatorsetdesc->
+
+Test --follow and multiple filelog heads
+
+  $ hg up -q 2
+  $ testlog -f g
+  []
+  []
+  <generatorsetdesc->
+  $ cat log.nodes
+  nodetag 2
+  nodetag 1
+  nodetag 0
+  $ hg up -q tip
+  $ testlog -f g
+  []
+  []
+  <generatorsetdesc->
+  $ cat log.nodes
+  nodetag 3
+  nodetag 2
+  nodetag 0
+
+Test --follow and multiple files
+
+  $ testlog -f g e
+  []
+  []
+  <generatorsetdesc->
+  $ cat log.nodes
+  nodetag 4
+  nodetag 3
+  nodetag 2
+  nodetag 1
+  nodetag 0
+
+Test --follow null parent
+
+  $ hg up -q null
+  $ testlog -f
+  []
+  []
+  <baseset []>
+
+Test --follow-first
+
+  $ hg up -q 3
+  $ echo ee > e
+  $ hg ci -Am "add another e" e
+  created new head
+  $ hg merge --tool internal:other 4
+  0 files updated, 1 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ echo merge > e
+  $ hg ci -m "merge 5 and 4"
+  $ testlog --follow-first
+  []
+  []
+  <generatorsetdesc->
+
+Cannot compare with log --follow-first FILE as it never worked
+
+  $ hg log -G --print-revset --follow-first e
+  []
+  []
+  <generatorsetdesc->
+  $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
+  \xe2\x97\x8d  6 merge 5 and 4 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 (esc)
+  \xe2\x97\x8b  5 add another e (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x95\xa7 (esc)
+
+Test --copies
+
+  $ hg log -G --copies --template "{rev} {desc|firstline} \
+  >   copies: {file_copies_switch}\n"
+  \xe2\x97\x8d  6 merge 5 and 4   copies: (esc)
+  \xe2\x94\x82\xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  5 add another e   copies: (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  4 mv dir/b e   copies: e (dir/b) (esc)
+  \xe2\x94\x82\xe2\x95\xb1 (esc)
+  \xe2\x97\x8b  3 mv a b; add d   copies: b (a)g (f) (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  2 mv b dir/b   copies: dir/b (b) (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  1 copy a b   copies: b (a)g (f) (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0 add a   copies: (esc)
+  
+Test "set:..." and parent revision
+
+  $ hg up -q 4
+  $ testlog "set:copied()"
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'p:set:copied()')))
+  <filteredset
+    <spanset- 0:7>,
+    <matchfiles patterns=['set:copied()'], include=[] exclude=[], default='relpath', rev=2147483647>>
+  $ testlog --include "set:copied()"
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'i:set:copied()')))
+  <filteredset
+    <spanset- 0:7>,
+    <matchfiles patterns=[], include=['set:copied()'] exclude=[], default='relpath', rev=2147483647>>
+  $ testlog -r "sort(file('set:copied()'), -rev)"
+  ["sort(file('set:copied()'), -rev)"]
+  []
+  <filteredset
+    <fullreposet- 0:7>,
+    <matchfiles patterns=['set:copied()'], include=[] exclude=[], default='glob', rev=None>>
+
+Test --removed
+
+  $ testlog --removed
+  []
+  []
+  <spanset- 0:7>
+  $ testlog --removed a
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'p:a')))
+  <filteredset
+    <spanset- 0:7>,
+    <matchfiles patterns=['a'], include=[] exclude=[], default='relpath', rev=2147483647>>
+  $ testlog --removed --follow a
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'p:a')))
+  <filteredset
+    <generatorsetdesc->,
+    <matchfiles patterns=['a'], include=[] exclude=[], default='relpath', rev=2147483647>>
+
+Test --patch and --stat with --follow and --follow-first
+
+  $ hg up -q 3
+  $ hg log -G --git --patch b
+  \xe2\x97\x8b  changeset:   1:216d4c92cf98 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x95\xa7  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+     summary:     copy a b
+  
+     diff --git a/a b/b
+     copy from a
+     copy to b
+  
+
+  $ hg log -G --git --stat b
+  \xe2\x97\x8b  changeset:   1:216d4c92cf98 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x95\xa7  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+     summary:     copy a b
+  
+      b |  0
+      1 files changed, 0 insertions(+), 0 deletions(-)
+  
+
+  $ hg log -G --git --patch --follow b
+  \xe2\x97\x8b  changeset:   1:216d4c92cf98 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     copy a b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82  diff --git a/a b/b (esc)
+  \xe2\x94\x82  copy from a (esc)
+  \xe2\x94\x82  copy to b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   0:f8035bb17114 (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add a
+  
+     diff --git a/a b/a
+     new file mode 100644
+     --- /dev/null
+     +++ b/a
+     @@ -0,0 +1,1 @@
+     +a
+  
+
+  $ hg log -G --git --stat --follow b
+  \xe2\x97\x8b  changeset:   1:216d4c92cf98 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     copy a b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82   b |  0 (esc)
+  \xe2\x94\x82   1 files changed, 0 insertions(+), 0 deletions(-) (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   0:f8035bb17114 (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add a
+  
+      a |  1 +
+      1 files changed, 1 insertions(+), 0 deletions(-)
+  
+
+  $ hg up -q 6
+  $ hg log -G --git --patch --follow-first e
+  \xe2\x97\x8d  changeset:   6:fc281d8ff18d (esc)
+  \xe2\x94\x82\xe2\x95\xb2   tag:         tip (esc)
+  \xe2\x94\x82 \xe2\x95\xa7  parent:      5:99b31f1c2782 (esc)
+  \xe2\x94\x82    parent:      4:17d952250a9d (esc)
+  \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     merge 5 and 4 (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82    diff --git a/e b/e (esc)
+  \xe2\x94\x82    --- a/e (esc)
+  \xe2\x94\x82    +++ b/e (esc)
+  \xe2\x94\x82    @@ -1,1 +1,1 @@ (esc)
+  \xe2\x94\x82    -ee (esc)
+  \xe2\x94\x82    +merge (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   5:99b31f1c2782 (esc)
+  \xe2\x94\x82  parent:      3:5918b8d165d1 (esc)
+  \xe2\x95\xa7  user:        test (esc)
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add another e
+  
+     diff --git a/e b/e
+     new file mode 100644
+     --- /dev/null
+     +++ b/e
+     @@ -0,0 +1,1 @@
+     +ee
+  
+
+Test old-style --rev
+
+  $ hg tag 'foo-bar'
+  $ testlog -r 'foo-bar'
+  ['foo-bar']
+  []
+  <baseset [6]>
+
+Test --follow and forward --rev
+
+  $ hg up -q 6
+  $ echo g > g
+  $ hg ci -Am 'add g' g
+  created new head
+  $ hg up -q 2
+  $ hg log -G --template "{rev} {desc|firstline}\n"
+  \xe2\x97\x8b  8 add g (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  7 Added tag foo-bar for changeset fc281d8ff18d (esc)
+  \xe2\x94\x82\xe2\x95\xb1 (esc)
+  \xe2\x97\x8b  6 merge 5 and 4 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  5 add another e (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  4 mv dir/b e (esc)
+  \xe2\x94\x82\xe2\x95\xb1 (esc)
+  \xe2\x97\x8b  3 mv a b; add d (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8d  2 mv b dir/b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  1 copy a b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0 add a (esc)
+  
+  $ hg archive -r 7 archive
+  $ grep changessincelatesttag archive/.hg_archival.txt
+  changessincelatesttag: 1
+  $ rm -r archive
+
+changessincelatesttag with no prior tag
+  $ hg archive -r 4 archive
+  $ grep changessincelatesttag archive/.hg_archival.txt
+  changessincelatesttag: 5
+
+  $ hg export 'all()'
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID f8035bb17114da16215af3436ec5222428ace8ee
+  # Parent  0000000000000000000000000000000000000000
+  add a
+  
+  diff -r 000000000000 -r f8035bb17114 a
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r 000000000000 -r f8035bb17114 aa
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/aa	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +aa
+  diff -r 000000000000 -r f8035bb17114 f
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/f	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +f
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 216d4c92cf98ff2b4641d508b76b529f3d424c92
+  # Parent  f8035bb17114da16215af3436ec5222428ace8ee
+  copy a b
+  
+  diff -r f8035bb17114 -r 216d4c92cf98 b
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r f8035bb17114 -r 216d4c92cf98 g
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/g	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +f
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID bb573313a9e8349099b6ea2b2fb1fc7f424446f3
+  # Parent  216d4c92cf98ff2b4641d508b76b529f3d424c92
+  mv b dir/b
+  
+  diff -r 216d4c92cf98 -r bb573313a9e8 b
+  --- a/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -a
+  diff -r 216d4c92cf98 -r bb573313a9e8 dir/b
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r 216d4c92cf98 -r bb573313a9e8 f
+  --- a/f	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/f	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,2 @@
+   f
+  +f
+  diff -r 216d4c92cf98 -r bb573313a9e8 g
+  --- a/g	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/g	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,2 @@
+   f
+  +g
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 5918b8d165d1364e78a66d02e66caa0133c5d1ed
+  # Parent  bb573313a9e8349099b6ea2b2fb1fc7f424446f3
+  mv a b; add d
+  
+  diff -r bb573313a9e8 -r 5918b8d165d1 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -a
+  diff -r bb573313a9e8 -r 5918b8d165d1 b
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r bb573313a9e8 -r 5918b8d165d1 d
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/d	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r bb573313a9e8 -r 5918b8d165d1 g
+  --- a/g	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/g	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,2 +1,2 @@
+   f
+  -g
+  +f
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 17d952250a9d03cc3dc77b199ab60e959b9b0260
+  # Parent  5918b8d165d1364e78a66d02e66caa0133c5d1ed
+  mv dir/b e
+  
+  diff -r 5918b8d165d1 -r 17d952250a9d dir/b
+  --- a/dir/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -a
+  diff -r 5918b8d165d1 -r 17d952250a9d e
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/e	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 99b31f1c2782e2deb1723cef08930f70fc84b37b
+  # Parent  5918b8d165d1364e78a66d02e66caa0133c5d1ed
+  add another e
+  
+  diff -r 5918b8d165d1 -r 99b31f1c2782 e
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/e	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +ee
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID fc281d8ff18d999ad6497b3d27390bcd695dcc73
+  # Parent  99b31f1c2782e2deb1723cef08930f70fc84b37b
+  # Parent  17d952250a9d03cc3dc77b199ab60e959b9b0260
+  merge 5 and 4
+  
+  diff -r 99b31f1c2782 -r fc281d8ff18d dir/b
+  --- a/dir/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -a
+  diff -r 99b31f1c2782 -r fc281d8ff18d e
+  --- a/e	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/e	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -ee
+  +merge
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 02dbb8e276b8ab7abfd07cab50c901647e75c2dd
+  # Parent  fc281d8ff18d999ad6497b3d27390bcd695dcc73
+  Added tag foo-bar for changeset fc281d8ff18d
+  
+  diff -r fc281d8ff18d -r 02dbb8e276b8 .hgtags
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +fc281d8ff18d999ad6497b3d27390bcd695dcc73 foo-bar
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 24c2e826ddebf80f9dcd60b856bdb8e6715c5449
+  # Parent  fc281d8ff18d999ad6497b3d27390bcd695dcc73
+  add g
+  
+  diff -r fc281d8ff18d -r 24c2e826ddeb g
+  --- a/g	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/g	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,2 +1,1 @@
+  -f
+  -f
+  +g
+  $ testlog --follow -r6 -r8 -r5 -r7 -r4
+  ['6', '8', '5', '7', '4']
+  []
+  <generatorsetdesc->
+
+Test --follow-first and forward --rev
+
+  $ testlog --follow-first -r6 -r8 -r5 -r7 -r4
+  ['6', '8', '5', '7', '4']
+  []
+  <generatorsetdesc->
+
+Test --follow and backward --rev
+
+  $ testlog --follow -r6 -r5 -r7 -r8 -r4
+  ['6', '5', '7', '8', '4']
+  []
+  <generatorsetdesc->
+
+Test --follow-first and backward --rev
+
+  $ testlog --follow-first -r6 -r5 -r7 -r8 -r4
+  ['6', '5', '7', '8', '4']
+  []
+  <generatorsetdesc->
+
+Test --follow with --rev of graphlog extension
+
+  $ hg --config extensions.graphlog= glog -qfr1
+  \xe2\x97\x8b  1:216d4c92cf98 (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0:f8035bb17114 (esc)
+  
+
+Test subdir
+
+  $ hg up -q 3
+  $ cd dir
+  $ testlog .
+  []
+  (func
+    (symbol '_matchfiles')
+    (list
+      (string 'r:')
+      (string 'd:relpath')
+      (string 'p:.')))
+  <filteredset
+    <spanset- 0:9>,
+    <matchfiles patterns=['.'], include=[] exclude=[], default='relpath', rev=2147483647>>
+  $ testlog ../b
+  []
+  (func
+    (symbol 'filelog')
+    (string '../b'))
+  <filteredset
+    <spanset- 0:9>, set([1])>
+  $ testlog -f ../b
+  []
+  []
+  <generatorsetdesc->
+  $ cd ..
+
+Test --hidden
+ (enable obsolete)
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution.createmarkers=True
+  > EOF
+
+  $ hg debugobsolete `hg id --debug -i -r 8`
+  obsoleted 1 changesets
+  $ testlog
+  []
+  []
+  <spanset- 0:9>
+  $ testlog --hidden
+  []
+  []
+  <spanset- 0:9>
+  $ hg log -G --template '{rev} {desc}\n'
+  \xe2\x97\x8b  7 Added tag foo-bar for changeset fc281d8ff18d (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  6 merge 5 and 4 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  5 add another e (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  4 mv dir/b e (esc)
+  \xe2\x94\x82\xe2\x95\xb1 (esc)
+  \xe2\x97\x8d  3 mv a b; add d (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  2 mv b dir/b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  1 copy a b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0 add a (esc)
+  
+
+A template without trailing newline should do something sane
+
+  $ hg log -G -r ::2 --template '{rev} {desc}'
+  \xe2\x97\x8b  2 mv b dir/b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  1 copy a b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0 add a (esc)
+  
+
+Extra newlines must be preserved
+
+  $ hg log -G -r ::2 --template '\n{rev} {desc}\n\n'
+  \xe2\x97\x8b (esc)
+  \xe2\x94\x82  2 mv b dir/b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b (esc)
+  \xe2\x94\x82  1 copy a b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b (esc)
+     0 add a
+  
+
+The almost-empty template should do something sane too ...
+
+  $ hg log -G -r ::2 --template '\n'
+  \xe2\x97\x8b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b (esc)
+  
+
+issue3772
+
+  $ hg log -G -r :null
+  \xe2\x97\x8b  changeset:   0:f8035bb17114 (esc)
+  \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x82  summary:     add a (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   -1:000000000000 (esc)
+     user:
+     date:        Thu Jan 01 00:00:00 1970 +0000
+  
+  $ hg log -G -r null:null
+  \xe2\x97\x8b  changeset:   -1:000000000000 (esc)
+     user:
+     date:        Thu Jan 01 00:00:00 1970 +0000
+  
+
+should not draw line down to null due to the magic of fullreposet
+
+  $ hg log -G -r 'all()' | tail -6
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   0:f8035bb17114 (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add a
+  
+
+  $ hg log -G -r 'branch(default)' | tail -6
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   0:f8035bb17114 (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     add a
+  
+
+working-directory revision
+
+  $ hg log -G -qr '. + wdir()'
+  \xe2\x97\x8b  2147483647:ffffffffffff (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8d  3:5918b8d165d1 (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x95\xa7 (esc)
+
+node template with changesetprinter:
+
+  $ hg log -Gqr 5:7 --config ui.graphnodetemplate='"{rev}"'
+  7  7:02dbb8e276b8
+  \xe2\x94\x82 (esc)
+  6    6:fc281d8ff18d
+  \xe2\x94\x82\xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 (esc)
+  5  5:99b31f1c2782
+  \xe2\x94\x82 (esc)
+  \xe2\x95\xa7 (esc)
+
+node template with changesettemplater (shared cache variable):
+
+  $ hg log -Gr 5:7 -T '{latesttag % "{rev} {tag}+{distance}"}\n' \
+  > --config ui.graphnodetemplate='{ifeq(latesttagdistance, 0, "#", graphnode)}'
+  \xe2\x97\x8b  7 foo-bar+1 (esc)
+  \xe2\x94\x82 (esc)
+  #    6 foo-bar+0
+  \xe2\x94\x82\xe2\x95\xb2 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 (esc)
+  \xe2\x97\x8b  5 null+5 (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x95\xa7 (esc)
+
+label() should just work in node template:
+
+  $ hg log -Gqr 7 --config extensions.color= --color=debug \
+  > --config ui.graphnodetemplate='{label("branch.{branch}", rev)}'
+  [branch.default\xe2\x94\x827]  [log.node|7:02dbb8e276b8] (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x95\xa7 (esc)
+
+  $ cd ..
+
+change graph edge styling
+
+  $ cd repo
+
+Setting HGPLAIN ignores graphmod styling:
+
+  $ HGPLAIN=1 hg log -G -r 'file("a")' -m
+  @  changeset:   36:08a19a744424
+  |  branch:      branch
+  |  tag:         tip
+  |  parent:      35:9159c3644c5e
+  |  parent:      35:9159c3644c5e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:36 1970 +0000
+  |  summary:     (36) buggy merge: identical parents
+  |
+  o    changeset:   32:d06dffa21a31
+  |\   parent:      27:886ed638191b
+  | |  parent:      31:621d83e11f67
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:32 1970 +0000
+  | |  summary:     (32) expand
+  | |
+  o |  changeset:   31:621d83e11f67
+  |\|  parent:      21:d42a756af44d
+  | |  parent:      30:6e11cd4b648f
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:31 1970 +0000
+  | |  summary:     (31) expand
+  | |
+  o |    changeset:   30:6e11cd4b648f
+  |\ \   parent:      28:44ecd0b9ae99
+  | | |  parent:      29:cd9bb2be7593
+  | | |  user:        test
+  | | |  date:        Thu Jan 01 00:00:30 1970 +0000
+  | | |  summary:     (30) expand
+  | | |
+  o | |    changeset:   28:44ecd0b9ae99
+  |\ \ \   parent:      1:6db2ef61d156
+  | | | |  parent:      26:7f25b6c2f0b9
+  | | | |  user:        test
+  | | | |  date:        Thu Jan 01 00:00:28 1970 +0000
+  | | | |  summary:     (28) merge zero known
+  | | | |
+  o | | |    changeset:   26:7f25b6c2f0b9
+  |\ \ \ \   parent:      18:1aa84d96232a
+  | | | | |  parent:      25:91da8ed57247
+  | | | | |  user:        test
+  | | | | |  date:        Thu Jan 01 00:00:26 1970 +0000
+  | | | | |  summary:     (26) merge one known; far right
+  | | | | |
+  | o-----+  changeset:   25:91da8ed57247
+  | | | | |  parent:      21:d42a756af44d
+  | | | | |  parent:      24:a9c19a3d96b7
+  | | | | |  user:        test
+  | | | | |  date:        Thu Jan 01 00:00:25 1970 +0000
+  | | | | |  summary:     (25) merge one known; far left
+  | | | | |
+  | o | | |    changeset:   24:a9c19a3d96b7
+  | |\ \ \ \   parent:      0:e6eb3150255d
+  | | | | | |  parent:      23:a01cddf0766d
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:24 1970 +0000
+  | | | | | |  summary:     (24) merge one known; immediate right
+  | | | | | |
+  | o---+ | |  changeset:   23:a01cddf0766d
+  | | | | | |  parent:      1:6db2ef61d156
+  | | | | | |  parent:      22:e0d9cccacb5d
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:23 1970 +0000
+  | | | | | |  summary:     (23) merge one known; immediate left
+  | | | | | |
+  | o-------+  changeset:   22:e0d9cccacb5d
+  | | | | | |  parent:      18:1aa84d96232a
+  |/ / / / /   parent:      21:d42a756af44d
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:22 1970 +0000
+  | | | | |    summary:     (22) merge two known; one far left, one far right
+  | | | | |
+  | | | | o    changeset:   21:d42a756af44d
+  | | | | |\   parent:      19:31ddc2c1573b
+  | | | | | |  parent:      20:d30ed6450e32
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:21 1970 +0000
+  | | | | | |  summary:     (21) expand
+  | | | | | |
+  +-+-------o  changeset:   20:d30ed6450e32
+  | | | | |    parent:      0:e6eb3150255d
+  | | | | |    parent:      18:1aa84d96232a
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:20 1970 +0000
+  | | | | |    summary:     (20) merge two known; two far right
+  | | | | |
+  | | | | o    changeset:   19:31ddc2c1573b
+  | | | | |\   parent:      15:1dda3f72782d
+  | | | | | |  parent:      17:44765d7c06e0
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:19 1970 +0000
+  | | | | | |  summary:     (19) expand
+  | | | | | |
+  o---+---+ |  changeset:   18:1aa84d96232a
+    | | | | |  parent:      1:6db2ef61d156
+   / / / / /   parent:      15:1dda3f72782d
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:18 1970 +0000
+  | | | | |    summary:     (18) merge two known; two far left
+  | | | | |
+  | | | | o    changeset:   17:44765d7c06e0
+  | | | | |\   parent:      12:86b91144a6e9
+  | | | | | |  parent:      16:3677d192927d
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:17 1970 +0000
+  | | | | | |  summary:     (17) expand
+  | | | | | |
+  +-+-------o  changeset:   16:3677d192927d
+  | | | | |    parent:      0:e6eb3150255d
+  | | | | |    parent:      1:6db2ef61d156
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:16 1970 +0000
+  | | | | |    summary:     (16) merge two known; one immediate right, one near right
+  | | | | |
+  | | | o |    changeset:   15:1dda3f72782d
+  | | | |\ \   parent:      13:22d8966a97e3
+  | | | | | |  parent:      14:8eac370358ef
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:15 1970 +0000
+  | | | | | |  summary:     (15) expand
+  | | | | | |
+  +-------o |  changeset:   14:8eac370358ef
+  | | | | |/   parent:      0:e6eb3150255d
+  | | | | |    parent:      12:86b91144a6e9
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:14 1970 +0000
+  | | | | |    summary:     (14) merge two known; one immediate right, one far right
+  | | | | |
+  | | | o |    changeset:   13:22d8966a97e3
+  | | | |\ \   parent:      9:7010c0af0a35
+  | | | | | |  parent:      11:832d76e6bdf2
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:13 1970 +0000
+  | | | | | |  summary:     (13) expand
+  | | | | | |
+  | +---+---o  changeset:   12:86b91144a6e9
+  | | | | |    parent:      1:6db2ef61d156
+  | | | | |    parent:      9:7010c0af0a35
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:12 1970 +0000
+  | | | | |    summary:     (12) merge two known; one immediate right, one far left
+  | | | | |
+  | | | | o    changeset:   11:832d76e6bdf2
+  | | | | |\   parent:      6:b105a072e251
+  | | | | | |  parent:      10:74c64d036d72
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:11 1970 +0000
+  | | | | | |  summary:     (11) expand
+  | | | | | |
+  +---------o  changeset:   10:74c64d036d72
+  | | | | |/   parent:      0:e6eb3150255d
+  | | | | |    parent:      6:b105a072e251
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:10 1970 +0000
+  | | | | |    summary:     (10) merge two known; one immediate left, one near right
+  | | | | |
+  | | | o |    changeset:   9:7010c0af0a35
+  | | | |\ \   parent:      7:b632bb1b1224
+  | | | | | |  parent:      8:7a0b11f71937
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:09 1970 +0000
+  | | | | | |  summary:     (9) expand
+  | | | | | |
+  +-------o |  changeset:   8:7a0b11f71937
+  | | | |/ /   parent:      0:e6eb3150255d
+  | | | | |    parent:      7:b632bb1b1224
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:08 1970 +0000
+  | | | | |    summary:     (8) merge two known; one immediate left, one far right
+  | | | | |
+  | | | o |    changeset:   7:b632bb1b1224
+  | | | |\ \   parent:      2:3d9a33b8d1e1
+  | | | | | |  parent:      5:4409d547b708
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:07 1970 +0000
+  | | | | | |  summary:     (7) expand
+  | | | | | |
+  | | | +---o  changeset:   6:b105a072e251
+  | | | | |/   parent:      2:3d9a33b8d1e1
+  | | | | |    parent:      5:4409d547b708
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:06 1970 +0000
+  | | | | |    summary:     (6) merge two known; one immediate left, one far left
+  | | | | |
+  | | | o |    changeset:   5:4409d547b708
+  | | | |\ \   parent:      3:27eef8ed80b4
+  | | | | | |  parent:      4:26a8bac39d9f
+  | | | | | |  user:        test
+  | | | | | |  date:        Thu Jan 01 00:00:05 1970 +0000
+  | | | | | |  summary:     (5) expand
+  | | | | | |
+  | +---o | |  changeset:   4:26a8bac39d9f
+  | | | |/ /   parent:      1:6db2ef61d156
+  | | | | |    parent:      3:27eef8ed80b4
+  | | | | |    user:        test
+  | | | | |    date:        Thu Jan 01 00:00:04 1970 +0000
+  | | | | |    summary:     (4) merge two known; one immediate left, one immediate right
+  | | | | |
+
+.. unless HGPLAINEXCEPT=graph is set:
+
+  $ HGPLAIN=1 HGPLAINEXCEPT=graph hg log -G -r 'file("a")' -m
+  \xe2\x97\x8d  changeset:   36:08a19a744424 (esc)
+  \xe2\x94\x86  branch:      branch (esc)
+  \xe2\x94\x86  tag:         tip (esc)
+  \xe2\x94\x86  parent:      35:9159c3644c5e (esc)
+  \xe2\x94\x86  parent:      35:9159c3644c5e (esc)
+  \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x86  date:        Thu Jan 01 00:00:36 1970 +0000 (esc)
+  \xe2\x94\x86  summary:     (36) buggy merge: identical parents (esc)
+  \xe2\x94\x86 (esc)
+  \xe2\x97\x8b  changeset:   32:d06dffa21a31 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      27:886ed638191b (esc)
+  \xe2\x94\x82 \xe2\x94\x86  parent:      31:621d83e11f67 (esc)
+  \xe2\x94\x82 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x86  date:        Thu Jan 01 00:00:32 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x86  summary:     (32) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x97\x8b \xe2\x94\x86  changeset:   31:621d83e11f67 (esc)
+  \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x86  parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x86  parent:      30:6e11cd4b648f (esc)
+  \xe2\x94\x82 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x86  date:        Thu Jan 01 00:00:31 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x86  summary:     (31) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x97\x8b \xe2\x94\x86  changeset:   30:6e11cd4b648f (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x86  parent:      29:cd9bb2be7593 (esc)
+  \xe2\x94\x82   \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x86  date:        Thu Jan 01 00:00:30 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x86  summary:     (30) expand (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x86  changeset:   28:44ecd0b9ae99 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x86  parent:      26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82   \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x86  date:        Thu Jan 01 00:00:28 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x86  summary:     (28) merge zero known (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x86  changeset:   26:7f25b6c2f0b9 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  parent:      25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  date:        Thu Jan 01 00:00:26 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  summary:     (26) merge one known; far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x86  changeset:   25:91da8ed57247 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x86  parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  parent:      24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  date:        Thu Jan 01 00:00:25 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86  summary:     (25) merge one known; far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x86  changeset:   24:a9c19a3d96b7 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x86  parent:      23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  date:        Thu Jan 01 00:00:24 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  summary:     (24) merge one known; immediate right (esc)
+  \xe2\x94\x82 \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x86  changeset:   23:a01cddf0766d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x86  parent:      22:e0d9cccacb5d (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  date:        Thu Jan 01 00:00:23 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82   \xe2\x94\x86  summary:     (23) merge one known; immediate left (esc)
+  \xe2\x94\x82 \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x86  changeset:   22:e0d9cccacb5d (esc)
+  \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x86\xe2\x95\xb1   parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x86    parent:      21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x86    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x86    date:        Thu Jan 01 00:00:22 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x86    summary:     (22) merge two known; one far left, one far right (esc)
+  \xe2\x94\x82 \xe2\x94\x86 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   21:d42a756af44d (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      19:31ddc2c1573b (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      20:d30ed6450e32 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:21 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (21) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b  changeset:   20:d30ed6450e32 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7  parent:      18:1aa84d96232a (esc)
+  \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:20 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (20) merge two known; two far right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   19:31ddc2c1573b (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      15:1dda3f72782d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:19 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (19) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82 \xe2\x94\x82  changeset:   18:1aa84d96232a (esc)
+  \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x82 \xe2\x94\x82  parent:      1:6db2ef61d156 (esc)
+  \xe2\x95\xa7 \xe2\x94\x82 \xe2\x94\x82  parent:      15:1dda3f72782d (esc)
+    \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+    \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:18 1970 +0000 (esc)
+    \xe2\x94\x82 \xe2\x94\x82  summary:     (18) merge two known; two far left (esc)
+   \xe2\x95\xb1 \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   17:44765d7c06e0 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:17 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (17) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b  changeset:   16:3677d192927d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7 \xe2\x95\xa7  parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82      user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82      date:        Thu Jan 01 00:00:16 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82      summary:     (16) merge two known; one immediate right, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   15:1dda3f72782d (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      13:22d8966a97e3 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:15 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (15) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   14:8eac370358ef (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2\xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x82  parent:      12:86b91144a6e9 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x82  date:        Thu Jan 01 00:00:14 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  summary:     (14) merge two known; one immediate right, one far right (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   13:22d8966a97e3 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:13 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (13) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80\xe2\x94\x80\xe2\x97\x8b  changeset:   12:86b91144a6e9 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      1:6db2ef61d156 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7  parent:      9:7010c0af0a35 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:12 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (12) merge two known; one immediate right, one far left (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   11:832d76e6bdf2 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb2   parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:11 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (11) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x97\x8b  changeset:   10:74c64d036d72 (esc)
+  \xe2\x94\x82 \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x95\xa7  parent:      6:b105a072e251 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82    date:        Thu Jan 01 00:00:10 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82    summary:     (10) merge two known; one immediate left, one near right (esc)
+  \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   9:7010c0af0a35 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  parent:      8:7a0b11f71937 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  date:        Thu Jan 01 00:00:09 1970 +0000 (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82  summary:     (9) expand (esc)
+  \xe2\x94\x82 \xe2\x94\x82 \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b \xe2\x94\x82  changeset:   8:7a0b11f71937 (esc)
+  \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82 \xe2\x94\x82  parent:      0:e6eb3150255d (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x82  parent:      7:b632bb1b1224 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x82  date:        Thu Jan 01 00:00:08 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  summary:     (8) merge two known; one immediate left, one far right (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x97\x8b \xe2\x94\x82  changeset:   7:b632bb1b1224 (esc)
+  \xe2\x94\x82\xe2\x95\xb2 \xe2\x95\xb2   parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7 \xe2\x94\x82  parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  user:        test (esc)
+  \xe2\x94\x82   \xe2\x94\x82  date:        Thu Jan 01 00:00:07 1970 +0000 (esc)
+  \xe2\x94\x82   \xe2\x94\x82  summary:     (7) expand (esc)
+  \xe2\x94\x82  \xe2\x95\xb1 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  changeset:   6:b105a072e251 (esc)
+  \xe2\x94\x82\xe2\x95\xb1\xe2\x94\x82  parent:      2:3d9a33b8d1e1 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7  parent:      5:4409d547b708 (esc)
+  \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:06 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (6) merge two known; one immediate left, one far left (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   5:4409d547b708 (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      3:27eef8ed80b4 (esc)
+  \xe2\x94\x82 \xe2\x95\xa7  parent:      4:26a8bac39d9f (esc)
+  \xe2\x94\x82    user:        test (esc)
+  \xe2\x94\x82    date:        Thu Jan 01 00:00:05 1970 +0000 (esc)
+  \xe2\x94\x82    summary:     (5) expand (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  changeset:   4:26a8bac39d9f (esc)
+  \xe2\x94\x82\xe2\x95\xb2   parent:      1:6db2ef61d156 (esc)
+  \xe2\x95\xa7 \xe2\x95\xa7  parent:      3:27eef8ed80b4 (esc)
+       user:        test
+       date:        Thu Jan 01 00:00:04 1970 +0000
+       summary:     (4) merge two known; one immediate left, one immediate right
+  
+  $ cd ..
+  $ cd repo
+
+behavior with newlines
+
+  $ hg log -G -r ::2 -T '{rev} {desc}'
+  \xe2\x97\x8b  2 (2) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  1 (1) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0 (0) root (esc)
+  
+
+  $ hg log -G -r ::2 -T '{rev} {desc}\n'
+  \xe2\x97\x8b  2 (2) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  1 (1) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0 (0) root (esc)
+  
+
+  $ hg log -G -r ::2 -T '{rev} {desc}\n\n'
+  \xe2\x97\x8b  2 (2) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  1 (1) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0 (0) root (esc)
+  
+
+  $ hg log -G -r ::2 -T '\n{rev} {desc}'
+  \xe2\x97\x8b (esc)
+  \xe2\x94\x82  2 (2) collapse (esc)
+  \xe2\x97\x8b (esc)
+  \xe2\x94\x82  1 (1) collapse (esc)
+  \xe2\x97\x8b (esc)
+     0 (0) root
+
+  $ hg log -G -r ::2 -T '{rev} {desc}\n\n\n'
+  \xe2\x97\x8b  2 (2) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  1 (1) collapse (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  0 (0) root (esc)
+  
+  
+  $ cd ..
+
+When inserting extra line nodes to handle more than 2 parents, ensure that
+the right node styles are used (issue5174):
+
+  $ hg init repo-issue5174
+  $ cd repo-issue5174
+  $ echo a > f0
+  $ hg ci -Aqm 0
+  $ echo a > f1
+  $ hg ci -Aqm 1
+  $ echo a > f2
+  $ hg ci -Aqm 2
+  $ hg co ".^"
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo a > f3
+  $ hg ci -Aqm 3
+  $ hg co ".^^"
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo a > f4
+  $ hg ci -Aqm 4
+  $ hg merge -r 2
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -qm 5
+  $ hg merge -r 3
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -qm 6
+  $ hg log -G -r '0 | 1 | 2 | 6'
+  \xe2\x97\x8d  changeset:   6:851fe89689ad (esc)
+  \xe2\x94\x86\xe2\x95\xb2   tag:         tip (esc)
+  \xe2\x94\x86 \xe2\x94\x86  parent:      5:4f1e3cf15f5d (esc)
+  \xe2\x94\x86 \xe2\x94\x86  parent:      3:b74ba7084d2d (esc)
+  \xe2\x94\x86 \xe2\x94\x86  user:        test (esc)
+  \xe2\x94\x86 \xe2\x94\x86  date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x86 \xe2\x94\x86  summary:     6 (esc)
+  \xe2\x94\x86 \xe2\x94\x86 (esc)
+  \xe2\x94\x86 \xe2\x95\xb2 (esc)
+  \xe2\x94\x86 \xe2\x94\x86\xe2\x95\xb2 (esc)
+  \xe2\x94\x86 \xe2\x97\x8b \xe2\x94\x86  changeset:   2:3e6599df4cce (esc)
+  \xe2\x94\x86 \xe2\x94\x86\xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x86 \xe2\x94\x86    date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x86 \xe2\x94\x86    summary:     2 (esc)
+  \xe2\x94\x86 \xe2\x94\x86 (esc)
+  \xe2\x94\x86 \xe2\x97\x8b  changeset:   1:bd9a55143933 (esc)
+  \xe2\x94\x86\xe2\x95\xb1   user:        test (esc)
+  \xe2\x94\x86    date:        Thu Jan 01 00:00:00 1970 +0000 (esc)
+  \xe2\x94\x86    summary:     1 (esc)
+  \xe2\x94\x86 (esc)
+  \xe2\x97\x8b  changeset:   0:870a5edc339c (esc)
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     0
+  
+
+  $ cd ..
+
+Multiple roots (issue5440):
+
+  $ hg init multiroots
+  $ cd multiroots
+  $ cat <<EOF > .hg/hgrc
+  > [ui]
+  > logtemplate = '{rev} {desc}\n\n'
+  > EOF
+
+  $ touch foo
+  $ hg ci -Aqm foo
+  $ hg co -q null
+  $ touch bar
+  $ hg ci -Aqm bar
+
+  $ hg log -Gr null:
+  \xe2\x97\x8d  1 bar (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x94\x82 \xe2\x97\x8b  0 foo (esc)
+  \xe2\x94\x82\xe2\x95\xb1 (esc)
+  \xe2\x97\x8b  -1 (esc)
+  
+  $ hg log -Gr null+0
+  \xe2\x97\x8b  0 foo (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  -1 (esc)
+  
+  $ hg log -Gr null+1
+  \xe2\x97\x8d  1 bar (esc)
+  \xe2\x94\x82 (esc)
+  \xe2\x97\x8b  -1 (esc)
+  
+
+  $ cd ..
--- a/tests/test-glog.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-glog.t	Thu Jul 19 13:55:54 2018 -0400
@@ -91,6 +91,7 @@
   >   revsetlang,
   >   smartset,
   > )
+  > from mercurial.utils import stringutil
   > 
   > def logrevset(repo, pats, opts):
   >     revs = logcmdutil._initialrevs(repo, opts)
@@ -112,7 +113,7 @@
   >             ui = repo.ui
   >             ui.write(b'%r\n' % (opts.get(b'rev', []),))
   >             ui.write(revsetlang.prettyformat(tree) + b'\n')
-  >             ui.write(smartset.prettyformat(revs) + b'\n')
+  >             ui.write(stringutil.prettyrepr(revs) + b'\n')
   >             revs = smartset.baseset()  # display no revisions
   >         return revs, filematcher
   >     extensions.wrapfunction(logcmdutil, 'getrevs', printrevset)
--- a/tests/test-graft.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-graft.t	Thu Jul 19 13:55:54 2018 -0400
@@ -213,7 +213,7 @@
   my e@1905859650ec+ other e@9c233e8e184d ancestor e@4c60f11aa304
   warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
   abort: unresolved conflicts, can't continue
-  (use 'hg resolve' and 'hg graft --continue --log')
+  (use 'hg resolve' and 'hg graft --continue')
   [255]
 
 Summary should mention graft:
@@ -236,15 +236,15 @@
   # 
   # To mark files as resolved:  hg resolve --mark FILE
   
-  # To continue:                hg graft --continue
-  # To abort:                   hg update --clean .    (warning: this will discard uncommitted changes)
+  # To continue:    hg graft --continue
+  # To abort:       hg update --clean . (warning: this will discard uncommitted changes)
   
 
 Commit while interrupted should fail:
 
   $ hg ci -m 'commit interrupted graft'
   abort: graft in progress
-  (use 'hg graft --continue' or 'hg update' to abort)
+  (use 'hg graft --continue' or 'hg graft --stop' to stop)
   [255]
 
 Abort the graft and try committing:
@@ -1373,3 +1373,758 @@
   note: graft of 7:d3c3f2b38ecc created no changes to commit
 
   $ cd ..
+
+Testing the reading of old format graftstate file with newer mercurial
+
+  $ hg init oldgraft
+  $ cd oldgraft
+  $ for ch in a b c; do echo foo > $ch; hg add $ch; hg ci -Aqm "added "$ch; done;
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  2:8be98ac1a569 added c
+  |
+  o  1:80e6d2c47cfe added b
+  |
+  o  0:f7ad41964313 added a
+  
+  $ hg up 0
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo bar > b
+  $ hg add b
+  $ hg ci -m "bar to b"
+  created new head
+  $ hg graft -r 1 -r 2
+  grafting 1:80e6d2c47cfe "added b"
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+Writing the nodes in old format to graftstate
+
+  $ hg log -r 1 -r 2 -T '{node}\n' > .hg/graftstate
+  $ echo foo > b
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg graft --continue
+  $ hg graft --continue
+  grafting 1:80e6d2c47cfe "added b"
+  grafting 2:8be98ac1a569 "added c"
+
+Testing that --user is preserved during conflicts and value is reused while
+running `hg graft --continue`
+
+  $ hg log -G
+  @  changeset:   5:711e9fa999f1
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added c
+  |
+  o  changeset:   4:e5ad7353b408
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added b
+  |
+  o  changeset:   3:9e887f7a939c
+  |  parent:      0:f7ad41964313
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     bar to b
+  |
+  | o  changeset:   2:8be98ac1a569
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     added c
+  | |
+  | o  changeset:   1:80e6d2c47cfe
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     added b
+  |
+  o  changeset:   0:f7ad41964313
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     added a
+  
+
+  $ hg up '.^^'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+  $ hg graft -r 1 -r 2 --user batman
+  grafting 1:80e6d2c47cfe "added b"
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ echo wat > b
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg graft --continue
+
+  $ hg graft --continue
+  grafting 1:80e6d2c47cfe "added b"
+  grafting 2:8be98ac1a569 "added c"
+
+  $ hg log -Gr 3::
+  @  changeset:   7:11a36ffaacf2
+  |  tag:         tip
+  |  user:        batman
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added c
+  |
+  o  changeset:   6:76803afc6511
+  |  parent:      3:9e887f7a939c
+  |  user:        batman
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added b
+  |
+  | o  changeset:   5:711e9fa999f1
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     added c
+  | |
+  | o  changeset:   4:e5ad7353b408
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     added b
+  |
+  o  changeset:   3:9e887f7a939c
+  |  parent:      0:f7ad41964313
+  ~  user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     bar to b
+  
+Test that --date is preserved and reused in `hg graft --continue`
+
+  $ hg up '.^^'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg graft -r 1 -r 2 --date '1234560000 120'
+  grafting 1:80e6d2c47cfe "added b"
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ echo foobar > b
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg graft --continue
+  $ hg graft --continue
+  grafting 1:80e6d2c47cfe "added b"
+  grafting 2:8be98ac1a569 "added c"
+
+  $ hg log -Gr '.^^::.'
+  @  changeset:   9:1896b76e007a
+  |  tag:         tip
+  |  user:        test
+  |  date:        Fri Feb 13 21:18:00 2009 -0002
+  |  summary:     added c
+  |
+  o  changeset:   8:ce2b4f1632af
+  |  parent:      3:9e887f7a939c
+  |  user:        test
+  |  date:        Fri Feb 13 21:18:00 2009 -0002
+  |  summary:     added b
+  |
+  o  changeset:   3:9e887f7a939c
+  |  parent:      0:f7ad41964313
+  ~  user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     bar to b
+  
+Test that --log is preserved and reused in `hg graft --continue`
+
+  $ hg up '.^^'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg graft -r 1 -r 2 --log
+  grafting 1:80e6d2c47cfe "added b"
+  merging b
+  warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ echo foobar > b
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg graft --continue
+
+  $ hg graft --continue
+  grafting 1:80e6d2c47cfe "added b"
+  grafting 2:8be98ac1a569 "added c"
+
+  $ hg log -GT "{rev}:{node|short} {desc}" -r '.^^::.'
+  @  11:30c1050a58b2 added c
+  |  (grafted from 8be98ac1a56990c2d9ca6861041b8390af7bd6f3)
+  o  10:ec7eda2313e2 added b
+  |  (grafted from 80e6d2c47cfe5b3185519568327a17a061c7efb6)
+  o  3:9e887f7a939c bar to b
+  |
+  ~
+
+  $ cd ..
+
+Testing the --stop flag of `hg graft` which stops the interrupted graft
+
+  $ hg init stopgraft
+  $ cd stopgraft
+  $ for ch in a b c d; do echo $ch > $ch; hg add $ch; hg ci -Aqm "added "$ch; done;
+
+  $ hg log -G
+  @  changeset:   3:9150fe93bec6
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added d
+  |
+  o  changeset:   2:155349b645be
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added c
+  |
+  o  changeset:   1:5f6d8a4bf34a
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     added b
+  |
+  o  changeset:   0:9092f1db7931
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     added a
+  
+  $ hg up '.^^'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+  $ echo foo > d
+  $ hg ci -Aqm "added foo to d"
+
+  $ hg graft --stop
+  abort: no interrupted graft found
+  [255]
+
+  $ hg graft -r 3
+  grafting 3:9150fe93bec6 "added d"
+  merging d
+  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ hg graft --stop --continue
+  abort: cannot use '--continue' and '--stop' together
+  [255]
+
+  $ hg graft --stop -U
+  abort: cannot specify any other flag with '--stop'
+  [255]
+  $ hg graft --stop --rev 4
+  abort: cannot specify any other flag with '--stop'
+  [255]
+  $ hg graft --stop --log
+  abort: cannot specify any other flag with '--stop'
+  [255]
+
+  $ hg graft --stop
+  stopped the interrupted graft
+  working directory is now at a0deacecd59d
+
+  $ hg diff
+
+  $ hg log -Gr '.'
+  @  changeset:   4:a0deacecd59d
+  |  tag:         tip
+  ~  parent:      1:5f6d8a4bf34a
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     added foo to d
+  
+  $ hg graft -r 2 -r 3
+  grafting 2:155349b645be "added c"
+  grafting 3:9150fe93bec6 "added d"
+  merging d
+  warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ hg graft --stop
+  stopped the interrupted graft
+  working directory is now at 75b447541a9e
+
+  $ hg diff
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  5:75b447541a9e added c
+  |
+  o  4:a0deacecd59d added foo to d
+  |
+  | o  3:9150fe93bec6 added d
+  | |
+  | o  2:155349b645be added c
+  |/
+  o  1:5f6d8a4bf34a added b
+  |
+  o  0:9092f1db7931 added a
+  
+  $ cd ..
+
+Testing the --abort flag for `hg graft` which aborts and rollback to state
+before the graft
+
+  $ hg init abortgraft
+  $ cd abortgraft
+  $ for ch in a b c d; do echo $ch > $ch; hg add $ch; hg ci -Aqm "added "$ch; done;
+
+  $ hg up '.^^'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+  $ echo x > x
+  $ hg ci -Aqm "added x"
+  $ hg up '.^'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo foo > c
+  $ hg ci -Aqm "added foo to c"
+
+  $ hg log -GT "{rev}:{node|short} {desc}"
+  @  5:36b793615f78 added foo to c
+  |
+  | o  4:863a25e1a9ea added x
+  |/
+  | o  3:9150fe93bec6 added d
+  | |
+  | o  2:155349b645be added c
+  |/
+  o  1:5f6d8a4bf34a added b
+  |
+  o  0:9092f1db7931 added a
+  
+  $ hg up 9150fe93bec6
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg graft --abort
+  abort: no interrupted graft to abort
+  [255]
+
+when stripping is required
+  $ hg graft -r 4 -r 5
+  grafting 4:863a25e1a9ea "added x"
+  grafting 5:36b793615f78 "added foo to c" (tip)
+  merging c
+  warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ hg graft --continue --abort
+  abort: cannot use '--continue' and '--abort' together
+  [255]
+
+  $ hg graft --abort --stop
+  abort: cannot use '--abort' and '--stop' together
+  [255]
+
+  $ hg graft --abort --currentuser
+  abort: cannot specify any other flag with '--abort'
+  [255]
+
+  $ hg graft --abort --edit
+  abort: cannot specify any other flag with '--abort'
+  [255]
+
+  $ hg graft --abort
+  graft aborted
+  working directory is now at 9150fe93bec6
+  $ hg log -GT "{rev}:{node|short} {desc}"
+  o  5:36b793615f78 added foo to c
+  |
+  | o  4:863a25e1a9ea added x
+  |/
+  | @  3:9150fe93bec6 added d
+  | |
+  | o  2:155349b645be added c
+  |/
+  o  1:5f6d8a4bf34a added b
+  |
+  o  0:9092f1db7931 added a
+  
+when stripping is not required
+  $ hg graft -r 5
+  grafting 5:36b793615f78 "added foo to c" (tip)
+  merging c
+  warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ hg graft --abort
+  graft aborted
+  working directory is now at 9150fe93bec6
+  $ hg log -GT "{rev}:{node|short} {desc}"
+  o  5:36b793615f78 added foo to c
+  |
+  | o  4:863a25e1a9ea added x
+  |/
+  | @  3:9150fe93bec6 added d
+  | |
+  | o  2:155349b645be added c
+  |/
+  o  1:5f6d8a4bf34a added b
+  |
+  o  0:9092f1db7931 added a
+  
+when some of the changesets became public
+
+  $ hg graft -r 4 -r 5
+  grafting 4:863a25e1a9ea "added x"
+  grafting 5:36b793615f78 "added foo to c" (tip)
+  merging c
+  warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ hg log -GT "{rev}:{node|short} {desc}"
+  @  6:6ec71c037d94 added x
+  |
+  | o  5:36b793615f78 added foo to c
+  | |
+  | | o  4:863a25e1a9ea added x
+  | |/
+  o |  3:9150fe93bec6 added d
+  | |
+  o |  2:155349b645be added c
+  |/
+  o  1:5f6d8a4bf34a added b
+  |
+  o  0:9092f1db7931 added a
+  
+  $ hg phase -r 6 --public
+
+  $ hg graft --abort
+  cannot clean up public changesets 6ec71c037d94
+  graft aborted
+  working directory is now at 6ec71c037d94
+
+when we created new changesets on top of existing one
+
+  $ hg up '.^^'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo y > y
+  $ hg ci -Aqm "added y"
+  $ echo z > z
+  $ hg ci -Aqm "added z"
+
+  $ hg up 3
+  1 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg log -GT "{rev}:{node|short} {desc}"
+  o  8:637f9e9bbfd4 added z
+  |
+  o  7:123221671fd4 added y
+  |
+  | o  6:6ec71c037d94 added x
+  | |
+  | | o  5:36b793615f78 added foo to c
+  | | |
+  | | | o  4:863a25e1a9ea added x
+  | | |/
+  | @ |  3:9150fe93bec6 added d
+  |/ /
+  o /  2:155349b645be added c
+  |/
+  o  1:5f6d8a4bf34a added b
+  |
+  o  0:9092f1db7931 added a
+  
+  $ hg graft -r 8 -r 7 -r 5
+  grafting 8:637f9e9bbfd4 "added z" (tip)
+  grafting 7:123221671fd4 "added y"
+  grafting 5:36b793615f78 "added foo to c"
+  merging c
+  warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ cd ..
+  $ hg init pullrepo
+  $ cd pullrepo
+  $ cat >> .hg/hgrc <<EOF
+  > [phases]
+  > publish=False
+  > EOF
+  $ hg pull ../abortgraft --config phases.publish=False
+  pulling from ../abortgraft
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 11 changesets with 9 changes to 8 files (+4 heads)
+  new changesets 9092f1db7931:6b98ff0062dd
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg up 9
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo w > w
+  $ hg ci -Aqm "added w" --config phases.publish=False
+
+  $ cd ../abortgraft
+  $ hg pull ../pullrepo
+  pulling from ../pullrepo
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 311dfc6cf3bf
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
+
+  $ hg graft --abort
+  new changesets detected on destination branch, can't strip
+  graft aborted
+  working directory is now at 6b98ff0062dd
+
+  $ cd ..
+
+============================
+Testing --no-commit option:|
+============================
+
+  $ hg init nocommit
+  $ cd nocommit
+  $ echo a > a
+  $ hg ci -qAma
+  $ echo b > b
+  $ hg ci -qAmb
+  $ hg up -q 0
+  $ echo c > c
+  $ hg ci -qAmc
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  2:d36c0562f908 c
+  |
+  | o  1:d2ae7f538514 b
+  |/
+  o  0:cb9a9f314b8b a
+  
+
+Check reporting when --no-commit used with non-applicable options:
+
+  $ hg graft 1 --no-commit -e
+  abort: cannot specify --no-commit and --edit together
+  [255]
+
+  $ hg graft 1 --no-commit --log
+  abort: cannot specify --no-commit and --log together
+  [255]
+
+  $ hg graft 1 --no-commit -D
+  abort: cannot specify --no-commit and --currentdate together
+  [255]
+
+Test --no-commit is working:
+  $ hg graft 1 --no-commit
+  grafting 1:d2ae7f538514 "b"
+
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  2:d36c0562f908 c
+  |
+  | o  1:d2ae7f538514 b
+  |/
+  o  0:cb9a9f314b8b a
+  
+
+  $ hg diff
+  diff -r d36c0562f908 b
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +b
+
+Prepare wrdir to check --no-commit is resepected after --continue:
+
+  $ hg up -qC
+  $ echo A>a
+  $ hg ci -qm "A in file a"
+  $ hg up -q 1
+  $ echo B>a
+  $ hg ci -qm "B in file a"
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  4:2aa9ad1006ff B in file a
+  |
+  | o  3:09e253b87e17 A in file a
+  | |
+  | o  2:d36c0562f908 c
+  | |
+  o |  1:d2ae7f538514 b
+  |/
+  o  0:cb9a9f314b8b a
+  
+
+  $ hg graft 3 --no-commit
+  grafting 3:09e253b87e17 "A in file a"
+  merging a
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+Resolve conflict:
+  $ echo A>a
+  $ hg resolve --mark
+  (no more unresolved files)
+  continue: hg graft --continue
+
+  $ hg graft --continue
+  grafting 3:09e253b87e17 "A in file a"
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  4:2aa9ad1006ff B in file a
+  |
+  | o  3:09e253b87e17 A in file a
+  | |
+  | o  2:d36c0562f908 c
+  | |
+  o |  1:d2ae7f538514 b
+  |/
+  o  0:cb9a9f314b8b a
+  
+  $ hg diff
+  diff -r 2aa9ad1006ff a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -B
+  +A
+
+  $ hg up -qC
+
+Check --no-commit is resepected when passed with --continue:
+
+  $ hg graft 3
+  grafting 3:09e253b87e17 "A in file a"
+  merging a
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+Resolve conflict:
+  $ echo A>a
+  $ hg resolve --mark
+  (no more unresolved files)
+  continue: hg graft --continue
+
+  $ hg graft --continue --no-commit
+  grafting 3:09e253b87e17 "A in file a"
+  $ hg diff
+  diff -r 2aa9ad1006ff a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -B
+  +A
+
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  4:2aa9ad1006ff B in file a
+  |
+  | o  3:09e253b87e17 A in file a
+  | |
+  | o  2:d36c0562f908 c
+  | |
+  o |  1:d2ae7f538514 b
+  |/
+  o  0:cb9a9f314b8b a
+  
+  $ hg up -qC
+
+Test --no-commit when graft multiple revisions:
+When there is conflict:
+  $ hg graft -r "2::3" --no-commit
+  grafting 2:d36c0562f908 "c"
+  grafting 3:09e253b87e17 "A in file a"
+  merging a
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  abort: unresolved conflicts, can't continue
+  (use 'hg resolve' and 'hg graft --continue')
+  [255]
+
+  $ echo A>a
+  $ hg resolve --mark
+  (no more unresolved files)
+  continue: hg graft --continue
+  $ hg graft --continue
+  grafting 3:09e253b87e17 "A in file a"
+  $ hg diff
+  diff -r 2aa9ad1006ff a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -B
+  +A
+  diff -r 2aa9ad1006ff c
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/c	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +c
+
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  @  4:2aa9ad1006ff B in file a
+  |
+  | o  3:09e253b87e17 A in file a
+  | |
+  | o  2:d36c0562f908 c
+  | |
+  o |  1:d2ae7f538514 b
+  |/
+  o  0:cb9a9f314b8b a
+  
+  $ hg up -qC
+
+When there is no conflict:
+  $ echo d>d
+  $ hg add d -q
+  $ hg ci -qmd
+  $ hg up 3 -q
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  o  5:baefa8927fc0 d
+  |
+  o  4:2aa9ad1006ff B in file a
+  |
+  | @  3:09e253b87e17 A in file a
+  | |
+  | o  2:d36c0562f908 c
+  | |
+  o |  1:d2ae7f538514 b
+  |/
+  o  0:cb9a9f314b8b a
+  
+
+  $ hg graft -r 1 -r 5 --no-commit
+  grafting 1:d2ae7f538514 "b"
+  grafting 5:baefa8927fc0 "d" (tip)
+  $ hg diff
+  diff -r 09e253b87e17 b
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +b
+  diff -r 09e253b87e17 d
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/d	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +d
+  $ hg log -GT "{rev}:{node|short} {desc}\n"
+  o  5:baefa8927fc0 d
+  |
+  o  4:2aa9ad1006ff B in file a
+  |
+  | @  3:09e253b87e17 A in file a
+  | |
+  | o  2:d36c0562f908 c
+  | |
+  o |  1:d2ae7f538514 b
+  |/
+  o  0:cb9a9f314b8b a
+  
+  $ cd ..
--- a/tests/test-grep.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-grep.t	Thu Jul 19 13:55:54 2018 -0400
@@ -23,11 +23,11 @@
 
 simple
 
-  $ hg grep '.*'
+  $ hg grep -r tip:0 '.*'
   port:4:export
   port:4:vaportight
   port:4:import/export
-  $ hg grep port port
+  $ hg grep -r tip:0 port port
   port:4:export
   port:4:vaportight
   port:4:import/export
@@ -35,27 +35,32 @@
 simple with color
 
   $ hg --config extensions.color= grep --config color.mode=ansi \
-  >     --color=always port port
+  >     --color=always port port -r tip:0
   \x1b[0;35mport\x1b[0m\x1b[0;36m:\x1b[0m\x1b[0;32m4\x1b[0m\x1b[0;36m:\x1b[0mex\x1b[0;31;1mport\x1b[0m (esc)
   \x1b[0;35mport\x1b[0m\x1b[0;36m:\x1b[0m\x1b[0;32m4\x1b[0m\x1b[0;36m:\x1b[0mva\x1b[0;31;1mport\x1b[0might (esc)
   \x1b[0;35mport\x1b[0m\x1b[0;36m:\x1b[0m\x1b[0;32m4\x1b[0m\x1b[0;36m:\x1b[0mim\x1b[0;31;1mport\x1b[0m/ex\x1b[0;31;1mport\x1b[0m (esc)
 
 simple templated
 
-  $ hg grep port \
+  $ hg grep port -r tip:0 \
   > -T '{file}:{rev}:{node|short}:{texts % "{if(matched, text|upper, text)}"}\n'
   port:4:914fa752cdea:exPORT
   port:4:914fa752cdea:vaPORTight
   port:4:914fa752cdea:imPORT/exPORT
 
-  $ hg grep port -T '{file}:{rev}:{texts}\n'
+  $ hg grep port -r tip:0 -T '{file}:{rev}:{texts}\n'
   port:4:export
   port:4:vaportight
   port:4:import/export
 
+  $ hg grep port -r tip:0 -T '{file}:{tags}:{texts}\n'
+  port:tip:export
+  port:tip:vaportight
+  port:tip:import/export
+
 simple JSON (no "change" field)
 
-  $ hg grep -Tjson port
+  $ hg grep -r tip:0 -Tjson port
   [
    {
     "date": [4, 0],
@@ -88,7 +93,7 @@
 
 simple JSON without matching lines
 
-  $ hg grep -Tjson -l port
+  $ hg grep -r tip:0 -Tjson -l port
   [
    {
     "date": [4, 0],
@@ -211,9 +216,9 @@
 
 other
 
-  $ hg grep -l port port
+  $ hg grep -r tip:0 -l port port
   port:4
-  $ hg grep import port
+  $ hg grep -r tip:0 import port
   port:4:import/export
 
   $ hg cp port port2
@@ -221,7 +226,7 @@
 
 follow
 
-  $ hg grep --traceback -f 'import\n\Z' port2
+  $ hg grep -r tip:0 --traceback -f 'import\n\Z' port2
   port:0:import
   
   $ echo deport >> port2
@@ -239,8 +244,8 @@
   port:0:1:+:spam:import
 
   $ hg up -q null
-  $ hg grep -f port
-  [1]
+  $ hg grep -r 'reverse(:.)' -f port
+  port:0:import
 
 Test wdir
 (at least, this shouldn't crash)
@@ -250,15 +255,18 @@
   $ hg stat
   M port2
   $ hg grep -r 'wdir()' port
-  abort: working directory revision cannot be specified
-  [255]
+  port2:2147483647:export
+  port2:2147483647:vaportight
+  port2:2147483647:import/export
+  port2:2147483647:deport
+  port2:2147483647:wport
 
   $ cd ..
   $ hg init t2
   $ cd t2
-  $ hg grep foobar foo
+  $ hg grep -r tip:0 foobar foo
   [1]
-  $ hg grep foobar
+  $ hg grep -r tip:0 foobar
   [1]
   $ echo blue >> color
   $ echo black >> color
@@ -271,16 +279,21 @@
   $ echo orange >> color
   $ echo blue >> color
   $ hg ci -m 3
-  $ hg grep orange
+  $ hg grep -r tip:0 orange
   color:3:orange
   $ hg grep --all orange
   color:3:+:orange
   color:2:-:orange
   color:1:+:orange
 
+  $ hg grep --diff orange
+  color:3:+:orange
+  color:2:-:orange
+  color:1:+:orange
+
 test substring match: '^' should only match at the beginning
 
-  $ hg grep '^.' --config extensions.color= --color debug
+  $ hg grep -r tip:0 '^.' --config extensions.color= --color debug
   [grep.filename|color][grep.sep|:][grep.rev|3][grep.sep|:][grep.match|b]lack
   [grep.filename|color][grep.sep|:][grep.rev|3][grep.sep|:][grep.match|o]range
   [grep.filename|color][grep.sep|:][grep.rev|3][grep.sep|:][grep.match|b]lue
@@ -290,7 +303,7 @@
   $ $PYTHON -c 'fp = open("noeol", "wb"); fp.write(b"no infinite loop"); fp.close();'
   $ hg ci -Amnoeol
   adding noeol
-  $ hg grep loop
+  $ hg grep -r tip:0 loop
   noeol:4:no infinite loop
 
   $ cd ..
@@ -307,7 +320,7 @@
   adding color
   $ hg rename color colour
   $ hg ci -Am rename
-  $ hg grep octarine
+  $ hg grep -r tip:0 octarine
   colour:1:octarine
   color:0:octarine
 
@@ -346,6 +359,10 @@
   color:3:-:red
   color:1:+:red
 
+  $ hg grep --diff red
+  color:3:-:red
+  color:1:+:red
+
 Issue3885: test that changing revision order does not alter the
 revisions printed, just their order.
 
@@ -357,6 +374,14 @@
   color:3:-:red
   color:1:+:red
 
+  $ hg grep --diff red -r "all()"
+  color:1:+:red
+  color:3:-:red
+
+  $ hg grep --diff red -r "reverse(all())"
+  color:3:-:red
+  color:1:+:red
+
   $ cd ..
 
   $ hg init a
@@ -367,4 +392,102 @@
   $ hg grep "MaCam" --all
   binfile.bin:0:+: Binary file matches
 
+  $ hg grep "MaCam" --diff
+  binfile.bin:0:+: Binary file matches
+
   $ cd ..
+
+Test for showing working of allfiles flag
+
+  $ hg init sng
+  $ cd sng
+  $ echo "unmod" >> um
+  $ hg ci -A -m "adds unmod to um"
+  adding um
+  $ echo "something else" >> new
+  $ hg ci -A -m "second commit"
+  adding new
+  $ hg grep -r "." "unmod"
+  [1]
+  $ hg grep -r "." "unmod" --all-files
+  um:1:unmod
+
+With --all-files, the working directory is searched by default
+
+  $ echo modified >> new
+  $ hg grep --all-files mod
+  new:modified
+  um:unmod
+
+ which can be overridden by -rREV
+
+  $ hg grep --all-files -r. mod
+  um:1:unmod
+
+commands.all-files can be negated by --no-all-files
+
+  $ hg grep --config commands.grep.all-files=True mod
+  new:modified
+  um:unmod
+  $ hg grep --config commands.grep.all-files=True --no-all-files mod
+  um:0:unmod
+
+--diff --all-files makes no sense since --diff is the option to grep history
+
+  $ hg grep --diff --all-files um
+  abort: --diff and --all-files are mutually exclusive
+  [255]
+
+but --diff should precede the commands.grep.all-files option
+
+  $ hg grep --config commands.grep.all-files=True --diff mod
+  um:0:+:unmod
+
+  $ cd ..
+
+Fix_Wdir(): test that passing wdir() t -r flag does greps on the
+files modified in the working directory
+
+  $ cd a
+  $ echo "abracadara" >> a
+  $ hg add a
+  $ hg grep -r "wdir()" "abra"
+  a:2147483647:abracadara
+
+  $ cd ..
+
+Change Default of grep by ui.tweakdefaults, that is, the files not in current
+working directory should not be grepp-ed on
+
+  $ hg init ab
+  $ cd ab
+  $ cat <<'EOF' >> .hg/hgrc
+  > [ui]
+  > tweakdefaults = True
+  > EOF
+  $ echo "some text">>file1
+  $ hg add file1
+  $ hg commit -m "adds file1"
+  $ hg mv file1 file2
+
+wdir revision is hidden by default:
+
+  $ hg grep "some"
+  file2:some text
+
+but it should be available in template dict:
+
+  $ hg grep "some" -Tjson
+  [
+   {
+    "date": [0, 0],
+    "file": "file2",
+    "line_number": 1,
+    "node": "ffffffffffffffffffffffffffffffffffffffff",
+    "rev": 2147483647,
+    "texts": [{"matched": true, "text": "some"}, {"matched": false, "text": " text"}],
+    "user": "test"
+   }
+  ]
+
+  $ cd ..
--- a/tests/test-hardlinks.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hardlinks.t	Thu Jul 19 13:55:54 2018 -0400
@@ -122,7 +122,7 @@
   $ cd r3/d1
   >>> f = open('data1', 'wb')
   >>> for x in range(10000):
-  ...     f.write("%s\n" % str(x))
+  ...     f.write(b"%d\n" % x) and None
   >>> f.close()
   $ for j in 0 1 2 3 4 5 6 7 8 9; do
   >   cat data1 >> f2
--- a/tests/test-help.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-help.t	Thu Jul 19 13:55:54 2018 -0400
@@ -110,6 +110,7 @@
    color         Colorizing Outputs
    config        Configuration Files
    dates         Date Formats
+   deprecated    Deprecated Features
    diffs         Diff Formats
    environment   Environment Variables
    extensions    Using Additional Features
@@ -189,6 +190,7 @@
    color         Colorizing Outputs
    config        Configuration Files
    dates         Date Formats
+   deprecated    Deprecated Features
    diffs         Diff Formats
    environment   Environment Variables
    extensions    Using Additional Features
@@ -889,6 +891,7 @@
    color         Colorizing Outputs
    config        Configuration Files
    dates         Date Formats
+   deprecated    Deprecated Features
    diffs         Diff Formats
    environment   Environment Variables
    extensions    Using Additional Features
@@ -2032,6 +2035,13 @@
   Date Formats
   </td></tr>
   <tr><td>
+  <a href="/help/deprecated">
+  deprecated
+  </a>
+  </td><td>
+  Deprecated Features
+  </td></tr>
+  <tr><td>
   <a href="/help/diffs">
   diffs
   </a>
--- a/tests/test-hgrc.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgrc.t	Thu Jul 19 13:55:54 2018 -0400
@@ -199,10 +199,10 @@
   $ cat > plain.py <<EOF
   > from mercurial import commands, extensions
   > def _config(orig, ui, repo, *values, **opts):
-  >     ui.write('plain: %r\n' % ui.plain())
+  >     ui.write(b'plain: %r\n' % ui.plain())
   >     return orig(ui, repo, *values, **opts)
   > def uisetup(ui):
-  >     extensions.wrapcommand(commands.table, 'config', _config)
+  >     extensions.wrapcommand(commands.table, b'config', _config)
   > EOF
   $ echo "[extensions]" >> $HGRC
   $ echo "plain=./plain.py" >> $HGRC
--- a/tests/test-hgweb-auth.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgweb-auth.py	Thu Jul 19 13:55:54 2018 -0400
@@ -3,10 +3,14 @@
 from mercurial import demandimport; demandimport.enable()
 from mercurial import (
     error,
+    pycompat,
     ui as uimod,
     url,
     util,
 )
+from mercurial.utils import (
+    stringutil,
+)
 
 urlerr = util.urlerr
 urlreq = util.urlreq
@@ -23,12 +27,8 @@
         ui.setconfig('auth', name, value)
     return ui
 
-def dumpdict(dict):
-    return '{' + ', '.join(['%s: %s' % (k, dict[k])
-                            for k in sorted(dict)]) + '}'
-
 def test(auth, urls=None):
-    print('CFG:', dumpdict(auth))
+    print('CFG:', pycompat.sysstr(stringutil.pprint(auth, bprefix=True)))
     prefixes = set()
     for k in auth:
         prefixes.add(k.split('.', 1)[0])
--- a/tests/test-hgweb-auth.py.out	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgweb-auth.py.out	Thu Jul 19 13:55:54 2018 -0400
@@ -1,7 +1,7 @@
 
 *** Test in-uri schemes
 
-CFG: {x.prefix: http://example.org}
+CFG: {b'x.prefix': b'http://example.org'}
 URI: http://example.org/foo
      ('x', 'x')
 URI: http://example.org/foo/bar
@@ -18,7 +18,7 @@
      abort
 URI: https://y@example.org/bar
      abort
-CFG: {x.prefix: https://example.org}
+CFG: {b'x.prefix': b'https://example.org'}
 URI: http://example.org/foo
      abort
 URI: http://example.org/foo/bar
@@ -35,7 +35,7 @@
      ('x', 'x')
 URI: https://y@example.org/bar
      abort
-CFG: {x.prefix: http://example.org, x.schemes: https}
+CFG: {b'x.prefix': b'http://example.org', b'x.schemes': b'https'}
 URI: http://example.org/foo
      ('x', 'x')
 URI: http://example.org/foo/bar
@@ -52,7 +52,7 @@
      abort
 URI: https://y@example.org/bar
      abort
-CFG: {x.prefix: https://example.org, x.schemes: http}
+CFG: {b'x.prefix': b'https://example.org', b'x.schemes': b'http'}
 URI: http://example.org/foo
      abort
 URI: http://example.org/foo/bar
@@ -72,7 +72,7 @@
 
 *** Test separately configured schemes
 
-CFG: {x.prefix: example.org, x.schemes: http}
+CFG: {b'x.prefix': b'example.org', b'x.schemes': b'http'}
 URI: http://example.org/foo
      ('x', 'x')
 URI: http://example.org/foo/bar
@@ -89,7 +89,7 @@
      abort
 URI: https://y@example.org/bar
      abort
-CFG: {x.prefix: example.org, x.schemes: https}
+CFG: {b'x.prefix': b'example.org', b'x.schemes': b'https'}
 URI: http://example.org/foo
      abort
 URI: http://example.org/foo/bar
@@ -106,7 +106,7 @@
      ('x', 'x')
 URI: https://y@example.org/bar
      abort
-CFG: {x.prefix: example.org, x.schemes: http https}
+CFG: {b'x.prefix': b'example.org', b'x.schemes': b'http https'}
 URI: http://example.org/foo
      ('x', 'x')
 URI: http://example.org/foo/bar
@@ -126,7 +126,7 @@
 
 *** Test prefix matching
 
-CFG: {x.prefix: http://example.org/foo, y.prefix: http://example.org/bar}
+CFG: {b'x.prefix': b'http://example.org/foo', b'y.prefix': b'http://example.org/bar'}
 URI: http://example.org/foo
      ('x', 'x')
 URI: http://example.org/foo/bar
@@ -143,7 +143,7 @@
      abort
 URI: https://y@example.org/bar
      abort
-CFG: {x.prefix: http://example.org/foo, y.prefix: http://example.org/foo/bar}
+CFG: {b'x.prefix': b'http://example.org/foo', b'y.prefix': b'http://example.org/foo/bar'}
 URI: http://example.org/foo
      ('x', 'x')
 URI: http://example.org/foo/bar
@@ -160,7 +160,7 @@
      abort
 URI: https://y@example.org/bar
      abort
-CFG: {x.prefix: *, y.prefix: https://example.org/bar}
+CFG: {b'x.prefix': b'*', b'y.prefix': b'https://example.org/bar'}
 URI: http://example.org/foo
      abort
 URI: http://example.org/foo/bar
@@ -180,13 +180,13 @@
 
 *** Test user matching
 
-CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None}
+CFG: {b'x.password': b'xpassword', b'x.prefix': b'http://example.org/foo', b'x.username': None}
 URI: http://y@example.org/foo
      ('y', 'xpassword')
-CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y}
+CFG: {b'x.password': b'xpassword', b'x.prefix': b'http://example.org/foo', b'x.username': None, b'y.password': b'ypassword', b'y.prefix': b'http://example.org/foo', b'y.username': b'y'}
 URI: http://y@example.org/foo
      ('y', 'ypassword')
-CFG: {x.password: xpassword, x.prefix: http://example.org/foo/bar, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y}
+CFG: {b'x.password': b'xpassword', b'x.prefix': b'http://example.org/foo/bar', b'x.username': None, b'y.password': b'ypassword', b'y.prefix': b'http://example.org/foo', b'y.username': b'y'}
 URI: http://y@example.org/foo/bar
      ('y', 'xpassword')
 
--- a/tests/test-hgweb-descend-empties.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgweb-descend-empties.t	Thu Jul 19 13:55:54 2018 -0400
@@ -93,11 +93,7 @@
   </tr>
   </thead>
   <tbody class="stripes2">
-  <tr class="fileline">
-    <td class="name"><a href="/file/tip/">[up]</a></td>
-    <td class="size"></td>
-    <td class="permissions">drwxr-xr-x</td>
-  </tr>
+  
   
   <tr class="fileline">
   <td class="name">
@@ -213,11 +209,7 @@
   </tr>
   </thead>
   <tbody class="stripes2">
-  <tr class="fileline">
-    <td class="name"><a href="/file/tip/?style=coal">[up]</a></td>
-    <td class="size"></td>
-    <td class="permissions">drwxr-xr-x</td>
-  </tr>
+  
   
   <tr class="fileline">
   <td class="name">
@@ -320,13 +312,7 @@
       <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">
-              <td>drwxr-xr-x</td>
-              <td></td>
-              <td></td>
-              <td><a href="/file/tip/?style=monoblue">[up]</a></td>
-              <td class="link">&nbsp;</td>
-          </tr>
+          
           
   <tr class="parity1">
   <td>drwxr-xr-x</td>
@@ -433,13 +419,7 @@
   
   <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>
-  <td style="font-family:monospace"></td>
-  <td style="font-family:monospace"></td>
-  <td><a href="/file/tip/?style=gitweb">[up]</a></td>
-  <td class="link">&nbsp;</td>
-  </tr>
+  
   
   <tr class="parity1">
   <td style="font-family:monospace">drwxr-xr-x</td>
@@ -524,40 +504,41 @@
   <h2><a href="/">Mercurial</a>  / files for changeset <a href="/rev/c9f45f7a1659">c9f45f7a1659</a>: /</h2>
   
   <table cellpadding="0" cellspacing="0">
-  <tr class="parity0">
-    <td><tt>drwxr-xr-x</tt>&nbsp;
-    <td>&nbsp;
-    <td>&nbsp;
-    <td><a href="/file/tip/?style=spartan">[up]</a>
-  </tr>
+  
   
   <tr class="parity1">
-  <td><tt>drwxr-xr-x</tt>&nbsp;
-  <td>&nbsp;
-  <td>&nbsp;
+  <td><tt>drwxr-xr-x</tt>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
   <td>
   <a href="/file/tip/a1?style=spartan">a1/</a>
   <a href="/file/tip/a1/a2/a3/a4?style=spartan">
   a2/a3/a4
   </a>
+  </td>
+  </tr>
   <tr class="parity0">
-  <td><tt>drwxr-xr-x</tt>&nbsp;
-  <td>&nbsp;
-  <td>&nbsp;
+  <td><tt>drwxr-xr-x</tt>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
   <td>
   <a href="/file/tip/b1?style=spartan">b1/</a>
   <a href="/file/tip/b1/b2/b3?style=spartan">
   b2/b3
   </a>
+  </td>
+  </tr>
   <tr class="parity1">
-  <td><tt>drwxr-xr-x</tt>&nbsp;
-  <td>&nbsp;
-  <td>&nbsp;
+  <td><tt>drwxr-xr-x</tt>&nbsp;</td>
+  <td>&nbsp;</td>
+  <td>&nbsp;</td>
   <td>
   <a href="/file/tip/d1?style=spartan">d1/</a>
   <a href="/file/tip/d1/d2?style=spartan">
   d2
   </a>
+  </td>
+  </tr>
   
   </table>
   
--- a/tests/test-hgweb-empty.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgweb-empty.t	Thu Jul 19 13:55:54 2018 -0400
@@ -263,6 +263,9 @@
   <li><a href="/file/tip">browse</a></li>
   </ul>
   <ul>
+  
+  </ul>
+  <ul>
    <li><a href="/help">help</a></li>
   </ul>
   <div class="atom-logo">
@@ -396,11 +399,7 @@
   </tr>
   </thead>
   <tbody class="stripes2">
-  <tr class="fileline">
-    <td class="name"><a href="/file/tip/">[up]</a></td>
-    <td class="size"></td>
-    <td class="permissions">drwxr-xr-x</td>
-  </tr>
+  
   
   
   </tbody>
--- a/tests/test-hgweb-json.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgweb-json.t	Thu Jul 19 13:55:54 2018 -0400
@@ -101,7 +101,7 @@
   
 
   $ echo '[web]' >> .hg/hgrc
-  $ echo 'allow_archive = bz2' >> .hg/hgrc
+  $ echo 'allow-archive = bz2' >> .hg/hgrc
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E error.log
   $ cat hg.pid >> $DAEMON_PIDS
 
@@ -1914,6 +1914,10 @@
         "topic": "dates"
       },
       {
+        "summary": "Deprecated Features",
+        "topic": "deprecated"
+      },
+      {
         "summary": "Diff Formats",
         "topic": "diffs"
       },
--- a/tests/test-hgweb-symrev.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgweb-symrev.t	Thu Jul 19 13:55:54 2018 -0400
@@ -30,7 +30,7 @@
   |
   o  0:43c799df6e75
   
-  $ hg serve --config web.allow_archive=zip -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
+  $ hg serve --config web.allow-archive=zip -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
 
   $ REVLINKS='href=[^>]+(rev=|/)(43c799df6e75|0|a7c1559b7bba|1|xyzzy|9d8c40cba617|2|tip|default)'
@@ -56,6 +56,7 @@
   <li><a href="/shortlog/tip?style=paper">log</a></li>
   <li><a href="/rev/tip?style=paper">changeset</a></li>
   <li><a href="/file/tip?style=paper">browse</a></li>
+  <a href="/archive/tip.zip">zip</a>
   <a href="/graph/tip?revcount=30&style=paper">less</a>
   <a href="/graph/tip?revcount=120&style=paper">more</a>
   | rev 2: <a href="/graph/43c799df6e75?style=paper">(0)</a> <a href="/graph/tip?style=paper">tip</a> 
@@ -72,7 +73,6 @@
   <li><a href="/rev/tip?style=paper">changeset</a></li>
   <a href="/archive/tip.zip">zip</a>
    directory / @ 2:<a href="/rev/9d8c40cba617?style=paper">9d8c40cba617</a>
-    <td class="name"><a href="/file/tip/?style=paper">[up]</a></td>
   <a href="/file/tip/dir?style=paper">
   <a href="/file/tip/dir/?style=paper">
   <a href="/file/tip/foo?style=paper">
@@ -123,6 +123,7 @@
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
   <li><a href="/file/xyzzy?style=paper">browse</a></li>
+  <a href="/archive/xyzzy.zip">zip</a>
   <a href="/graph/xyzzy?revcount=30&style=paper">less</a>
   <a href="/graph/xyzzy?revcount=120&style=paper">more</a>
   | rev 1: <a href="/graph/43c799df6e75?style=paper">(0)</a> <a href="/graph/tip?style=paper">tip</a> 
@@ -138,7 +139,6 @@
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
   <a href="/archive/xyzzy.zip">zip</a>
    directory / @ 1:<a href="/rev/a7c1559b7bba?style=paper">a7c1559b7bba</a>
-    <td class="name"><a href="/file/xyzzy/?style=paper">[up]</a></td>
   <a href="/file/xyzzy/dir?style=paper">
   <a href="/file/xyzzy/dir/?style=paper">
   <a href="/file/xyzzy/foo?style=paper">
@@ -256,6 +256,7 @@
   <li><a href="/shortlog/tip?style=coal">log</a></li>
   <li><a href="/rev/tip?style=coal">changeset</a></li>
   <li><a href="/file/tip?style=coal">browse</a></li>
+  <a href="/archive/tip.zip">zip</a>
   <a href="/graph/tip?revcount=30&style=coal">less</a>
   <a href="/graph/tip?revcount=120&style=coal">more</a>
   | rev 2: <a href="/graph/43c799df6e75?style=coal">(0)</a> <a href="/graph/tip?style=coal">tip</a> 
@@ -272,7 +273,6 @@
   <li><a href="/rev/tip?style=coal">changeset</a></li>
   <a href="/archive/tip.zip">zip</a>
    directory / @ 2:<a href="/rev/9d8c40cba617?style=coal">9d8c40cba617</a>
-    <td class="name"><a href="/file/tip/?style=coal">[up]</a></td>
   <a href="/file/tip/dir?style=coal">
   <a href="/file/tip/dir/?style=coal">
   <a href="/file/tip/foo?style=coal">
@@ -323,6 +323,7 @@
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
   <li><a href="/file/xyzzy?style=coal">browse</a></li>
+  <a href="/archive/xyzzy.zip">zip</a>
   <a href="/graph/xyzzy?revcount=30&style=coal">less</a>
   <a href="/graph/xyzzy?revcount=120&style=coal">more</a>
   | rev 1: <a href="/graph/43c799df6e75?style=coal">(0)</a> <a href="/graph/tip?style=coal">tip</a> 
@@ -338,7 +339,6 @@
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
   <a href="/archive/xyzzy.zip">zip</a>
    directory / @ 1:<a href="/rev/a7c1559b7bba?style=coal">a7c1559b7bba</a>
-    <td class="name"><a href="/file/xyzzy/?style=coal">[up]</a></td>
   <a href="/file/xyzzy/dir?style=coal">
   <a href="/file/xyzzy/dir/?style=coal">
   <a href="/file/xyzzy/foo?style=coal">
@@ -489,7 +489,7 @@
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=gitweb' | egrep $REVLINKS
   <a href="/shortlog/tip?style=gitweb">shortlog</a> |
   <a href="/log/tip?style=gitweb">changelog</a> |
-  <a href="/file/tip?style=gitweb">files</a> |
+  <a href="/file/tip?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
   <a href="/graph/tip?revcount=30&style=gitweb">less</a>
   <a href="/graph/tip?revcount=120&style=gitweb">more</a>
   | <a href="/graph/43c799df6e75?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> 
@@ -520,7 +520,6 @@
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=gitweb' | egrep $REVLINKS
   <a href="/rev/tip?style=gitweb">changeset</a>  | <a href="/archive/tip.zip">zip</a>  |
-  <td><a href="/file/tip/?style=gitweb">[up]</a></td>
   <a href="/file/tip/dir?style=gitweb">dir</a>
   <a href="/file/tip/dir/?style=gitweb"></a>
   <a href="/file/tip/dir?style=gitweb">files</a>
@@ -582,7 +581,7 @@
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=gitweb' | egrep $REVLINKS
   <a href="/shortlog/xyzzy?style=gitweb">shortlog</a> |
   <a href="/log/xyzzy?style=gitweb">changelog</a> |
-  <a href="/file/xyzzy?style=gitweb">files</a> |
+  <a href="/file/xyzzy?style=gitweb">files</a> | <a href="/archive/xyzzy.zip">zip</a>  |
   <a href="/graph/xyzzy?revcount=30&style=gitweb">less</a>
   <a href="/graph/xyzzy?revcount=120&style=gitweb">more</a>
   | <a href="/graph/43c799df6e75?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> 
@@ -594,7 +593,6 @@
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=gitweb' | egrep $REVLINKS
   <a href="/rev/xyzzy?style=gitweb">changeset</a>  | <a href="/archive/xyzzy.zip">zip</a>  |
-  <td><a href="/file/xyzzy/?style=gitweb">[up]</a></td>
   <a href="/file/xyzzy/dir?style=gitweb">dir</a>
   <a href="/file/xyzzy/dir/?style=gitweb"></a>
   <a href="/file/xyzzy/dir?style=gitweb">files</a>
@@ -731,6 +729,7 @@
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=monoblue' | egrep $REVLINKS
               <li><a href="/file/tip?style=monoblue">files</a></li>
+              <li><a href="/archive/tip.zip">zip</a></li>
               <a href="/rev/9d8c40cba617?style=monoblue">third</a>
               <a href="/rev/a7c1559b7bba?style=monoblue">second</a>
               <a href="/rev/43c799df6e75?style=monoblue">first</a>
@@ -760,7 +759,6 @@
               <li><a href="/graph/tip?style=monoblue">graph</a></li>
           <li><a href="/rev/tip?style=monoblue">changeset</a></li>
           <li><a href="/archive/tip.zip">zip</a></li>
-              <td><a href="/file/tip/?style=monoblue">[up]</a></td>
   <a href="/file/tip/dir?style=monoblue">dir</a>
   <a href="/file/tip/dir/?style=monoblue"></a>
   <td><a href="/file/tip/dir?style=monoblue">files</a></td>
@@ -813,6 +811,7 @@
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=monoblue' | egrep $REVLINKS
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
+              <li><a href="/archive/xyzzy.zip">zip</a></li>
               <a href="/rev/a7c1559b7bba?style=monoblue">second</a>
               <a href="/rev/43c799df6e75?style=monoblue">first</a>
           <a href="/graph/xyzzy?revcount=30&style=monoblue">less</a>
@@ -823,7 +822,6 @@
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
           <li><a href="/rev/xyzzy?style=monoblue">changeset</a></li>
           <li><a href="/archive/xyzzy.zip">zip</a></li>
-              <td><a href="/file/xyzzy/?style=monoblue">[up]</a></td>
   <a href="/file/xyzzy/dir?style=monoblue">dir</a>
   <a href="/file/xyzzy/dir/?style=monoblue"></a>
   <td><a href="/file/xyzzy/dir?style=monoblue">files</a></td>
@@ -964,10 +962,9 @@
   <a href="/rev/tip?style=spartan">changeset</a>
   <a href="/archive/tip.zip">zip</a> 
   <h2><a href="/">Mercurial</a>  / files for changeset <a href="/rev/9d8c40cba617">9d8c40cba617</a>: /</h2>
-    <td><a href="/file/tip/?style=spartan">[up]</a>
   <a href="/file/tip/dir?style=spartan">dir/</a>
   <a href="/file/tip/dir/?style=spartan">
-  <td><a href="/file/tip/foo?style=spartan">foo</a>
+  <td><a href="/file/tip/foo?style=spartan">foo</a></td>
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=spartan&rev=all()' | egrep $REVLINKS
   <a href="/archive/tip.zip">zip</a> 
@@ -1037,10 +1034,9 @@
   <a href="/rev/xyzzy?style=spartan">changeset</a>
   <a href="/archive/xyzzy.zip">zip</a> 
   <h2><a href="/">Mercurial</a>  / files for changeset <a href="/rev/a7c1559b7bba">a7c1559b7bba</a>: /</h2>
-    <td><a href="/file/xyzzy/?style=spartan">[up]</a>
   <a href="/file/xyzzy/dir?style=spartan">dir/</a>
   <a href="/file/xyzzy/dir/?style=spartan">
-  <td><a href="/file/xyzzy/foo?style=spartan">foo</a>
+  <td><a href="/file/xyzzy/foo?style=spartan">foo</a></td>
 
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=spartan' | egrep $REVLINKS
   <a href="/log/xyzzy?style=spartan">changelog</a>
--- a/tests/test-hgweb.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgweb.t	Thu Jul 19 13:55:54 2018 -0400
@@ -287,11 +287,7 @@
   </tr>
   </thead>
   <tbody class="stripes2">
-  <tr class="fileline">
-    <td class="name"><a href="/file/tip/">[up]</a></td>
-    <td class="size"></td>
-    <td class="permissions">drwxr-xr-x</td>
-  </tr>
+  
   
   <tr class="fileline">
   <td class="name">
@@ -340,7 +336,7 @@
 
   $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
   200 Script output follows
-  content-length: 9059
+  content-length: 9074
   content-type: text/css
   
   body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
@@ -419,6 +415,7 @@
     background: #ffc;
     border: 1px solid yellow;
     border-radius: 5px;
+    z-index: 15;
   }
   
   #searchform:hover div#hint { display: block; }
@@ -802,6 +799,29 @@
   200 Script output follows
    changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
 
+no '[up]' entry in file view when in root directory
+
+  $ get-with-headers.py localhost:$HGPORT 'file/tip?style=paper' | grep -F '[up]'
+  [1]
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=paper' | grep -F '[up]'
+  <a href="/file/tip/?style=paper">[up]</a>
+  $ get-with-headers.py localhost:$HGPORT 'file/tip?style=coal' | grep -F '[up]'
+  [1]
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=coal' | grep -F '[up]'
+  <a href="/file/tip/?style=coal">[up]</a>
+  $ get-with-headers.py localhost:$HGPORT 'file/tip?style=gitweb' | grep -F '[up]'
+  [1]
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=gitweb' | grep -F '[up]'
+  <a href="/file/tip/?style=gitweb">[up]</a>
+  $ get-with-headers.py localhost:$HGPORT 'file/tip?style=monoblue' | grep -F '[up]'
+  [1]
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=monoblue' | grep -F '[up]'
+  <a href="/file/tip/?style=monoblue">[up]</a>
+  $ get-with-headers.py localhost:$HGPORT 'file/tip?style=spartan' | grep -F '[up]'
+  [1]
+  $ get-with-headers.py localhost:$HGPORT 'file/tip/da?style=spartan' | grep -F '[up]'
+  <a href="/file/tip/?style=spartan">[up]</a>
+
 no style can be loaded from directories other than the specified paths
 
   $ mkdir -p x/templates/fallback
--- a/tests/test-hgwebdir-paths.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hgwebdir-paths.py	Thu Jul 19 13:55:54 2018 -0400
@@ -10,30 +10,30 @@
 )
 hgwebdir = hgwebdir_mod.hgwebdir
 
-os.mkdir('webdir')
-os.chdir('webdir')
+os.mkdir(b'webdir')
+os.chdir(b'webdir')
 
-webdir = os.path.realpath('.')
+webdir = os.path.realpath(b'.')
 
 u = uimod.ui.load()
-hg.repository(u, 'a', create=1)
-hg.repository(u, 'b', create=1)
-os.chdir('b')
-hg.repository(u, 'd', create=1)
-os.chdir('..')
-hg.repository(u, 'c', create=1)
-os.chdir('..')
+hg.repository(u, b'a', create=1)
+hg.repository(u, b'b', create=1)
+os.chdir(b'b')
+hg.repository(u, b'd', create=1)
+os.chdir(b'..')
+hg.repository(u, b'c', create=1)
+os.chdir(b'..')
 
-paths = {'t/a/': '%s/a' % webdir,
-         'b': '%s/b' % webdir,
-         'coll': '%s/*' % webdir,
-         'rcoll': '%s/**' % webdir}
+paths = {b't/a/': b'%s/a' % webdir,
+         b'b': b'%s/b' % webdir,
+         b'coll': b'%s/*' % webdir,
+         b'rcoll': b'%s/**' % webdir}
 
-config = os.path.join(webdir, 'hgwebdir.conf')
-configfile = open(config, 'w')
-configfile.write('[paths]\n')
+config = os.path.join(webdir, b'hgwebdir.conf')
+configfile = open(config, 'wb')
+configfile.write(b'[paths]\n')
 for k, v in paths.items():
-    configfile.write('%s = %s\n' % (k, v))
+    configfile.write(b'%s = %s\n' % (k, v))
 configfile.close()
 
 confwd = hgwebdir(config)
--- a/tests/test-histedit-fold.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-histedit-fold.t	Thu Jul 19 13:55:54 2018 -0400
@@ -306,8 +306,8 @@
   # 
   # To mark files as resolved:  hg resolve --mark FILE
   
-  # To continue:                hg histedit --continue
-  # To abort:                   hg histedit --abort
+  # To continue:    hg histedit --continue
+  # To abort:       hg histedit --abort
   
   $ hg resolve -l
   U file
@@ -478,14 +478,8 @@
   1:199b6bb90248 b
   0:6c795aa153cb a
 
-Setup the proper environment variable symbol for the platform, to be subbed
-into the hook command.
-#if windows
-  $ NODE="%HG_NODE%"
-#else
-  $ NODE="\$HG_NODE"
-#endif
-  $ hg histedit 6c795aa153cb --config hooks.commit="echo commit $NODE" --commands - 2>&1 << EOF | fixbundle
+  $ hg histedit 6c795aa153cb --config hooks.commit='echo commit $HG_NODE' --config hooks.tonative.commit=True \
+  >     --commands - 2>&1 << EOF | fixbundle
   > pick 199b6bb90248 b
   > fold a1a953ffb4b0 c
   > pick 6c795aa153cb a
@@ -496,8 +490,25 @@
   1:9599899f62c0 a
   0:79b99e9c8e49 b
 
+Test unix -> windows style variable substitution in external hooks.
+
+  $ cat > $TESTTMP/tmp.hgrc <<'EOF'
+  > [hooks]
+  > pre-add = echo no variables
+  > post-add = echo ran $HG_ARGS, literal \$non-var, 'also $non-var', $HG_RESULT
+  > tonative.post-add = True
+  > EOF
+
   $ echo "foo" > amended.txt
-  $ hg add amended.txt
+  $ HGRCPATH=$TESTTMP/tmp.hgrc hg add -v amended.txt
+  running hook pre-add: echo no variables
+  no variables
+  adding amended.txt
+  converting hook "post-add" to native (windows !)
+  running hook post-add: echo ran %HG_ARGS%, literal $non-var, "also $non-var", %HG_RESULT% (windows !)
+  running hook post-add: echo ran $HG_ARGS, literal \$non-var, 'also $non-var', $HG_RESULT (no-windows !)
+  ran add -v amended.txt, literal $non-var, "also $non-var", 0 (windows !)
+  ran add -v amended.txt, literal $non-var, also $non-var, 0 (no-windows !)
   $ hg ci -q --config extensions.largefiles= --amend -I amended.txt
   The fsmonitor extension is incompatible with the largefiles extension and has been disabled. (fsmonitor !)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-histedit-no-backup.t	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,70 @@
+  $ . "$TESTDIR/histedit-helpers.sh"
+
+Enable extension used by this test
+  $ cat >>$HGRCPATH <<EOF
+  > [extensions]
+  > histedit=
+  > EOF
+
+==========================================
+Test history-editing-backup config option|
+==========================================
+Repo setup:
+  $ hg init foo
+  $ cd foo
+  $ echo first>file
+  $ hg ci -qAm one
+  $ echo second>>file
+  $ hg ci -m two
+  $ echo third>>file
+  $ hg ci -m three
+  $ echo forth>>file
+  $ hg ci -m four
+  $ hg log -G --style compact
+  @  3[tip]   7d5187087c79   1970-01-01 00:00 +0000   test
+  |    four
+  |
+  o  2   80d23dfa866d   1970-01-01 00:00 +0000   test
+  |    three
+  |
+  o  1   6153eb23e623   1970-01-01 00:00 +0000   test
+  |    two
+  |
+  o  0   36b4bdd91f5b   1970-01-01 00:00 +0000   test
+       one
+  
+Test when `history-editing-backup` config option is enabled:
+  $ hg histedit -r '36b4bdd91f5b' --commands - << EOF
+  > pick 36b4bdd91f5b 0 one
+  > pick 6153eb23e623 1 two
+  > roll 80d23dfa866d 2 three
+  > edit 7d5187087c79 3 four
+  > EOF
+  merging file
+  Editing (7d5187087c79), you may commit or record as needed now.
+  (hg histedit --continue to resume)
+  [1]
+  $ hg histedit --abort
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/foo/.hg/strip-backup/1d8f701c7b35-cf7be322-backup.hg
+  saved backup bundle to $TESTTMP/foo/.hg/strip-backup/5c0056670bce-b54b65d0-backup.hg
+
+Test when `history-editing-backup` config option is not enabled
+Enable config option:
+  $ cat >>$HGRCPATH <<EOF
+  > [ui]
+  > history-editing-backup=False
+  > EOF
+
+  $ hg histedit -r '36b4bdd91f5b' --commands - << EOF
+  > pick 36b4bdd91f5b 0 one
+  > pick 6153eb23e623 1 two
+  > roll 80d23dfa866d 2 three
+  > edit 7d5187087c79 3 four
+  > EOF
+  merging file
+  Editing (7d5187087c79), you may commit or record as needed now.
+  (hg histedit --continue to resume)
+  [1]
+  $ hg histedit --abort
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-http-api-httpv2.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-http-api-httpv2.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,3 +1,5 @@
+#require no-chg
+
   $ . $TESTDIR/wireprotohelpers.sh
   $ enabledummycommands
 
--- a/tests/test-http-api.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-http-api.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,3 +1,5 @@
+#require no-chg
+
   $ send() {
   >   hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
   > }
--- a/tests/test-http-bad-server.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-http-bad-server.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require killdaemons serve zstd
+#require serve zstd
 
 Client version is embedded in HTTP request and is effectively dynamic. Pin the
 version so behavior is deterministic.
@@ -120,7 +120,7 @@
   write(41) -> Content-Type: application/mercurial-0.1\r\n
   write(21) -> Content-Length: 436\r\n
   write(2) -> \r\n
-  write(436) -> batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(436) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(4? from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob)
   readline(1? from -1) -> (1?) Accept-Encoding* (glob)
   read limit reached; closing socket
@@ -161,7 +161,7 @@
   write(41) -> Content-Type: application/mercurial-0.1\r\n
   write(21) -> Content-Length: 436\r\n
   write(2) -> \r\n
-  write(436) -> batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(436) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(13? from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob)
   readline(1?? from -1) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(8? from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -218,7 +218,7 @@
   write(41) -> Content-Type: application/mercurial-0.1\r\n
   write(21) -> Content-Length: 449\r\n
   write(2) -> \r\n
-  write(449) -> batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(449) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(1?? from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n (glob)
   readline(1?? from -1) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(1?? from -1) -> (41) content-type: application/mercurial-0.1\r\n (glob)
@@ -329,7 +329,7 @@
   write(41 from 41) -> (577) Content-Type: application/mercurial-0.1\r\n
   write(21 from 21) -> (556) Content-Length: 436\r\n
   write(2 from 2) -> (554) \r\n
-  write(436 from 436) -> (118) batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(436 from 436) -> (118) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -377,7 +377,7 @@
   write(41 from 41) -> (642) Content-Type: application/mercurial-0.1\r\n
   write(21 from 21) -> (621) Content-Length: 436\r\n
   write(2 from 2) -> (619) \r\n
-  write(436 from 436) -> (183) batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(436 from 436) -> (183) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -429,7 +429,7 @@
   write(41 from 41) -> (789) Content-Type: application/mercurial-0.1\r\n
   write(21 from 21) -> (768) Content-Length: 436\r\n
   write(2 from 2) -> (766) \r\n
-  write(436 from 436) -> (330) batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(436 from 436) -> (330) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -490,7 +490,7 @@
   write(41 from 41) -> (827) Content-Type: application/mercurial-0.1\r\n
   write(21 from 21) -> (806) Content-Length: 436\r\n
   write(2 from 2) -> (804) \r\n
-  write(436 from 436) -> (368) batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(436 from 436) -> (368) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
@@ -553,7 +553,7 @@
   write(41 from 41) -> (851) Content-Type: application/mercurial-0.1\r\n
   write(21 from 21) -> (830) Content-Length: 436\r\n
   write(2 from 2) -> (828) \r\n
-  write(436 from 436) -> (392) batch branchmap bundle2=HG20%0Abookmarks%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps%0Arev-branch-cache changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
+  write(436 from 436) -> (392) batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=generaldelta,revlogv1 unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash
   readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(-1) -> (27) Accept-Encoding: identity\r\n
   readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
--- a/tests/test-http-branchmap.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-http-branchmap.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
   $ hgserve() {
   >     hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid \
   >       -E errors.log -v $@ > startup.log
@@ -60,8 +58,8 @@
 verify 7e7d56fe4833 (encoding fallback in branchmap to maintain compatibility with 1.3.x)
 
   $ cat <<EOF > oldhg
-  > import sys
-  > from mercurial import ui, hg, commands
+  > import threading
+  > from mercurial import dispatch, hg, ui, wireprotoserver
   > 
   > class StdoutWrapper(object):
   >     def __init__(self, stdout):
@@ -79,12 +77,12 @@
   >     def __getattr__(self, name):
   >         return getattr(self._file, name)
   > 
-  > sys.stdout = StdoutWrapper(getattr(sys.stdout, 'buffer', sys.stdout))
-  > sys.stderr = StdoutWrapper(getattr(sys.stderr, 'buffer', sys.stderr))
-  > 
+  > dispatch.initstdio()
   > myui = ui.ui.load()
+  > fout = StdoutWrapper(myui.fout)
+  > myui.fout = myui.ferr
   > repo = hg.repository(myui, b'a')
-  > commands.serve(myui, repo, stdio=True, cmdserver=False)
+  > wireprotoserver._runsshserver(myui, repo, myui.fin, fout, threading.Event())
   > EOF
   $ echo baz >> b/foo
   $ hg -R b ci -m baz
--- a/tests/test-http-permissions.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-http-permissions.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
   $ cat > fakeremoteuser.py << EOF
   > import os
   > from mercurial.hgweb import hgweb_mod
--- a/tests/test-http-protocol.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-http-protocol.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,3 +1,5 @@
+#require no-chg
+
   $ . $TESTDIR/wireprotohelpers.sh
 
   $ cat >> $HGRCPATH << EOF
--- a/tests/test-http.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-http.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require killdaemons serve
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-hybridencode.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-hybridencode.py	Thu Jul 19 13:55:54 2018 -0400
@@ -1,471 +1,877 @@
 from __future__ import absolute_import, print_function
+
+import unittest
+
 from mercurial import (
     store,
 )
 
-def show(s):
-    # show test input
-    print("A = '%s'" % s.encode("string_escape"))
-
-    # show the result of the C implementation, if available
-    h = store._pathencode(s)
-    print("B = '%s'" % h.encode("string_escape"))
+class hybridencodetests(unittest.TestCase):
+    def hybridencode(self, input, want):
 
-    # compare it with reference implementation in Python
-    r = store._hybridencode(s, True)
-    if h != r:
-        print("R = '%s'" % r.encode("string_escape"))
-    print()
-
-show("data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&'()+,-.;=[]^`{}")
+        # Check the C implementation if it's in use
+        got = store._pathencode(input)
+        self.assertEqual(want, got)
+        # Check the reference implementation in Python
+        refgot = store._hybridencode(input, True)
+        self.assertEqual(want, refgot)
 
-print("uppercase char X is encoded as _x")
-show("data/ABCDEFGHIJKLMNOPQRSTUVWXYZ")
-
-print("underbar is doubled")
-show("data/_")
-
-print("tilde is character-encoded")
-show("data/~")
+    def testnoencodingrequired(self):
+        self.hybridencode(
+            b'data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&\'()+,-.;=[]^`{}',
+            b'data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&\'()+,-.;=[]^`{}')
 
-print("characters in ASCII code range 1..31")
-show('data/\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'
-          '\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f')
+    def testuppercasechars(self): # uppercase char X is encoded as _x
+        self.hybridencode(
+            b'data/ABCDEFGHIJKLMNOPQRSTUVWXYZ',
+            b'data/_a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z')
+
+    def testunderbar(self): # underbar is doubled
+        self.hybridencode(b'data/_', b'data/__')
 
-print("characters in ASCII code range 126..255")
-show('data/\x7e\x7f'
-          '\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f'
-          '\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f')
-show('data/\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf'
-          '\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf')
-show('data/\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf'
-          '\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf')
-show('data/\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef'
-          '\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff')
+    def testtilde(self): # tilde is character-encoded
+        self.hybridencode(b'data/~', b'data/~7e')
 
-print("Windows reserved characters")
-show('data/less <, greater >, colon :, double-quote ", backslash \\'
-           ', pipe |, question-mark ?, asterisk *')
-
-print("encoding directories ending in .hg, .i or .d with '.hg' suffix")
-show('data/x.h.i/x.hg/x.i/x.d/foo')
-show('data/a.hg/a.i/a.d/foo')
-show('data/au.hg/au.i/au.d/foo')
-show('data/aux.hg/aux.i/aux.d/foo')
-show('data/auxy.hg/auxy.i/auxy.d/foo')
+    def testcontrolchars(self): # characters in ASCII code range 1..31
+        self.hybridencode(
+            (b'data/\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
+             b'\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e'
+             b'\x1f'),
+            (b'data/~01~02~03~04~05~06~07~08~09~0a~0b~0c~0d~0e~0f~10~11~12~13'
+             b'~14~15~16~17~18~19~1a~1b~1c~1d~1e~1f'))
 
-print("but these are not encoded on *filenames*")
-show('data/foo/x.hg')
-show('data/foo/x.i')
-show('data/foo/x.d')
-show('data/foo/a.hg')
-show('data/foo/a.i')
-show('data/foo/a.d')
-show('data/foo/au.hg')
-show('data/foo/au.i')
-show('data/foo/au.d')
-show('data/foo/aux.hg')
-show('data/foo/aux.i')
-show('data/foo/aux.d')
-show('data/foo/auxy.hg')
-show('data/foo/auxy.i')
-show('data/foo/auxy.d')
+    def testhighascii(self):# characters in ASCII code range 126..255
+        self.hybridencode(
+            (b'data/~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c'
+             b'\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b'
+             b'\x9c\x9d\x9e\x9f'),
+            (b'data/~7e~7f~80~81~82~83~84~85~86~87~88~89~8a~8b~8c~8d~8e~8f~90'
+             b'~91~92~93~94~95~96~97~98~99~9a~9b~9c~9d~9e~9f'))
+        self.hybridencode(
+            (b'data/\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad'
+             b'\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc'
+             b'\xbd\xbe\xbf'),
+            (b'data/~a0~a1~a2~a3~a4~a5~a6~a7~a8~a9~aa~ab~ac~ad~ae~af~b0~b1~b2'
+             b'~b3~b4~b5~b6~b7~b8~b9~ba~bb~bc~bd~be~bf'))
+        self.hybridencode(
+            (b'data/\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca'
+             b'\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6'
+             b'\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf'),
+            (b'data/~c0~c1~c2~c3~c4~c5~c6~c7~c8~c9~ca~cb~cc~cd~ce~cf~d0~d1~d2'
+             b'~d3~d4~d5~d6~d7~d8~d9~da~db~dc~dd~de~df'))
+        self.hybridencode(
+            (b'data/\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed'
+             b'\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd'
+             b'\xfe\xff'),
+            (b'data/~e0~e1~e2~e3~e4~e5~e6~e7~e8~e9~ea~eb~ec~ed~ee~ef~f0~f1~f2'
+             b'~f3~f4~f5~f6~f7~f8~f9~fa~fb~fc~fd~fe~ff'))
 
-print("plain .hg, .i and .d directories have the leading dot encoded")
-show('data/.hg/.i/.d/foo')
-
-show('data/aux.bla/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c.i')
+    def testwinreserved(self): # Windows reserved characters
+        self.hybridencode(
+            (b'data/less <, greater >, colon :, double-quote ", backslash \\, '
+             b'pipe |, question-mark ?, asterisk *'),
+            (b'data/less ~3c, greater ~3e, colon ~3a, double-quote ~22, '
+             b'backslash ~5c, pipe ~7c, question-mark ~3f, asterisk ~2a'))
 
-show('data/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/'
-     'TENTH/ELEVENTH/LOREMIPSUM.TXT.i')
-show('data/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/'
-     'wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules'
-     '.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider.i')
-show('data/AUX.THE-QUICK-BROWN-FOX-JU:MPS-OVER-THE-LAZY-DOG-THE-QUICK-'
-     'BROWN-FOX-JUMPS-OVER-THE-LAZY-DOG.TXT.i')
-show('data/Project Planning/Resources/AnotherLongDirectoryName/'
-     'Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt')
-show('data/Project.Planning/Resources/AnotherLongDirectoryName/'
-     'Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt')
-show('data/foo.../foo   / /a./_. /__/.x../    bla/.FOO/something.i')
+    def testhgreserved(self):
+        # encoding directories ending in .hg, .i or .d with '.hg' suffix
+        self.hybridencode(b'data/x.h.i/x.hg/x.i/x.d/foo',
+                          b'data/x.h.i.hg/x.hg.hg/x.i.hg/x.d.hg/foo')
+        self.hybridencode(b'data/a.hg/a.i/a.d/foo',
+                          b'data/a.hg.hg/a.i.hg/a.d.hg/foo')
+        self.hybridencode(b'data/au.hg/au.i/au.d/foo',
+                          b'data/au.hg.hg/au.i.hg/au.d.hg/foo')
+        self.hybridencode(b'data/aux.hg/aux.i/aux.d/foo',
+                          b'data/au~78.hg.hg/au~78.i.hg/au~78.d.hg/foo')
+        self.hybridencode(b'data/auxy.hg/auxy.i/auxy.d/foo',
+                          b'data/auxy.hg.hg/auxy.i.hg/auxy.d.hg/foo')
+        # but these are not encoded on *filenames*
+        self.hybridencode(b'data/foo/x.hg', b'data/foo/x.hg')
+        self.hybridencode(b'data/foo/x.i', b'data/foo/x.i')
+        self.hybridencode(b'data/foo/x.d', b'data/foo/x.d')
+        self.hybridencode(b'data/foo/a.hg', b'data/foo/a.hg')
+        self.hybridencode(b'data/foo/a.i', b'data/foo/a.i')
+        self.hybridencode(b'data/foo/a.d', b'data/foo/a.d')
+        self.hybridencode(b'data/foo/au.hg', b'data/foo/au.hg')
+        self.hybridencode(b'data/foo/au.i', b'data/foo/au.i')
+        self.hybridencode(b'data/foo/au.d', b'data/foo/au.d')
+        self.hybridencode(b'data/foo/aux.hg', b'data/foo/au~78.hg')
+        self.hybridencode(b'data/foo/aux.i', b'data/foo/au~78.i')
+        self.hybridencode(b'data/foo/aux.d', b'data/foo/au~78.d')
+        self.hybridencode(b'data/foo/auxy.hg', b'data/foo/auxy.hg')
+        self.hybridencode(b'data/foo/auxy.i', b'data/foo/auxy.i')
+        self.hybridencode(b'data/foo/auxy.d', b'data/foo/auxy.d')
 
-show('data/c/co/com/com0/com1/com2/com3/com4/com5/com6/com7/com8/com9')
-show('data/C/CO/COM/COM0/COM1/COM2/COM3/COM4/COM5/COM6/COM7/COM8/COM9')
-show('data/c.x/co.x/com.x/com0.x/com1.x/com2.x/com3.x/com4.x/com5.x'
-                                        '/com6.x/com7.x/com8.x/com9.x')
-show('data/x.c/x.co/x.com0/x.com1/x.com2/x.com3/x.com4/x.com5'
-                                        '/x.com6/x.com7/x.com8/x.com9')
-show('data/cx/cox/comx/com0x/com1x/com2x/com3x/com4x/com5x'
-                                            '/com6x/com7x/com8x/com9x')
-show('data/xc/xco/xcom0/xcom1/xcom2/xcom3/xcom4/xcom5'
-                                            '/xcom6/xcom7/xcom8/xcom9')
-
-show('data/l/lp/lpt/lpt0/lpt1/lpt2/lpt3/lpt4/lpt5/lpt6/lpt7/lpt8/lpt9')
-show('data/L/LP/LPT/LPT0/LPT1/LPT2/LPT3/LPT4/LPT5/LPT6/LPT7/LPT8/LPT9')
-show('data/l.x/lp.x/lpt.x/lpt0.x/lpt1.x/lpt2.x/lpt3.x/lpt4.x/lpt5.x'
-                                        '/lpt6.x/lpt7.x/lpt8.x/lpt9.x')
-show('data/x.l/x.lp/x.lpt/x.lpt0/x.lpt1/x.lpt2/x.lpt3/x.lpt4/x.lpt5'
-                                        '/x.lpt6/x.lpt7/x.lpt8/x.lpt9')
-show('data/lx/lpx/lptx/lpt0x/lpt1x/lpt2x/lpt3x/lpt4x/lpt5x'
-                                            '/lpt6x/lpt7x/lpt8x/lpt9x')
-show('data/xl/xlp/xlpt/xlpt0/xlpt1/xlpt2/xlpt3/xlpt4/xlpt5'
-                                            '/xlpt6/xlpt7/xlpt8/xlpt9')
-
-show('data/con/p/pr/prn/a/au/aux/n/nu/nul')
-show('data/CON/P/PR/PRN/A/AU/AUX/N/NU/NUL')
-show('data/con.x/p.x/pr.x/prn.x/a.x/au.x/aux.x/n.x/nu.x/nul.x')
-show('data/x.con/x.p/x.pr/x.prn/x.a/x.au/x.aux/x.n/x.nu/x.nul')
-show('data/conx/px/prx/prnx/ax/aux/auxx/nx/nux/nulx')
-show('data/xcon/xp/xpr/xprn/xa/xau/xaux/xn/xnu/xnul')
+        # plain .hg, .i and .d directories have the leading dot encoded
+        self.hybridencode(b'data/.hg/.i/.d/foo',
+                          b'data/~2ehg.hg/~2ei.hg/~2ed.hg/foo')
 
-show('data/a./au./aux./auxy./aux.')
-show('data/c./co./con./cony./con.')
-show('data/p./pr./prn./prny./prn.')
-show('data/n./nu./nul./nuly./nul.')
-show('data/l./lp./lpt./lpt1./lpt1y./lpt1.')
-show('data/lpt9./lpt9y./lpt9.')
-show('data/com./com1./com1y./com1.')
-show('data/com9./com9y./com9.')
-
-show('data/a /au /aux /auxy /aux ')
-
-print("largest unhashed path")
-show('data/123456789-123456789-123456789-123456789-123456789-'
-          'unhashed--xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-
-print("shortest hashed path")
-show('data/123456789-123456789-123456789-123456789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-
-print("changing one char in part that's hashed away produces a different hash")
-show('data/123456789-123456789-123456789-123456789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxy-'
-          '123456789-123456')
+    def testmisclongcases(self):
+        self.hybridencode(
+            (b'data/aux.bla/bla.aux/prn/PRN/lpt/com3/nul/'
+             b'coma/foo.NUL/normal.c.i'),
+            (b'data/au~78.bla/bla.aux/pr~6e/_p_r_n/lpt/co~6d3'
+             b'/nu~6c/coma/foo._n_u_l/normal.c.i'))
+        self.hybridencode(
+            (b'data/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH'
+             b'/TENTH/ELEVENTH/LOREMIPSUM.TXT.i'),
+            (b'dh/au~78/second/x.prn/fourth/fi~3afth/sixth/seventh/eighth/'
+             b'nineth/tenth/loremia20419e358ddff1bf8751e38288aff1d7c32ec05.i'))
+        self.hybridencode(
+            (b'data/enterprise/openesbaddons/contrib-imola/corba-bc/'
+             b'netbeansplugin/wsdlExtension/src/main/java/META-INF/services'
+             b'/org.netbeans.modules.xml.wsdl.bindingsupport.spi.'
+             b'ExtensibilityElementTemplateProvider.i'),
+            (b'dh/enterpri/openesba/contrib-/corba-bc/netbeans/wsdlexte/src/'
+             b'main/java/org.net7018f27961fdf338a598a40c4683429e7ffb9743.i'))
+        self.hybridencode(
+            (b'data/AUX.THE-QUICK-BROWN-FOX-JU:MPS-OVER-THE-LAZY-DOG-THE-'
+             b'QUICK-BROWN-FOX-JUMPS-OVER-THE-LAZY-DOG.TXT.i'),
+            (b'dh/au~78.the-quick-brown-fox-ju~3amps-over-the-lazy-dog-the-'
+             b'quick-brown-fox-jud4dcadd033000ab2b26eb66bae1906bcb15d4a70.i'))
+        self.hybridencode(
+            (b'data/Project Planning/Resources/AnotherLongDirectoryName/Follow'
+             b'edbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt'),
+            (b'dh/project_/resource/anotherl/followed/andanoth/andthenanextrem'
+             b'elylongfilenaf93030515d9849cfdca52937c2204d19f83913e5.txt'))
+        self.hybridencode(
+            (b'data/Project.Planning/Resources/AnotherLongDirectoryName/Follo'
+             b'wedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt'),
+            (b'dh/project_/resource/anotherl/followed/andanoth/andthenanextre'
+             b'melylongfilena0fd7c506f5c9d58204444fc67e9499006bd2d445.txt'))
+        self.hybridencode(
+            b'data/foo.../foo   / /a./_. /__/.x../    bla/.FOO/something.i',
+            (b'data/foo..~2e/foo  ~20/~20/a~2e/__.~20/____/~2ex.~2e/~20   bla/'
+             b'~2e_f_o_o/something.i'))
+        self.hybridencode(
+            b'data/c/co/com/com0/com1/com2/com3/com4/com5/com6/com7/com8/com9',
+            (b'data/c/co/com/com0/co~6d1/co~6d2/co~6d3/co~6d4/co~6d5/co~6d6/'
+             b'co~6d7/co~6d8/co~6d9'))
+        self.hybridencode(
+            b'data/C/CO/COM/COM0/COM1/COM2/COM3/COM4/COM5/COM6/COM7/COM8/COM9',
+            (b'data/_c/_c_o/_c_o_m/_c_o_m0/_c_o_m1/_c_o_m2/_c_o_m3/_c_o_m4/'
+             b'_c_o_m5/_c_o_m6/_c_o_m7/_c_o_m8/_c_o_m9'))
+        self.hybridencode(
+            (b'data/c.x/co.x/com.x/com0.x/com1.x/com2.x/com3.x/com4.x/com5.x/'
+             b'com6.x/com7.x/com8.x/com9.x'),
+            (b'data/c.x/co.x/com.x/com0.x/co~6d1.x/co~6d2.x/co~6d3.x/co~6d4.x'
+             b'/co~6d5.x/co~6d6.x/co~6d7.x/co~6d8.x/co~6d9.x'))
+        self.hybridencode(
+            (b'data/x.c/x.co/x.com0/x.com1/x.com2/x.com3/x.com4/x.com5/x.com6'
+             b'/x.com7/x.com8/x.com9'),
+            (b'data/x.c/x.co/x.com0/x.com1/x.com2/x.com3/x.com4/x.com5/x.com6'
+             b'/x.com7/x.com8/x.com9'))
+        self.hybridencode(
+            (b'data/cx/cox/comx/com0x/com1x/com2x/com3x/com4x/com5x/com6x/'
+             b'com7x/com8x/com9x'),
+            (b'data/cx/cox/comx/com0x/com1x/com2x/com3x/com4x/com5x/com6x/'
+             b'com7x/com8x/com9x'))
+        self.hybridencode(
+            (b'data/xc/xco/xcom0/xcom1/xcom2/xcom3/xcom4/xcom5/xcom6/xcom7/'
+             b'xcom8/xcom9'),
+            (b'data/xc/xco/xcom0/xcom1/xcom2/xcom3/xcom4/xcom5/xcom6/xcom7/'
+             b'xcom8/xcom9'))
+        self.hybridencode(
+            b'data/l/lp/lpt/lpt0/lpt1/lpt2/lpt3/lpt4/lpt5/lpt6/lpt7/lpt8/lpt9',
+            (b'data/l/lp/lpt/lpt0/lp~741/lp~742/lp~743/lp~744/lp~745/lp~746/'
+             b'lp~747/lp~748/lp~749'))
+        self.hybridencode(
+            b'data/L/LP/LPT/LPT0/LPT1/LPT2/LPT3/LPT4/LPT5/LPT6/LPT7/LPT8/LPT9',
+            (b'data/_l/_l_p/_l_p_t/_l_p_t0/_l_p_t1/_l_p_t2/_l_p_t3/_l_p_t4/'
+             b'_l_p_t5/_l_p_t6/_l_p_t7/_l_p_t8/_l_p_t9'))
+        self.hybridencode(
+            (b'data/l.x/lp.x/lpt.x/lpt0.x/lpt1.x/lpt2.x/lpt3.x/lpt4.x/lpt5.x/'
+             b'lpt6.x/lpt7.x/lpt8.x/lpt9.x'),
+            (b'data/l.x/lp.x/lpt.x/lpt0.x/lp~741.x/lp~742.x/lp~743.x/lp~744.x/'
+             b'lp~745.x/lp~746.x/lp~747.x/lp~748.x/lp~749.x'))
+        self.hybridencode(
+            (b'data/x.l/x.lp/x.lpt/x.lpt0/x.lpt1/x.lpt2/x.lpt3/x.lpt4/x.lpt5/'
+             b'x.lpt6/x.lpt7/x.lpt8/x.lpt9'),
+            (b'data/x.l/x.lp/x.lpt/x.lpt0/x.lpt1/x.lpt2/x.lpt3/x.lpt4/x.lpt5'
+             b'/x.lpt6/x.lpt7/x.lpt8/x.lpt9'))
+        self.hybridencode(
+            (b'data/lx/lpx/lptx/lpt0x/lpt1x/lpt2x/lpt3x/lpt4x/lpt5x/lpt6x/'
+             b'lpt7x/lpt8x/lpt9x'),
+            (b'data/lx/lpx/lptx/lpt0x/lpt1x/lpt2x/lpt3x/lpt4x/lpt5x/lpt6x/'
+             b'lpt7x/lpt8x/lpt9x'))
+        self.hybridencode(
+            (b'data/xl/xlp/xlpt/xlpt0/xlpt1/xlpt2/xlpt3/xlpt4/xlpt5/xlpt6/'
+             b'xlpt7/xlpt8/xlpt9'),
+            (b'data/xl/xlp/xlpt/xlpt0/xlpt1/xlpt2/xlpt3/xlpt4/xlpt5/xlpt6/'
+             b'xlpt7/xlpt8/xlpt9'))
+        self.hybridencode(b'data/con/p/pr/prn/a/au/aux/n/nu/nul',
+                          b'data/co~6e/p/pr/pr~6e/a/au/au~78/n/nu/nu~6c')
+        self.hybridencode(
+            b'data/CON/P/PR/PRN/A/AU/AUX/N/NU/NUL',
+            b'data/_c_o_n/_p/_p_r/_p_r_n/_a/_a_u/_a_u_x/_n/_n_u/_n_u_l')
+        self.hybridencode(
+            b'data/con.x/p.x/pr.x/prn.x/a.x/au.x/aux.x/n.x/nu.x/nul.x',
+            b'data/co~6e.x/p.x/pr.x/pr~6e.x/a.x/au.x/au~78.x/n.x/nu.x/nu~6c.x')
+        self.hybridencode(
+            b'data/x.con/x.p/x.pr/x.prn/x.a/x.au/x.aux/x.n/x.nu/x.nul',
+            b'data/x.con/x.p/x.pr/x.prn/x.a/x.au/x.aux/x.n/x.nu/x.nul')
+        self.hybridencode(b'data/conx/px/prx/prnx/ax/aux/auxx/nx/nux/nulx',
+                          b'data/conx/px/prx/prnx/ax/au~78/auxx/nx/nux/nulx')
+        self.hybridencode(b'data/xcon/xp/xpr/xprn/xa/xau/xaux/xn/xnu/xnul',
+                          b'data/xcon/xp/xpr/xprn/xa/xau/xaux/xn/xnu/xnul')
+        self.hybridencode(b'data/a./au./aux./auxy./aux.',
+                          b'data/a~2e/au~2e/au~78~2e/auxy~2e/au~78~2e')
+        self.hybridencode(b'data/c./co./con./cony./con.',
+                          b'data/c~2e/co~2e/co~6e~2e/cony~2e/co~6e~2e')
+        self.hybridencode(b'data/p./pr./prn./prny./prn.',
+                          b'data/p~2e/pr~2e/pr~6e~2e/prny~2e/pr~6e~2e')
+        self.hybridencode(b'data/n./nu./nul./nuly./nul.',
+                          b'data/n~2e/nu~2e/nu~6c~2e/nuly~2e/nu~6c~2e')
+        self.hybridencode(
+            b'data/l./lp./lpt./lpt1./lpt1y./lpt1.',
+            b'data/l~2e/lp~2e/lpt~2e/lp~741~2e/lpt1y~2e/lp~741~2e')
+        self.hybridencode(b'data/lpt9./lpt9y./lpt9.',
+                          b'data/lp~749~2e/lpt9y~2e/lp~749~2e')
+        self.hybridencode(b'data/com./com1./com1y./com1.',
+                          b'data/com~2e/co~6d1~2e/com1y~2e/co~6d1~2e')
+        self.hybridencode(b'data/com9./com9y./com9.',
+                          b'data/co~6d9~2e/com9y~2e/co~6d9~2e')
+        self.hybridencode(b'data/a /au /aux /auxy /aux ',
+                          b'data/a~20/au~20/aux~20/auxy~20/aux~20')
 
-print("uppercase hitting length limit due to encoding")
-show('data/A23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/Z23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-
-print("compare with lowercase not hitting limit")
-show('data/a23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/z23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-
-print("not hitting limit with any of these")
-show("data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&'()+,-.;="
-          "[]^`{}xxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-"
-          "123456789-12345")
-
-print("underbar hitting length limit due to encoding")
-show('data/_23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-
-print("tilde hitting length limit due to encoding")
-show('data/~23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
+    def testhashingboundarycases(self):
+        # largest unhashed path
+        self.hybridencode(
+            (b'data/123456789-123456789-123456789-123456789-123456789-unhashed'
+             b'--xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'),
+            (b'data/123456789-123456789-123456789-123456789-123456789-unhashed'
+             b'--xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'))
+        # shortest hashed path
+        self.hybridencode(
+            (b'data/123456789-123456789-123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/123456789-123456789-123456789-123456789-123456789-hashed---'
+             b'-xxxxxxxxx-xxxxxxxe9c55002b50bf5181e7a6fc1f60b126e2a6fcf71'))
 
-print("Windows reserved characters hitting length limit")
-show('data/<23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/>23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/:23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/"23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/\\23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/|23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/?23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/*23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
+    def testhashing(self):
+        # changing one char in part that's hashed away produces a different hash
+        self.hybridencode(
+            (b'data/123456789-123456789-123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxy-123456789-123456'),
+            (b'dh/123456789-123456789-123456789-123456789-123456789-hashed---'
+             b'-xxxxxxxxx-xxxxxxxd24fa4455faf8a94350c18e5eace7c2bb17af706'))
+        # uppercase hitting length limit due to encoding
+        self.hybridencode(
+            (b'data/A23456789-123456789-123456789-123456789-123456789-'
+             b'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/a23456789-123456789-123456789-123456789-123456789-'
+             b'xxxxxxxxx-xxxxxxxxx-xxxxxxx'
+             b'cbbc657029b41b94ed510d05feb6716a5c03bc6b'))
+        self.hybridencode(
+            (b'data/Z23456789-123456789-123456789-123456789-123456789-'
+             b'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx'
+             b'-xxxxxxxxx-xxxxxxx938f32a725c89512833fb96b6602dd9ebff51ddd'))
+        # compare with lowercase not hitting limit
+        self.hybridencode(
+            (b'data/a23456789-123456789-123456789-123456789-123456789-'
+             b'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-'
+             b'12345'),
+            (b'data/a23456789-123456789-123456789-123456789-123456789-'
+             b'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-'
+             b'12345'))
+        self.hybridencode(
+            (b'data/z23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789'
+             b'-12345'),
+            (b'data/z23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-'
+             b'12345'))
+        # not hitting limit with any of these
+        self.hybridencode(
+            (b'data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&\'()+,-.;=[]^`{}'
+             b'xxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'),
+            (b'data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&\'()+,-.;=[]^`{}'
+             b'xxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'))
+        # underbar hitting length limit due to encoding
+        self.hybridencode(
+            (b'data/_23456789-123456789-123456789-123456789-123456789-'
+             b'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-'
+             b'12345'),
+            (b'dh/_23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-'
+             b'xxxxxxxxx-xxxxxxx9921a01af50feeabc060ce00eee4cba6efc31d2b'))
+
+        # tilde hitting length limit due to encoding
+        self.hybridencode(
+            (b'data/~23456789-123456789-123456789-123456789-123456789-'
+             b'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-'
+             b'12345'),
+            (b'dh/~7e23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'9cec6f97d569c10995f785720044ea2e4227481b'))
 
-print("initial space hitting length limit")
-show('data/ 23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-
-print("initial dot hitting length limit")
-show('data/.23456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-
-print("trailing space in filename hitting length limit")
-show('data/123456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-1234 ')
+    def testwinreservedoverlimit(self):
+        # Windows reserved characters hitting length limit
+        self.hybridencode(
+            (b'data/<23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/~3c23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxee'
+             b'67d8f275876ca1ef2500fc542e63c885c4e62d'))
+        self.hybridencode(
+            (b'data/>23456789-123456789-123456789-123456789-123456789-'
+             b'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/~3e23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'387a85a5b1547cc9136310c974df716818458ddb'))
+        self.hybridencode(
+            (b'data/:23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/~3a23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'2e4154fb571d13d22399c58cc4ef4858e4b75999'))
+        self.hybridencode(
+            (b'data/"23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/~2223456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'fc7e3ec7b0687ee06ed8c32fef0eb0c1980259f5'))
+        self.hybridencode(
+            (b'data/\\23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/~5c23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'944e1f2b7110687e116e0d151328ac648b06ab4a'))
+        self.hybridencode(
+            (b'data/|23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/~7c23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'28b23dd3fd0242946334126ab62bcd772aac32f4'))
+        self.hybridencode(
+            (b'data/?23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/~3f23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'a263022d3994d2143d98f94f431eef8b5e7e0f8a'))
+        self.hybridencode(
+            (b'data/*23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/~2a23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'0e7e6020e3c00ba7bb7893d84ca2966fbf53e140'))
 
-print("trailing dot in filename hitting length limit")
-show('data/123456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-1234.')
+    def testinitialspacelenlimit(self):
+        # initial space hitting length limit
+        self.hybridencode(
+            (b'data/ 23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/~2023456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'92acbc78ef8c0b796111629a02601f07d8aec4ea'))
 
-print("initial space in directory hitting length limit")
-show('data/ x/456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
+    def testinitialdotlenlimit(self):
+        # initial dot hitting length limit
+        self.hybridencode(
+            (b'data/.23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/~2e23456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'dbe19cc6505b3515ab9228cebf877ad07075168f'))
+
+    def testtrailingspacelenlimit(self):
+        # trailing space in filename hitting length limit
+        self.hybridencode(
+            (b'data/123456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-1234 '),
+            (b'dh/123456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxx'
+             b'0025dc73e04f97426db4893e3bf67d581dc6d066'))
 
-print("initial dot in directory hitting length limit")
-show('data/.x/456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
+    def testtrailingdotlenlimit(self):
+        # trailing dot in filename hitting length limit
+        self.hybridencode(
+            (b'data/123456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-'
+             b'1234.'),
+            (b'dh/123456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxx'
+             b'85a16cf03ee7feba8a5abc626f1ba9886d01e89d'))
+
+    def testinitialspacedirlenlimit(self):
+        # initial space in directory hitting length limit
+        self.hybridencode(
+            (b'data/ x/456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/~20x/456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'1b3a3b712b2ac00d6af14ae8b4c14fdbf904f516'))
 
-print("trailing space in directory hitting length limit")
-show('data/x /456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
+    def testinitialdotdirlenlimit(self):
+        # initial dot in directory hitting length limit
+        self.hybridencode(
+            (b'data/.x/456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/~2ex/456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'39dbc4c193a5643a8936fc69c3363cd7ac91ab14'))
+
+    def testtrailspacedirlenlimit(self):
+        # trailing space in directory hitting length limit
+        self.hybridencode(
+            (b'data/x /456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/x~20/456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'2253c341df0b5290790ad312cd8499850f2273e5'))
 
-print("trailing dot in directory hitting length limit")
-show('data/x./456789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
+    def testtrailingdotdirlenlimit(self):
+        # trailing dot in directory hitting length limit
+        self.hybridencode(
+            (b'data/x./456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/x~2e/456789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'cc0324d696d34562b44b5138db08ee1594ccc583'))
 
-print("with directories that need direncoding, hitting length limit")
-show('data/x.i/56789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/x.d/56789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/x.hg/5789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
+    def testdirencodinglenlimit(self):
+        # with directories that need direncoding, hitting length limit
+        self.hybridencode(
+            (b'data/x.i/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-'
+             b'12345'),
+            (b'dh/x.i.hg/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxx'
+             b'a4c4399bdf81c67dbbbb7060aa0124d8dea94f74'))
+        self.hybridencode(
+            (b'data/x.d/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/x.d.hg/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxx'
+             b'1303fa90473b230615f5b3ea7b660e881ae5270a'))
+        self.hybridencode(
+            (b'data/x.hg/5789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/x.hg.hg/5789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxx'
+             b'26d724a8af68e7a4e4455e6602ea9adbd0eb801f'))
 
-print("Windows reserved filenames, hitting length limit")
-show('data/con/56789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/prn/56789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/aux/56789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/nul/56789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/com1/6789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/com9/6789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/lpt1/6789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-show('data/lpt9/6789-123456789-123456789-123456789-123456789-'
-          'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
+    def testwinreservedfilenameslimit(self):
+        # Windows reserved filenames, hitting length limit
+        self.hybridencode(
+            (b'data/con/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-12345'),
+            (b'dh/co~6e/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'c0794d4f4c605a2617900eb2563d7113cf6ea7d3'))
+        self.hybridencode(
+            (b'data/prn/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/pr~6e/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'64db876e1a9730e27236cb9b167aff942240e932'))
+        self.hybridencode(
+            (b'data/aux/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/au~78/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'8a178558405ca6fb4bbd75446dfa186f06751a0d'))
+        self.hybridencode(
+            (b'data/nul/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/nu~6c/56789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'c5e51b6fec1bd07bd243b053a0c3f7209855b886'))
+        self.hybridencode(
+            (b'data/com1/6789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/co~6d1/6789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'32f5f44ece3bb62b9327369ca84cc19c86259fcd'))
+        self.hybridencode(
+            (b'data/com9/6789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/co~6d9/6789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'734360b28c66a3230f55849fe8926206d229f990'))
+        self.hybridencode(
+            (b'data/lpt1/6789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/lp~741/6789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'e6f16ab4b6b0637676b2842b3345c9836df46ef7'))
+        self.hybridencode(
+            (b'data/lpt9/6789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-12345'),
+            (b'dh/lp~749/6789-123456789-123456789-123456789-123456789'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxx'
+             b'a475814c51acead3e44f2ff801f0c4903f986157'))
 
-print("non-reserved names, just not hitting limit")
-show('data/123456789-123456789-123456789-123456789-123456789-'
-          '/com/com0/lpt/lpt0/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12345')
-
-print("hashed path with largest untruncated 1st dir")
-show('data/12345678/-123456789-123456789-123456789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-
-print("hashed path with smallest truncated 1st dir")
-show('data/123456789/123456789-123456789-123456789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testnonreservednolimit(self):
+        # non-reserved names, just not hitting limit
+        self.hybridencode(
+            (b'data/123456789-123456789-123456789-123456789-123456789-'
+             b'/com/com0/lpt/lpt0/'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'),
+            (b'data/123456789-123456789-123456789-123456789-123456789-'
+             b'/com/com0/lpt/lpt0/'
+             b'-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'))
 
-print("hashed path with largest untruncated two dirs")
-show('data/12345678/12345678/9-123456789-123456789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashedpathuntrucfirst(self):
+        # hashed path with largest untruncated 1st dir
+        self.hybridencode(
+            (b'data/12345678/-123456789-123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/-123456789-123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxx4e9e9e384d00929a93b6835fbf976eb32321ff3c'))
 
-print("hashed path with smallest truncated two dirs")
-show('data/123456789/123456789/123456789-123456789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashedpathsmallesttrucdir(self):
+        # hashed path with smallest truncated 1st dir
+        self.hybridencode(
+            (b'data/123456789/123456789-123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/123456789-123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxx1f4e4ec5f2be76e109bfaa8e31c062fe426d5490'))
 
-print("hashed path with largest untruncated three dirs")
-show('data/12345678/12345678/12345678/89-123456789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashedlargesttwountruc(self):
+        # hashed path with largest untruncated two dirs
+        self.hybridencode(
+            (b'data/12345678/12345678/9-123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/9-123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxx3332d8329d969cf835542a9f2cbcfb385b6cf39d'))
 
-print("hashed path with smallest truncated three dirs")
-show('data/123456789/123456789/123456789/123456789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashedpathsmallesttrunctwodirs(self):
+        # hashed path with smallest truncated two dirs
+        self.hybridencode(
+            (b'data/123456789/123456789/123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/123456789-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx'
+             b'9699559798247dffa18717138859be5f8874840e'))
 
-print("hashed path with largest untruncated four dirs")
-show('data/12345678/12345678/12345678/12345678/789-123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashuntruncthree(self):
+        # hashed path with largest untruncated three dirs
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/89-123456789-123456789-'
+             b'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-123456'),
+            (b'dh/12345678/12345678/12345678/89-123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxf0a2b053bb1369cce02f78c217d6a7aaea18c439'))
 
-print("hashed path with smallest truncated four dirs")
-show('data/123456789/123456789/123456789/123456789/123456789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashtruncthree(self):
+        # hashed path with smallest truncated three dirs
+        self.hybridencode(
+            (b'data/123456789/123456789/123456789/123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/123456789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-'
+             b'1c6f8284967384ec13985a046d3553179d9d03cd'))
 
-print("hashed path with largest untruncated five dirs")
-show('data/12345678/12345678/12345678/12345678/12345678/6789-'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashuntrucfour(self):
+        # hashed path with largest untruncated four dirs
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/789-123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxx0d30c99049d8f0ff97b94d4ef302027e8d54c6fd'))
 
-print("hashed path with smallest truncated five dirs")
-show('data/123456789/123456789/123456789/123456789/123456789/'
-          'hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashtruncfour(self):
+        # hashed path with smallest truncated four dirs
+        self.hybridencode(
+            (b'data/123456789/123456789/123456789/123456789/123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/123456789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-x'
+             b'46162779e1a771810b37a737f82ae7ed33771402'))
 
-print("hashed path with largest untruncated six dirs")
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/ed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashuntruncfive(self):
+        # hashed path with largest untruncated five dirs
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/6789-hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/6789-hashed'
+             b'----xxxxxxxxx-xxxxxxxbfe752ddc8b003c2790c66a9f2eb1ea75c114390'))
 
-print("hashed path with smallest truncated six dirs")
-show('data/123456789/123456789/123456789/123456789/123456789/'
-          '123456789/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashtruncfive(self):
+        # hashed path with smallest truncated five dirs
+        self.hybridencode(
+            (b'data/123456789/123456789/123456789/123456789/123456789/hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/hashed'
+             b'----xxxxxxxxx-xxxxxxxxx-xx'
+             b'b94c27b3532fa880cdd572b1c514785cab7b6ff2'))
+
+    def testhashuntruncsix(self):
+        # hashed path with largest untruncated six dirs
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'ed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'ed----xxxxxxxxx-xxxxxxx'
+             b'cd8cc5483a0f3be409e0e5d4bf9e36e113c59235'))
 
-print("hashed path with largest untruncated seven dirs")
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/xxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashtruncsix(self):
+        # hashed path with smallest truncated six dirs
+        self.hybridencode(
+            (b'data/123456789/123456789/123456789/123456789/123456789/'
+              b'123456789/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+              b'123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'xxxxxxxxx-xxxxxxxxx-xxx'
+             b'47dd6f616f833a142da00701b334cebbf640da06'))
 
-print("hashed path with smallest truncated seven dirs")
-show('data/123456789/123456789/123456789/123456789/123456789/'
-          '123456789/123456789/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashuntrunc7(self):
+        # hashed path with largest untruncated seven dirs
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/xxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/xxxxxx-xxxxxxx'
+             b'1c8ed635229fc22efe51035feeadeb4c8a0ecb82'))
 
-print("hashed path with largest untruncated eight dirs")
-print("(directory 8 is dropped because it hits _maxshortdirslen)")
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345678/xxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashtrunc7(self):
+        # hashed path with smallest truncated seven dirs
+        self.hybridencode(
+            (b'data/123456789/123456789/123456789/123456789/123456789/'
+              b'123456789/123456789/'
+              b'xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/123'
+             b'45678/xxxxxxxxx-xxxx298ff7d33f8ce6db57930837ffea2fb2f48bb926'))
 
-print("hashed path with smallest truncated eight dirs")
-print("(directory 8 is dropped because it hits _maxshortdirslen)")
-show('data/123456789/123456789/123456789/123456789/123456789/'
-          '123456789/123456789/123456789/xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashuntrunc8(self):
+        # hashed path with largest untruncated eight dirs
+        # (directory 8 is dropped because it hits _maxshortdirslen)
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345678/xxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/1'
+             b'2345678/xxxxxxx-xxxxxxc8996ccd41b471f768057181a4d59d2febe7277d'))
 
-print("hashed path with largest non-dropped directory 8")
-print("(just not hitting the _maxshortdirslen boundary)")
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashtrunc8(self):
+        # hashed path with smallest truncated eight dirs
+        # (directory 8 is dropped because it hits _maxshortdirslen)
+        self.hybridencode(
+            (b'data/123456789/123456789/123456789/123456789/123456789/'
+             b'123456789/123456789/123456789/xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/xxxxxxxxx-xxxx'
+             b'4fa04a839a6bda93e1c21c713f2edcbd16e8890d'))
 
-print("...adding one truncated char to dir 1..7 won't drop dir 8")
-show('data/12345678x/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-show('data/12345678/12345678x/12345678/12345678/12345678/12345'
-          '678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-show('data/12345678/12345678/12345678x/12345678/12345678/12345'
-          '678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-show('data/12345678/12345678/12345678/12345678x/12345678/12345'
-          '678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-show('data/12345678/12345678/12345678/12345678/12345678x/12345'
-          '678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678x/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678x/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashnondropped8(self):
+        # hashed path with largest non-dropped directory 8
+        # (just not hitting the _maxshortdirslen boundary)
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789'
+             b'-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12345/-xxxxxxx'
+             b'4d43d1ccaa20efbfe99ec779dc063611536ff2c5'))
+        # ...adding one truncated char to dir 1..7 won't drop dir 8
+        self.hybridencode(
+            (b'data/12345678x/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/1234'
+             b'5678/12345/xxxxxxxx0f9efce65189cc60fd90fe4ffd49d7b58bbe0f2e'))
+        self.hybridencode(
+            (b'data/12345678/12345678x/12345678/12345678/12345678/12345678'
+             b'/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/1234'
+             b'5678/12345/xxxxxxxx945ca395708cafdd54a94501859beabd3e243921'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678x/12345678/12345678/12345678/12'
+             b'345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/1234'
+             b'5678/12345/xxxxxxxxac62bf6898c4fd0502146074547c11caa751a327'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678x/12345678/12345678/12'
+             b'345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/1234'
+             b'5678/12345/xxxxxxxx2ae5a2baed7983fae8974d0ca06c6bf08b9aee92'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678x/12345678/'
+             b'12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/1234'
+             b'5678/12345/xxxxxxxx214aba07b6687532a43d1e9eaf6e88cfca96b68c'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678x'
+             b'/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/1234'
+             b'5678/12345/xxxxxxxxe7a022ae82f0f55cf4e0498e55ba59ea4ebb55bf'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678x/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345'
+             b'678/12345/xxxxxxxxb51ce61164996a80f36ce3cfe64b62d519aedae3'))
 
-print("hashed path with shortest dropped directory 8")
-print("(just hitting the _maxshortdirslen boundary)")
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/123456/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-
-print("hashed path that drops dir 8 due to dot or space at end is")
-print("encoded, and thus causing to hit _maxshortdirslen")
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/1234./-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/1234 /-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashedpathshortestdropped8(self):
+        # hashed path with shortest dropped directory 8
+        # (just hitting the _maxshortdirslen boundary)
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/123456/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/xxxxxxxxx-xxxx'
+             b'11fa9873cc6c3215eae864528b5530a04efc6cfe'))
 
-print("... with dir 8 short enough for encoding")
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12./xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12 /xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-123456')
+    def testhashedpathdropsdir8fortrailingdotspace(self):
+        # hashed path that drops dir 8 due to dot or space at end is
+        # encoded, and thus causing to hit _maxshortdirslen
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/1234./-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/-xxxxxxxxx-xxx'
+             b'602df9b45bec564e2e1f0645d5140dddcc76ed58'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/1234 /-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
+             b'123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/-xxxxxxxxx-xxx'
+             b'd99ff212bc84b4d1f70cd6b0071e3ef69d4e12ce'))
+        # ... with dir 8 short enough for encoding
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12./xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx'
+             b'-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12~2e/'
+             b'xx-xxxxx7baeb5ed7f14a586ee1cacecdbcbff70032d1b3c'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12 '
+             b'/xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12~20/'
+             b'xx-xxxxxcf79ca9795f77d7f75745da36807e5d772bd5182'))
 
-print('''Extensions are replicated on hashed paths. Note that
-we only get to encode files that end in .i or .d inside the
-store. Encoded filenames are thus bound in length.''')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.345.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.345.d')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.34567.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.345678.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456789.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456789-.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456789-1.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456789-12.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456789-123.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456789-1234.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456789-12345.i')
-show('data/12345678/12345678/12345678/12345678/12345678/12345'
-          '678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-'
-          '123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWX'
-          'YZ-abcdefghjiklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPRSTU'
-          'VWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxx'
-          'xxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwww'
-          'wwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i')
+    def testextensionsreplicatedonhashedpaths(self):
+        # Extensions are replicated on hashed paths. Note that
+        # we only get to encode files that end in .i or .d inside the
+        # store. Encoded filenames are thus bound in length.
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'45.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxxc10ad03b5755ed524f5286aab1815dfe07729438.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'45.d'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxx9eec83381f2b39ef5ac8b4ecdf2c94f7983f57c8.d'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxxb7796dc7d175cfb0bb8a7728f58f6ebec9042568.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'4567.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxxb515857a6bfeef017c4894d8df42458ac65d55b8.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'45678.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxxb05a0f247bc0a776211cd6a32ab714fd9cc09f2b.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456789.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxxf192b48bff08d9e0e12035fb52bc58c70de72c94.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456789-.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxx435551e0ed4c7b083b9ba83cee916670e02e80ad.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456789-1.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxxa7f74eb98d8d58b716356dfd26e2f9aaa65d6a9a.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456789-12.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxxed68d9bd43b931f0b100267fee488d65a0c66f62.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456789-123.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxx5cea44de2b642d2ba2b4a30693ffb1049644d698.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456789-1234.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxx68462f62a7f230b39c1b5400d73ec35920990b7e.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456789-12345.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxx4cb852a314c6da240a83eec94761cdd71c6ec22e.i'))
+        self.hybridencode(
+            (b'data/12345678/12345678/12345678/12345678/12345678/12345678/'
+             b'12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3'
+             b'456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-'
+             b'abcdefghjiklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPRSTUVWXYZ'
+             b'-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx'
+             b'-xxxxxxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww'
+             b'-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i'),
+            (b'dh/12345678/12345678/12345678/12345678/12345678/12345678/12'
+             b'345678/12345/-xxxxx93352aa50377751d9e5ebdf52da1e6e69a6887a6.i'))
 
-print("paths outside data/ can be encoded")
-show('metadata/dir/00manifest.i')
-show('metadata/12345678/12345678/12345678/12345678/12345678/'
-          '12345678/12345678/12345678/12345678/12345678/12345678/'
-          '12345678/12345678/00manifest.i')
+    def testpathsoutsidedata(self):
+        # paths outside data/ can be encoded
+        self.hybridencode(b'metadata/dir/00manifest.i',
+                          b'metadata/dir/00manifest.i')
+        self.hybridencode(
+            (b'metadata/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12345678/00manifest.i'),
+            (b'dh/ata/12345678/12345678/12345678/12345678/12345678'
+             b'/12345678/12345678/00manife'
+             b'0a4da1f89aa2aa9eb0896eb451288419049781b4.i'))
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
--- a/tests/test-hybridencode.py.out	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,500 +0,0 @@
-A = 'data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&\'()+,-.;=[]^`{}'
-B = 'data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&\'()+,-.;=[]^`{}'
-
-uppercase char X is encoded as _x
-A = 'data/ABCDEFGHIJKLMNOPQRSTUVWXYZ'
-B = 'data/_a_b_c_d_e_f_g_h_i_j_k_l_m_n_o_p_q_r_s_t_u_v_w_x_y_z'
-
-underbar is doubled
-A = 'data/_'
-B = 'data/__'
-
-tilde is character-encoded
-A = 'data/~'
-B = 'data/~7e'
-
-characters in ASCII code range 1..31
-A = 'data/\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f'
-B = 'data/~01~02~03~04~05~06~07~08~09~0a~0b~0c~0d~0e~0f~10~11~12~13~14~15~16~17~18~19~1a~1b~1c~1d~1e~1f'
-
-characters in ASCII code range 126..255
-A = 'data/~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f'
-B = 'data/~7e~7f~80~81~82~83~84~85~86~87~88~89~8a~8b~8c~8d~8e~8f~90~91~92~93~94~95~96~97~98~99~9a~9b~9c~9d~9e~9f'
-
-A = 'data/\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf'
-B = 'data/~a0~a1~a2~a3~a4~a5~a6~a7~a8~a9~aa~ab~ac~ad~ae~af~b0~b1~b2~b3~b4~b5~b6~b7~b8~b9~ba~bb~bc~bd~be~bf'
-
-A = 'data/\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf'
-B = 'data/~c0~c1~c2~c3~c4~c5~c6~c7~c8~c9~ca~cb~cc~cd~ce~cf~d0~d1~d2~d3~d4~d5~d6~d7~d8~d9~da~db~dc~dd~de~df'
-
-A = 'data/\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'
-B = 'data/~e0~e1~e2~e3~e4~e5~e6~e7~e8~e9~ea~eb~ec~ed~ee~ef~f0~f1~f2~f3~f4~f5~f6~f7~f8~f9~fa~fb~fc~fd~fe~ff'
-
-Windows reserved characters
-A = 'data/less <, greater >, colon :, double-quote ", backslash \\, pipe |, question-mark ?, asterisk *'
-B = 'data/less ~3c, greater ~3e, colon ~3a, double-quote ~22, backslash ~5c, pipe ~7c, question-mark ~3f, asterisk ~2a'
-
-encoding directories ending in .hg, .i or .d with '.hg' suffix
-A = 'data/x.h.i/x.hg/x.i/x.d/foo'
-B = 'data/x.h.i.hg/x.hg.hg/x.i.hg/x.d.hg/foo'
-
-A = 'data/a.hg/a.i/a.d/foo'
-B = 'data/a.hg.hg/a.i.hg/a.d.hg/foo'
-
-A = 'data/au.hg/au.i/au.d/foo'
-B = 'data/au.hg.hg/au.i.hg/au.d.hg/foo'
-
-A = 'data/aux.hg/aux.i/aux.d/foo'
-B = 'data/au~78.hg.hg/au~78.i.hg/au~78.d.hg/foo'
-
-A = 'data/auxy.hg/auxy.i/auxy.d/foo'
-B = 'data/auxy.hg.hg/auxy.i.hg/auxy.d.hg/foo'
-
-but these are not encoded on *filenames*
-A = 'data/foo/x.hg'
-B = 'data/foo/x.hg'
-
-A = 'data/foo/x.i'
-B = 'data/foo/x.i'
-
-A = 'data/foo/x.d'
-B = 'data/foo/x.d'
-
-A = 'data/foo/a.hg'
-B = 'data/foo/a.hg'
-
-A = 'data/foo/a.i'
-B = 'data/foo/a.i'
-
-A = 'data/foo/a.d'
-B = 'data/foo/a.d'
-
-A = 'data/foo/au.hg'
-B = 'data/foo/au.hg'
-
-A = 'data/foo/au.i'
-B = 'data/foo/au.i'
-
-A = 'data/foo/au.d'
-B = 'data/foo/au.d'
-
-A = 'data/foo/aux.hg'
-B = 'data/foo/au~78.hg'
-
-A = 'data/foo/aux.i'
-B = 'data/foo/au~78.i'
-
-A = 'data/foo/aux.d'
-B = 'data/foo/au~78.d'
-
-A = 'data/foo/auxy.hg'
-B = 'data/foo/auxy.hg'
-
-A = 'data/foo/auxy.i'
-B = 'data/foo/auxy.i'
-
-A = 'data/foo/auxy.d'
-B = 'data/foo/auxy.d'
-
-plain .hg, .i and .d directories have the leading dot encoded
-A = 'data/.hg/.i/.d/foo'
-B = 'data/~2ehg.hg/~2ei.hg/~2ed.hg/foo'
-
-A = 'data/aux.bla/bla.aux/prn/PRN/lpt/com3/nul/coma/foo.NUL/normal.c.i'
-B = 'data/au~78.bla/bla.aux/pr~6e/_p_r_n/lpt/co~6d3/nu~6c/coma/foo._n_u_l/normal.c.i'
-
-A = 'data/AUX/SECOND/X.PRN/FOURTH/FI:FTH/SIXTH/SEVENTH/EIGHTH/NINETH/TENTH/ELEVENTH/LOREMIPSUM.TXT.i'
-B = 'dh/au~78/second/x.prn/fourth/fi~3afth/sixth/seventh/eighth/nineth/tenth/loremia20419e358ddff1bf8751e38288aff1d7c32ec05.i'
-
-A = 'data/enterprise/openesbaddons/contrib-imola/corba-bc/netbeansplugin/wsdlExtension/src/main/java/META-INF/services/org.netbeans.modules.xml.wsdl.bindingsupport.spi.ExtensibilityElementTemplateProvider.i'
-B = 'dh/enterpri/openesba/contrib-/corba-bc/netbeans/wsdlexte/src/main/java/org.net7018f27961fdf338a598a40c4683429e7ffb9743.i'
-
-A = 'data/AUX.THE-QUICK-BROWN-FOX-JU:MPS-OVER-THE-LAZY-DOG-THE-QUICK-BROWN-FOX-JUMPS-OVER-THE-LAZY-DOG.TXT.i'
-B = 'dh/au~78.the-quick-brown-fox-ju~3amps-over-the-lazy-dog-the-quick-brown-fox-jud4dcadd033000ab2b26eb66bae1906bcb15d4a70.i'
-
-A = 'data/Project Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt'
-B = 'dh/project_/resource/anotherl/followed/andanoth/andthenanextremelylongfilenaf93030515d9849cfdca52937c2204d19f83913e5.txt'
-
-A = 'data/Project.Planning/Resources/AnotherLongDirectoryName/Followedbyanother/AndAnother/AndThenAnExtremelyLongFileName.txt'
-B = 'dh/project_/resource/anotherl/followed/andanoth/andthenanextremelylongfilena0fd7c506f5c9d58204444fc67e9499006bd2d445.txt'
-
-A = 'data/foo.../foo   / /a./_. /__/.x../    bla/.FOO/something.i'
-B = 'data/foo..~2e/foo  ~20/~20/a~2e/__.~20/____/~2ex.~2e/~20   bla/~2e_f_o_o/something.i'
-
-A = 'data/c/co/com/com0/com1/com2/com3/com4/com5/com6/com7/com8/com9'
-B = 'data/c/co/com/com0/co~6d1/co~6d2/co~6d3/co~6d4/co~6d5/co~6d6/co~6d7/co~6d8/co~6d9'
-
-A = 'data/C/CO/COM/COM0/COM1/COM2/COM3/COM4/COM5/COM6/COM7/COM8/COM9'
-B = 'data/_c/_c_o/_c_o_m/_c_o_m0/_c_o_m1/_c_o_m2/_c_o_m3/_c_o_m4/_c_o_m5/_c_o_m6/_c_o_m7/_c_o_m8/_c_o_m9'
-
-A = 'data/c.x/co.x/com.x/com0.x/com1.x/com2.x/com3.x/com4.x/com5.x/com6.x/com7.x/com8.x/com9.x'
-B = 'data/c.x/co.x/com.x/com0.x/co~6d1.x/co~6d2.x/co~6d3.x/co~6d4.x/co~6d5.x/co~6d6.x/co~6d7.x/co~6d8.x/co~6d9.x'
-
-A = 'data/x.c/x.co/x.com0/x.com1/x.com2/x.com3/x.com4/x.com5/x.com6/x.com7/x.com8/x.com9'
-B = 'data/x.c/x.co/x.com0/x.com1/x.com2/x.com3/x.com4/x.com5/x.com6/x.com7/x.com8/x.com9'
-
-A = 'data/cx/cox/comx/com0x/com1x/com2x/com3x/com4x/com5x/com6x/com7x/com8x/com9x'
-B = 'data/cx/cox/comx/com0x/com1x/com2x/com3x/com4x/com5x/com6x/com7x/com8x/com9x'
-
-A = 'data/xc/xco/xcom0/xcom1/xcom2/xcom3/xcom4/xcom5/xcom6/xcom7/xcom8/xcom9'
-B = 'data/xc/xco/xcom0/xcom1/xcom2/xcom3/xcom4/xcom5/xcom6/xcom7/xcom8/xcom9'
-
-A = 'data/l/lp/lpt/lpt0/lpt1/lpt2/lpt3/lpt4/lpt5/lpt6/lpt7/lpt8/lpt9'
-B = 'data/l/lp/lpt/lpt0/lp~741/lp~742/lp~743/lp~744/lp~745/lp~746/lp~747/lp~748/lp~749'
-
-A = 'data/L/LP/LPT/LPT0/LPT1/LPT2/LPT3/LPT4/LPT5/LPT6/LPT7/LPT8/LPT9'
-B = 'data/_l/_l_p/_l_p_t/_l_p_t0/_l_p_t1/_l_p_t2/_l_p_t3/_l_p_t4/_l_p_t5/_l_p_t6/_l_p_t7/_l_p_t8/_l_p_t9'
-
-A = 'data/l.x/lp.x/lpt.x/lpt0.x/lpt1.x/lpt2.x/lpt3.x/lpt4.x/lpt5.x/lpt6.x/lpt7.x/lpt8.x/lpt9.x'
-B = 'data/l.x/lp.x/lpt.x/lpt0.x/lp~741.x/lp~742.x/lp~743.x/lp~744.x/lp~745.x/lp~746.x/lp~747.x/lp~748.x/lp~749.x'
-
-A = 'data/x.l/x.lp/x.lpt/x.lpt0/x.lpt1/x.lpt2/x.lpt3/x.lpt4/x.lpt5/x.lpt6/x.lpt7/x.lpt8/x.lpt9'
-B = 'data/x.l/x.lp/x.lpt/x.lpt0/x.lpt1/x.lpt2/x.lpt3/x.lpt4/x.lpt5/x.lpt6/x.lpt7/x.lpt8/x.lpt9'
-
-A = 'data/lx/lpx/lptx/lpt0x/lpt1x/lpt2x/lpt3x/lpt4x/lpt5x/lpt6x/lpt7x/lpt8x/lpt9x'
-B = 'data/lx/lpx/lptx/lpt0x/lpt1x/lpt2x/lpt3x/lpt4x/lpt5x/lpt6x/lpt7x/lpt8x/lpt9x'
-
-A = 'data/xl/xlp/xlpt/xlpt0/xlpt1/xlpt2/xlpt3/xlpt4/xlpt5/xlpt6/xlpt7/xlpt8/xlpt9'
-B = 'data/xl/xlp/xlpt/xlpt0/xlpt1/xlpt2/xlpt3/xlpt4/xlpt5/xlpt6/xlpt7/xlpt8/xlpt9'
-
-A = 'data/con/p/pr/prn/a/au/aux/n/nu/nul'
-B = 'data/co~6e/p/pr/pr~6e/a/au/au~78/n/nu/nu~6c'
-
-A = 'data/CON/P/PR/PRN/A/AU/AUX/N/NU/NUL'
-B = 'data/_c_o_n/_p/_p_r/_p_r_n/_a/_a_u/_a_u_x/_n/_n_u/_n_u_l'
-
-A = 'data/con.x/p.x/pr.x/prn.x/a.x/au.x/aux.x/n.x/nu.x/nul.x'
-B = 'data/co~6e.x/p.x/pr.x/pr~6e.x/a.x/au.x/au~78.x/n.x/nu.x/nu~6c.x'
-
-A = 'data/x.con/x.p/x.pr/x.prn/x.a/x.au/x.aux/x.n/x.nu/x.nul'
-B = 'data/x.con/x.p/x.pr/x.prn/x.a/x.au/x.aux/x.n/x.nu/x.nul'
-
-A = 'data/conx/px/prx/prnx/ax/aux/auxx/nx/nux/nulx'
-B = 'data/conx/px/prx/prnx/ax/au~78/auxx/nx/nux/nulx'
-
-A = 'data/xcon/xp/xpr/xprn/xa/xau/xaux/xn/xnu/xnul'
-B = 'data/xcon/xp/xpr/xprn/xa/xau/xaux/xn/xnu/xnul'
-
-A = 'data/a./au./aux./auxy./aux.'
-B = 'data/a~2e/au~2e/au~78~2e/auxy~2e/au~78~2e'
-
-A = 'data/c./co./con./cony./con.'
-B = 'data/c~2e/co~2e/co~6e~2e/cony~2e/co~6e~2e'
-
-A = 'data/p./pr./prn./prny./prn.'
-B = 'data/p~2e/pr~2e/pr~6e~2e/prny~2e/pr~6e~2e'
-
-A = 'data/n./nu./nul./nuly./nul.'
-B = 'data/n~2e/nu~2e/nu~6c~2e/nuly~2e/nu~6c~2e'
-
-A = 'data/l./lp./lpt./lpt1./lpt1y./lpt1.'
-B = 'data/l~2e/lp~2e/lpt~2e/lp~741~2e/lpt1y~2e/lp~741~2e'
-
-A = 'data/lpt9./lpt9y./lpt9.'
-B = 'data/lp~749~2e/lpt9y~2e/lp~749~2e'
-
-A = 'data/com./com1./com1y./com1.'
-B = 'data/com~2e/co~6d1~2e/com1y~2e/co~6d1~2e'
-
-A = 'data/com9./com9y./com9.'
-B = 'data/co~6d9~2e/com9y~2e/co~6d9~2e'
-
-A = 'data/a /au /aux /auxy /aux '
-B = 'data/a~20/au~20/aux~20/auxy~20/aux~20'
-
-largest unhashed path
-A = 'data/123456789-123456789-123456789-123456789-123456789-unhashed--xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'data/123456789-123456789-123456789-123456789-123456789-unhashed--xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-
-shortest hashed path
-A = 'data/123456789-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/123456789-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxe9c55002b50bf5181e7a6fc1f60b126e2a6fcf71'
-
-changing one char in part that's hashed away produces a different hash
-A = 'data/123456789-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxy-123456789-123456'
-B = 'dh/123456789-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxd24fa4455faf8a94350c18e5eace7c2bb17af706'
-
-uppercase hitting length limit due to encoding
-A = 'data/A23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxcbbc657029b41b94ed510d05feb6716a5c03bc6b'
-
-A = 'data/Z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxx938f32a725c89512833fb96b6602dd9ebff51ddd'
-
-compare with lowercase not hitting limit
-A = 'data/a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'data/a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-
-A = 'data/z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'data/z23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-
-not hitting limit with any of these
-A = 'data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&\'()+,-.;=[]^`{}xxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'data/abcdefghijklmnopqrstuvwxyz0123456789 !#%&\'()+,-.;=[]^`{}xxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-
-underbar hitting length limit due to encoding
-A = 'data/_23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/_23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxx9921a01af50feeabc060ce00eee4cba6efc31d2b'
-
-tilde hitting length limit due to encoding
-A = 'data/~23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~7e23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx9cec6f97d569c10995f785720044ea2e4227481b'
-
-Windows reserved characters hitting length limit
-A = 'data/<23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~3c23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxee67d8f275876ca1ef2500fc542e63c885c4e62d'
-
-A = 'data/>23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~3e23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx387a85a5b1547cc9136310c974df716818458ddb'
-
-A = 'data/:23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~3a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx2e4154fb571d13d22399c58cc4ef4858e4b75999'
-
-A = 'data/"23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~2223456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxfc7e3ec7b0687ee06ed8c32fef0eb0c1980259f5'
-
-A = 'data/\\23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~5c23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx944e1f2b7110687e116e0d151328ac648b06ab4a'
-
-A = 'data/|23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~7c23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx28b23dd3fd0242946334126ab62bcd772aac32f4'
-
-A = 'data/?23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~3f23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxa263022d3994d2143d98f94f431eef8b5e7e0f8a'
-
-A = 'data/*23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~2a23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx0e7e6020e3c00ba7bb7893d84ca2966fbf53e140'
-
-initial space hitting length limit
-A = 'data/ 23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~2023456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx92acbc78ef8c0b796111629a02601f07d8aec4ea'
-
-initial dot hitting length limit
-A = 'data/.23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~2e23456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxdbe19cc6505b3515ab9228cebf877ad07075168f'
-
-trailing space in filename hitting length limit
-A = 'data/123456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-1234 '
-B = 'dh/123456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxx0025dc73e04f97426db4893e3bf67d581dc6d066'
-
-trailing dot in filename hitting length limit
-A = 'data/123456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-1234.'
-B = 'dh/123456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxx85a16cf03ee7feba8a5abc626f1ba9886d01e89d'
-
-initial space in directory hitting length limit
-A = 'data/ x/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~20x/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx1b3a3b712b2ac00d6af14ae8b4c14fdbf904f516'
-
-initial dot in directory hitting length limit
-A = 'data/.x/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/~2ex/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx39dbc4c193a5643a8936fc69c3363cd7ac91ab14'
-
-trailing space in directory hitting length limit
-A = 'data/x /456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/x~20/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx2253c341df0b5290790ad312cd8499850f2273e5'
-
-trailing dot in directory hitting length limit
-A = 'data/x./456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/x~2e/456789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxcc0324d696d34562b44b5138db08ee1594ccc583'
-
-with directories that need direncoding, hitting length limit
-A = 'data/x.i/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/x.i.hg/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxa4c4399bdf81c67dbbbb7060aa0124d8dea94f74'
-
-A = 'data/x.d/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/x.d.hg/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxx1303fa90473b230615f5b3ea7b660e881ae5270a'
-
-A = 'data/x.hg/5789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/x.hg.hg/5789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxx26d724a8af68e7a4e4455e6602ea9adbd0eb801f'
-
-Windows reserved filenames, hitting length limit
-A = 'data/con/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/co~6e/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxc0794d4f4c605a2617900eb2563d7113cf6ea7d3'
-
-A = 'data/prn/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/pr~6e/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx64db876e1a9730e27236cb9b167aff942240e932'
-
-A = 'data/aux/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/au~78/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx8a178558405ca6fb4bbd75446dfa186f06751a0d'
-
-A = 'data/nul/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/nu~6c/56789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxc5e51b6fec1bd07bd243b053a0c3f7209855b886'
-
-A = 'data/com1/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/co~6d1/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx32f5f44ece3bb62b9327369ca84cc19c86259fcd'
-
-A = 'data/com9/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/co~6d9/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxx734360b28c66a3230f55849fe8926206d229f990'
-
-A = 'data/lpt1/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/lp~741/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxe6f16ab4b6b0637676b2842b3345c9836df46ef7'
-
-A = 'data/lpt9/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'dh/lp~749/6789-123456789-123456789-123456789-123456789-xxxxxxxxx-xxxxxxxxx-xxxxxa475814c51acead3e44f2ff801f0c4903f986157'
-
-non-reserved names, just not hitting limit
-A = 'data/123456789-123456789-123456789-123456789-123456789-/com/com0/lpt/lpt0/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-B = 'data/123456789-123456789-123456789-123456789-123456789-/com/com0/lpt/lpt0/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12345'
-
-hashed path with largest untruncated 1st dir
-A = 'data/12345678/-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/-123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxx4e9e9e384d00929a93b6835fbf976eb32321ff3c'
-
-hashed path with smallest truncated 1st dir
-A = 'data/123456789/123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/123456789-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxx1f4e4ec5f2be76e109bfaa8e31c062fe426d5490'
-
-hashed path with largest untruncated two dirs
-A = 'data/12345678/12345678/9-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/9-123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxx3332d8329d969cf835542a9f2cbcfb385b6cf39d'
-
-hashed path with smallest truncated two dirs
-A = 'data/123456789/123456789/123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/123456789-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx9699559798247dffa18717138859be5f8874840e'
-
-hashed path with largest untruncated three dirs
-A = 'data/12345678/12345678/12345678/89-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/89-123456789-123456789-hashed----xxxxxxxxx-xxxxxxxf0a2b053bb1369cce02f78c217d6a7aaea18c439'
-
-hashed path with smallest truncated three dirs
-A = 'data/123456789/123456789/123456789/123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/123456789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-1c6f8284967384ec13985a046d3553179d9d03cd'
-
-hashed path with largest untruncated four dirs
-A = 'data/12345678/12345678/12345678/12345678/789-123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/789-123456789-hashed----xxxxxxxxx-xxxxxxx0d30c99049d8f0ff97b94d4ef302027e8d54c6fd'
-
-hashed path with smallest truncated four dirs
-A = 'data/123456789/123456789/123456789/123456789/123456789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/123456789-hashed----xxxxxxxxx-xxxxxxxxx-x46162779e1a771810b37a737f82ae7ed33771402'
-
-hashed path with largest untruncated five dirs
-A = 'data/12345678/12345678/12345678/12345678/12345678/6789-hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/6789-hashed----xxxxxxxxx-xxxxxxxbfe752ddc8b003c2790c66a9f2eb1ea75c114390'
-
-hashed path with smallest truncated five dirs
-A = 'data/123456789/123456789/123456789/123456789/123456789/hashed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/hashed----xxxxxxxxx-xxxxxxxxx-xxb94c27b3532fa880cdd572b1c514785cab7b6ff2'
-
-hashed path with largest untruncated six dirs
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/ed----xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/ed----xxxxxxxxx-xxxxxxxcd8cc5483a0f3be409e0e5d4bf9e36e113c59235'
-
-hashed path with smallest truncated six dirs
-A = 'data/123456789/123456789/123456789/123456789/123456789/123456789/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxxxx-xxxxxxxxx-xxx47dd6f616f833a142da00701b334cebbf640da06'
-
-hashed path with largest untruncated seven dirs
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxx-xxxxxxx1c8ed635229fc22efe51035feeadeb4c8a0ecb82'
-
-hashed path with smallest truncated seven dirs
-A = 'data/123456789/123456789/123456789/123456789/123456789/123456789/123456789/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxxxx-xxxx298ff7d33f8ce6db57930837ffea2fb2f48bb926'
-
-hashed path with largest untruncated eight dirs
-(directory 8 is dropped because it hits _maxshortdirslen)
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxx-xxxxxxc8996ccd41b471f768057181a4d59d2febe7277d'
-
-hashed path with smallest truncated eight dirs
-(directory 8 is dropped because it hits _maxshortdirslen)
-A = 'data/123456789/123456789/123456789/123456789/123456789/123456789/123456789/123456789/xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxxxx-xxxx4fa04a839a6bda93e1c21c713f2edcbd16e8890d'
-
-hashed path with largest non-dropped directory 8
-(just not hitting the _maxshortdirslen boundary)
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxx4d43d1ccaa20efbfe99ec779dc063611536ff2c5'
-
-...adding one truncated char to dir 1..7 won't drop dir 8
-A = 'data/12345678x/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxx0f9efce65189cc60fd90fe4ffd49d7b58bbe0f2e'
-
-A = 'data/12345678/12345678x/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxx945ca395708cafdd54a94501859beabd3e243921'
-
-A = 'data/12345678/12345678/12345678x/12345678/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxac62bf6898c4fd0502146074547c11caa751a327'
-
-A = 'data/12345678/12345678/12345678/12345678x/12345678/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxx2ae5a2baed7983fae8974d0ca06c6bf08b9aee92'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678x/12345678/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxx214aba07b6687532a43d1e9eaf6e88cfca96b68c'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678x/12345678/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxe7a022ae82f0f55cf4e0498e55ba59ea4ebb55bf'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678x/12345/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/xxxxxxxxb51ce61164996a80f36ce3cfe64b62d519aedae3'
-
-hashed path with shortest dropped directory 8
-(just hitting the _maxshortdirslen boundary)
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/123456/xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/xxxxxxxxx-xxxx11fa9873cc6c3215eae864528b5530a04efc6cfe'
-
-hashed path that drops dir 8 due to dot or space at end is
-encoded, and thus causing to hit _maxshortdirslen
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/1234./-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/-xxxxxxxxx-xxx602df9b45bec564e2e1f0645d5140dddcc76ed58'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/1234 /-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/-xxxxxxxxx-xxxd99ff212bc84b4d1f70cd6b0071e3ef69d4e12ce'
-
-... with dir 8 short enough for encoding
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12./xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12~2e/xx-xxxxx7baeb5ed7f14a586ee1cacecdbcbff70032d1b3c'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12 /xx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-123456'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12~20/xx-xxxxxcf79ca9795f77d7f75745da36807e5d772bd5182'
-
-Extensions are replicated on hashed paths. Note that
-we only get to encode files that end in .i or .d inside the
-store. Encoded filenames are thus bound in length.
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.345.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxc10ad03b5755ed524f5286aab1815dfe07729438.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.345.d'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx9eec83381f2b39ef5ac8b4ecdf2c94f7983f57c8.d'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxb7796dc7d175cfb0bb8a7728f58f6ebec9042568.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.34567.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxb515857a6bfeef017c4894d8df42458ac65d55b8.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.345678.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxb05a0f247bc0a776211cd6a32ab714fd9cc09f2b.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxf192b48bff08d9e0e12035fb52bc58c70de72c94.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx435551e0ed4c7b083b9ba83cee916670e02e80ad.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-1.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxa7f74eb98d8d58b716356dfd26e2f9aaa65d6a9a.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxed68d9bd43b931f0b100267fee488d65a0c66f62.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-123.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx5cea44de2b642d2ba2b4a30693ffb1049644d698.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-1234.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx68462f62a7f230b39c1b5400d73ec35920990b7e.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx4cb852a314c6da240a83eec94761cdd71c6ec22e.i'
-
-A = 'data/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-123456789-12.3456789-12345-ABCDEFGHIJKLMNOPRSTUVWXYZ-abcdefghjiklmnopqrstuvwxyz-ABCDEFGHIJKLMNOPRSTUVWXYZ-1234567890-xxxxxxxxx-xxxxxxxxx-xxxxxxxx-xxxxxxxxx-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww-wwwwwwwww.i'
-B = 'dh/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345/-xxxxx93352aa50377751d9e5ebdf52da1e6e69a6887a6.i'
-
-paths outside data/ can be encoded
-A = 'metadata/dir/00manifest.i'
-B = 'metadata/dir/00manifest.i'
-
-A = 'metadata/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/12345678/00manifest.i'
-B = 'dh/ata/12345678/12345678/12345678/12345678/12345678/12345678/12345678/00manife0a4da1f89aa2aa9eb0896eb451288419049781b4.i'
-
--- a/tests/test-impexp-branch.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-impexp-branch.t	Thu Jul 19 13:55:54 2018 -0400
@@ -75,8 +75,8 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   >>> import re
   >>> p = open('../r1.patch', 'rb').read()
-  >>> p = re.sub(r'Parent\s+', 'Parent ', p)
-  >>> open('../r1-ws.patch', 'wb').write(p)
+  >>> p = re.sub(br'Parent\s+', b'Parent ', p)
+  >>> open('../r1-ws.patch', 'wb').write(p) and None
   $ hg import --exact ../r1-ws.patch
   applying ../r1-ws.patch
 
--- a/tests/test-import-context.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-import-context.t	Thu Jul 19 13:55:54 2018 -0400
@@ -10,7 +10,7 @@
   > fp = open(path, 'wb')
   > for i, pattern in enumerate(patterns):
   >     count = int(pattern[0:-1])
-  >     char = pattern[-1] + '\n'
+  >     char = pattern[-1].encode('utf8') + b'\n'
   >     if not lasteol and i == len(patterns) - 1:
   >         fp.write((char*count)[:-1])
   >     else:
--- a/tests/test-import-git.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-import-git.t	Thu Jul 19 13:55:54 2018 -0400
@@ -566,7 +566,7 @@
   >>> fp = open('binary.diff', 'rb')
   >>> data = fp.read()
   >>> fp.close()
-  >>> open('binary.diff', 'wb').write(data.replace(b'\n', b'\r\n'))
+  >>> open('binary.diff', 'wb').write(data.replace(b'\n', b'\r\n')) and None
   $ rm binary2
   $ hg import --no-commit binary.diff
   applying binary.diff
--- a/tests/test-import-merge.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-import-merge.t	Thu Jul 19 13:55:54 2018 -0400
@@ -143,7 +143,7 @@
   $ hg export 2 | head -7 > ../a.patch
   $ hg export tip > out
   >>> apatch = open("../a.patch", "ab")
-  >>> apatch.write("".join(open("out").readlines()[7:]))
+  >>> apatch.write(b"".join(open("out", 'rb').readlines()[7:])) and None
 
   $ cd ..
   $ hg clone -qr0 repo3 repo3-clone
--- a/tests/test-known.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-known.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 = Test the known() protocol function =
 
 Create a test repository:
--- a/tests/test-largefiles-wireproto.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-largefiles-wireproto.t	Thu Jul 19 13:55:54 2018 -0400
@@ -26,7 +26,7 @@
   > patterns=glob:**.dat
   > usercache=${USERCACHE}
   > [web]
-  > allow_archive = zip
+  > allow-archive = zip
   > [hooks]
   > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
   > EOF
--- a/tests/test-largefiles.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-largefiles.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1860,6 +1860,8 @@
   $ hg add --normal new-largefile
   new-largefile: up to 69 MB of RAM may be required to manage this file
   (use 'hg revert new-largefile' to cancel the pending addition)
+  $ hg revert new-largefile
+  $ hg --config ui.large-file-limit=22M add --normal new-largefile
 
 Test explicit commit of switch between normal and largefile - make sure both
 the add and the remove is committed.
--- a/tests/test-lfs-largefiles.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-lfs-largefiles.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require no-reposimplestore
+#require no-reposimplestore no-chg
 
 This tests the interaction between the largefiles and lfs extensions, and
 conversion from largefiles -> lfs.
--- a/tests/test-lfs-pointer.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-lfs-pointer.py	Thu Jul 19 13:55:54 2018 -0400
@@ -6,6 +6,10 @@
 # make it runnable using python directly without run-tests.py
 sys.path[0:0] = [os.path.join(os.path.dirname(__file__), '..')]
 
+# Import something from Mercurial, so the module loader gets initialized.
+from mercurial import pycompat
+del pycompat  # unused for now
+
 from hgext.lfs import pointer
 
 def tryparse(text):
@@ -14,28 +18,28 @@
         r = pointer.deserialize(text)
         print('ok')
     except Exception as ex:
-        print(ex)
+        print((b'%s' % ex).decode('ascii'))
     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')
+t = (b'version https://git-lfs.github.com/spec/v1\n'
+     b'oid sha256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1'
+     b'258daaa5e2ca24d17e2393\n'
+     b'size 12345\n'
+     b'x-foo extra-information\n')
 
-tryparse('')
+tryparse(b'')
 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'))
+tryparse(t.replace(b'git-lfs', b'unknown'))
+tryparse(t.replace(b'v1\n', b'v1\n\n'))
+tryparse(t.replace(b'sha256', b'ahs256'))
+tryparse(t.replace(b'sha256:', b''))
+tryparse(t.replace(b'12345', b'0x12345'))
+tryparse(t.replace(b'extra-information', b'extra\0information'))
+tryparse(t.replace(b'extra-information', b'extra\ninformation'))
+tryparse(t.replace(b'x-foo', b'x_foo'))
+tryparse(t.replace(b'oid', b'blobid'))
+tryparse(t.replace(b'size', b'size-bytes').replace(b'oid', b'object-id'))
--- a/tests/test-lfs-pointer.py.out	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-lfs-pointer.py.out	Thu Jul 19 13:55:54 2018 -0400
@@ -1,12 +1,12 @@
-missed keys: oid, size
+missing lfs pointer keys: oid, size
 ok
-unexpected value: version='https://unknown.github.com/spec/v1'
+unexpected lfs pointer 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'
+unexpected lfs pointer value: oid='ahs256:4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393'
+unexpected lfs pointer value: oid='4d7a214614ab2935c943f9e0ff69d22eadbb8f32b1258daaa5e2ca24d17e2393'
+unexpected lfs pointer 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
+unexpected lfs pointer key: x_foo
+missing lfs pointer keys: oid
+missing lfs pointer keys: oid, size
--- a/tests/test-lfs-serve-access.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-lfs-serve-access.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require serve no-reposimplestore
+#require serve no-reposimplestore no-chg
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-lfs-serve.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-lfs-serve.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,5 @@
 #testcases lfsremote-on lfsremote-off
-#require serve no-reposimplestore
+#require serve no-reposimplestore no-chg
 
 This test splits `hg serve` with and without using the extension into separate
 tests cases.  The tests are broken down as follows, where "LFS"/"No-LFS"
--- a/tests/test-lfs-test-server.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-lfs-test-server.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require no-reposimplestore
+#require no-reposimplestore no-chg
 #testcases git-server hg-server
 
 #if git-server
@@ -880,7 +880,6 @@
   $LOCALIP - - [$LOGDATE$] "POST /.git/info/lfs/objects/batch HTTP/1.1" 200 - (glob)
 #endif
 
-  $ rm $DAEMON_PIDS
   $ mkdir $TESTTMP/lfs-server2
   $ cd $TESTTMP/lfs-server2
 #if no-windows git-server
--- a/tests/test-lfs.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-lfs.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require no-reposimplestore
+#require no-reposimplestore no-chg
 
 # Initial setup
 
@@ -515,6 +515,21 @@
   d: binary=False
   b55353847f02 tip
 
+Binary blobs don't need to be present to be skipped in filesets.  (And their
+absence doesn't cause an abort.)
+
+  $ rm .hg/store/lfs/objects/96/a296d224f285c67bee93c30f8a309157f0daa35dc5b87e410b78630a09cfc7
+  $ rm .hg/store/lfs/objects/92/f76135a4baf4faccb8586a60faf830c2bdfce147cefa188aaf4b790bd01b7e
+
+  $ hg files --debug -r . 'set:eol("unix")' --config 'experimental.lfs.disableusercache=True'
+  lfs: found c04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b7adeeafb5474c4adae9faa80 in the local lfs store
+           2   b
+  lfs: found 5dde896887f6754c9b15bfe3a441ae4806df2fde94001311e08bf110622e0bbe in the local lfs store
+
+  $ hg files --debug -r . 'set:binary()' --config 'experimental.lfs.disableusercache=True'
+           2   a
+           3   c
+
   $ cd ..
 
 # Test fctx.cmp fastpath - diff without LFS blobs
--- a/tests/test-log.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-log.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1943,7 +1943,7 @@
 test that parent prevent a changeset to be hidden
 
   $ hg up 1 -q --hidden
-  updating to a hidden changeset a765632148dc
+  updated to hidden changeset a765632148dc
   (hidden revision 'a765632148dc' is pruned)
   $ hg log --template='{rev}:{node}\n'
   1:a765632148dc55d38c35c4f247c618701886cb2f
--- a/tests/test-logexchange.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-logexchange.t	Thu Jul 19 13:55:54 2018 -0400
@@ -2,10 +2,10 @@
 =============================================
 
   $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > ssh = $PYTHON "$TESTDIR/dummyssh"
   > [alias]
   > glog = log -G -T '{rev}:{node|short}  {desc}'
-  > [experimental]
-  > remotenames = True
   > [extensions]
   > remotenames =
   > show =
@@ -52,7 +52,13 @@
 
   $ cd ..
 
-  $ hg clone server client
+  $ hg clone ssh://user@dummy/server client
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 9 changesets with 9 changes to 9 files (+1 heads)
+  new changesets 18d04c59bb5d:3e1487808078
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -333,3 +339,59 @@
      default/bar               6:87d6d6676308
      default/foo               3:62615734edd5
    * foo                       8:3e1487808078
+
+Testing the remotenames sychronization during `hg push`
+-------------------------------------------------------
+
+  $ cd ../server/
+  $ hg bookmark foo
+  moving bookmark 'foo' forward from 62615734edd5
+
+After the push, default/foo should move to rev 8
+  $ cd ../client/
+  $ hg push
+  pushing to ssh://user@dummy/server
+  searching for changes
+  no changes found
+  [1]
+  $ hg log -Gr 'remotenames()'
+  @  changeset:   8:3e1487808078
+  :  branch:      wat
+  :  bookmark:    foo
+  :  tag:         tip
+  :  remote bookmark:  default/foo
+  :  hoisted name:  foo
+  :  remote branch:  $TESTTMP/server2/wat
+  :  remote branch:  default/wat
+  :  parent:      4:aa98ab95a928
+  :  user:        test
+  :  date:        Thu Jan 01 00:00:00 1970 +0000
+  :  summary:     added bar
+  :
+  : o  changeset:   7:ec2426147f0e
+  : |  remote branch:  $TESTTMP/server2/default
+  : |  remote branch:  default/default
+  : |  user:        test
+  : |  date:        Thu Jan 01 00:00:00 1970 +0000
+  : |  summary:     Added h
+  : |
+  : o  changeset:   6:87d6d6676308
+  :/   remote bookmark:  $TESTTMP/server2/bar
+  :    remote bookmark:  default/bar
+  :    hoisted name:  bar
+  :    user:        test
+  :    date:        Thu Jan 01 00:00:00 1970 +0000
+  :    summary:     Added g
+  :
+  o  changeset:   3:62615734edd5
+  |  remote bookmark:  $TESTTMP/server2/foo
+  ~  user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     Added d
+  
+  $ hg bookmarks
+     $TESTTMP/server2/bar 6:87d6d6676308
+     $TESTTMP/server2/foo 3:62615734edd5
+     default/bar               6:87d6d6676308
+     default/foo               8:3e1487808078
+   * foo                       8:3e1487808078
--- a/tests/test-manifest.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-manifest.t	Thu Jul 19 13:55:54 2018 -0400
@@ -37,11 +37,23 @@
   $ hg files -r . -X b
   a
   l
+  $ hg files -T '{path} {size} {flags}\n'
+  a 2 
+  b/a 2 x
+  l 1 l
+  $ hg files -T '{path} {node|shortest}\n' -r.
+  a 5bdc
+  b/a 5bdc
+  l 5bdc
 
   $ hg manifest -v
   644   a
   755 * b/a
   644 @ l
+  $ hg manifest -T '{path} {rev}\n'
+  a 1
+  b/a 1
+  l 1
 
   $ hg manifest --debug
   b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644   a
--- a/tests/test-merge-criss-cross.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-merge-criss-cross.t	Thu Jul 19 13:55:54 2018 -0400
@@ -410,6 +410,21 @@
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
+Test the greatest common ancestor returning multiple changesets
+
+  $ hg log -r 'heads(commonancestors(head()))'
+  changeset:   1:0f6b37dbe527
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1 first change f1
+  
+  changeset:   2:d1d156401c1b
+  parent:      0:40494bf2444c
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 first change f2
+  
+
   $ cd ..
 
 http://stackoverflow.com/questions/9350005/how-do-i-specify-a-merge-base-to-use-in-a-hg-merge/9430810
--- a/tests/test-merge-tools.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-merge-tools.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,7 +1,8 @@
 test merge-tools configuration - mostly exercising filemerge.py
 
   $ unset HGMERGE # make sure HGMERGE doesn't interfere with the test
-  $ hg init
+  $ hg init repo
+  $ cd repo
 
 revision 0
 
@@ -328,6 +329,183 @@
   # hg resolve --list
   R f
 
+executable set to python script that succeeds:
+
+  $ cat > "$TESTTMP/myworkingmerge.py" <<EOF
+  > def myworkingmergefn(ui, repo, args, **kwargs):
+  >     return False
+  > EOF
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:myworkingmergefn"
+  merging f
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  M f
+  # hg resolve --list
+  R f
+
+executable set to python script that fails:
+
+  $ cat > "$TESTTMP/mybrokenmerge.py" <<EOF
+  > def mybrokenmergefn(ui, repo, args, **kwargs):
+  >     ui.write(b"some fail message\n")
+  >     return True
+  > EOF
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/mybrokenmerge.py:mybrokenmergefn"
+  merging f
+  some fail message
+  abort: $TESTTMP/mybrokenmerge.py hook failed
+  [255]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  ? f.orig
+  # hg resolve --list
+  U f
+
+executable set to python script that is missing function:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:missingFunction"
+  merging f
+  abort: $TESTTMP/myworkingmerge.py does not have function: missingFunction
+  [255]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  ? f.orig
+  # hg resolve --list
+  U f
+
+executable set to missing python script:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/missingpythonscript.py:mergefn"
+  merging f
+  abort: loading python merge script failed: $TESTTMP/missingpythonscript.py
+  [255]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  ? f.orig
+  # hg resolve --list
+  U f
+
+executable set to python script but callable function is missing:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py"
+  abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py
+  [255]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  # hg resolve --list
+  U f
+
+executable set to python script but callable function is empty string:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/myworkingmerge.py:"
+  abort: invalid 'python:' syntax: python:$TESTTMP/myworkingmerge.py:
+  [255]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  # hg resolve --list
+  U f
+
+executable set to python script but callable function is missing and path contains colon:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config merge-tools.true.executable="python:$TESTTMP/some:dir/myworkingmerge.py"
+  abort: invalid 'python:' syntax: python:$TESTTMP/some:dir/myworkingmerge.py
+  [255]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  # hg resolve --list
+  U f
+
+executable set to python script filename that contains spaces:
+
+  $ mkdir -p "$TESTTMP/my path"
+  $ cat > "$TESTTMP/my path/my working merge with spaces in filename.py" <<EOF
+  > def myworkingmergefn(ui, repo, args, **kwargs):
+  >     return False
+  > EOF
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config "merge-tools.true.executable=python:$TESTTMP/my path/my working merge with spaces in filename.py:myworkingmergefn"
+  merging f
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  M f
+  # hg resolve --list
+  R f
+
 #if unix-permissions
 
 environment variables in true.executable are handled:
@@ -1446,7 +1624,7 @@
   merging f
   labellocal: "working copy"
   labelother: "merge rev"
-  output (arg): "$TESTTMP/f"
+  output (arg): "$TESTTMP/repo/f"
   output (contents):
   <<<<<<< working copy: uitmpl 1
   revision 1
@@ -1485,7 +1663,7 @@
   merging f
   labellocal: "working copy: tooltmpl ef83787e2614"
   labelother: "merge rev: tooltmpl 0185f4e0cf02"
-  output (arg): "$TESTTMP/f"
+  output (arg): "$TESTTMP/repo/f"
   output (contents):
   <<<<<<< working copy: tooltmpl ef83787e2614
   revision 1
@@ -1585,7 +1763,7 @@
   $ hg update -q -C 2
   $ hg merge -y -r tip --tool echo --config merge-tools.echo.args='$base $local $other $output'
   merging f and f.txt to f.txt
-  */f~base.* */f~local.*.txt */f~other.*.txt $TESTTMP/f.txt (glob)
+  */f~base.* */f~local.*.txt */f~other.*.txt $TESTTMP/repo/f.txt (glob)
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
@@ -1600,7 +1778,7 @@
   >    --config merge-tools.echo.args='$base $local $other $output' \
   >    --config experimental.mergetempdirprefix=$TESTTMP/hgmerge.
   merging f and f.txt to f.txt
-  $TESTTMP/hgmerge.*/f~base $TESTTMP/hgmerge.*/f~local.txt $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/f.txt (glob)
+  $TESTTMP/hgmerge.*/f~base $TESTTMP/hgmerge.*/f~local.txt $TESTTMP/hgmerge.*/f~other.txt $TESTTMP/repo/f.txt (glob)
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
@@ -1668,3 +1846,5 @@
   couldn't find merge tool true (for pattern f)
   couldn't find merge tool true
   f = false
+
+  $ cd ..
--- a/tests/test-merge6.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-merge6.t	Thu Jul 19 13:55:54 2018 -0400
@@ -42,6 +42,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   new changesets b90e70beeb58
+  1 local changesets published
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-minifileset.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-minifileset.py	Thu Jul 19 13:55:54 2018 -0400
@@ -18,21 +18,24 @@
         if f(*args):
             print('unexpected: %r should exclude %r' % (text, args))
 
-check('all()', [('a.php', 123), ('b.txt', 0)], [])
-check('none()', [], [('a.php', 123), ('b.txt', 0)])
-check('!!!!((!(!!all())))', [], [('a.php', 123), ('b.txt', 0)])
+check(b'all()', [(b'a.php', 123), (b'b.txt', 0)], [])
+check(b'none()', [], [(b'a.php', 123), (b'b.txt', 0)])
+check(b'!!!!((!(!!all())))', [], [(b'a.php', 123), (b'b.txt', 0)])
 
-check('"path:a" & (**.b | **.c)', [('a/b.b', 0), ('a/c.c', 0)], [('b/c.c', 0)])
-check('(path:a & **.b) | **.c',
-      [('a/b.b', 0), ('a/c.c', 0), ('b/c.c', 0)], [])
+check(b'"path:a" & (**.b | **.c)',
+      [(b'a/b.b', 0), (b'a/c.c', 0)], [(b'b/c.c', 0)])
+check(b'(path:a & **.b) | **.c',
+      [(b'a/b.b', 0), (b'a/c.c', 0), (b'b/c.c', 0)], [])
 
-check('**.bin - size("<20B")', [('b.bin', 21)], [('a.bin', 11), ('b.txt', 21)])
+check(b'**.bin - size("<20B")',
+      [(b'b.bin', 21)], [(b'a.bin', 11), (b'b.txt', 21)])
 
-check('!!**.bin or size(">20B") + "path:bin" or !size(">10")',
-      [('a.bin', 11), ('b.txt', 21), ('bin/abc', 11)],
-      [('a.notbin', 11), ('b.txt', 11), ('bin2/abc', 11)])
+check(b'!!**.bin or size(">20B") + "path:bin" or !size(">10")',
+      [(b'a.bin', 11), (b'b.txt', 21), (b'bin/abc', 11)],
+      [(b'a.notbin', 11), (b'b.txt', 11), (b'bin2/abc', 11)])
 
-check('(**.php and size(">10KB")) | **.zip | ("path:bin" & !"path:bin/README") '
-      ' | size(">1M")',
-      [('a.php', 15000), ('a.zip', 0), ('bin/a', 0), ('bin/README', 1e7)],
-      [('a.php', 5000), ('b.zip2', 0), ('t/bin/a', 0), ('bin/README', 1)])
+check(
+    b'(**.php and size(">10KB")) | **.zip | ("path:bin" & !"path:bin/README") '
+    b' | size(">1M")',
+    [(b'a.php', 15000), (b'a.zip', 0), (b'bin/a', 0), (b'bin/README', 1e7)],
+    [(b'a.php', 5000), (b'b.zip2', 0), (b't/bin/a', 0), (b'bin/README', 1)])
--- a/tests/test-minirst.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-minirst.py	Thu Jul 19 13:55:54 2018 -0400
@@ -1,11 +1,13 @@
 from __future__ import absolute_import, print_function
-import pprint
 from mercurial import (
     minirst,
 )
+from mercurial.utils import (
+    stringutil,
+)
 
 def debugformat(text, form, **kwargs):
-    if form == 'html':
+    if form == b'html':
         print("html format:")
         out = minirst.format(text, style=form, **kwargs)
     else:
@@ -14,11 +16,11 @@
 
     print("-" * 70)
     if type(out) == tuple:
-        print(out[0][:-1])
+        print(out[0][:-1].decode('utf8'))
         print("-" * 70)
-        pprint.pprint(out[1])
+        print(stringutil.pprint(out[1]).decode('utf8'))
     else:
-        print(out[:-1])
+        print(out[:-1].decode('utf8'))
     print("-" * 70)
     print()
 
@@ -26,7 +28,7 @@
     print("== %s ==" % title)
     debugformat(text, 60, **kwargs)
     debugformat(text, 30, **kwargs)
-    debugformat(text, 'html', **kwargs)
+    debugformat(text, b'html', **kwargs)
 
 paragraphs = b"""
 This is some text in the first paragraph.
@@ -37,7 +39,7 @@
  \n  \n   \nThe third and final paragraph.
 """
 
-debugformats(b'paragraphs', paragraphs)
+debugformats('paragraphs', paragraphs)
 
 definitions = b"""
 A Term
@@ -52,7 +54,7 @@
     Definition.
 """
 
-debugformats(b'definitions', definitions)
+debugformats('definitions', definitions)
 
 literals = br"""
 The fully minimized form is the most
@@ -76,7 +78,7 @@
       with '::' disappears in the final output.
 """
 
-debugformats(b'literals', literals)
+debugformats('literals', literals)
 
 lists = b"""
 - This is the first list item.
@@ -127,7 +129,7 @@
 * This is the third bullet
 """
 
-debugformats(b'lists', lists)
+debugformats('lists', lists)
 
 options = b"""
 There is support for simple option lists,
@@ -153,7 +155,7 @@
 --foo bar baz
 """
 
-debugformats(b'options', options)
+debugformats('options', options)
 
 fields = b"""
 :a: First item.
@@ -166,7 +168,7 @@
 :much too large: This key is big enough to get its own line.
 """
 
-debugformats(b'fields', fields)
+debugformats('fields', fields)
 
 containers = b"""
 Normal output.
@@ -184,14 +186,14 @@
       Debug output.
 """
 
-debugformats(b'containers (normal)', containers)
-debugformats(b'containers (verbose)', containers, keep=['verbose'])
-debugformats(b'containers (debug)', containers, keep=['debug'])
-debugformats(b'containers (verbose debug)', containers,
-            keep=['verbose', 'debug'])
+debugformats('containers (normal)', containers)
+debugformats('containers (verbose)', containers, keep=[b'verbose'])
+debugformats('containers (debug)', containers, keep=[b'debug'])
+debugformats('containers (verbose debug)', containers,
+            keep=[b'verbose', b'debug'])
 
 roles = b"""Please see :hg:`add`."""
-debugformats(b'roles', roles)
+debugformats('roles', roles)
 
 
 sections = b"""
@@ -207,7 +209,7 @@
 Markup: ``foo`` and :hg:`help`
 ------------------------------
 """
-debugformats(b'sections', sections)
+debugformats('sections', sections)
 
 
 admonitions = b"""
@@ -225,7 +227,7 @@
    This is danger
 """
 
-debugformats(b'admonitions', admonitions)
+debugformats('admonitions', admonitions)
 
 comments = b"""
 Some text.
@@ -241,7 +243,7 @@
 Empty comment above
 """
 
-debugformats(b'comments', comments)
+debugformats('comments', comments)
 
 
 data = [[b'a', b'b', b'c'],
@@ -251,9 +253,9 @@
 rst = minirst.maketable(data, 2, True)
 table = b''.join(rst)
 
-print(table)
+print(table.decode('utf8'))
 
-debugformats(b'table', table)
+debugformats('table', table)
 
 data = [[b's', b'long', b'line\ngoes on here'],
         [b'', b'xy', b'tried to fix here\n        by indenting']]
@@ -261,7 +263,6 @@
 rst = minirst.maketable(data, 1, False)
 table = b''.join(rst)
 
-print(table)
+print(table.decode('utf8'))
 
-debugformats(b'table+nl', table)
-
+debugformats('table+nl', table)
--- a/tests/test-mq-qclone-http.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-mq-qclone-http.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 hide outer repo
   $ hg init
 
--- a/tests/test-mq-qimport.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-mq-qimport.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
   $ cat > writelines.py <<EOF
   > import sys
   > path = sys.argv[1]
--- a/tests/test-narrow-commit.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-narrow-commit.t	Thu Jul 19 13:55:54 2018 -0400
@@ -49,8 +49,9 @@
 
   $ mkdir outside
   $ touch outside/f1
-  $ hg debugwalk -I 'relglob:f1'
-  matcher: <includematcher includes='(?:(?:|.*/)f1(?:/|$))'>
+  $ hg debugwalk -v -I 'relglob:f1'
+  * matcher:
+  <includematcher includes='(?:(?:|.*/)f1(?:/|$))'>
   f  inside/f1  inside/f1
   $ hg add outside/f1
   abort: cannot track 'outside/f1' - it is outside the narrow clone
--- a/tests/test-narrow-expanddirstate.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-narrow-expanddirstate.t	Thu Jul 19 13:55:54 2018 -0400
@@ -72,29 +72,31 @@
   >   for f in repo[b'.'].manifest().walk(added):
   >     repo.dirstate.normallookup(f)
   > 
-  > def makeds(ui, repo):
-  >   def wrapds(orig, self):
-  >     ds = orig(self)
-  >     class expandingdirstate(ds.__class__):
-  >       @hgutil.propertycache
-  >       def _map(self):
-  >         ret = super(expandingdirstate, self)._map
-  >         with repo.wlock(), repo.lock(), repo.transaction(
-  >             b'expandnarrowspec'):
-  >           expandnarrowspec(ui, repo,
-  >                            encoding.environ.get(b'DIRSTATEINCLUDES'))
-  >         return ret
-  >     ds.__class__ = expandingdirstate
-  >     return ds
-  >   return wrapds
+  > def wrapds(ui, repo, ds):
+  >   class expandingdirstate(ds.__class__):
+  >     @hgutil.propertycache
+  >     def _map(self):
+  >       ret = super(expandingdirstate, self)._map
+  >       with repo.wlock(), repo.lock(), repo.transaction(
+  >           b'expandnarrowspec'):
+  >         expandnarrowspec(ui, repo,
+  >                          encoding.environ.get(b'DIRSTATEINCLUDES'))
+  >       return ret
+  >   ds.__class__ = expandingdirstate
+  >   return ds
   > 
   > def reposetup(ui, repo):
-  >   extensions.wrapfilecache(localrepo.localrepository, b'dirstate',
-  >                            makeds(ui, repo))
-  >   def overridepatch(orig, *args, **kwargs):
+  >   class expandingrepo(repo.__class__):
+  >     def _makedirstate(self):
+  >       dirstate = super(expandingrepo, self)._makedirstate()
+  >       return wrapds(ui, repo, dirstate)
+  >   repo.__class__ = expandingrepo
+  > 
+  > def extsetup(unused_ui):
+  >   def overridepatch(orig, ui, repo, *args, **kwargs):
   >     with repo.wlock():
   >       expandnarrowspec(ui, repo, encoding.environ.get(b'PATCHINCLUDES'))
-  >       return orig(*args, **kwargs)
+  >       return orig(ui, repo, *args, **kwargs)
   > 
   >   extensions.wrapfunction(patch, b'patch', overridepatch)
   > EOF
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-nointerrupt.t	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,83 @@
+Dummy extension simulating unsafe long running command
+  $ cat > sleepext.py <<EOF
+  > import itertools
+  > import time
+  > 
+  > from mercurial.i18n import _
+  > from mercurial import registrar
+  > 
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > 
+  > @command(b'sleep', [], _(b'TIME'), norepo=True)
+  > def sleep(ui, sleeptime=b"1", **opts):
+  >     with ui.uninterruptable():
+  >         for _i in itertools.repeat(None, int(sleeptime)):
+  >             time.sleep(1)
+  >         ui.warn(b"end of unsafe operation\n")
+  >     ui.warn(b"%s second(s) passed\n" % sleeptime)
+  > EOF
+
+Kludge to emulate timeout(1) which is not generally available.
+  $ cat > timeout.py <<EOF
+  > from __future__ import print_function
+  > import argparse
+  > import signal
+  > import subprocess
+  > import sys
+  > import time
+  > 
+  > ap = argparse.ArgumentParser()
+  > ap.add_argument('-s', nargs=1, default='SIGTERM')
+  > ap.add_argument('duration', nargs=1, type=int)
+  > ap.add_argument('argv', nargs='*')
+  > opts = ap.parse_args()
+  > try:
+  >     sig = int(opts.s[0])
+  > except ValueError:
+  >     sname = opts.s[0]
+  >     if not sname.startswith('SIG'):
+  >         sname = 'SIG' + sname
+  >     sig = getattr(signal, sname)
+  > proc = subprocess.Popen(opts.argv)
+  > time.sleep(opts.duration[0])
+  > proc.poll()
+  > if proc.returncode is None:
+  >     proc.send_signal(sig)
+  >     proc.wait()
+  >     sys.exit(124)
+  > EOF
+
+Set up repository
+  $ hg init repo
+  $ cd repo
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > sleepext = ../sleepext.py
+  > EOF
+
+Test ctrl-c
+  $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2
+  interrupted!
+  [124]
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > nointerrupt = yes
+  > EOF
+
+  $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2
+  interrupted!
+  [124]
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > nointerrupt-interactiveonly = False
+  > EOF
+
+  $ python $TESTTMP/timeout.py -s INT 1 hg sleep 2
+  shutting down cleanly
+  press ^C again to terminate immediately (dangerous)
+  end of unsafe operation
+  interrupted!
+  [124]
--- a/tests/test-notify.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-notify.t	Thu Jul 19 13:55:54 2018 -0400
@@ -131,6 +131,9 @@
   notify.diffstat
     Set to True to include a diffstat before diff content. Default: True.
   
+  notify.showfunc
+    If set, override "diff.showfunc" for the diff content. Default: None.
+  
   notify.merge
     If True, send notifications for merge changesets. Default: True.
   
@@ -647,3 +650,99 @@
   To: baz@test.com, foo@bar
   
   with template
+
+showfunc diff
+  $ cat <<EOF >> $HGRCPATH
+  > showfunc = True
+  > template =
+  > maxdiff = -1
+  > EOF
+  $ cd a
+  $ cat > f1 << EOF
+  > int main() {
+  >     int a = 0;
+  >     int b = 1;
+  >     int c = 2;
+  >     int d = 3;
+  >     return a + b + c + d;
+  > }
+  > EOF
+  $ hg commit -Am addfunction
+  adding f1
+  $ hg --cwd ../b pull ../a
+  pulling from ../a
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets b86bc16ff894
+  MIME-Version: 1.0
+  Content-Type: text/plain; charset="us-ascii"
+  Content-Transfer-Encoding: 7bit
+  Date: * (glob)
+  Subject: addfunction
+  From: test@test.com
+  X-Hg-Notification: changeset b86bc16ff894
+  Message-Id: <hg.b86bc16ff894.*.*@*> (glob)
+  To: baz@test.com, foo@bar
+  
+  changeset b86bc16ff894
+  diffs (11 lines):
+  
+  diff -r 14721b538ae3 -r b86bc16ff894 f1
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/f1	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,7 @@
+  +int main() {
+  +    int a = 0;
+  +    int b = 1;
+  +    int c = 2;
+  +    int d = 3;
+  +    return a + b + c + d;
+  +}
+  (run 'hg update' to get a working copy)
+  $ cat > f1 << EOF
+  > int main() {
+  >     int a = 0;
+  >     int b = 1;
+  >     int c = 2;
+  >     int e = 3;
+  >     return a + b + c + e;
+  > }
+  > EOF
+  $ hg commit -m changefunction
+  $ hg --cwd ../b --config notify.showfunc=True pull ../a
+  pulling from ../a
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  new changesets e81040e9838c
+  MIME-Version: 1.0
+  Content-Type: text/plain; charset="us-ascii"
+  Content-Transfer-Encoding: 7bit
+  Date: * (glob)
+  Subject: changefunction
+  From: test@test.com
+  X-Hg-Notification: changeset e81040e9838c
+  Message-Id: <hg.e81040e9838c.*.*@*> (glob)
+  To: baz@test.com, foo@bar
+  
+  changeset e81040e9838c
+  diffs (12 lines):
+  
+  diff -r b86bc16ff894 -r e81040e9838c f1
+  --- a/f1	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/f1	Thu Jan 01 00:00:00 1970 +0000
+  @@ -2,6 +2,6 @@ int main() {
+       int a = 0;
+       int b = 1;
+       int c = 2;
+  -    int d = 3;
+  -    return a + b + c + d;
+  +    int e = 3;
+  +    return a + b + c + e;
+   }
+  (run 'hg update' to get a working copy)
--- a/tests/test-obshistory.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-obshistory.t	Thu Jul 19 13:55:54 2018 -0400
@@ -55,9 +55,9 @@
   (use --hidden to access hidden revisions)
   [255]
   $ hg update --hidden "desc(A0)"
-  updating to a hidden changeset 471f378eab4c
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' was rewritten as: 4ae3a4151de9)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test output with pruned commit
 ==============================
@@ -118,9 +118,9 @@
   (use --hidden to access hidden revisions)
   [255]
   $ hg up --hidden -r 'desc(B0)'
-  updating to a hidden changeset 0dec01379d3b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 0dec01379d3b
   (hidden revision '0dec01379d3b' is pruned)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test output with splitted commit
 ================================
@@ -195,9 +195,9 @@
   (use --hidden to access hidden revisions)
   [255]
   $ hg update --hidden 'min(desc(A0))'
-  updating to a hidden changeset 471597cad322
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471597cad322
   (hidden revision '471597cad322' was split as: 337fec4d2edc, f257fde29c7a)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test output with lots of splitted commit
 ========================================
@@ -294,9 +294,9 @@
   (use --hidden to access hidden revisions)
   [255]
   $ hg update --hidden 'min(desc(A0))'
-  updating to a hidden changeset de7290d8b885
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset de7290d8b885
   (hidden revision 'de7290d8b885' was split as: 337fec4d2edc, f257fde29c7a and 2 more)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test output with folded commit
 ==============================
@@ -373,17 +373,17 @@
   (use --hidden to access hidden revisions)
   [255]
   $ hg update --hidden 'desc(A0)'
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' was rewritten as: eb5a0daa2192)
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg update 0dec01379d3b
   abort: hidden revision '0dec01379d3b' was rewritten as: eb5a0daa2192!
   (use --hidden to access hidden revisions)
   [255]
   $ hg update --hidden 'desc(B0)'
-  updating to a hidden changeset 0dec01379d3b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 0dec01379d3b
   (hidden revision '0dec01379d3b' was rewritten as: eb5a0daa2192)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test output with divergence
 ===========================
@@ -416,9 +416,9 @@
      summary:     ROOT
   
   $ hg update --hidden 'desc(A0)'
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' was rewritten as: fdf9bde5129a)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg commit --amend -m "A2"
   2 new content-divergent changesets
   $ hg log --hidden -G
@@ -456,9 +456,9 @@
   (use --hidden to access hidden revisions)
   [255]
   $ hg update --hidden 'desc(A0)'
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' has diverged)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test output with amended + folded commit
 ========================================
@@ -551,13 +551,13 @@
   (use --hidden to access hidden revisions)
   [255]
   $ hg update --hidden 'desc(A0)'
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' was rewritten as: eb5a0daa2192)
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg update --hidden 0dec01379d3b
-  updating to a hidden changeset 0dec01379d3b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 0dec01379d3b
   (hidden revision '0dec01379d3b' was rewritten as: eb5a0daa2192)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg update 0dec01379d3b
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg update --hidden 'desc(B0)'
--- a/tests/test-obsmarker-template.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-obsmarker-template.t	Thu Jul 19 13:55:54 2018 -0400
@@ -75,9 +75,9 @@
 Check templates
 ---------------
   $ hg up 'desc(A0)' --hidden
-  updating to a hidden changeset 471f378eab4c
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' was rewritten as: d004c8f274b9)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Predecessors template should show current revision as it is the working copy
   $ hg tlog
@@ -148,9 +148,9 @@
      summary:     ROOT
   
   $ hg up 'desc(A1)' --hidden
-  updating to a hidden changeset a468dc9b3633
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset a468dc9b3633
   (hidden revision 'a468dc9b3633' was rewritten as: d004c8f274b9)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Predecessors template should show current revision as it is the working copy
   $ hg tlog
@@ -417,9 +417,9 @@
 ---------------
 
   $ hg up 'obsolete()' --hidden
-  updating to a hidden changeset 471597cad322
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471597cad322
   (hidden revision '471597cad322' was split as: 337fec4d2edc, f257fde29c7a)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Predecessors template should show current revision as it is the working copy
   $ hg tlog
@@ -627,9 +627,9 @@
 ---------------
 
   $ hg up 'desc(A0)' --hidden
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' was rewritten as: eb5a0daa2192)
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
 Predecessors template should show current revision as it is the working copy
   $ hg tlog
@@ -653,9 +653,9 @@
   o  ea207398892e
   
   $ hg up 'desc(B0)' --hidden
-  updating to a hidden changeset 0dec01379d3b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 0dec01379d3b
   (hidden revision '0dec01379d3b' was rewritten as: eb5a0daa2192)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Predecessors template should show both predecessors as they should be both
 displayed
@@ -820,9 +820,9 @@
      summary:     ROOT
   
   $ hg update --hidden 'desc(A0)'
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' was rewritten as: fdf9bde5129a)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg commit --amend -m "A2"
   2 new content-divergent changesets
   $ hg log --hidden -G
@@ -894,9 +894,9 @@
 ---------------
 
   $ hg up 'desc(A0)' --hidden
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' has diverged)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Predecessors template should show current revision as it is the working copy
   $ hg tlog
@@ -1161,9 +1161,9 @@
 ---------------
 
   $ hg up 'desc(A0)' --hidden
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' was rewritten as: eb5a0daa2192)
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
 Predecessors template should show current revision as it is the working copy
   $ hg tlog
@@ -1187,9 +1187,9 @@
   o  ea207398892e
   
   $ hg up 'desc(B0)' --hidden
-  updating to a hidden changeset 0dec01379d3b
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 0dec01379d3b
   (hidden revision '0dec01379d3b' was rewritten as: eb5a0daa2192)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Predecessors template should both predecessors as they are visible
   $ hg tlog
@@ -1219,9 +1219,9 @@
   o  ea207398892e
   
   $ hg up 'desc(B1)' --hidden
-  updating to a hidden changeset b7ea6d14e664
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset b7ea6d14e664
   (hidden revision 'b7ea6d14e664' was rewritten as: eb5a0daa2192)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Predecessors template should both predecessors as they are visible
   $ hg tlog
@@ -1623,9 +1623,9 @@
   
 
   $ hg up -r "desc(B0)" --hidden
-  updating to a hidden changeset 0dec01379d3b
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset 0dec01379d3b
   (hidden revision '0dec01379d3b' is pruned)
-  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
   o  f897c6137566
   |    Predecessors: 2:0dec01379d3b
@@ -2087,9 +2087,9 @@
   o  ea207398892e
   
   $ hg up --hidden 4
-  updating to a hidden changeset 9bd10a0775e4
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 9bd10a0775e4
   (hidden revision '9bd10a0775e4' has diverged)
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg rebase -r 7 -d 8 --config extensions.rebase=
   rebasing 7:ba2ed02b0c9a "Add A,B,C"
   $ hg tlog
@@ -2333,9 +2333,9 @@
   obsoleted 1 changesets
 
   $ hg up -r "desc(A0)" --hidden
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' is pruned)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg commit --amend -m "A2"
   $ hg debugobsolete --record-parent `getid "."`
   obsoleted 1 changesets
@@ -2344,9 +2344,9 @@
 ------------
 
   $ hg up "desc(A0)" --hidden
-  updating to a hidden changeset 471f378eab4c
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471f378eab4c
   (hidden revision '471f378eab4c' is pruned)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg tlog
   @  471f378eab4c
   |
@@ -2499,9 +2499,9 @@
 ---------------
 
   $ hg up 'desc("A0")' --hidden
-  updating to a hidden changeset 471597cad322
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 471597cad322
   (hidden revision '471597cad322' is pruned)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 # todo: the obsfate output is not ideal
   $ hg fatelog
@@ -2512,9 +2512,9 @@
   o  ea207398892e
   
   $ hg up -r 'desc("A2")' --hidden
-  updating to a hidden changeset 0d0ef4bdf70e
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset 0d0ef4bdf70e
   (hidden revision '0d0ef4bdf70e' is pruned)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg fatelog --hidden
   @  0d0ef4bdf70e
@@ -2581,3 +2581,61 @@
      date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     ROOT
   
+
+Test metadata encoding (issue5754)
+==================================
+
+  $ hg init $TESTTMP/metadata-encoding
+  $ cd $TESTTMP/metadata-encoding
+  $ cat <<'EOF' >> .hg/hgrc
+  > [extensions]
+  > amend =
+  > EOF
+  $ $PYTHON <<'EOF'
+  > with open('test1', 'wb') as f:
+  >    f.write(b't\xe8st1') and None
+  > with open('test2', 'wb') as f:
+  >    f.write(b't\xe8st2') and None
+  > EOF
+  $ mkcommit ROOT
+  $ HGENCODING=latin-1 HGUSER="`cat test1`" mkcommit A0
+  $ echo 42 >> A0
+  $ hg amend -m "A1" --note "`cat test2`"
+  $ HGENCODING=latin-1 hg amend -m "A2" \
+  > --config devel.user.obsmarker="`cat test2`"
+  $ mkcommit B0
+  $ HGENCODING=latin-1 hg debugobsolete -u "`cat test2`" "`getid 'desc(B0)'`"
+  obsoleted 1 changesets
+
+metadata should be stored in UTF-8, and debugobsolete doesn't decode it to
+local encoding since the command is supposed to show unmodified content:
+
+  $ HGENCODING=latin-1 hg debugobsolete
+  5f66a482f0bb2fcaccfc215554ad5eb9f40b50f5 718c0d00cee1429bdb73064e0d88908c601507a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '9', 'note': 't\xc3\xa8st2', 'operation': 'amend', 'user': 't\xc3\xa8st1'}
+  718c0d00cee1429bdb73064e0d88908c601507a8 1132562159b35bb27e1d6b80c80ee94a1659a4da 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 't\xc3\xa8st2'}
+  e1724525bc3bec4472d7915a02811b938004a7a2 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 't\xc3\xa8st2'}
+
+metadata should be converted back to local encoding when displaying:
+
+  $ HGENCODING=latin-1 hg fatelog --hidden
+  @  e1724525bc3b
+  |    Obsfate: pruned by t\xe8st2 (at 1970-01-01 00:00 +0000); (esc)
+  o  1132562159b3
+  |
+  | x  718c0d00cee1
+  |/     Obsfate: rewritten using amend as 3:1132562159b3 by t\xe8st2 (at 1970-01-01 00:00 +0000); (esc)
+  | x  5f66a482f0bb
+  |/     Obsfate: rewritten using amend as 2:718c0d00cee1 by t\xe8st1 (at 1970-01-01 00:00 +0000); (esc)
+  o  ea207398892e
+  
+  $ HGENCODING=utf-8 hg fatelog --hidden
+  @  e1724525bc3b
+  |    Obsfate: pruned by t\xc3\xa8st2 (at 1970-01-01 00:00 +0000); (esc)
+  o  1132562159b3
+  |
+  | x  718c0d00cee1
+  |/     Obsfate: rewritten using amend as 3:1132562159b3 by t\xc3\xa8st2 (at 1970-01-01 00:00 +0000); (esc)
+  | x  5f66a482f0bb
+  |/     Obsfate: rewritten using amend as 2:718c0d00cee1 by t\xc3\xa8st1 (at 1970-01-01 00:00 +0000); (esc)
+  o  ea207398892e
+  
--- a/tests/test-obsolete-checkheads.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-obsolete-checkheads.t	Thu Jul 19 13:55:54 2018 -0400
@@ -80,6 +80,7 @@
   pulling from $TESTTMP/remote
   searching for changes
   no changes found
+  1 local changesets published
   $ hg log -G --hidden
   @  71e3228bffe1 (draft) add new
   |
--- a/tests/test-obsolete-divergent.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-obsolete-divergent.t	Thu Jul 19 13:55:54 2018 -0400
@@ -704,16 +704,16 @@
   > from mercurial import registrar, scmutil
   > cmdtable = {}
   > command = registrar.command(cmdtable)
-  > @command('cleanup')
+  > @command(b'cleanup')
   > def cleanup(ui, repo):
   >     def node(expr):
   >         unfi = repo.unfiltered()
   >         rev = unfi.revs(expr).first()
   >         return unfi.changelog.node(rev)
-  >     with repo.wlock(), repo.lock(), repo.transaction('delayedstrip'):
-  >         mapping = {node('desc(B1)'): [node('desc(B3)')],
-  >                    node('desc(B3)'): [node('desc(B4)')]}
-  >         scmutil.cleanupnodes(repo, mapping, 'test')
+  >     with repo.wlock(), repo.lock(), repo.transaction(b'delayedstrip'):
+  >         mapping = {node(b'desc(B1)'): [node(b'desc(B3)')],
+  >                    node(b'desc(B3)'): [node(b'desc(B4)')]}
+  >         scmutil.cleanupnodes(repo, mapping, b'test')
   > EOF
 
   $ rm .hg/localtags
--- a/tests/test-obsolete.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-obsolete.t	Thu Jul 19 13:55:54 2018 -0400
@@ -62,7 +62,7 @@
   $ hg tip
   -1:000000000000 (public) [tip ] 
   $ hg up --hidden tip --quiet
-  updating to a hidden changeset 97b7c2d76b18
+  updated to hidden changeset 97b7c2d76b18
   (hidden revision '97b7c2d76b18' is pruned)
 
 Killing a single changeset with itself should fail
@@ -931,7 +931,7 @@
   $ echo "evolution.exchange=True" >> $HGRCPATH
   $ echo "evolution.createmarkers=True" >> $HGRCPATH
 
-  $ rm hg.pid access.log errors.log
+  $ rm access.log errors.log
 #endif
 
 Several troubles on the same changeset (create an unstable and bumped changeset)
@@ -1318,16 +1318,18 @@
   $ cat >$TESTTMP/test_extension.py  << EOF
   > from __future__ import absolute_import
   > from mercurial.i18n import _
-  > from mercurial import cmdutil, registrar
+  > from mercurial import cmdutil, pycompat, registrar
+  > from mercurial.utils import stringutil
   > 
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > @command(b"amendtransient",[], _(b'hg amendtransient [rev]'))
   > def amend(ui, repo, *pats, **opts):
-  >   opts['message'] = 'Test'
-  >   opts['logfile'] = None
-  >   cmdutil.amend(ui, repo, repo['.'], {}, pats, opts)
-  >   ui.write(b'%s\n' % repo.changelog.headrevs())
+  >   opts = pycompat.byteskwargs(opts)
+  >   opts[b'message'] = b'Test'
+  >   opts[b'logfile'] = None
+  >   cmdutil.amend(ui, repo, repo[b'.'], {}, pats, opts)
+  >   ui.write(b'%s\n' % stringutil.pprint(repo.changelog.headrevs()))
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
@@ -1365,7 +1367,7 @@
   >   hidden = repoview.filterrevs(repo, b'visible')
   >   if sorted(hidden1) != sorted(hidden):
   >     print("cache inconsistency")
-  >  bkmstoreinst._repo.currenttransaction().addpostclose('test_extension', trhook)
+  >  bkmstoreinst._repo.currenttransaction().addpostclose(b'test_extension', trhook)
   >  orig(bkmstoreinst, *args, **kwargs)
   > def extsetup(ui):
   >   extensions.wrapfunction(bookmarks.bmstore, '_recordchange',
--- a/tests/test-pager-legacy.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-pager-legacy.t	Thu Jul 19 13:55:54 2018 -0400
@@ -247,9 +247,9 @@
   > from mercurial import registrar, commands
   > cmdtable = {}
   > command = registrar.command(cmdtable)
-  > @command(b'fortytwo', [], 'fortytwo', norepo=True)
+  > @command(b'fortytwo', [], b'fortytwo', norepo=True)
   > def fortytwo(ui, *opts):
-  >     ui.write('42\n')
+  >     ui.write(b'42\n')
   >     return 42
   > EOF
 
--- a/tests/test-pager.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-pager.t	Thu Jul 19 13:55:54 2018 -0400
@@ -260,9 +260,9 @@
   > from mercurial import commands, registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
-  > @command(b'fortytwo', [], 'fortytwo', norepo=True)
+  > @command(b'fortytwo', [], b'fortytwo', norepo=True)
   > def fortytwo(ui, *opts):
-  >     ui.write('42\n')
+  >     ui.write(b'42\n')
   >     return 42
   > EOF
 
@@ -377,8 +377,8 @@
   $ cat > $TESTTMP/pushbufferpager.py <<EOF
   > def uisetup(ui):
   >     ui.pushbuffer()
-  >     ui.pager('mycmd')
-  >     ui.write('content\n')
+  >     ui.pager(b'mycmd')
+  >     ui.write(b'content\n')
   >     ui.write(ui.popbuffer())
   > EOF
 
--- a/tests/test-parse-date.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-parse-date.t	Thu Jul 19 13:55:54 2018 -0400
@@ -243,8 +243,8 @@
   >>> today = datetime.date.today().strftime("%b %d")
   >>> yesterday = (datetime.date.today() - datetime.timedelta(days=1)).strftime("%b %d")
   >>> dates = open('dates', 'w')
-  >>> dates.write(today + '\n')
-  >>> dates.write(yesterday + '\n')
+  >>> dates.write(today + '\n') and None
+  >>> dates.write(yesterday + '\n') and None
   >>> dates.close()
   $ hg ci -d "`sed -n '1p' dates`" -m "today is a good day to code"
   $ hg log -d today --template '{desc}\n'
--- a/tests/test-parseindex.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-parseindex.t	Thu Jul 19 13:55:54 2018 -0400
@@ -26,6 +26,7 @@
   summary:     change foo
   
   $ cat >> test.py << EOF
+  > from __future__ import print_function
   > from mercurial import changelog, vfs
   > from mercurial.node import *
   > 
@@ -56,9 +57,9 @@
   >     return wrapper
   > 
   > cl = changelog.changelog(opener('.hg/store'))
-  > print len(cl), 'revisions:'
+  > print(len(cl), 'revisions:')
   > for r in cl:
-  >     print short(cl.node(r))
+  >     print(short(cl.node(r)))
   > EOF
   $ $PYTHON test.py
   2 revisions:
@@ -74,33 +75,34 @@
   $ cd a
 
   $ $PYTHON <<EOF
+  > from __future__ import print_function
   > from mercurial import changelog, vfs
   > cl = changelog.changelog(vfs.vfs('.hg/store'))
-  > print 'good heads:'
+  > print('good heads:')
   > for head in [0, len(cl) - 1, -1]:
-  >     print'%s: %r' % (head, cl.reachableroots(0, [head], [0]))
-  > print 'bad heads:'
+  >     print('%s: %r' % (head, cl.reachableroots(0, [head], [0])))
+  > print('bad heads:')
   > for head in [len(cl), 10000, -2, -10000, None]:
-  >     print '%s:' % head,
+  >     print('%s:' % head, end=' ')
   >     try:
   >         cl.reachableroots(0, [head], [0])
-  >         print 'uncaught buffer overflow?'
+  >         print('uncaught buffer overflow?')
   >     except (IndexError, TypeError) as inst:
-  >         print inst
-  > print 'good roots:'
+  >         print(inst)
+  > print('good roots:')
   > for root in [0, len(cl) - 1, -1]:
-  >     print '%s: %r' % (root, cl.reachableroots(root, [len(cl) - 1], [root]))
-  > print 'out-of-range roots are ignored:'
+  >     print('%s: %r' % (root, cl.reachableroots(root, [len(cl) - 1], [root])))
+  > print('out-of-range roots are ignored:')
   > for root in [len(cl), 10000, -2, -10000]:
-  >     print '%s: %r' % (root, cl.reachableroots(root, [len(cl) - 1], [root]))
-  > print 'bad roots:'
+  >     print('%s: %r' % (root, cl.reachableroots(root, [len(cl) - 1], [root])))
+  > print('bad roots:')
   > for root in [None]:
-  >     print '%s:' % root,
+  >     print('%s:' % root, end=' ')
   >     try:
   >         cl.reachableroots(root, [len(cl) - 1], [root])
-  >         print 'uncaught error?'
+  >         print('uncaught error?')
   >     except TypeError as inst:
-  >         print inst
+  >         print(inst)
   > EOF
   good heads:
   0: [0]
@@ -137,10 +139,10 @@
 
   $ $PYTHON <<EOF
   > data = open("limit/.hg/store/00changelog.i", "rb").read()
-  > for n, p in [('limit', '\0\0\0\x02'), ('segv', '\0\x01\0\0')]:
+  > for n, p in [(b'limit', b'\0\0\0\x02'), (b'segv', b'\0\x01\0\0')]:
   >     # corrupt p1 at rev0 and p2 at rev1
   >     d = data[:24] + p + data[28:127 + 28] + p + data[127 + 32:]
-  >     open(n + "/.hg/store/00changelog.i", "wb").write(d)
+  >     open(n + b"/.hg/store/00changelog.i", "wb").write(d)
   > EOF
 
   $ hg -R limit debugindex -f1 -c
@@ -164,6 +166,7 @@
         1       2        1       -1    base         66         65         66   1.01538        66         0    0.00000
 
   $ cat <<EOF > test.py
+  > from __future__ import print_function
   > import sys
   > from mercurial import changelog, vfs
   > cl = changelog.changelog(vfs.vfs(sys.argv[1]))
@@ -177,12 +180,12 @@
   >     ('find_deepest', lambda: cl.ancestor(n0, n1)),
   >     ]
   > for l, f in ops:
-  >     print l + ':',
+  >     print(l + ':', end=' ')
   >     try:
   >         f()
-  >         print 'uncaught buffer overflow?'
-  >     except ValueError, inst:
-  >         print inst
+  >         print('uncaught buffer overflow?')
+  >     except ValueError as inst:
+  >         print(inst)
   > EOF
 
   $ $PYTHON test.py limit/.hg/store
--- a/tests/test-parseindex2.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-parseindex2.py	Thu Jul 19 13:55:54 2018 -0400
@@ -15,6 +15,7 @@
 )
 from mercurial import (
     policy,
+    pycompat,
 )
 
 parsers = policy.importmod(r'parsers')
@@ -24,7 +25,7 @@
     return int(q & 0xFFFF)
 
 def offset_type(offset, type):
-    return long(long(offset) << 16 | type)
+    return int(int(offset) << 16 | type)
 
 indexformatng = ">Qiiiiii20s12x"
 
@@ -65,46 +66,50 @@
 
     return index, cache
 
-data_inlined = '\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x01\x8c' \
-    '\x00\x00\x04\x07\x00\x00\x00\x00\x00\x00\x15\x15\xff\xff\xff' \
-    '\xff\xff\xff\xff\xff\xebG\x97\xb7\x1fB\x04\xcf\x13V\x81\tw\x1b' \
-    'w\xdduR\xda\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
-    'x\x9c\x9d\x93?O\xc30\x10\xc5\xf7|\x8a\xdb\x9a\xa8m\x06\xd8*\x95' \
-    '\x81B\xa1\xa2\xa2R\xcb\x86Pd\x9a\x0b5$vd_\x04\xfd\xf6\x9c\xff@' \
-    '\x11!\x0b\xd9\xec\xf7\xbbw\xe7gG6\xad6\x04\xdaN\xc0\x92\xa0$)' \
-    '\xb1\x82\xa2\xd1%\x16\xa4\x8b7\xa9\xca\xd4-\xb2Y\x02\xfc\xc9' \
-    '\xcaS\xf9\xaeX\xed\xb6\xd77Q\x02\x83\xd4\x19\xf5--Y\xea\xe1W' \
-    '\xab\xed\x10\xceR\x0f_\xdf\xdf\r\xe1,\xf5\xf0\xcb\xf5 \xceR\x0f' \
-    '_\xdc\x0e\x0e\xc3R\x0f_\xae\x96\x9b!\x9e\xa5\x1e\xbf\xdb,\x06' \
-    '\xc7q\x9a/\x88\x82\xc3B\xea\xb5\xb4TJ\x93\xb6\x82\x0e\xe16\xe6' \
-    'KQ\xdb\xaf\xecG\xa3\xd1 \x01\xd3\x0b_^\xe8\xaa\xa0\xae\xad\xd1' \
-    '&\xbef\x1bz\x08\xb0|\xc9Xz\x06\xf6Z\x91\x90J\xaa\x17\x90\xaa' \
-    '\xd2\xa6\x11$5C\xcf\xba#\xa0\x03\x02*2\x92-\xfc\xb1\x94\xdf\xe2' \
-    '\xae\xb8\'m\x8ey0^\x85\xd3\x82\xb4\xf0`:\x9c\x00\x8a\xfd\x01' \
-    '\xb0\xc6\x86\x8b\xdd\xae\x80\xf3\xa9\x9fd\x16\n\x00R%\x1a\x06' \
-    '\xe9\xd8b\x98\x1d\xf4\xf3+\x9bf\x01\xd8p\x1b\xf3.\xed\x9f^g\xc3' \
-    '^\xd9W81T\xdb\xd5\x04sx|\xf2\xeb\xd6`%?x\xed"\x831\xbf\xf3\xdc' \
-    'b\xeb%gaY\xe1\xad\x9f\xb9f\'1w\xa9\xa5a\x83s\x82J\xb98\xbc4\x8b' \
-    '\x83\x00\x9f$z\xb8#\xa5\xb1\xdf\x98\xd9\xec\x1b\x89O\xe3Ts\x9a4' \
-    '\x17m\x8b\xfc\x8f\xa5\x95\x9a\xfc\xfa\xed,\xe5|\xa1\xfe\x15\xb9' \
-    '\xbc\xb2\x93\x1f\xf2\x95\xff\xdf,\x1a\xc5\xe7\x17*\x93Oz:>\x0e'
+data_inlined = (
+    b'\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x01\x8c'
+    b'\x00\x00\x04\x07\x00\x00\x00\x00\x00\x00\x15\x15\xff\xff\xff'
+    b'\xff\xff\xff\xff\xff\xebG\x97\xb7\x1fB\x04\xcf\x13V\x81\tw\x1b'
+    b'w\xdduR\xda\xc6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+    b'x\x9c\x9d\x93?O\xc30\x10\xc5\xf7|\x8a\xdb\x9a\xa8m\x06\xd8*\x95'
+    b'\x81B\xa1\xa2\xa2R\xcb\x86Pd\x9a\x0b5$vd_\x04\xfd\xf6\x9c\xff@'
+    b'\x11!\x0b\xd9\xec\xf7\xbbw\xe7gG6\xad6\x04\xdaN\xc0\x92\xa0$)'
+    b'\xb1\x82\xa2\xd1%\x16\xa4\x8b7\xa9\xca\xd4-\xb2Y\x02\xfc\xc9'
+    b'\xcaS\xf9\xaeX\xed\xb6\xd77Q\x02\x83\xd4\x19\xf5--Y\xea\xe1W'
+    b'\xab\xed\x10\xceR\x0f_\xdf\xdf\r\xe1,\xf5\xf0\xcb\xf5 \xceR\x0f'
+    b'_\xdc\x0e\x0e\xc3R\x0f_\xae\x96\x9b!\x9e\xa5\x1e\xbf\xdb,\x06'
+    b'\xc7q\x9a/\x88\x82\xc3B\xea\xb5\xb4TJ\x93\xb6\x82\x0e\xe16\xe6'
+    b'KQ\xdb\xaf\xecG\xa3\xd1 \x01\xd3\x0b_^\xe8\xaa\xa0\xae\xad\xd1'
+    b'&\xbef\x1bz\x08\xb0|\xc9Xz\x06\xf6Z\x91\x90J\xaa\x17\x90\xaa'
+    b'\xd2\xa6\x11$5C\xcf\xba#\xa0\x03\x02*2\x92-\xfc\xb1\x94\xdf\xe2'
+    b'\xae\xb8\'m\x8ey0^\x85\xd3\x82\xb4\xf0`:\x9c\x00\x8a\xfd\x01'
+    b'\xb0\xc6\x86\x8b\xdd\xae\x80\xf3\xa9\x9fd\x16\n\x00R%\x1a\x06'
+    b'\xe9\xd8b\x98\x1d\xf4\xf3+\x9bf\x01\xd8p\x1b\xf3.\xed\x9f^g\xc3'
+    b'^\xd9W81T\xdb\xd5\x04sx|\xf2\xeb\xd6`%?x\xed"\x831\xbf\xf3\xdc'
+    b'b\xeb%gaY\xe1\xad\x9f\xb9f\'1w\xa9\xa5a\x83s\x82J\xb98\xbc4\x8b'
+    b'\x83\x00\x9f$z\xb8#\xa5\xb1\xdf\x98\xd9\xec\x1b\x89O\xe3Ts\x9a4'
+    b'\x17m\x8b\xfc\x8f\xa5\x95\x9a\xfc\xfa\xed,\xe5|\xa1\xfe\x15\xb9'
+    b'\xbc\xb2\x93\x1f\xf2\x95\xff\xdf,\x1a\xc5\xe7\x17*\x93Oz:>\x0e'
+    )
 
-data_non_inlined = '\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19' \
-    '\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff' \
-    '\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d' \
-    '\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
-    '\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00' \
-    '\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff' \
-    '\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh' \
-    '\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' \
-    '\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00' \
-    '\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n' \
-    '\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00' \
-    '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F' \
-    '\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01' \
-    '\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1' \
-    '\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00' \
-    '\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+data_non_inlined = (
+    b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19'
+    b'\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff'
+    b'\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d'
+    b'\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+    b'\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00'
+    b'\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff'
+    b'\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh'
+    b'\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+    b'\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00'
+    b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n'
+    b'\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00'
+    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F'
+    b'\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01'
+    b'\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1'
+    b'\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00'
+    b'\x00\x00\x00\x00\x00\x00\x00\x00\x00'
+    )
 
 def parse_index2(data, inline):
     index, chunkcache = parsers.parse_index2(data, inline)
@@ -145,7 +150,7 @@
 def testversionfail(testnumber, hexversion):
     stdout, stderr = importparsers(hexversion)
     # We include versionerrortext to distinguish from other ImportErrors.
-    errtext = "ImportError: %s" % parsers.versionerrortext
+    errtext = b"ImportError: %s" % pycompat.sysbytes(parsers.versionerrortext)
     if errtext not in stdout:
         printhexfail(testnumber, hexversion, stdout,
                      expected="stdout to contain %r" % errtext)
--- a/tests/test-patch-offset.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-patch-offset.t	Thu Jul 19 13:55:54 2018 -0400
@@ -8,7 +8,7 @@
   > fp = open(path, 'wb')
   > for pattern in patterns:
   >     count = int(pattern[0:-1])
-  >     char = pattern[-1] + '\n'
+  >     char = pattern[-1].encode('utf8') + b'\n'
   >     fp.write(char*count)
   > fp.close()
   > EOF
--- a/tests/test-pathencode.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-pathencode.py	Thu Jul 19 13:55:54 2018 -0400
@@ -16,6 +16,7 @@
 import sys
 import time
 from mercurial import (
+    pycompat,
     store,
 )
 
@@ -24,15 +25,15 @@
 except NameError:
     xrange = range
 
-validchars = set(map(chr, range(0, 256)))
+validchars = set(map(pycompat.bytechr, range(0, 256)))
 alphanum = range(ord('A'), ord('Z'))
 
-for c in '\0/':
+for c in (b'\0', b'/'):
     validchars.remove(c)
 
-winreserved = ('aux con prn nul'.split() +
-               ['com%d' % i for i in xrange(1, 10)] +
-               ['lpt%d' % i for i in xrange(1, 10)])
+winreserved = (b'aux con prn nul'.split() +
+               [b'com%d' % i for i in xrange(1, 10)] +
+               [b'lpt%d' % i for i in xrange(1, 10)])
 
 def casecombinations(names):
     '''Build all case-diddled combinations of names.'''
@@ -44,7 +45,7 @@
             for c in itertools.combinations(xrange(len(r)), i):
                 d = r
                 for j in c:
-                    d = ''.join((d[:j], d[j].upper(), d[j + 1:]))
+                    d = b''.join((d[:j], d[j:j + 1].upper(), d[j + 1:]))
                 combos.add(d)
     return sorted(combos)
 
@@ -78,19 +79,19 @@
 # looking at filelog names from a real-world, very large repo.
 
 probtable = (
-    ('t', 9.828), ('e', 9.042), ('s', 8.011), ('a', 6.801), ('i', 6.618),
-    ('g', 5.053), ('r', 5.030), ('o', 4.887), ('p', 4.363), ('n', 4.258),
-    ('l', 3.830), ('h', 3.693), ('_', 3.659), ('.', 3.377), ('m', 3.194),
-    ('u', 2.364), ('d', 2.296), ('c', 2.163), ('b', 1.739), ('f', 1.625),
-    ('6', 0.666), ('j', 0.610), ('y', 0.554), ('x', 0.487), ('w', 0.477),
-    ('k', 0.476), ('v', 0.473), ('3', 0.336), ('1', 0.335), ('2', 0.326),
-    ('4', 0.310), ('5', 0.305), ('9', 0.302), ('8', 0.300), ('7', 0.299),
-    ('q', 0.298), ('0', 0.250), ('z', 0.223), ('-', 0.118), ('C', 0.095),
-    ('T', 0.087), ('F', 0.085), ('B', 0.077), ('S', 0.076), ('P', 0.076),
-    ('L', 0.059), ('A', 0.058), ('N', 0.051), ('D', 0.049), ('M', 0.046),
-    ('E', 0.039), ('I', 0.035), ('R', 0.035), ('G', 0.028), ('U', 0.026),
-    ('W', 0.025), ('O', 0.017), ('V', 0.015), ('H', 0.013), ('Q', 0.011),
-    ('J', 0.007), ('K', 0.005), ('+', 0.004), ('X', 0.003), ('Y', 0.001),
+    (b't', 9.828), (b'e', 9.042), (b's', 8.011), (b'a', 6.801), (b'i', 6.618),
+    (b'g', 5.053), (b'r', 5.030), (b'o', 4.887), (b'p', 4.363), (b'n', 4.258),
+    (b'l', 3.830), (b'h', 3.693), (b'_', 3.659), (b'.', 3.377), (b'm', 3.194),
+    (b'u', 2.364), (b'd', 2.296), (b'c', 2.163), (b'b', 1.739), (b'f', 1.625),
+    (b'6', 0.666), (b'j', 0.610), (b'y', 0.554), (b'x', 0.487), (b'w', 0.477),
+    (b'k', 0.476), (b'v', 0.473), (b'3', 0.336), (b'1', 0.335), (b'2', 0.326),
+    (b'4', 0.310), (b'5', 0.305), (b'9', 0.302), (b'8', 0.300), (b'7', 0.299),
+    (b'q', 0.298), (b'0', 0.250), (b'z', 0.223), (b'-', 0.118), (b'C', 0.095),
+    (b'T', 0.087), (b'F', 0.085), (b'B', 0.077), (b'S', 0.076), (b'P', 0.076),
+    (b'L', 0.059), (b'A', 0.058), (b'N', 0.051), (b'D', 0.049), (b'M', 0.046),
+    (b'E', 0.039), (b'I', 0.035), (b'R', 0.035), (b'G', 0.028), (b'U', 0.026),
+    (b'W', 0.025), (b'O', 0.017), (b'V', 0.015), (b'H', 0.013), (b'Q', 0.011),
+    (b'J', 0.007), (b'K', 0.005), (b'+', 0.004), (b'X', 0.003), (b'Y', 0.001),
     )
 
 for c, _ in probtable:
@@ -121,12 +122,12 @@
 
 # Special suffixes.
 
-internalsuffixcombos = casecombinations('.hg .i .d'.split())
+internalsuffixcombos = casecombinations(b'.hg .i .d'.split())
 
 # The last component of a path, before a slash or at the end of a name.
 
 lasttable = resttable + (
-    (lambda rng: '', 95),
+    (lambda rng: b'', 95),
     (lambda rng: rng.choice(internalsuffixcombos), 5),
     )
 
@@ -142,13 +143,13 @@
         l += len(p)
         ps.append(p)
     ps.append(pickfrom(rng, lasttable)(rng))
-    return ''.join(ps)
+    return b''.join(ps)
 
 def makepath(rng, j, k):
     '''Construct a complete pathname.'''
 
-    return ('data/' + '/'.join(makepart(rng, k) for _ in xrange(j)) +
-            rng.choice(['.d', '.i']))
+    return (b'data/' + b'/'.join(makepart(rng, k) for _ in xrange(j)) +
+            rng.choice([b'.d', b'.i']))
 
 def genpath(rng, count):
     '''Generate random pathnames with gradually increasing lengths.'''
--- a/tests/test-phases-exchange.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-phases-exchange.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,7 +1,6 @@
-#require killdaemons
-
   $ cat >> $HGRCPATH << EOF
   > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
   > phasereport=$TESTDIR/testlib/ext-phase-report.py
   > EOF
 
@@ -169,6 +168,7 @@
   pulling from ../alpha
   searching for changes
   no changes found
+  1 local changesets published
   test-debug-phase: move rev 2: 1 -> 0
   $ hgph
   o  4 public a-D - b555f63b6063
@@ -285,6 +285,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   new changesets b555f63b6063
+  3 local changesets published
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-debug-phase: move rev 2: 1 -> 0
@@ -333,6 +334,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   new changesets d6bcb4f74035:145e75495359
+  4 local changesets published
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-debug-phase: move rev 3: 1 -> 0
@@ -406,6 +408,7 @@
   pulling from ../alpha
   searching for changes
   no changes found
+  3 local changesets published
   test-debug-phase: move rev 3: 1 -> 0
   test-debug-phase: move rev 5: 1 -> 0
   test-debug-phase: move rev 6: 1 -> 0
@@ -1172,6 +1175,8 @@
   $ hg phase f54f1bb90ff3
   2: draft
 
+  $ killdaemons.py
+
 put the changeset in the draft state again
 (first test after this one expect to be able to copy)
 
@@ -1377,3 +1382,180 @@
   o  9 draft a-G - 3e27b6f1eee1
   |
   ~
+
+Test phases exchange when a phaseroot is on a merge
+
+  $ hg init mergetest
+  $ cd mergetest
+  > cat > .hg/hgrc << EOF
+  > [phases]
+  > publish = false
+  > EOF
+
+  $ hg debugdrawdag << EOF
+  > E Z
+  > |\|
+  > D Y
+  > | |
+  > C X
+  > |/
+  > B
+  > |
+  > A
+  > EOF
+  test-debug-phase: new rev 0:  x -> 1
+  test-debug-phase: new rev 1:  x -> 1
+  test-debug-phase: new rev 2:  x -> 1
+  test-debug-phase: new rev 3:  x -> 1
+  test-debug-phase: new rev 4:  x -> 1
+  test-debug-phase: new rev 5:  x -> 1
+  test-debug-phase: new rev 6:  x -> 1
+  test-debug-phase: new rev 7:  x -> 1
+
+  $ hg phase --public -r D
+  test-debug-phase: move rev 0: 1 -> 0
+  test-debug-phase: move rev 1: 1 -> 0
+  test-debug-phase: move rev 2: 1 -> 0
+  test-debug-phase: move rev 4: 1 -> 0
+
+  $ hg log -G -T '{shortest(node, 5)} {phase}'
+  o  bb947 draft
+  |
+  | o  5ac28 draft
+  |/|
+  o |  13b7b draft
+  | |
+  | o  f5853 public
+  | |
+  o |  c67c4 draft
+  | |
+  | o  26805 public
+  |/
+  o  11247 public
+  |
+  o  426ba public
+  
+  $ cd ..
+
+Works with default settings
+
+  $ hg -R mergetest serve -p $HGPORT -d --pid-file=hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ hg clone -U http://localhost:$HGPORT mergetest-normal
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+1 heads)
+  new changesets 426bada5c675:bb94757e651a
+  test-debug-phase: new rev 0:  x -> 0
+  test-debug-phase: new rev 1:  x -> 0
+  test-debug-phase: new rev 2:  x -> 0
+  test-debug-phase: new rev 3:  x -> 1
+  test-debug-phase: new rev 4:  x -> 0
+  test-debug-phase: new rev 5:  x -> 1
+  test-debug-phase: new rev 6:  x -> 1
+  test-debug-phase: new rev 7:  x -> 1
+
+  $ hg -R mergetest-normal log -G -T '{shortest(node, 5)} {phase}'
+  o  bb947 draft
+  |
+  | o  5ac28 draft
+  |/|
+  o |  13b7b draft
+  | |
+  | o  f5853 public
+  | |
+  o |  c67c4 draft
+  | |
+  | o  26805 public
+  |/
+  o  11247 public
+  |
+  o  426ba public
+  
+  $ killdaemons.py
+
+With legacy listkeys over bundle2
+(issue 5939: public phase was lost on 26805 and f5853 before, due to a bug
+of phase heads computation)
+
+  $ hg -R mergetest --config devel.legacy.exchange=phases serve -p $HGPORT -d --pid-file=hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ hg clone -U http://localhost:$HGPORT mergetest-nobinarypart
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+1 heads)
+  new changesets 426bada5c675:bb94757e651a
+  test-debug-phase: new rev 0:  x -> 0
+  test-debug-phase: new rev 1:  x -> 0
+  test-debug-phase: new rev 2:  x -> 0
+  test-debug-phase: new rev 3:  x -> 1
+  test-debug-phase: new rev 4:  x -> 0
+  test-debug-phase: new rev 5:  x -> 1
+  test-debug-phase: new rev 6:  x -> 1
+  test-debug-phase: new rev 7:  x -> 1
+
+  $ hg -R mergetest-nobinarypart log -G -T '{shortest(node, 5)} {phase}'
+  o  bb947 draft
+  |
+  | o  5ac28 draft
+  |/|
+  o |  13b7b draft
+  | |
+  | o  f5853 public
+  | |
+  o |  c67c4 draft
+  | |
+  | o  26805 public
+  |/
+  o  11247 public
+  |
+  o  426ba public
+  
+  $ killdaemons.py
+
+Without bundle2
+(issue 5939: public phase was lost on 26805 and f5853 before, due to a bug
+of phase heads computation)
+
+  $ hg -R mergetest serve -p $HGPORT -d --pid-file=hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+
+  $ hg --config devel.legacy.exchange=bundle1 clone -U http://localhost:$HGPORT mergetest-bundle1
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+1 heads)
+  new changesets 426bada5c675:bb94757e651a
+  test-debug-phase: new rev 0:  x -> 0
+  test-debug-phase: new rev 1:  x -> 0
+  test-debug-phase: new rev 2:  x -> 0
+  test-debug-phase: new rev 3:  x -> 1
+  test-debug-phase: new rev 4:  x -> 0
+  test-debug-phase: new rev 5:  x -> 1
+  test-debug-phase: new rev 6:  x -> 1
+  test-debug-phase: new rev 7:  x -> 1
+
+  $ hg -R mergetest-bundle1 log -G -T '{shortest(node, 5)} {phase}'
+  o  bb947 draft
+  |
+  | o  5ac28 draft
+  |/|
+  o |  13b7b draft
+  | |
+  | o  f5853 public
+  | |
+  o |  c67c4 draft
+  | |
+  | o  26805 public
+  |/
+  o  11247 public
+  |
+  o  426ba public
+  
--- a/tests/test-profile.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-profile.t	Thu Jul 19 13:55:54 2018 -0400
@@ -69,7 +69,7 @@
   > from mercurial import registrar, commands
   > cmdtable = {}
   > command = registrar.command(cmdtable)
-  > @command(b'sleep', [], 'hg sleep')
+  > @command(b'sleep', [], b'hg sleep')
   > def sleep(ui, *args, **kwargs):
   >     time.sleep(0.1)
   > EOF
@@ -123,13 +123,13 @@
   >     yield
   >     print('fooprof: end profile')
   > def extsetup(ui):
-  >     ui.write('fooprof: loaded\n')
+  >     ui.write(b'fooprof: loaded\n')
   > EOF
 
   $ cat > otherextension.py <<EOF
   > from __future__ import absolute_import
   > def extsetup(ui):
-  >     ui.write('otherextension: loaded\n')
+  >     ui.write(b'otherextension: loaded\n')
   > EOF
 
   $ hg init b
--- a/tests/test-progress.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-progress.t	Thu Jul 19 13:55:54 2018 -0400
@@ -16,10 +16,10 @@
   > time.time = incrementingtime()
   > 
   > @command(b'loop',
-  >     [('', 'total', '', 'override for total'),
-  >     ('', 'nested', False, 'show nested results'),
-  >     ('', 'parallel', False, 'show parallel sets of results')],
-  >     'hg loop LOOPS',
+  >     [(b'', b'total', b'', b'override for total'),
+  >     (b'', b'nested', False, b'show nested results'),
+  >     (b'', b'parallel', False, b'show parallel sets of results')],
+  >     b'hg loop LOOPS',
   >     norepo=True)
   > def loop(ui, loops, **opts):
   >     loops = int(loops)
@@ -33,20 +33,22 @@
   >         nested = True
   >     loops = abs(loops)
   > 
+  >     progress = ui.makeprogress(topiclabel, unit='loopnum', total=total)
+  >     other = ui.makeprogress('other', unit='othernum', total=total)
   >     for i in range(loops):
-  >         ui.progress(topiclabel, i, getloopitem(i), 'loopnum', total)
+  >         progress.update(i, item=getloopitem(i))
   >         if opts.get('parallel'):
-  >             ui.progress('other', i, 'other.%d' % i, 'othernum', total)
+  >             other.update(i, item='other.%d' % i)
   >         if nested:
   >             nested_steps = 2
   >             if i and i % 4 == 0:
   >                 nested_steps = 5
+  >             nested = ui.makeprogress('nested', unit='nestnum',
+  >                                      total=nested_steps)
   >             for j in range(nested_steps):
-  >                 ui.progress(
-  >                   'nested', j, 'nested.%d' % j, 'nestnum', nested_steps)
-  >             ui.progress(
-  >               'nested', None, 'nested.done', 'nestnum', nested_steps)
-  >     ui.progress(topiclabel, None, 'loop.done', 'loopnum', total)
+  >                 nested.update(j, item='nested.%d' % j)
+  >             nested.complete()
+  >     progress.complete()
   > 
   > topiclabel = 'loop'
   > def getloopitem(i):
--- a/tests/test-pull-branch.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-pull-branch.t	Thu Jul 19 13:55:54 2018 -0400
@@ -170,6 +170,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   new changesets 7d8ffa4c0b22
+  13 local changesets published
   (run 'hg heads' to see heads)
 
 Make changes on default and branchC on tt
@@ -183,6 +184,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   new changesets 2b94b54b6b5f
+  1 local changesets published
   (run 'hg heads' to see heads)
   $ hg up -C default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -219,6 +221,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files (+2 heads)
   new changesets eed40c14b407:e634733b0309
+  1 local changesets published
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ cd ..
--- a/tests/test-pull-bundle.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-pull-bundle.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,3 +1,5 @@
+#require no-chg
+
   $ hg init repo
   $ cd repo
   $ echo foo > foo
@@ -44,9 +46,9 @@
   $ hg bundle --base 1 -r 2 .hg/2.hg
   1 changesets found
   $ cat <<EOF > .hg/pullbundles.manifest
-  > 2.hg heads=effea6de0384e684f44435651cb7bd70b8735bd4 bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
-  > 1.hg heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
-  > 0.hg heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
+  > 2.hg BUNDLESPEC=none-v2 heads=effea6de0384e684f44435651cb7bd70b8735bd4 bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
+  > 1.hg BUNDLESPEC=bzip2-v2 heads=ed1b79f46b9a29f5a6efa59cf12fcfca43bead5a bases=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
+  > 0.hg BUNDLESPEC=gzip-v2 heads=bbd179dfa0a71671c253b3ae0aa1513b60d199fa
   > EOF
   $ hg --config blackbox.track=debug --debug serve -p $HGPORT2 -d --pid-file=../repo.pid
   listening at http://*:$HGPORT2/ (bound to $LOCALIP:$HGPORT2) (glob) (?)
--- a/tests/test-pull-http.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-pull-http.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 #if no-windows
 For debugging: this is a pretty simple test that is a good candidate
 for tracking down network-related bugs. Sometimes a command in this
--- a/tests/test-pull-update.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-pull-update.t	Thu Jul 19 13:55:54 2018 -0400
@@ -27,6 +27,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   new changesets 107cefe13e42
+  1 local changesets published
   abort: uncommitted changes
   [255]
   $ hg --config extensions.strip= strip --no-backup tip
@@ -58,6 +59,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   new changesets 800c91d5bfc1
+  1 local changesets published
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "107cefe13e42: m"
   1 other heads for branch "default"
@@ -80,6 +82,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (-1 heads)
   new changesets 483b76ad4309
+  1 local changesets published
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Similarity between "hg update" and "hg pull -u" in handling bookmark
@@ -108,6 +111,7 @@
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark active-after-pull
   new changesets f815b3da6163
+  1 local changesets published
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark active-after-pull)
 
@@ -137,6 +141,7 @@
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark active-after-pull
   new changesets f815b3da6163
+  1 local changesets published
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark active-after-pull)
 
@@ -175,6 +180,7 @@
   adding file changes
   added 2 changesets with 1 changes to 1 files
   new changesets f815b3da6163:b5e4babfaaa7
+  1 local changesets published
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
@@ -202,6 +208,7 @@
   adding file changes
   added 2 changesets with 1 changes to 1 files
   new changesets f815b3da6163:b5e4babfaaa7
+  1 local changesets published
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
@@ -229,6 +236,7 @@
   adding file changes
   added 2 changesets with 1 changes to 1 files
   new changesets f815b3da6163:b5e4babfaaa7
+  1 local changesets published
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
--- a/tests/test-pull.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-pull.t	Thu Jul 19 13:55:54 2018 -0400
@@ -109,12 +109,12 @@
 It's tricky to make file:// URLs working on every platform with
 regular shell commands.
 
-  $ URL=`$PYTHON -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
+  $ URL=`$PYTHON -c "from __future__ import print_function; import os; print('file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
   $ hg pull -q "$URL"
   abort: file:// URLs can only refer to localhost
   [255]
 
-  $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
+  $ URL=`$PYTHON -c "from __future__ import print_function; import os; print('file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
   $ hg pull -q "$URL"
 
 SEC: check for unsafe ssh url
--- a/tests/test-push-http.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-push-http.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require killdaemons
+#require no-chg
 
 #testcases bundle1 bundle2
 
--- a/tests/test-py3-commands.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-py3-commands.t	Thu Jul 19 13:55:54 2018 -0400
@@ -198,28 +198,28 @@
   $ $PYTHON3 $HGBIN log -Tjson
   [
    {
-    "rev": 1,
-    "node": "e1e9167203d450ca2f558af628955b5f5afd4489",
+    "bookmarks": [],
     "branch": "default",
-    "phase": "draft",
-    "user": "test",
     "date": [0, 0],
     "desc": "message",
-    "bookmarks": [],
+    "node": "e1e9167203d450ca2f558af628955b5f5afd4489",
+    "parents": ["71c96e924262969ff0d8d3d695b0f75412ccc3d8"],
+    "phase": "draft",
+    "rev": 1,
     "tags": ["tip"],
-    "parents": ["71c96e924262969ff0d8d3d695b0f75412ccc3d8"]
+    "user": "test"
    },
    {
-    "rev": 0,
-    "node": "71c96e924262969ff0d8d3d695b0f75412ccc3d8",
+    "bookmarks": [],
     "branch": "default",
-    "phase": "draft",
-    "user": "test",
     "date": [0, 0],
     "desc": "commit performed in Python 3",
-    "bookmarks": [],
+    "node": "71c96e924262969ff0d8d3d695b0f75412ccc3d8",
+    "parents": ["0000000000000000000000000000000000000000"],
+    "phase": "draft",
+    "rev": 0,
     "tags": [],
-    "parents": ["0000000000000000000000000000000000000000"]
+    "user": "test"
    }
   ]
 
--- a/tests/test-rebase-conflicts.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-rebase-conflicts.t	Thu Jul 19 13:55:54 2018 -0400
@@ -80,8 +80,8 @@
   # 
   # To mark files as resolved:  hg resolve --mark FILE
   
-  # To continue:                hg rebase --continue
-  # To abort:                   hg rebase --abort
+  # To continue:    hg rebase --continue
+  # To abort:       hg rebase --abort
   
 
 Try to continue without solving the conflict:
--- a/tests/test-rebase-inmemory.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-rebase-inmemory.t	Thu Jul 19 13:55:54 2018 -0400
@@ -4,6 +4,7 @@
   > amend=
   > rebase=
   > debugdrawdag=$TESTDIR/drawdag.py
+  > strip=
   > [rebase]
   > experimental.inmemory=1
   > [diff]
@@ -155,4 +156,356 @@
   |/
   o  0: b173517d0057 'a'
   
+Test dry-run rebasing
+  $ hg init repo3
+  $ cd repo3
+  $ echo a>a
+  $ hg ci -Aqma
+  $ echo b>b
+  $ hg ci -Aqmb
+  $ echo c>c
+  $ hg ci -Aqmc
+  $ echo d>d
+  $ hg ci -Aqmd
+  $ echo e>e
+  $ hg ci -Aqme
 
+  $ hg up 1 -q
+  $ echo f>f
+  $ hg ci -Amf
+  adding f
+  created new head
+  $ echo g>g
+  $ hg ci -Aqmg
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  @  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+Make sure it throws error while passing --continue or --abort with --dry-run
+  $ hg rebase -s 2 -d 6 -n --continue
+  abort: cannot specify both --dry-run and --continue
+  [255]
+  $ hg rebase -s 2 -d 6 -n --abort
+  abort: cannot specify both --dry-run and --abort
+  [255]
+
+Check dryrun gives correct results when there is no conflict in rebasing
+  $ hg rebase -s 2 -d 6 -n
+  starting dry-run rebase; repository will not be changed
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  rebasing 4:e860deea161a "e"
+  dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
+
+  $ hg diff
+  $ hg status
+
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  @  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+Check dryrun working with --collapse when there is no conflict
+  $ hg rebase -s 2 -d 6 -n --collapse
+  starting dry-run rebase; repository will not be changed
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  rebasing 4:e860deea161a "e"
+  dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
+
+Check dryrun gives correct results when there is conflict in rebasing
+Make a conflict:
+  $ hg up 6 -q
+  $ echo conflict>e
+  $ hg ci -Aqm "conflict with e"
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  @  7:d2c195b28050 test
+  |  conflict with e
+  |
+  o  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+  $ hg rebase -s 2 -d 7 -n
+  starting dry-run rebase; repository will not be changed
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  rebasing 4:e860deea161a "e"
+  merging e
+  transaction abort!
+  rollback completed
+  hit a merge conflict
+  [1]
+  $ hg diff
+  $ hg status
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  @  7:d2c195b28050 test
+  |  conflict with e
+  |
+  o  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+Check dryrun working with --collapse when there is conflicts
+  $ hg rebase -s 2 -d 7 -n --collapse
+  starting dry-run rebase; repository will not be changed
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  rebasing 4:e860deea161a "e"
+  merging e
+  hit a merge conflict
+  [1]
+
+==========================
+Test for --confirm option|
+==========================
+  $ cd ..
+  $ hg clone repo3 repo4 -q
+  $ cd repo4
+  $ hg strip 7 -q
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  @  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+Check it gives error when both --dryrun and --confirm is used:
+  $ hg rebase -s 2 -d . --confirm --dry-run
+  abort: cannot specify both --confirm and --dry-run
+  [255]
+  $ hg rebase -s 2 -d . --confirm --abort
+  abort: cannot specify both --confirm and --abort
+  [255]
+  $ hg rebase -s 2 -d . --confirm --continue
+  abort: cannot specify both --confirm and --continue
+  [255]
+
+Test --confirm option when there are no conflicts:
+  $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
+  > n
+  > EOF
+  starting in-memory rebase
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  rebasing 4:e860deea161a "e"
+  rebase completed successfully
+  apply changes (yn)? n
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  @  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+  $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
+  > y
+  > EOF
+  starting in-memory rebase
+  rebasing 2:177f92b77385 "c"
+  rebasing 3:055a42cdd887 "d"
+  rebasing 4:e860deea161a "e"
+  rebase completed successfully
+  apply changes (yn)? y
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  o  9:9fd28f55f6dc test
+  |  e
+  |
+  o  8:12cbf031f469 test
+  |  d
+  |
+  o  7:c83b1da5b1ae test
+  |  c
+  |
+  @  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+Test --confirm option when there is a conflict
+  $ hg up tip -q
+  $ echo ee>e
+  $ hg ci --amend -m "conflict with e" -q
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  @  9:906d72f66a59 test
+  |  conflict with e
+  |
+  o  8:12cbf031f469 test
+  |  d
+  |
+  o  7:c83b1da5b1ae test
+  |  c
+  |
+  o  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
+  $ hg rebase -s 4 -d . --keep --confirm
+  starting in-memory rebase
+  rebasing 4:e860deea161a "e"
+  merging e
+  hit a merge conflict
+  [1]
+  $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
+  @  9:906d72f66a59 test
+  |  conflict with e
+  |
+  o  8:12cbf031f469 test
+  |  d
+  |
+  o  7:c83b1da5b1ae test
+  |  c
+  |
+  o  6:baf10c5166d4 test
+  |  g
+  |
+  o  5:6343ca3eff20 test
+  |  f
+  |
+  | o  4:e860deea161a test
+  | |  e
+  | |
+  | o  3:055a42cdd887 test
+  | |  d
+  | |
+  | o  2:177f92b77385 test
+  |/   c
+  |
+  o  1:d2ae7f538514 test
+  |  b
+  |
+  o  0:cb9a9f314b8b test
+     a
+  
--- a/tests/test-rebase-interruptions.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-rebase-interruptions.t	Thu Jul 19 13:55:54 2018 -0400
@@ -333,12 +333,8 @@
 
   $ cp -R a3 hook-pretxncommit
   $ cd hook-pretxncommit
-#if windows
-  $ NODE="%HG_NODE%"
-#else
-  $ NODE="\$HG_NODE"
-#endif
-  $ hg rebase --source 2 --dest 5 --tool internal:other --config "hooks.pretxncommit=hg log -r $NODE | grep \"summary:     C\""
+  $ hg rebase --source 2 --dest 5 --tool internal:other \
+  >     --config 'hooks.tonative.pretxncommit=True' --config 'hooks.pretxncommit=hg log -r $HG_NODE | grep "summary:     C"'
   rebasing 2:965c486023db "C"
   summary:     C
   rebasing 6:a0b2430ebfb8 "F" (tip)
--- a/tests/test-rebase-obsolete.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-rebase-obsolete.t	Thu Jul 19 13:55:54 2018 -0400
@@ -281,7 +281,7 @@
 even though it is hidden (until we're moved there).
 
   $ hg --hidden up -qr 'first(hidden())'
-  updating to a hidden changeset 42ccdea3bb16
+  updated to hidden changeset 42ccdea3bb16
   (hidden revision '42ccdea3bb16' is pruned)
   $ hg rebase --rev 13 --dest 15
   rebasing 13:98f6af4ee953 "C"
@@ -642,9 +642,9 @@
 Test hidden changesets in the rebase set (issue4504)
 
   $ hg up --hidden 9
-  updating to a hidden changeset 4bde274eefcf
+  3 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset 4bde274eefcf
   (hidden revision '4bde274eefcf' was rewritten as: acd174b7ab39)
-  3 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo J > J
   $ hg add J
   $ hg commit -m J
@@ -764,9 +764,9 @@
   $ hg commit --amend -m B1
   $ hg commit --amend -m B2
   $ hg up --hidden 'desc(B0)'
-  updating to a hidden changeset a8b11f55fb19
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  updated to hidden changeset a8b11f55fb19
   (hidden revision 'a8b11f55fb19' was rewritten as: 261e70097290)
-  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo C > C
   $ hg add C
   $ hg commit -m C
@@ -790,9 +790,9 @@
 Even when the chain include missing node
 
   $ hg up --hidden 'desc(B0)'
-  updating to a hidden changeset a8b11f55fb19
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset a8b11f55fb19
   (hidden revision 'a8b11f55fb19' was rewritten as: 261e70097290)
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo D > D
   $ hg add D
   $ hg commit -m D
@@ -909,9 +909,9 @@
   $ hg add bar
   $ hg commit --amend -m "10'"
   $ hg up 10 --hidden
-  updating to a hidden changeset 121d9e3bc4c6
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  updated to hidden changeset 121d9e3bc4c6
   (hidden revision '121d9e3bc4c6' was rewritten as: 77d874d096a2)
-  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo "bar" > foo
   $ hg add foo
   $ hg commit -m "bar foo"
@@ -1738,7 +1738,7 @@
   $ rm .hg/localtags
 
   $ hg update -q $C --hidden
-  updating to a hidden changeset 7829726be4dc
+  updated to hidden changeset 7829726be4dc
   (hidden revision '7829726be4dc' is pruned)
   $ hg rebase -s $B -d $D
   rebasing 1:2ec65233581b "B"
--- a/tests/test-rebuildstate.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-rebuildstate.t	Thu Jul 19 13:55:54 2018 -0400
@@ -4,9 +4,9 @@
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > @command(b'debugadddrop',
-  >   [('', 'drop', False, 'drop file from dirstate', 'FILE'),
-  >    ('', 'normal-lookup', False, 'add file to dirstate', 'FILE')],
-  >     'hg debugadddrop')
+  >   [(b'', b'drop', False, b'drop file from dirstate', b'FILE'),
+  >    (b'', b'normal-lookup', False, b'add file to dirstate', b'FILE')],
+  >     b'hg debugadddrop')
   > def debugadddrop(ui, repo, *pats, **opts):
   >   '''Add or drop unnamed arguments to or from the dirstate'''
   >   drop = opts.get('drop')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-removeemptydirs.t	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,330 @@
+Tests for experimental.removeemptydirs
+
+  $ NO_RM=--config=experimental.removeemptydirs=0
+  $ isdir() { if [ -d $1 ]; then echo yes; else echo no; fi }
+  $ isfile() { if [ -f $1 ]; then echo yes; else echo no; fi }
+
+`hg rm` of the last file in a directory:
+  $ hg init hgrm
+  $ cd hgrm
+  $ mkdir somedir
+  $ echo hi > somedir/foo
+  $ hg ci -qAm foo
+  $ isdir somedir
+  yes
+  $ hg rm somedir/foo
+  $ isdir somedir
+  no
+  $ hg revert -qa
+  $ isdir somedir
+  yes
+  $ hg $NO_RM rm somedir/foo
+  $ isdir somedir
+  yes
+  $ ls somedir
+  $ cd $TESTTMP
+
+`hg mv` of the last file in a directory:
+  $ hg init hgmv
+  $ cd hgmv
+  $ mkdir somedir
+  $ mkdir destdir
+  $ echo hi > somedir/foo
+  $ hg ci -qAm foo
+  $ isdir somedir
+  yes
+  $ hg mv somedir/foo destdir/foo
+  $ isdir somedir
+  no
+  $ hg revert -qa
+(revert doesn't get rid of destdir/foo?)
+  $ rm destdir/foo
+  $ isdir somedir
+  yes
+  $ hg $NO_RM mv somedir/foo destdir/foo
+  $ isdir somedir
+  yes
+  $ ls somedir
+  $ cd $TESTTMP
+
+Updating to a commit that doesn't have the directory:
+  $ hg init hgupdate
+  $ cd hgupdate
+  $ echo hi > r0
+  $ hg ci -qAm r0
+  $ mkdir somedir
+  $ echo hi > somedir/foo
+  $ hg ci -qAm r1
+  $ isdir somedir
+  yes
+  $ hg co -q -r ".^"
+  $ isdir somedir
+  no
+  $ hg co -q tip
+  $ isdir somedir
+  yes
+  $ hg $NO_RM co -q -r ".^"
+  $ isdir somedir
+  yes
+  $ ls somedir
+  $ cd $TESTTMP
+
+Rebasing across a commit that doesn't have the directory, from inside the
+directory:
+  $ hg init hgrebase
+  $ cd hgrebase
+  $ echo hi > r0
+  $ hg ci -qAm r0
+  $ mkdir somedir
+  $ echo hi > somedir/foo
+  $ hg ci -qAm first_rebase_source
+  $ hg $NO_RM co -q -r ".^"
+  $ echo hi > somedir/bar
+  $ hg ci -qAm first_rebase_dest
+  $ hg $NO_RM co -q -r ".^"
+  $ echo hi > somedir/baz
+  $ hg ci -qAm second_rebase_dest
+  $ hg co -qr 'desc(first_rebase_source)'
+  $ cd $TESTTMP/hgrebase/somedir
+  $ hg --config extensions.rebase= rebase -qr . -d 'desc(first_rebase_dest)'
+  current directory was removed
+  (consider changing to repo root: $TESTTMP/hgrebase)
+  $ cd $TESTTMP/hgrebase/somedir
+(The current node is the rebased first_rebase_source on top of
+first_rebase_dest)
+This should not output anything about current directory being removed:
+  $ hg $NO_RM --config extensions.rebase= rebase -qr . -d 'desc(second_rebase_dest)'
+  $ cd $TESTTMP
+
+Histediting across a commit that doesn't have the directory, from inside the
+directory (reordering nodes):
+  $ hg init hghistedit
+  $ cd hghistedit
+  $ echo hi > r0
+  $ hg ci -qAm r0
+  $ echo hi > r1
+  $ hg ci -qAm r1
+  $ echo hi > r2
+  $ hg ci -qAm r2
+  $ mkdir somedir
+  $ echo hi > somedir/foo
+  $ hg ci -qAm migrating_revision
+  $ cat > histedit_commands <<EOF
+  > pick 89079fab8aee 0 r0
+  > pick e6d271df3142 1 r1
+  > pick 89e25aa83f0f 3 migrating_revision
+  > pick b550aa12d873 2 r2
+  > EOF
+  $ cd $TESTTMP/hghistedit/somedir
+  $ hg --config extensions.histedit= histedit -q --commands ../histedit_commands
+
+histedit doesn't output anything when the current diretory is removed. We rely
+on the tests being commonly run on machines where the current directory
+disappearing from underneath us actually has an observable effect, such as an
+error or no files listed
+#if linuxormacos
+  $ isfile foo
+  no
+#endif
+  $ cd $TESTTMP/hghistedit/somedir
+  $ isfile foo
+  yes
+
+  $ cd $TESTTMP/hghistedit
+  $ cat > histedit_commands <<EOF
+  > pick 89079fab8aee 0 r0
+  > pick 7c7a22c6009f 3 migrating_revision
+  > pick e6d271df3142 1 r1
+  > pick 40a53c2d4276 2 r2
+  > EOF
+  $ cd $TESTTMP/hghistedit/somedir
+  $ hg $NO_RM --config extensions.histedit= histedit -q --commands ../histedit_commands
+Regardless of system, we should always get a 'yes' here.
+  $ isfile foo
+  yes
+  $ cd $TESTTMP
+
+This is essentially the exact test from issue5826, just cleaned up a little:
+
+  $ hg init issue5826_withrm
+  $ cd issue5826_withrm
+
+Let's only turn this on for this repo so that we don't contaminate later tests.
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > histedit =
+  > EOF
+Commit three revisions that each create a directory:
+
+  $ mkdir foo
+  $ touch foo/bar
+  $ hg commit -qAm "add foo"
+
+  $ mkdir bar
+  $ touch bar/bar
+  $ hg commit -qAm "add bar"
+
+  $ mkdir baz
+  $ touch baz/bar
+  $ hg commit -qAm "add baz"
+
+Enter the first directory:
+
+  $ cd foo
+
+Histedit doing 'pick, pick, fold':
+
+  $ hg histedit --commands /dev/stdin <<EOF
+  > pick 6274c77c93c3 1 add bar
+  > pick ff70a87b588f 0 add foo
+  > fold 9992bb0ac0db 2 add baz
+  > EOF
+  abort: $ENOENT$
+  [255]
+
+Go back to the repo root after losing it as part of that operation:
+  $ cd $TESTTMP/issue5826_withrm
+
+Note the lack of a non-zero exit code from this function - it exits
+successfully, but doesn't really do anything.
+  $ hg histedit --continue
+  9992bb0ac0db: cannot fold - working copy is not a descendant of previous commit 5c806432464a
+  saved backup bundle to $TESTTMP/issue5826_withrm/.hg/strip-backup/ff70a87b588f-e94f9789-histedit.hg
+
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  2:94e3f9fae1d6 fold-temp-revision 9992bb0ac0db
+  1:5c806432464a add foo
+  0:d17db4b0303a add bar
+
+Now test that again with experimental.removeemptydirs=false:
+  $ hg init issue5826_norm
+  $ cd issue5826_norm
+
+Let's only turn this on for this repo so that we don't contaminate later tests.
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > histedit =
+  > [experimental]
+  > removeemptydirs = false
+  > EOF
+Commit three revisions that each create a directory:
+
+  $ mkdir foo
+  $ touch foo/bar
+  $ hg commit -qAm "add foo"
+
+  $ mkdir bar
+  $ touch bar/bar
+  $ hg commit -qAm "add bar"
+
+  $ mkdir baz
+  $ touch baz/bar
+  $ hg commit -qAm "add baz"
+
+Enter the first directory:
+
+  $ cd foo
+
+Histedit doing 'pick, pick, fold':
+
+  $ hg histedit --commands /dev/stdin <<EOF
+  > pick 6274c77c93c3 1 add bar
+  > pick ff70a87b588f 0 add foo
+  > fold 9992bb0ac0db 2 add baz
+  > EOF
+  saved backup bundle to $TESTTMP/issue5826_withrm/issue5826_norm/.hg/strip-backup/5c806432464a-cd4c8d86-histedit.hg
+
+Note the lack of a 'cd' being necessary here, and we don't need to 'histedit
+--continue'
+
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  1:b9eddaa97cbc add foo
+  ***
+  add baz
+  0:d17db4b0303a add bar
+
+  $ cd $TESTTMP
+
+Testing `hg split` being run from inside of a directory that was created in the
+commit being split:
+
+  $ hg init hgsplit
+  $ cd hgsplit
+  $ cat >> .hg/hgrc << EOF
+  > [ui]
+  > interactive = 1
+  > [extensions]
+  > split =
+  > EOF
+  $ echo anchor > anchor.txt
+  $ hg ci -qAm anchor
+
+Create a changeset with '/otherfile_in_root' and 'somedir/foo', then try to
+split it.
+  $ echo otherfile > otherfile_in_root
+  $ mkdir somedir
+  $ cd somedir
+  $ echo hi > foo
+  $ hg ci -qAm split_me
+(Note: need to make this file not in this directory, or else the bug doesn't
+reproduce; we're using a separate file due to concerns of portability on
+`echo -e`)
+  $ cat > ../split_commands << EOF
+  > n
+  > y
+  > y
+  > a
+  > EOF
+  $ cat ../split_commands | hg split
+  current directory was removed
+  (consider changing to repo root: $TESTTMP/hgsplit)
+  diff --git a/otherfile_in_root b/otherfile_in_root
+  new file mode 100644
+  examine changes to 'otherfile_in_root'? [Ynesfdaq?] n
+  
+  diff --git a/somedir/foo b/somedir/foo
+  new file mode 100644
+  examine changes to 'somedir/foo'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +hi
+  record change 2/2 to 'somedir/foo'? [Ynesfdaq?] y
+  
+  abort: $ENOENT$
+  [255]
+
+Let's try that again without the rmdir
+  $ cd $TESTTMP/hgsplit/somedir
+Show that the previous split didn't do anything
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  1:e26b22a4f0b7 split_me
+  0:7e53273730c0 anchor
+  $ hg status
+  ? split_commands
+Try again
+  $ cat ../split_commands | hg $NO_RM split
+  diff --git a/otherfile_in_root b/otherfile_in_root
+  new file mode 100644
+  examine changes to 'otherfile_in_root'? [Ynesfdaq?] n
+  
+  diff --git a/somedir/foo b/somedir/foo
+  new file mode 100644
+  examine changes to 'somedir/foo'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +hi
+  record change 2/2 to 'somedir/foo'? [Ynesfdaq?] y
+  
+  created new head
+  diff --git a/otherfile_in_root b/otherfile_in_root
+  new file mode 100644
+  examine changes to 'otherfile_in_root'? [Ynesfdaq?] a
+  
+  saved backup bundle to $TESTTMP/hgsplit/.hg/strip-backup/*-split.hg (glob)
+Show that this split did something
+  $ hg log -T '{rev}:{node|short} {desc}\n'
+  2:a440f24fca4f split_me
+  1:c994f20276ab split_me
+  0:7e53273730c0 anchor
+  $ hg status
+  ? split_commands
--- a/tests/test-rename-after-merge.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-rename-after-merge.t	Thu Jul 19 13:55:54 2018 -0400
@@ -36,6 +36,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   new changesets d2ae7f538514
+  1 local changesets published
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge
--- a/tests/test-rename-dir-merge.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-rename-dir-merge.t	Thu Jul 19 13:55:54 2018 -0400
@@ -219,6 +219,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   new changesets 7d51ed18da25
+  1 local changesets published
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge
--- a/tests/test-resolve.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-resolve.t	Thu Jul 19 13:55:54 2018 -0400
@@ -162,6 +162,10 @@
    }
   ]
 
+  $ hg resolve -l -T '{path} {status} {p1rev} {p2rev}\n'
+  file1 R 2 1
+  file2 U 2 1
+
 resolve -m without paths should mark all resolved
 
   $ hg resolve -m
--- a/tests/test-revert-interactive.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-revert-interactive.t	Thu Jul 19 13:55:54 2018 -0400
@@ -22,16 +22,16 @@
   $ mkdir -p a/folder1 a/folder2
   $ cd a
   $ hg init
-  >>> open('f', 'wb').write("1\n2\n3\n4\n5\n")
+  >>> open('f', 'wb').write(b"1\n2\n3\n4\n5\n") and None
   $ hg add f ; hg commit -m "adding f"
   $ cat f > folder1/g ; hg add folder1/g ; hg commit -m "adding folder1/g"
   $ cat f > folder2/h ; hg add folder2/h ; hg commit -m "adding folder2/h"
   $ cat f > folder1/i ; hg add folder1/i ; hg commit -m "adding folder1/i"
-  >>> open('f', 'wb').write("a\n1\n2\n3\n4\n5\nb\n")
+  >>> open('f', 'wb').write(b"a\n1\n2\n3\n4\n5\nb\n") and None
   $ hg commit -m "modifying f"
-  >>> open('folder1/g', 'wb').write("c\n1\n2\n3\n4\n5\nd\n")
+  >>> open('folder1/g', 'wb').write(b"c\n1\n2\n3\n4\n5\nd\n") and None
   $ hg commit -m "modifying folder1/g"
-  >>> open('folder2/h', 'wb').write("e\n1\n2\n3\n4\n5\nf\n")
+  >>> open('folder2/h', 'wb').write(b"e\n1\n2\n3\n4\n5\nf\n") and None
   $ hg commit -m "modifying folder2/h"
   $ hg tip
   changeset:   6:59dd6e4ab63a
@@ -182,7 +182,7 @@
   
   $ ls folder1/
   g
-  >>> open('folder1/g', 'wb').write("1\n2\n3\n4\n5\nd\n")
+  >>> open('folder1/g', 'wb').write(b"1\n2\n3\n4\n5\nd\n") and None
 
 
   $ hg update -C 6
--- a/tests/test-revlog-raw.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-revlog-raw.py	Thu Jul 19 13:55:54 2018 -0400
@@ -13,10 +13,10 @@
 )
 
 # TESTTMP is optional. This makes it convenient to run without run-tests.py
-tvfs = vfs.vfs(encoding.environ.get('TESTTMP', b'/tmp'))
+tvfs = vfs.vfs(encoding.environ.get(b'TESTTMP', b'/tmp'))
 
 # Enable generaldelta otherwise revlog won't use delta as expected by the test
-tvfs.options = {'generaldelta': True, 'revlogv1': True}
+tvfs.options = {b'generaldelta': True, b'revlogv1': True}
 
 # The test wants to control whether to use delta explicitly, based on
 # "storedeltachains".
@@ -116,21 +116,21 @@
                 deltaparent = min(0, parentrev)
             if not rlog.candelta(deltaparent, r):
                 deltaparent = -1
-            return {'node': rlog.node(r), 'p1': pnode, 'p2': node.nullid,
-                    'cs': rlog.node(rlog.linkrev(r)), 'flags': rlog.flags(r),
-                    'deltabase': rlog.node(deltaparent),
-                    'delta': rlog.revdiff(deltaparent, r)}
+            return {b'node': rlog.node(r), b'p1': pnode, b'p2': node.nullid,
+                    b'cs': rlog.node(rlog.linkrev(r)), b'flags': rlog.flags(r),
+                    b'deltabase': rlog.node(deltaparent),
+                    b'delta': rlog.revdiff(deltaparent, r)}
 
         def deltaiter(self):
             chain = None
             for chunkdata in iter(lambda: self.deltachunk(chain), {}):
-                node = chunkdata['node']
-                p1 = chunkdata['p1']
-                p2 = chunkdata['p2']
-                cs = chunkdata['cs']
-                deltabase = chunkdata['deltabase']
-                delta = chunkdata['delta']
-                flags = chunkdata['flags']
+                node = chunkdata[b'node']
+                p1 = chunkdata[b'p1']
+                p2 = chunkdata[b'p2']
+                cs = chunkdata[b'cs']
+                deltabase = chunkdata[b'deltabase']
+                delta = chunkdata[b'delta']
+                flags = chunkdata[b'flags']
 
                 chain = node
 
@@ -166,9 +166,9 @@
         flags = rlog.flags(r)
         ifh = dfh = None
         try:
-            ifh = dlog.opener(dlog.indexfile, 'a+')
+            ifh = dlog.opener(dlog.indexfile, b'a+')
             if not dlog._inline:
-                dfh = dlog.opener(dlog.datafile, 'a+')
+                dfh = dlog.opener(dlog.datafile, b'a+')
             dlog._addrevision(rlog.node(r), text, tr, r, p1, p2, flags,
                               cachedelta, ifh, dfh)
         finally:
@@ -305,7 +305,7 @@
         checkrevlog(rl2, expected)
         print('addgroupcopy test passed')
         # Copy via revlog.clone
-        rl3 = newrevlog(name='_destrevlog3.i', recreate=True)
+        rl3 = newrevlog(name=b'_destrevlog3.i', recreate=True)
         rl.clone(tr, rl3)
         checkrevlog(rl3, expected)
         print('clone test passed')
--- a/tests/test-revlog-v2.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-revlog-v2.t	Thu Jul 19 13:55:54 2018 -0400
@@ -31,7 +31,7 @@
 Unknown flags to revlog are rejected
 
   >>> with open('.hg/store/00changelog.i', 'wb') as fh:
-  ...     fh.write(b'\x00\x04\xde\xad')
+  ...     fh.write(b'\x00\x04\xde\xad') and None
 
   $ hg log
   abort: unknown flags (0x04) in version 57005 revlog 00changelog.i!
--- a/tests/test-revlog.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-revlog.t	Thu Jul 19 13:55:54 2018 -0400
@@ -4,7 +4,7 @@
 Flags on revlog version 0 are rejected
 
   >>> with open('.hg/store/00changelog.i', 'wb') as fh:
-  ...     fh.write(b'\x00\x01\x00\x00')
+  ...     fh.write(b'\x00\x01\x00\x00') and None
 
   $ hg log
   abort: unknown flags (0x01) in version 0 revlog 00changelog.i!
@@ -13,7 +13,7 @@
 Unknown flags on revlog version 1 are rejected
 
   >>> with open('.hg/store/00changelog.i', 'wb') as fh:
-  ...     fh.write(b'\x00\x04\x00\x01')
+  ...     fh.write(b'\x00\x04\x00\x01') and None
 
   $ hg log
   abort: unknown flags (0x04) in version 1 revlog 00changelog.i!
@@ -22,7 +22,7 @@
 Unknown version is rejected
 
   >>> with open('.hg/store/00changelog.i', 'wb') as fh:
-  ...     fh.write(b'\x00\x00\x00\x02')
+  ...     fh.write(b'\x00\x00\x00\x02') and None
 
   $ hg log
   abort: unknown version (2) in revlog 00changelog.i!
--- a/tests/test-revset.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-revset.t	Thu Jul 19 13:55:54 2018 -0400
@@ -42,8 +42,8 @@
   >     registrar,
   >     revset,
   >     revsetlang,
-  >     smartset,
   > )
+  > from mercurial.utils import stringutil
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > @command(b'debugrevlistspec',
@@ -63,7 +63,7 @@
   >     func = revset.match(ui, expr, lookup=revset.lookupfn(repo))
   >     revs = func(repo)
   >     if ui.verbose:
-  >         ui.note(b"* set:\n", smartset.prettyformat(revs), b"\n")
+  >         ui.note(b"* set:\n", stringutil.prettyrepr(revs), b"\n")
   >     for c in revs:
   >         ui.write(b"%d\n" % c)
   > EOF
@@ -1041,6 +1041,34 @@
   2
   3
 
+test common ancestors
+
+  $ hg log -T '{rev}\n' -r 'commonancestors(7 + 9)'
+  0
+  1
+  2
+  4
+
+  $ hg log -T '{rev}\n' -r 'commonancestors(head())'
+  0
+  1
+  2
+  4
+
+  $ hg log -T '{rev}\n' -r 'commonancestors(9)'
+  0
+  1
+  2
+  4
+  8
+  9
+
+test ancestor variants of empty revision
+
+  $ log 'ancestor(none())'
+  $ log 'ancestors(none())'
+  $ log 'commonancestors(none())'
+
 test ancestors with depth limit
 
  (depth=0 selects the node itself)
@@ -1357,8 +1385,40 @@
   6
   7
   9
+
+Test heads
+
   $ log 'heads(6::)'
   7
+
+ heads() can be computed in subset '9:'
+
+  $ hg debugrevspec -s '9: & heads(all())'
+  * set:
+  <filteredset
+    <filteredset
+      <baseset [9]>,
+      <spanset+ 0:10>>,
+    <not
+      <filteredset
+        <baseset [9]>, set([0, 1, 2, 3, 4, 5, 6, 8])>>>
+  9
+
+ but should follow the order of the subset
+
+  $ log 'heads(all())'
+  7
+  9
+  $ log 'heads(tip:0)'
+  7
+  9
+  $ log 'tip:0 & heads(all())'
+  9
+  7
+  $ log 'tip:0 & heads(0:tip)'
+  9
+  7
+
   $ log 'keyword(issue)'
   6
   $ log 'keyword("test a")'
@@ -1713,8 +1773,6 @@
 
 Test hexadecimal revision
   $ log 'id(2)'
-  abort: 00changelog.i@2: ambiguous identifier!
-  [255]
   $ log 'id(23268)'
   4
   $ log 'id(2785f51eece)'
@@ -1783,6 +1841,16 @@
   6
   7
   2147483647
+  $ hg debugrevspec '0:wdir() & ancestor(wdir())'
+  2147483647
+  $ hg debugrevspec '0:wdir() & ancestor(.:wdir())'
+  4
+  $ hg debugrevspec '0:wdir() & ancestor(wdir(), wdir())'
+  2147483647
+  $ hg debugrevspec '0:wdir() & ancestor(wdir(), tip)'
+  4
+  $ hg debugrevspec 'null:wdir() & ancestor(wdir(), null)'
+  -1
   $ hg debugrevspec 'wdir()~0'
   2147483647
   $ hg debugrevspec 'p1(wdir())'
@@ -1876,9 +1944,9 @@
   $ hg debugrevspec '0:wdir() & fffb'
   abort: 00changelog.i@fffb: ambiguous identifier!
   [255]
-BROKEN should be '2' (node lookup uses unfiltered repo since dc25ed84bee8)
+BROKEN should be '2' (node lookup uses unfiltered repo)
   $ hg debugrevspec '0:wdir() & id(fffb)'
-  2
+BROKEN should be '2' (node lookup uses unfiltered repo)
   $ hg debugrevspec '0:wdir() & ffff8'
   4
   $ hg debugrevspec '0:wdir() & fffff'
--- a/tests/test-revset2.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-revset2.t	Thu Jul 19 13:55:54 2018 -0400
@@ -584,6 +584,9 @@
   hg: parse error: empty string is not a valid revision
   [255]
 
+test empty revset
+  $ hg log 'none()'
+
 we can use patterns when searching for tags
 
   $ log 'tag("1..*")'
@@ -1589,11 +1592,11 @@
   > 
   > revsetpredicate = registrar.revsetpredicate()
   > 
-  > @revsetpredicate('custom1()')
+  > @revsetpredicate(b'custom1()')
   > def custom1(repo, subset, x):
   >     return revset.baseset([1])
   > 
-  > raise error.Abort('intentional failure of loading extension')
+  > raise error.Abort(b'intentional failure of loading extension')
   > EOF
   $ cat <<EOF > .hg/hgrc
   > [extensions]
@@ -1611,14 +1614,14 @@
   > from mercurial import encoding, registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
-  > @command('printprevset')
+  > @command(b'printprevset')
   > def printprevset(ui, repo):
   >     alias = {}
-  >     p = encoding.environ.get('P')
+  >     p = encoding.environ.get(b'P')
   >     if p:
-  >         alias['P'] = p
-  >     revs = repo.anyrevs(['P'], user=True, localalias=alias)
-  >     ui.write('P=%r\n' % list(revs))
+  >         alias[b'P'] = p
+  >     revs = repo.anyrevs([b'P'], user=True, localalias=alias)
+  >     ui.write(b'P=%r\n' % list(revs))
   > EOF
 
   $ cat >> .hg/hgrc <<EOF
@@ -1831,3 +1834,21 @@
         (keyvalue
           (symbol 'depth')
           (symbol '1')))))
+
+test commonancestors and its optimization
+
+  $ hg debugrevspec --verify -p analyzed -p optimized 'heads(commonancestors(head()))'
+  * analyzed:
+  (func
+    (symbol 'heads')
+    (func
+      (symbol 'commonancestors')
+      (func
+        (symbol 'head')
+        None)))
+  * optimized:
+  (func
+    (symbol '_commonancestorheads')
+    (func
+      (symbol 'head')
+      None))
--- a/tests/test-run-tests.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-run-tests.py	Thu Jul 19 13:55:54 2018 -0400
@@ -40,7 +40,7 @@
     assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
            b'single backslash or unknown char'
     test = run_tests.TTest(b'test-run-test.t', b'.', b'.')
-    match = test.linematch(expected, output)
+    match, exact = test.linematch(expected, output)
     if isinstance(match, str):
         return 'special: ' + match
     elif isinstance(match, bytes):
--- a/tests/test-run-tests.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-run-tests.t	Thu Jul 19 13:55:54 2018 -0400
@@ -120,6 +120,49 @@
   python hash seed: * (glob)
   [1]
 
+test how multiple globs gets matched with lines in output
+  $ cat > test-failure-globs.t <<EOF
+  >   $ echo "context"; echo "context"; \
+  >     echo "key: 1"; echo "value: not a"; \
+  >     echo "key: 2"; echo "value: not b"; \
+  >     echo "key: 3"; echo "value: c"; \
+  >     echo "key: 4"; echo "value: d"
+  >   context
+  >   context
+  >   key: 1
+  >   value: a
+  >   key: 2
+  >   value: b
+  >   key: 3
+  >   value: * (glob)
+  >   key: 4
+  >   value: * (glob)
+  > EOF
+  $ rt test-failure-globs.t
+  
+  --- $TESTTMP/test-failure-globs.t
+  +++ $TESTTMP/test-failure-globs.t.err
+  @@ -2,9 +2,9 @@
+     context
+     context
+     key: 1
+  -  value: a
+  +  value: not a
+     key: 2
+  -  value: b
+  +  value: not b
+     key: 3
+     value: * (glob)
+     key: 4
+  
+  ERROR: test-failure-globs.t output changed
+  !
+  Failed test-failure-globs.t: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+  $ rm test-failure-globs.t
+
 test diff colorisation
 
 #if no-windows pygments
@@ -374,6 +417,7 @@
 
   $ cat .testtimes
   test-empty.t * (glob)
+  test-failure-globs.t * (glob)
   test-failure-unicode.t * (glob)
   test-failure.t * (glob)
   test-success.t * (glob)
@@ -574,7 +618,6 @@
   # Ran 1 tests, 0 skipped, 0 failed.
   $ rm test-serve-inuse.t
   $ killdaemons.py $DAEMON_PIDS
-  $ rm $DAEMON_PIDS
 
 Running In Debug Mode
 ======================
@@ -1203,6 +1246,17 @@
   $ echo dead:beef::1
   $LOCALIP (glob)
 
+Add support for external test formatter
+=======================================
+
+  $ CUSTOM_TEST_RESULT=basic_test_result $PYTHON $TESTDIR/run-tests.py --with-hg=`which hg` "$@" test-success.t test-failure.t
+  
+  # Ran 2 tests, 0 skipped, 0 failed.
+  ON_START! <__main__.TestSuite tests=[<__main__.TTest testMethod=test-failure.t>, <__main__.TTest testMethod=test-success.t>]>
+  FAILURE! test-failure.t output changed
+  SUCCESS! test-success.t
+  ON_END!
+
 Test reusability for third party tools
 ======================================
 
@@ -1497,9 +1551,9 @@
      $ [ $V = C ]
    #endif
   
-  ERROR: test-cases-abc.t (case B) output changed
+  ERROR: test-cases-abc.t#B output changed
   !.
-  Failed test-cases-abc.t (case B): output changed
+  Failed test-cases-abc.t#B: output changed
   # Ran 3 tests, 0 skipped, 1 failed.
   python hash seed: * (glob)
   [1]
@@ -1520,9 +1574,9 @@
      $ [ $V = C ]
    #endif
   
-  ERROR: test-cases-abc.t (case B) output changed
+  ERROR: test-cases-abc.t#B output changed
   !.
-  Failed test-cases-abc.t (case B): output changed
+  Failed test-cases-abc.t#B: output changed
   # Ran 2 tests, 0 skipped, 1 failed.
   python hash seed: * (glob)
   [1]
@@ -1545,9 +1599,9 @@
      $ [ $V = C ]
    #endif
   
-  ERROR: test-cases-abc.t (case B) output changed
+  ERROR: test-cases-abc.t#B output changed
   !.
-  Failed test-cases-abc.t (case B): output changed
+  Failed test-cases-abc.t#B: output changed
   # Ran 2 tests, 0 skipped, 1 failed.
   python hash seed: * (glob)
   [1]
@@ -1572,7 +1626,152 @@
   ..
   # Ran 2 tests, 0 skipped, 0 failed.
 
+Support running a specific test case
+
+  $ rt "test-cases-abc.t#B"
+  
+  --- $TESTTMP/anothertests/cases/test-cases-abc.t
+  +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+  @@ -7,7 +7,7 @@
+     $ V=C
+   #endif
+     $ echo $V | sed 's/A/C/'
+  -  C
+  +  B
+   #if C
+     $ [ $V = C ]
+   #endif
+  
+  ERROR: test-cases-abc.t#B output changed
+  !
+  Failed test-cases-abc.t#B: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+Support running multiple test cases in the same file
+
+  $ rt test-cases-abc.t#B test-cases-abc.t#C
+  
+  --- $TESTTMP/anothertests/cases/test-cases-abc.t
+  +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+  @@ -7,7 +7,7 @@
+     $ V=C
+   #endif
+     $ echo $V | sed 's/A/C/'
+  -  C
+  +  B
+   #if C
+     $ [ $V = C ]
+   #endif
+  
+  ERROR: test-cases-abc.t#B output changed
+  !.
+  Failed test-cases-abc.t#B: output changed
+  # Ran 2 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+Support ignoring invalid test cases
+
+  $ rt test-cases-abc.t#B test-cases-abc.t#D
+  
+  --- $TESTTMP/anothertests/cases/test-cases-abc.t
+  +++ $TESTTMP/anothertests/cases/test-cases-abc.t.B.err
+  @@ -7,7 +7,7 @@
+     $ V=C
+   #endif
+     $ echo $V | sed 's/A/C/'
+  -  C
+  +  B
+   #if C
+     $ [ $V = C ]
+   #endif
+  
+  ERROR: test-cases-abc.t#B output changed
+  !
+  Failed test-cases-abc.t#B: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+Support running complex test cases names
+
+  $ cat > test-cases-advanced-cases.t <<'EOF'
+  > #testcases simple case-with-dashes casewith_-.chars
+  >   $ echo $TESTCASE
+  >   simple
+  > EOF
+
+  $ cat test-cases-advanced-cases.t
+  #testcases simple case-with-dashes casewith_-.chars
+    $ echo $TESTCASE
+    simple
+
+  $ rt test-cases-advanced-cases.t
+  
+  --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
+  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.case-with-dashes.err
+  @@ -1,3 +1,3 @@
+   #testcases simple case-with-dashes casewith_-.chars
+     $ echo $TESTCASE
+  -  simple
+  +  case-with-dashes
+  
+  ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
+  !
+  --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
+  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.casewith_-.chars.err
+  @@ -1,3 +1,3 @@
+   #testcases simple case-with-dashes casewith_-.chars
+     $ echo $TESTCASE
+  -  simple
+  +  casewith_-.chars
+  
+  ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
+  !.
+  Failed test-cases-advanced-cases.t#case-with-dashes: output changed
+  Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
+  # Ran 3 tests, 0 skipped, 2 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ rt "test-cases-advanced-cases.t#case-with-dashes"
+  
+  --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
+  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.case-with-dashes.err
+  @@ -1,3 +1,3 @@
+   #testcases simple case-with-dashes casewith_-.chars
+     $ echo $TESTCASE
+  -  simple
+  +  case-with-dashes
+  
+  ERROR: test-cases-advanced-cases.t#case-with-dashes output changed
+  !
+  Failed test-cases-advanced-cases.t#case-with-dashes: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ rt "test-cases-advanced-cases.t#casewith_-.chars"
+  
+  --- $TESTTMP/anothertests/cases/test-cases-advanced-cases.t
+  +++ $TESTTMP/anothertests/cases/test-cases-advanced-cases.t.casewith_-.chars.err
+  @@ -1,3 +1,3 @@
+   #testcases simple case-with-dashes casewith_-.chars
+     $ echo $TESTCASE
+  -  simple
+  +  casewith_-.chars
+  
+  ERROR: test-cases-advanced-cases.t#casewith_-.chars output changed
+  !
+  Failed test-cases-advanced-cases.t#casewith_-.chars: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
 Test automatic pattern replacement
+==================================
 
   $ cat << EOF >> common-pattern.py
   > substitutions = [
--- a/tests/test-serve.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-serve.t	Thu Jul 19 13:55:54 2018 -0400
@@ -6,10 +6,11 @@
   >        | sed -e "s/:$HGPORT1\\([^0-9]\\)/:HGPORT1\1/g" \
   >              -e "s/:$HGPORT2\\([^0-9]\\)/:HGPORT2\1/g" \
   >              -e 's/http:\/\/[^/]*\//http:\/\/localhost\//'
-  >    cat hg.pid >> "$DAEMON_PIDS"
+  >    if [ -f hg.pid ]; then
+  >        killdaemons.py hg.pid
+  >    fi
   >    echo % errors
   >    cat errors.log
-  >    killdaemons.py hg.pid
   > }
 
   $ hg init test
--- a/tests/test-share.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-share.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
   $ echo "[extensions]"      >> $HGRCPATH
   $ echo "share = "          >> $HGRCPATH
 
@@ -362,6 +360,7 @@
   searching for changes
   no changes found
   adding remote bookmark bm3
+  1 local changesets published
   $ hg boo
      bm1                       3:b87954705719
    * bm3                       4:62f4ded848e4
--- a/tests/test-shelve.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-shelve.t	Thu Jul 19 13:55:54 2018 -0400
@@ -68,9 +68,12 @@
    -l --list                list current shelves
    -m --message TEXT        use text as shelve message
    -n --name NAME           use the given name for the shelved commit
-   -p --patch               show patch
+   -p --patch               output patches for changes (provide the names of the
+                            shelved changes as positional arguments)
    -i --interactive         interactive mode, only works while creating a shelve
-      --stat                output diffstat-style summary of changes
+      --stat                output diffstat-style summary of changes (provide
+                            the names of the shelved changes as positional
+                            arguments)
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
       --mq                  operate on patch repository
@@ -136,9 +139,7 @@
 cleaning the branches made for name checking tests
 
   $ hg up default -q
-  $ hg strip 3 -q
-  $ hg strip 2 -q
-  $ hg strip 1 -q
+  $ hg strip e9177275307e+6a6d231f43d+882bae7c62c2 -q
 
 create an mq patch - shelving should work fine with a patch applied
 
@@ -215,7 +216,6 @@
   unshelving change 'default-01'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 4:32c69314e062 "changes to: [mq]: second.patch" (tip)
   merging a/a
 
   $ hg revert --all -q
@@ -337,7 +337,6 @@
   unshelving change 'default'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 5:32c69314e062 "changes to: [mq]: second.patch" (tip)
   merging a/a
   warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -356,8 +355,8 @@
   # 
   # To mark files as resolved:  hg resolve --mark FILE
   
-  # To continue:                hg unshelve --continue
-  # To abort:                   hg unshelve --abort
+  # To continue:    hg unshelve --continue
+  # To abort:       hg unshelve --abort
   
 
 ensure that we have a merge with unresolved conflicts
@@ -380,11 +379,11 @@
   +++ b/a/a
   @@ -1,2 +1,6 @@
    a
-  +<<<<<<< dest:   * - shelve: pending changes temporary commit (glob)
+  +<<<<<<< shelve:       562f7831e574 - shelve: pending changes temporary commit
    c
   +=======
   +a
-  +>>>>>>> source: 32c69314e062 - shelve: changes to: [mq]: second.patch
+  +>>>>>>> working-copy: 32c69314e062 - shelve: changes to: [mq]: second.patch
   diff --git a/b/b b/b.rename/b
   rename from b/b
   rename to b.rename/b
@@ -408,7 +407,6 @@
   R b/b
   ? a/a.orig
   $ hg unshelve -a
-  rebase aborted
   unshelve of 'default' aborted
   $ hg heads -q
   3:2e69b451d1ea
@@ -462,7 +460,6 @@
   (continue: hg unshelve --continue)
   [255]
   $ hg unshelve -c
-  rebasing 5:32c69314e062 "changes to: [mq]: second.patch" (tip)
   unshelve of 'default' complete
 
 ensure the repo is as we hope
@@ -533,7 +530,6 @@
   unshelving change 'default'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 6:2f694dd83a13 "changes to: second" (tip)
   merging a/a
   $ hg parents -q
   4:33f7f61e6c5e
@@ -556,9 +552,8 @@
   unshelving change 'default'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 6:2f694dd83a13 "changes to: second" (tip)
   merging a/a
-  note: rebase of 6:2f694dd83a13 created no changes to commit
+  note: unshelved changes already existed in the working copy
   $ hg parents -q
   4:33f7f61e6c5e
   $ hg shelve -l
@@ -632,7 +627,7 @@
   $ hg commit -Aqm xy
   $ echo z >> x
   $ hg commit -Aqm z
-  $ hg up 0
+  $ hg up 5c4c67fb7dce
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ printf 'a\nx\ny\nz\n' > x
   $ hg commit -Aqm xyz
@@ -640,14 +635,13 @@
   $ hg shelve
   shelved as default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg rebase -d 1 --config extensions.rebase=
+  $ hg rebase -d 6c103be8f4e4 --config extensions.rebase=
   rebasing 2:323bfa07f744 "xyz" (tip)
   merging x
   saved backup bundle to $TESTTMP/shelverebase/.hg/strip-backup/323bfa07f744-78114325-rebase.hg
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 4:82a0d7d6ba61 "changes to: xyz" (tip)
   $ hg status
   M z
 
@@ -661,7 +655,7 @@
   $ hg ci -Aqm a
   $ touch b
   $ hg ci -Aqm b
-  $ hg up -q 0
+  $ hg up -q 3903775176ed
   $ touch c
   $ hg ci -Aqm c
 
@@ -670,11 +664,10 @@
   $ hg shelve
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg up -q 1
+  $ hg up -q 0e067c57feba
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 3:958bcbd1776e "changes to: c" (tip)
   $ hg status
   A d
 
@@ -683,12 +676,11 @@
   $ hg shelve
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg up 0
+  $ hg up 3903775176ed
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 3:013284d9655e "changes to: b" (tip)
   $ hg status
   A d
 
@@ -701,7 +693,7 @@
   $ hg shelve
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg debugobsolete `hg --debug id -i -r 1`
+  $ hg debugobsolete `hg log -r 0e067c57feba -T '{node}'`
   obsoleted 1 changesets
   $ hg unshelve
   unshelving change 'default'
@@ -790,7 +782,6 @@
   unshelving change 'default'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 5:81152db69da7 "changes to: commit stuff" (tip)
   merging f
   warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -810,16 +801,15 @@
   M f
   ? f.orig
   $ cat f
-  <<<<<<< dest:   5f6b880e719b - shelve: pending changes temporary commit
+  <<<<<<< shelve:       5f6b880e719b - shelve: pending changes temporary commit
   g
   =======
   f
-  >>>>>>> source: 81152db69da7 - shelve: changes to: commit stuff
+  >>>>>>> working-copy: 81152db69da7 - shelve: changes to: commit stuff
   $ cat f.orig
   g
   $ hg unshelve --abort -t false
   tool option will be ignored
-  rebase aborted
   unshelve of 'default' aborted
   $ hg st
   M a
@@ -830,7 +820,6 @@
   unshelving change 'default'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 5:81152db69da7 "changes to: commit stuff" (tip)
   $ hg st
   M a
   A f
@@ -846,7 +835,6 @@
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 5:81152db69da7 "changes to: commit stuff" (tip)
   merging f
   warning: conflicts while merging f! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -855,15 +843,14 @@
   M f
   ? f.orig
   $ cat f
-  <<<<<<< dest:   * - test: intermediate other change (glob)
+  <<<<<<< shelve:       6b563750f973 - test: intermediate other change
   g
   =======
   f
-  >>>>>>> source: 81152db69da7 - shelve: changes to: commit stuff
+  >>>>>>> working-copy: 81152db69da7 - shelve: changes to: commit stuff
   $ cat f.orig
   g
   $ hg unshelve --abort
-  rebase aborted
   unshelve of 'default' aborted
   $ hg st
   ? f.orig
@@ -874,7 +861,7 @@
 Recreate some conflict again
 
   $ cd ../repo
-  $ hg up -C -r 3
+  $ hg up -C -r 2e69b451d1ea
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark test)
   $ echo y >> a/a
@@ -889,7 +876,6 @@
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 5:e42a7da90865 "changes to: second" (tip)
   merging a/a
   warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -906,8 +892,7 @@
   (no more unresolved files)
   continue: hg unshelve --continue
   $ hg unshelve -c
-  rebasing 5:e42a7da90865 "changes to: second" (tip)
-  note: rebase of 5:e42a7da90865 created no changes to commit
+  note: unshelved changes already existed in the working copy
   unshelve of 'default' complete
   $ hg bookmark
    * test                      4:33f7f61e6c5e
@@ -1000,7 +985,6 @@
   unshelving change 'test'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 6:96a1354f65f6 "changes to: create conflict" (tip)
   merging a/a
   $ hg bookmark
    * test                      4:33f7f61e6c5e
@@ -1073,8 +1057,33 @@
   $ hg shelve --patch default nonexistentshelf
   abort: cannot find shelf nonexistentshelf
   [255]
+
+when the user asks for a patch, we assume they want the most recent shelve if
+they don't provide a shelve name
+
   $ hg shelve --patch
-  abort: --patch expects at least one shelf
+  default-01      (*)* changes to: create conflict (glob)
+  
+  diff --git a/shelf-patch-b b/shelf-patch-b
+  new file mode 100644
+  --- /dev/null
+  +++ b/shelf-patch-b
+  @@ -0,0 +1,1 @@
+  +patch b
+
+  $ cd ..
+
+you shouldn't be able to ask for the patch/stats of the most recent shelve if
+there are no shelves
+
+  $ hg init noshelves
+  $ cd noshelves
+
+  $ hg shelve --patch
+  abort: there are no shelves to show
+  [255]
+  $ hg shelve --stat
+  abort: there are no shelves to show
   [255]
 
   $ cd ..
@@ -1177,13 +1186,12 @@
   $ hg unshelve --keep default
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 7:206bf5d4f922 "changes to: create conflict" (tip)
   ==== preupdate:
   VISIBLE 6:66b86db80ee4
   ACTUAL  5:703117a2acfb
   ====
   ==== preupdate:
-  VISIBLE 8:a0e04704317e
+  VISIBLE 8:92fdbb7b4de7
   ACTUAL  5:703117a2acfb
   ====
   ==== preupdate:
@@ -1204,7 +1212,7 @@
 
 == test visibility to external update hook
 
-  $ hg update -q -C 5
+  $ hg update -q -C 703117a2acfb
 
   $ cat >> .hg/hgrc <<EOF
   > [hooks]
@@ -1222,7 +1230,6 @@
   $ hg unshelve --keep default
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 7:206bf5d4f922 "changes to: create conflict" (tip)
   ==== update:
   VISIBLE 6:66b86db80ee4
   VISIBLE 7:206bf5d4f922
@@ -1269,30 +1276,19 @@
 
 test Abort unshelve always gets user out of the unshelved state
 ---------------------------------------------------------------
-Wreak havoc on the unshelve process
-  $ rm .hg/unshelverebasestate
-  $ hg unshelve --abort
+
+with a corrupted shelve state file
+  $ sed 's/ae8c668541e8/123456789012/' .hg/shelvedstate > ../corrupt-shelvedstate
+  $ mv ../corrupt-shelvedstate .hg/shelvestate
+  $ hg unshelve --abort 2>&1 | grep 'aborted'
   unshelve of 'default' aborted
-  abort: $ENOENT$* (glob)
-  [255]
-Can the user leave the current state?
-  $ hg up -C .
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-
-Try again but with a corrupted shelve state file
-  $ hg strip -r 2 -r 1 -q
-  $ hg up -r 0 -q
-  $ echo '' > root
-  $ hg shelve -q
-  $ echo 'contADDent' > root
-  $ hg unshelve -q
-  warning: conflicts while merging root! (edit, then use 'hg resolve --mark')
-  unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
-  [1]
-  $ sed 's/ae8c668541e8/123456789012/' .hg/shelvedstate > ../corrupt-shelvedstate
-  $ mv ../corrupt-shelvedstate .hg/histedit-state
-  $ hg unshelve --abort 2>&1 | grep 'rebase aborted'
-  rebase aborted
+  $ hg summary
+  parent: 0:ae8c668541e8 tip
+   root
+  branch: default
+  commit: 1 modified
+  update: (current)
+  phases: 1 draft
   $ hg up -C .
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1373,7 +1369,6 @@
   unshelving change 'default'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 1:098df96e7410 "(changes in empty repository)" (tip)
   merging unknown
   $ hg status
   A unknown
@@ -1394,7 +1389,6 @@
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 1:098df96e7410 "(changes in empty repository)" (tip)
   merging unknown
   $ hg status
   M unknown
@@ -1527,7 +1521,6 @@
   unshelving change 'default'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 2:425c97ef07f3 "changes to: a" (tip)
   merging a
   warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -1538,7 +1531,6 @@
   (no more unresolved files)
   continue: hg unshelve --continue
   $ hg unshelve --continue
-  rebasing 2:425c97ef07f3 "changes to: a" (tip)
   marked working directory as branch test
   unshelve of 'default' complete
   $ cat a
@@ -1558,12 +1550,11 @@
   $ hg shelve
   shelved as test
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg update -r default
+  $ hg update -r 7049e48789d7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg unshelve
   unshelving change 'test'
   rebasing shelved changes
-  rebasing 2:357525f34729 "changes to: test-commit" (tip)
   $ hg status
   A b
   $ hg branch
@@ -1604,7 +1595,6 @@
   unshelving change 'default'
   temporarily committing pending changes (restore with 'hg unshelve --abort')
   rebasing shelved changes
-  rebasing 2:425c97ef07f3 "changes to: a" (tip)
   merging a
   warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -1622,7 +1612,6 @@
   (no more unresolved files)
   continue: hg unshelve --continue
   $ hg unshelve --continue
-  rebasing 2:425c97ef07f3 "changes to: a" (tip)
   unshelve of 'default' complete
   $ cat a
   aaabbbccc
@@ -1678,7 +1667,6 @@
   $ hg unshelve
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 1:396ea74229f9 "(changes in empty repository)" (tip)
   merging file
   warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -1717,7 +1705,6 @@
   $ hg unshelve --keep
   unshelving change 'default'
   rebasing shelved changes
-  rebasing 2:3fbe6fbb0bef "changes to: 1" (tip)
   merging file
   warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -1726,7 +1713,6 @@
   (no more unresolved files)
   continue: hg unshelve --continue
   $ hg unshelve --continue
-  rebasing 2:3fbe6fbb0bef "changes to: 1" (tip)
   unshelve of 'default' complete
   $ hg shelve --list
   default         (*s ago) * changes to: 1 (glob)
@@ -1776,7 +1762,6 @@
   $ hg unshelve
   unshelving change 'ashelve'
   rebasing shelved changes
-  rebasing 2:003d2d94241c "changes to: root" (tip)
   merging a
   warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
@@ -1799,7 +1784,6 @@
   continue: hg unshelve --continue
 mercurial does not crash
   $ hg unshelve --continue
-  rebasing 2:003d2d94241c "changes to: root" (tip)
   unshelve of 'ashelve' complete
   $ cd ..
 
--- a/tests/test-simple-update.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-simple-update.t	Thu Jul 19 13:55:54 2018 -0400
@@ -31,6 +31,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   new changesets 30aff43faee1
+  1 local changesets published
   (run 'hg update' to get a working copy)
 
   $ hg verify
@@ -64,7 +65,7 @@
 
   $ cat <<EOF > forceworker.py
   > from mercurial import extensions, worker
-  > def nocost(orig, ui, costperop, nops):
+  > def nocost(orig, ui, costperop, nops, threadsafe=True):
   >     return worker._numworkers(ui) > 1
   > def uisetup(ui):
   >     extensions.wrapfunction(worker, 'worthwhile', nocost)
--- a/tests/test-simplekeyvaluefile.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-simplekeyvaluefile.py	Thu Jul 19 13:55:54 2018 -0400
@@ -44,11 +44,11 @@
         self.vfs = mockvfs()
 
     def testbasicwritingiandreading(self):
-        dw = {'key1': 'value1', 'Key2': 'value2'}
-        scmutil.simplekeyvaluefile(self.vfs, 'kvfile').write(dw)
-        self.assertEqual(sorted(self.vfs.read('kvfile').split('\n')),
-                         ['', 'Key2=value2', 'key1=value1'])
-        dr = scmutil.simplekeyvaluefile(self.vfs, 'kvfile').read()
+        dw = {b'key1': b'value1', b'Key2': b'value2'}
+        scmutil.simplekeyvaluefile(self.vfs, b'kvfile').write(dw)
+        self.assertEqual(sorted(self.vfs.read(b'kvfile').split(b'\n')),
+                         [b'', b'Key2=value2', b'key1=value1'])
+        dr = scmutil.simplekeyvaluefile(self.vfs, b'kvfile').read()
         self.assertEqual(dr, dw)
 
     if not getattr(unittest.TestCase, 'assertRaisesRegex', False):
@@ -58,33 +58,33 @@
             unittest.TestCase.assertRaisesRegexp)
 
     def testinvalidkeys(self):
-        d = {'0key1': 'value1', 'Key2': 'value2'}
+        d = {b'0key1': b'value1', b'Key2': b'value2'}
         with self.assertRaisesRegex(error.ProgrammingError,
                                      'keys must start with a letter.*'):
-            scmutil.simplekeyvaluefile(self.vfs, 'kvfile').write(d)
+            scmutil.simplekeyvaluefile(self.vfs, b'kvfile').write(d)
 
-        d = {'key1@': 'value1', 'Key2': 'value2'}
+        d = {b'key1@': b'value1', b'Key2': b'value2'}
         with self.assertRaisesRegex(error.ProgrammingError, 'invalid key.*'):
-            scmutil.simplekeyvaluefile(self.vfs, 'kvfile').write(d)
+            scmutil.simplekeyvaluefile(self.vfs, b'kvfile').write(d)
 
     def testinvalidvalues(self):
-        d = {'key1': 'value1', 'Key2': 'value2\n'}
+        d = {b'key1': b'value1', b'Key2': b'value2\n'}
         with self.assertRaisesRegex(error.ProgrammingError,  'invalid val.*'):
-            scmutil.simplekeyvaluefile(self.vfs, 'kvfile').write(d)
+            scmutil.simplekeyvaluefile(self.vfs, b'kvfile').write(d)
 
     def testcorruptedfile(self):
-        self.vfs.contents['badfile'] = 'ababagalamaga\n'
+        self.vfs.contents[b'badfile'] = b'ababagalamaga\n'
         with self.assertRaisesRegex(error.CorruptedState,
                                      'dictionary.*element.*'):
-            scmutil.simplekeyvaluefile(self.vfs, 'badfile').read()
+            scmutil.simplekeyvaluefile(self.vfs, b'badfile').read()
 
     def testfirstline(self):
-        dw = {'key1': 'value1'}
-        scmutil.simplekeyvaluefile(self.vfs, 'fl').write(dw, firstline='1.0')
-        self.assertEqual(self.vfs.read('fl'), '1.0\nkey1=value1\n')
-        dr = scmutil.simplekeyvaluefile(self.vfs, 'fl')\
+        dw = {b'key1': b'value1'}
+        scmutil.simplekeyvaluefile(self.vfs, b'fl').write(dw, firstline=b'1.0')
+        self.assertEqual(self.vfs.read(b'fl'), b'1.0\nkey1=value1\n')
+        dr = scmutil.simplekeyvaluefile(self.vfs, b'fl')\
                     .read(firstlinenonkeyval=True)
-        self.assertEqual(dr, {'__firstline': '1.0', 'key1': 'value1'})
+        self.assertEqual(dr, {b'__firstline': b'1.0', b'key1': b'value1'})
 
 if __name__ == "__main__":
     silenttestrunner.main(__name__)
--- a/tests/test-simplemerge.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-simplemerge.py	Thu Jul 19 13:55:54 2018 -0400
@@ -35,12 +35,12 @@
     incorporating the changes from both BASE->OTHER and BASE->THIS.
     All three will typically be sequences of lines."""
     def __init__(self, base, a, b):
-        basetext = '\n'.join([i.strip('\n') for i in base] + [''])
-        atext = '\n'.join([i.strip('\n') for i in a] + [''])
-        btext = '\n'.join([i.strip('\n') for i in b] + [''])
+        basetext = b'\n'.join([i.strip(b'\n') for i in base] + [b''])
+        atext = b'\n'.join([i.strip(b'\n') for i in a] + [b''])
+        btext = b'\n'.join([i.strip(b'\n') for i in b] + [b''])
         if (stringutil.binary(basetext) or stringutil.binary(atext)
             or stringutil.binary(btext)):
-            raise error.Abort("don't know how to merge binary files")
+            raise error.Abort(b"don't know how to merge binary files")
         simplemerge.Merge3Text.__init__(self, basetext, atext, btext,
                                         base, a, b)
 
@@ -52,7 +52,7 @@
 ############################################################
 # test case data from the gnu diffutils manual
 # common base
-TZU = split_lines("""     The Nameless is the origin of Heaven and Earth;
+TZU = split_lines(b"""     The Nameless is the origin of Heaven and Earth;
      The named is the mother of all things.
 
      Therefore let there always be non-being,
@@ -67,7 +67,7 @@
      The door of all subtleties!
 """)
 
-LAO = split_lines("""     The Way that can be told of is not the eternal Way;
+LAO = split_lines(b"""     The Way that can be told of is not the eternal Way;
      The name that can be named is not the eternal name.
      The Nameless is the origin of Heaven and Earth;
      The Named is the mother of all things.
@@ -81,7 +81,7 @@
 """)
 
 
-TAO = split_lines("""     The Way that can be told of is not the eternal Way;
+TAO = split_lines(b"""     The Way that can be told of is not the eternal Way;
      The name that can be named is not the eternal name.
      The Nameless is the origin of Heaven and Earth;
      The named is the mother of all things.
@@ -98,7 +98,7 @@
 
 """)
 
-MERGED_RESULT = split_lines("""\
+MERGED_RESULT = split_lines(b"""\
      The Way that can be told of is not the eternal Way;
      The name that can be named is not the eternal name.
      The Nameless is the origin of Heaven and Earth;
@@ -109,13 +109,13 @@
        so we may see their result.
      The two are the same,
      But after they are produced,
-       they have different names.
-<<<<<<< LAO
-=======
+       they have different names.\
+\n<<<<<<< LAO\
+\n=======
 
        -- The Way of Lao-Tzu, tr. Wing-tsit Chan
-
->>>>>>> TAO
+\
+\n>>>>>>> TAO
 """)
 
 class TestMerge3(TestCase):
@@ -124,167 +124,167 @@
 
     def test_no_changes(self):
         """No conflicts because nothing changed"""
-        m3 = Merge3(['aaa', 'bbb'],
-                    ['aaa', 'bbb'],
-                    ['aaa', 'bbb'])
+        m3 = Merge3([b'aaa', b'bbb'],
+                    [b'aaa', b'bbb'],
+                    [b'aaa', b'bbb'])
 
-        self.assertEquals(m3.find_unconflicted(),
-                          [(0, 2)])
+        self.assertEqual(m3.find_unconflicted(),
+                         [(0, 2)])
 
-        self.assertEquals(list(m3.find_sync_regions()),
-                          [(0, 2,
-                            0, 2,
-                            0, 2),
-                           (2, 2,  2, 2,  2, 2)])
+        self.assertEqual(list(m3.find_sync_regions()),
+                         [(0, 2,
+                           0, 2,
+                           0, 2),
+                          (2, 2,  2, 2,  2, 2)])
 
-        self.assertEquals(list(m3.merge_regions()),
-                          [('unchanged', 0, 2)])
+        self.assertEqual(list(m3.merge_regions()),
+                         [(b'unchanged', 0, 2)])
 
-        self.assertEquals(list(m3.merge_groups()),
-                          [('unchanged', ['aaa', 'bbb'])])
+        self.assertEqual(list(m3.merge_groups()),
+                         [(b'unchanged', [b'aaa', b'bbb'])])
 
     def test_front_insert(self):
-        m3 = Merge3(['zz'],
-                    ['aaa', 'bbb', 'zz'],
-                    ['zz'])
+        m3 = Merge3([b'zz'],
+                    [b'aaa', b'bbb', b'zz'],
+                    [b'zz'])
 
         # todo: should use a sentinel at end as from get_matching_blocks
         # to match without zz
-        self.assertEquals(list(m3.find_sync_regions()),
-                          [(0, 1,  2, 3,  0, 1),
-                           (1, 1,  3, 3,  1, 1)])
+        self.assertEqual(list(m3.find_sync_regions()),
+                         [(0, 1,  2, 3,  0, 1),
+                          (1, 1,  3, 3,  1, 1)])
 
-        self.assertEquals(list(m3.merge_regions()),
-                          [('a', 0, 2),
-                           ('unchanged', 0, 1)])
+        self.assertEqual(list(m3.merge_regions()),
+                         [(b'a', 0, 2),
+                          (b'unchanged', 0, 1)])
 
-        self.assertEquals(list(m3.merge_groups()),
-                          [('a', ['aaa', 'bbb']),
-                           ('unchanged', ['zz'])])
+        self.assertEqual(list(m3.merge_groups()),
+                         [(b'a', [b'aaa', b'bbb']),
+                          (b'unchanged', [b'zz'])])
 
     def test_null_insert(self):
         m3 = Merge3([],
-                    ['aaa', 'bbb'],
+                    [b'aaa', b'bbb'],
                     [])
         # todo: should use a sentinel at end as from get_matching_blocks
         # to match without zz
-        self.assertEquals(list(m3.find_sync_regions()),
-                          [(0, 0,  2, 2,  0, 0)])
+        self.assertEqual(list(m3.find_sync_regions()),
+                         [(0, 0,  2, 2,  0, 0)])
 
-        self.assertEquals(list(m3.merge_regions()),
-                          [('a', 0, 2)])
+        self.assertEqual(list(m3.merge_regions()),
+                         [(b'a', 0, 2)])
 
-        self.assertEquals(list(m3.merge_lines()),
-                          ['aaa', 'bbb'])
+        self.assertEqual(list(m3.merge_lines()),
+                         [b'aaa', b'bbb'])
 
     def test_no_conflicts(self):
         """No conflicts because only one side changed"""
-        m3 = Merge3(['aaa', 'bbb'],
-                    ['aaa', '111', 'bbb'],
-                    ['aaa', 'bbb'])
+        m3 = Merge3([b'aaa', b'bbb'],
+                    [b'aaa', b'111', b'bbb'],
+                    [b'aaa', b'bbb'])
 
-        self.assertEquals(m3.find_unconflicted(),
-                          [(0, 1), (1, 2)])
+        self.assertEqual(m3.find_unconflicted(),
+                         [(0, 1), (1, 2)])
 
-        self.assertEquals(list(m3.find_sync_regions()),
-                          [(0, 1,  0, 1,  0, 1),
-                           (1, 2,  2, 3,  1, 2),
-                           (2, 2,  3, 3,  2, 2)])
+        self.assertEqual(list(m3.find_sync_regions()),
+                         [(0, 1,  0, 1,  0, 1),
+                          (1, 2,  2, 3,  1, 2),
+                          (2, 2,  3, 3,  2, 2)])
 
-        self.assertEquals(list(m3.merge_regions()),
-                          [('unchanged', 0, 1),
-                           ('a', 1, 2),
-                           ('unchanged', 1, 2)])
+        self.assertEqual(list(m3.merge_regions()),
+                         [(b'unchanged', 0, 1),
+                          (b'a', 1, 2),
+                          (b'unchanged', 1, 2)])
 
     def test_append_a(self):
-        m3 = Merge3(['aaa\n', 'bbb\n'],
-                    ['aaa\n', 'bbb\n', '222\n'],
-                    ['aaa\n', 'bbb\n'])
+        m3 = Merge3([b'aaa\n', b'bbb\n'],
+                    [b'aaa\n', b'bbb\n', b'222\n'],
+                    [b'aaa\n', b'bbb\n'])
 
-        self.assertEquals(''.join(m3.merge_lines()),
-                          'aaa\nbbb\n222\n')
+        self.assertEqual(b''.join(m3.merge_lines()),
+                         b'aaa\nbbb\n222\n')
 
     def test_append_b(self):
-        m3 = Merge3(['aaa\n', 'bbb\n'],
-                    ['aaa\n', 'bbb\n'],
-                    ['aaa\n', 'bbb\n', '222\n'])
+        m3 = Merge3([b'aaa\n', b'bbb\n'],
+                    [b'aaa\n', b'bbb\n'],
+                    [b'aaa\n', b'bbb\n', b'222\n'])
 
-        self.assertEquals(''.join(m3.merge_lines()),
-                          'aaa\nbbb\n222\n')
+        self.assertEqual(b''.join(m3.merge_lines()),
+                         b'aaa\nbbb\n222\n')
 
     def test_append_agreement(self):
-        m3 = Merge3(['aaa\n', 'bbb\n'],
-                    ['aaa\n', 'bbb\n', '222\n'],
-                    ['aaa\n', 'bbb\n', '222\n'])
+        m3 = Merge3([b'aaa\n', b'bbb\n'],
+                    [b'aaa\n', b'bbb\n', b'222\n'],
+                    [b'aaa\n', b'bbb\n', b'222\n'])
 
-        self.assertEquals(''.join(m3.merge_lines()),
-                          'aaa\nbbb\n222\n')
+        self.assertEqual(b''.join(m3.merge_lines()),
+                         b'aaa\nbbb\n222\n')
 
     def test_append_clash(self):
-        m3 = Merge3(['aaa\n', 'bbb\n'],
-                    ['aaa\n', 'bbb\n', '222\n'],
-                    ['aaa\n', 'bbb\n', '333\n'])
+        m3 = Merge3([b'aaa\n', b'bbb\n'],
+                    [b'aaa\n', b'bbb\n', b'222\n'],
+                    [b'aaa\n', b'bbb\n', b'333\n'])
 
-        ml = m3.merge_lines(name_a='a',
-                            name_b='b',
-                            start_marker='<<',
-                            mid_marker='--',
-                            end_marker='>>')
-        self.assertEquals(''.join(ml),
-                          'aaa\n'
-                          'bbb\n'
-                          '<< a\n'
-                          '222\n'
-                          '--\n'
-                          '333\n'
-                          '>> b\n'
+        ml = m3.merge_lines(name_a=b'a',
+                            name_b=b'b',
+                            start_marker=b'<<',
+                            mid_marker=b'--',
+                            end_marker=b'>>')
+        self.assertEqual(b''.join(ml),
+                         b'aaa\n'
+                         b'bbb\n'
+                         b'<< a\n'
+                         b'222\n'
+                         b'--\n'
+                         b'333\n'
+                         b'>> b\n'
                          )
 
     def test_insert_agreement(self):
-        m3 = Merge3(['aaa\n', 'bbb\n'],
-                    ['aaa\n', '222\n', 'bbb\n'],
-                    ['aaa\n', '222\n', 'bbb\n'])
+        m3 = Merge3([b'aaa\n', b'bbb\n'],
+                    [b'aaa\n', b'222\n', b'bbb\n'],
+                    [b'aaa\n', b'222\n', b'bbb\n'])
 
-        ml = m3.merge_lines(name_a='a',
-                            name_b='b',
-                            start_marker='<<',
-                            mid_marker='--',
-                            end_marker='>>')
-        self.assertEquals(''.join(ml), 'aaa\n222\nbbb\n')
+        ml = m3.merge_lines(name_a=b'a',
+                            name_b=b'b',
+                            start_marker=b'<<',
+                            mid_marker=b'--',
+                            end_marker=b'>>')
+        self.assertEqual(b''.join(ml), b'aaa\n222\nbbb\n')
 
 
     def test_insert_clash(self):
         """Both try to insert lines in the same place."""
-        m3 = Merge3(['aaa\n', 'bbb\n'],
-                    ['aaa\n', '111\n', 'bbb\n'],
-                    ['aaa\n', '222\n', 'bbb\n'])
+        m3 = Merge3([b'aaa\n', b'bbb\n'],
+                    [b'aaa\n', b'111\n', b'bbb\n'],
+                    [b'aaa\n', b'222\n', b'bbb\n'])
 
-        self.assertEquals(m3.find_unconflicted(),
-                          [(0, 1), (1, 2)])
+        self.assertEqual(m3.find_unconflicted(),
+                         [(0, 1), (1, 2)])
 
-        self.assertEquals(list(m3.find_sync_regions()),
-                          [(0, 1,  0, 1,  0, 1),
-                           (1, 2,  2, 3,  2, 3),
-                           (2, 2,  3, 3,  3, 3)])
+        self.assertEqual(list(m3.find_sync_regions()),
+                         [(0, 1,  0, 1,  0, 1),
+                          (1, 2,  2, 3,  2, 3),
+                          (2, 2,  3, 3,  3, 3)])
 
-        self.assertEquals(list(m3.merge_regions()),
-                          [('unchanged', 0, 1),
-                           ('conflict', 1, 1,  1, 2,  1, 2),
-                           ('unchanged', 1, 2)])
+        self.assertEqual(list(m3.merge_regions()),
+                         [(b'unchanged', 0, 1),
+                          (b'conflict', 1, 1,  1, 2,  1, 2),
+                          (b'unchanged', 1, 2)])
 
-        self.assertEquals(list(m3.merge_groups()),
-                          [('unchanged', ['aaa\n']),
-                           ('conflict', [], ['111\n'], ['222\n']),
-                           ('unchanged', ['bbb\n']),
-                           ])
+        self.assertEqual(list(m3.merge_groups()),
+                         [(b'unchanged', [b'aaa\n']),
+                          (b'conflict', [], [b'111\n'], [b'222\n']),
+                          (b'unchanged', [b'bbb\n']),
+                          ])
 
-        ml = m3.merge_lines(name_a='a',
-                            name_b='b',
-                            start_marker='<<',
-                            mid_marker='--',
-                            end_marker='>>')
-        self.assertEquals(''.join(ml),
-'''aaa
+        ml = m3.merge_lines(name_a=b'a',
+                            name_b=b'b',
+                            start_marker=b'<<',
+                            mid_marker=b'--',
+                            end_marker=b'>>')
+        self.assertEqual(b''.join(ml),
+b'''aaa
 << a
 111
 --
@@ -295,64 +295,64 @@
 
     def test_replace_clash(self):
         """Both try to insert lines in the same place."""
-        m3 = Merge3(['aaa', '000', 'bbb'],
-                    ['aaa', '111', 'bbb'],
-                    ['aaa', '222', 'bbb'])
+        m3 = Merge3([b'aaa', b'000', b'bbb'],
+                    [b'aaa', b'111', b'bbb'],
+                    [b'aaa', b'222', b'bbb'])
 
-        self.assertEquals(m3.find_unconflicted(),
-                          [(0, 1), (2, 3)])
+        self.assertEqual(m3.find_unconflicted(),
+                         [(0, 1), (2, 3)])
 
-        self.assertEquals(list(m3.find_sync_regions()),
-                          [(0, 1,  0, 1,  0, 1),
+        self.assertEqual(list(m3.find_sync_regions()),
+                         [(0, 1,  0, 1,  0, 1),
                            (2, 3,  2, 3,  2, 3),
                            (3, 3,  3, 3,  3, 3)])
 
     def test_replace_multi(self):
         """Replacement with regions of different size."""
-        m3 = Merge3(['aaa', '000', '000', 'bbb'],
-                    ['aaa', '111', '111', '111', 'bbb'],
-                    ['aaa', '222', '222', '222', '222', 'bbb'])
+        m3 = Merge3([b'aaa', b'000', b'000', b'bbb'],
+                    [b'aaa', b'111', b'111', b'111', b'bbb'],
+                    [b'aaa', b'222', b'222', b'222', b'222', b'bbb'])
 
-        self.assertEquals(m3.find_unconflicted(),
-                          [(0, 1), (3, 4)])
+        self.assertEqual(m3.find_unconflicted(),
+                         [(0, 1), (3, 4)])
 
 
-        self.assertEquals(list(m3.find_sync_regions()),
-                          [(0, 1,  0, 1,  0, 1),
-                           (3, 4,  4, 5,  5, 6),
-                           (4, 4,  5, 5,  6, 6)])
+        self.assertEqual(list(m3.find_sync_regions()),
+                         [(0, 1,  0, 1,  0, 1),
+                          (3, 4,  4, 5,  5, 6),
+                          (4, 4,  5, 5,  6, 6)])
 
     def test_merge_poem(self):
         """Test case from diff3 manual"""
         m3 = Merge3(TZU, LAO, TAO)
-        ml = list(m3.merge_lines('LAO', 'TAO'))
-        self.log('merge result:')
-        self.log(''.join(ml))
-        self.assertEquals(ml, MERGED_RESULT)
+        ml = list(m3.merge_lines(b'LAO', b'TAO'))
+        self.log(b'merge result:')
+        self.log(b''.join(ml))
+        self.assertEqual(ml, MERGED_RESULT)
 
     def test_binary(self):
         with self.assertRaises(error.Abort):
-            Merge3(['\x00'], ['a'], ['b'])
+            Merge3([b'\x00'], [b'a'], [b'b'])
 
     def test_dos_text(self):
-        base_text = 'a\r\n'
-        this_text = 'b\r\n'
-        other_text = 'c\r\n'
+        base_text = b'a\r\n'
+        this_text = b'b\r\n'
+        other_text = b'c\r\n'
         m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
                     this_text.splitlines(True))
-        m_lines = m3.merge_lines('OTHER', 'THIS')
-        self.assertEqual('<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
-            '>>>>>>> THIS\r\n'.splitlines(True), list(m_lines))
+        m_lines = m3.merge_lines(b'OTHER', b'THIS')
+        self.assertEqual(b'<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
+                         b'>>>>>>> THIS\r\n'.splitlines(True), list(m_lines))
 
     def test_mac_text(self):
-        base_text = 'a\r'
-        this_text = 'b\r'
-        other_text = 'c\r'
+        base_text = b'a\r'
+        this_text = b'b\r'
+        other_text = b'c\r'
         m3 = Merge3(base_text.splitlines(True), other_text.splitlines(True),
                     this_text.splitlines(True))
-        m_lines = m3.merge_lines('OTHER', 'THIS')
-        self.assertEqual('<<<<<<< OTHER\rc\r=======\rb\r'
-            '>>>>>>> THIS\r'.splitlines(True), list(m_lines))
+        m_lines = m3.merge_lines(b'OTHER', b'THIS')
+        self.assertEqual(b'<<<<<<< OTHER\rc\r=======\rb\r'
+                         b'>>>>>>> THIS\r'.splitlines(True), list(m_lines))
 
 if __name__ == '__main__':
     # hide the timer
--- a/tests/test-split.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-split.t	Thu Jul 19 13:55:54 2018 -0400
@@ -91,10 +91,11 @@
   $ hg forget dirty
   $ rm dirty
 
-Split a head
+Make a clean directory for future tests to build off of
 
-  $ cp -R . ../b
-  $ cp -R . ../c
+  $ cp -R . ../clean
+
+Split a head
 
   $ hg bookmark r3
 
@@ -263,6 +264,7 @@
 
 Split a head while working parent is not that head
 
+  $ cp -R $TESTTMP/clean $TESTTMP/b
   $ cd $TESTTMP/b
 
   $ hg up 0 -q
@@ -302,6 +304,7 @@
 
 Split a non-head
 
+  $ cp -R $TESTTMP/clean $TESTTMP/c
   $ cd $TESTTMP/c
   $ echo d > d
   $ hg ci -m d1 -A d
@@ -529,3 +532,36 @@
   o  0:426bada5c675 A
   
 #endif
+
+Preserve secret phase in split
+
+  $ cp -R $TESTTMP/clean $TESTTMP/phases1
+  $ cd $TESTTMP/phases1
+  $ hg phase --secret -fr tip
+  $ hg log -T '{short(node)} {phase}\n'
+  1df0d5c5a3ab secret
+  a61bcde8c529 draft
+  $ runsplit tip >/dev/null
+  $ hg log -T '{short(node)} {phase}\n'
+  00eebaf8d2e2 secret
+  a09ad58faae3 secret
+  e704349bd21b secret
+  a61bcde8c529 draft
+
+Do not move things to secret even if phases.new-commit=secret
+
+  $ cp -R $TESTTMP/clean $TESTTMP/phases2
+  $ cd $TESTTMP/phases2
+  $ cat >> .hg/hgrc <<EOF
+  > [phases]
+  > new-commit=secret
+  > EOF
+  $ hg log -T '{short(node)} {phase}\n'
+  1df0d5c5a3ab draft
+  a61bcde8c529 draft
+  $ runsplit tip >/dev/null
+  $ hg log -T '{short(node)} {phase}\n'
+  00eebaf8d2e2 draft
+  a09ad58faae3 draft
+  e704349bd21b draft
+  a61bcde8c529 draft
--- a/tests/test-ssh-bundle1.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ssh-bundle1.t	Thu Jul 19 13:55:54 2018 -0400
@@ -444,11 +444,11 @@
   > 
   > def wrappedpush(orig, repo, *args, **kwargs):
   >     res = orig(repo, *args, **kwargs)
-  >     repo.ui.write('local stdout\n')
+  >     repo.ui.write(b'local stdout\n')
   >     return res
   > 
   > def extsetup(ui):
-  >     extensions.wrapfunction(exchange, 'push', wrappedpush)
+  >     extensions.wrapfunction(exchange, b'push', wrappedpush)
   > EOF
 
   $ cat >> .hg/hgrc << EOF
@@ -537,7 +537,7 @@
 
   $ cat > $TESTTMP/failhook << EOF
   > def hook(ui, repo, **kwargs):
-  >     ui.write('hook failure!\n')
+  >     ui.write(b'hook failure!\n')
   >     ui.flush()
   >     return 1
   > EOF
--- a/tests/test-ssh-proto-unbundle.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ssh-proto-unbundle.t	Thu Jul 19 13:55:54 2018 -0400
@@ -93,7 +93,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 115:
   e>     abort: incompatible Mercurial client; bundle2 required\n
   e>     (see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n
@@ -144,7 +143,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 115:
   e>     abort: incompatible Mercurial client; bundle2 required\n
   e>     (see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n
@@ -161,10 +159,12 @@
   > import sys
   > def hook1line(ui, repo, **kwargs):
   >     ui.write(b'ui.write 1 line\n')
+  >     ui.flush()
   >     return 1
   > def hook2lines(ui, repo, **kwargs):
   >     ui.write(b'ui.write 2 lines 1\n')
   >     ui.write(b'ui.write 2 lines 2\n')
+  >     ui.flush()
   >     return 1
   > def hook1lineflush(ui, repo, **kwargs):
   >     ui.write(b'ui.write 1 line flush\n')
@@ -181,21 +181,31 @@
   >     ui.write_err(b'ui.write_err 1\n')
   >     ui.write(b'ui.write 2\n')
   >     ui.write_err(b'ui.write_err 2\n')
+  >     ui.flush()
   >     return 1
   > def hookprintstdout(ui, repo, **kwargs):
   >     print('printed line')
+  >     sys.stdout.flush()
   >     return 1
   > def hookprintandwrite(ui, repo, **kwargs):
   >     print('print 1')
+  >     sys.stdout.flush()
   >     ui.write(b'ui.write 1\n')
+  >     ui.flush()
   >     print('print 2')
+  >     sys.stdout.flush()
   >     ui.write(b'ui.write 2\n')
+  >     ui.flush()
   >     return 1
   > def hookprintstderrandstdout(ui, repo, **kwargs):
   >     print('stdout 1')
+  >     sys.stdout.flush()
   >     print('stderr 1', file=sys.stderr)
+  >     sys.stderr.flush()
   >     print('stdout 2')
+  >     sys.stdout.flush()
   >     print('stderr 2', file=sys.stderr)
+  >     sys.stderr.flush()
   >     return 1
   > EOF
 
@@ -262,7 +272,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 196:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -319,7 +328,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 196:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -390,7 +398,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 218:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -448,7 +455,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 218:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -520,7 +526,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 202:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -577,7 +582,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 202:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -648,7 +652,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 206:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -706,7 +709,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 206:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -778,7 +780,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 232:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -838,7 +839,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 232:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -912,7 +912,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 193:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -969,7 +968,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 193:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1040,16 +1038,15 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 218:
   e>     adding changesets\n
   e>     adding manifests\n
   e>     adding file changes\n
   e>     added 1 changesets with 1 changes to 1 files\n
+  e>     print 1\n
   e>     ui.write 1\n
+  e>     print 2\n
   e>     ui.write 2\n
-  e>     print 1\n
-  e>     print 2\n
   e>     transaction abort!\n
   e>     rollback completed\n
   e>     abort: pretxnchangegroup.fail hook failed\n
@@ -1100,16 +1097,15 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 218:
   e>     adding changesets\n
   e>     adding manifests\n
   e>     adding file changes\n
   e>     added 1 changesets with 1 changes to 1 files\n
+  e>     print 1\n
   e>     ui.write 1\n
+  e>     print 2\n
   e>     ui.write 2\n
-  e>     print 1\n
-  e>     print 2\n
   e>     transaction abort!\n
   e>     rollback completed\n
   e>     abort: pretxnchangegroup.fail hook failed\n
@@ -1174,16 +1170,15 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 216:
   e>     adding changesets\n
   e>     adding manifests\n
   e>     adding file changes\n
   e>     added 1 changesets with 1 changes to 1 files\n
+  e>     stdout 1\n
   e>     stderr 1\n
+  e>     stdout 2\n
   e>     stderr 2\n
-  e>     stdout 1\n
-  e>     stdout 2\n
   e>     transaction abort!\n
   e>     rollback completed\n
   e>     abort: pretxnchangegroup.fail hook failed\n
@@ -1234,16 +1229,15 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 216:
   e>     adding changesets\n
   e>     adding manifests\n
   e>     adding file changes\n
   e>     added 1 changesets with 1 changes to 1 files\n
+  e>     stdout 1\n
   e>     stderr 1\n
+  e>     stdout 2\n
   e>     stderr 2\n
-  e>     stdout 1\n
-  e>     stdout 2\n
   e>     transaction abort!\n
   e>     rollback completed\n
   e>     abort: pretxnchangegroup.fail hook failed\n
@@ -1314,7 +1308,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 212:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1372,7 +1365,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 212:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1445,7 +1437,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 212:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1503,7 +1494,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 212:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1578,7 +1568,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 230:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1638,7 +1627,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 230:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1721,7 +1709,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 273:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1731,10 +1718,10 @@
   e>     shell stderr 1\n
   e>     shell stdout 2\n
   e>     shell stderr 2\n
+  e>     stdout 1\n
   e>     stderr 1\n
+  e>     stdout 2\n
   e>     stderr 2\n
-  e>     stdout 1\n
-  e>     stdout 2\n
   e>     transaction abort!\n
   e>     rollback completed\n
   e>     abort: pretxnchangegroup.b hook failed\n
@@ -1785,7 +1772,6 @@
   o> read(1) -> 1: 0
   result: 0
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 273:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1795,10 +1781,10 @@
   e>     shell stderr 1\n
   e>     shell stdout 2\n
   e>     shell stderr 2\n
+  e>     stdout 1\n
   e>     stderr 1\n
+  e>     stdout 2\n
   e>     stderr 2\n
-  e>     stdout 1\n
-  e>     stdout 2\n
   e>     transaction abort!\n
   e>     rollback completed\n
   e>     abort: pretxnchangegroup.b hook failed\n
@@ -1863,7 +1849,6 @@
   o> read(1) -> 1: 1
   result: 1
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 100:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1916,7 +1901,6 @@
   o> read(1) -> 1: 1
   result: 1
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 100:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -1995,7 +1979,6 @@
   o> read(1) -> 1: 1
   result: 1
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 152:
   e>     adding changesets\n
   e>     adding manifests\n
@@ -2052,7 +2035,6 @@
   o> read(1) -> 1: 1
   result: 1
   remote output: 
-  o> read(-1) -> 0: 
   e> read(-1) -> 152:
   e>     adding changesets\n
   e>     adding manifests\n
--- a/tests/test-ssh-proto.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ssh-proto.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,3 +1,5 @@
+#require no-chg
+
   $ cat > hgrc-sshv2 << EOF
   > %include $HGRCPATH
   > [experimental]
@@ -1152,7 +1154,6 @@
   i>     hello\n
   o> readline() -> 1:
   o>     \n
-  o> read(-1) -> 0: 
   e> read(-1) -> 42:
   e>     cannot upgrade protocols multiple times\n
   e>     -\n
@@ -1244,7 +1245,6 @@
   i>     invalid\n
   o> readline() -> 1:
   o>     \n
-  o> read(-1) -> 0: 
   e> read(-1) -> 46:
   e>     malformed handshake protocol: missing hello\n
   e>     -\n
@@ -1264,7 +1264,6 @@
   i>     invalid\n
   o> readline() -> 1:
   o>     \n
-  o> read(-1) -> 0: 
   e> read(-1) -> 48:
   e>     malformed handshake protocol: missing between\n
   e>     -\n
@@ -1286,7 +1285,6 @@
   i>     invalid\n
   o> readline() -> 1:
   o>     \n
-  o> read(-1) -> 0: 
   e> read(-1) -> 49:
   e>     malformed handshake protocol: missing pairs 81\n
   e>     -\n
--- a/tests/test-ssh.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ssh.t	Thu Jul 19 13:55:54 2018 -0400
@@ -230,7 +230,7 @@
   namespaces	
   phases	
   $ hg book foo -r 0
-  $ hg out -B
+  $ hg out -B --config paths.default=bogus://invalid --config paths.default:pushurl=`hg paths default`
   comparing with ssh://user@dummy/remote
   searching for changed bookmarks
      foo                       1160648e36ce
@@ -272,12 +272,14 @@
   $ cat <<EOF > $TESTTMP/badhook
   > import sys
   > sys.stdout.write("KABOOM\n")
+  > sys.stdout.flush()
   > EOF
 
   $ cat <<EOF > $TESTTMP/badpyhook.py
   > import sys
   > def hook(ui, repo, hooktype, **kwargs):
   >     sys.stdout.write("KABOOM IN PROCESS\n")
+  >     sys.stdout.flush()
   > EOF
 
   $ cat <<EOF >> ../remote/.hg/hgrc
@@ -455,11 +457,12 @@
   > 
   > def wrappedpush(orig, repo, *args, **kwargs):
   >     res = orig(repo, *args, **kwargs)
-  >     repo.ui.write('local stdout\n')
+  >     repo.ui.write(b'local stdout\n')
+  >     repo.ui.flush()
   >     return res
   > 
   > def extsetup(ui):
-  >     extensions.wrapfunction(exchange, 'push', wrappedpush)
+  >     extensions.wrapfunction(exchange, b'push', wrappedpush)
   > EOF
 
   $ cat >> .hg/hgrc << EOF
@@ -569,7 +572,7 @@
 
   $ cat > $TESTTMP/failhook << EOF
   > def hook(ui, repo, **kwargs):
-  >     ui.write('hook failure!\n')
+  >     ui.write(b'hook failure!\n')
   >     ui.flush()
   >     return 1
   > EOF
--- a/tests/test-static-http.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-static-http.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,4 +1,4 @@
-#require killdaemons no-reposimplestore
+#require no-reposimplestore
 
   $ hg clone http://localhost:$HGPORT/ copy
   abort: * (glob)
--- a/tests/test-status-inprocess.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-status-inprocess.py	Thu Jul 19 13:55:54 2018 -0400
@@ -1,12 +1,24 @@
 #!/usr/bin/env python
 from __future__ import absolute_import, print_function
 
+import sys
+
 from mercurial import (
     commands,
     localrepo,
     ui as uimod,
 )
 
+print_ = print
+def print(*args, **kwargs):
+    """print() wrapper that flushes stdout buffers to avoid py3 buffer issues
+
+    We could also just write directly to sys.stdout.buffer the way the
+    ui object will, but this was easier for porting the test.
+    """
+    print_(*args, **kwargs)
+    sys.stdout.flush()
+
 u = uimod.ui.load()
 
 print('% creating repo')
--- a/tests/test-status-terse.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-status-terse.t	Thu Jul 19 13:55:54 2018 -0400
@@ -67,6 +67,48 @@
   ? x/
   ? y/
 
+Run from subdirectory
+  $ hg status --terse u --cwd x/l
+  ? .hgignore
+  ? a
+  ? b
+  ? x/
+  ? y/
+  $ relstatus() {
+  >   hg status --terse u --config commands.status.relative=1 "$@";
+  > }
+This should probably have {"l/", "m/", "n/"} instead of {"."}. They should
+probably come after "../y/".
+  $ relstatus --cwd x
+  ? ../.hgignore
+  ? ../a
+  ? ../b
+  ? .
+  ? ../y/
+This should probably have {"u/", "../m/", "../n/"} instead of {"../"}.
+  $ relstatus --cwd x/l
+  ? ../../.hgignore
+  ? ../../a
+  ? ../../b
+  ? ../
+  ? ../../y/
+This should probably have {"a/", "bb", "../aa", "../../m/", "../../n/"}
+instead of {"../../"}.
+  $ relstatus --cwd x/l/u
+  ? ../../../.hgignore
+  ? ../../../a
+  ? ../../../b
+  ? ../../
+  ? ../../../y/
+This should probably have {"bb", "../bb", "../../aa", "../../../m/",
+"../../../n/"} instead of {"../../../"}.
+  $ relstatus --cwd x/l/u/a
+  ? ../../../../.hgignore
+  ? ../../../../a
+  ? ../../../../b
+  ? ../../../
+  ? ../../../../y/
+
   $ hg add x/aa x/bb .hgignore
   $ hg status --terse au
   A .hgignore
@@ -183,3 +225,55 @@
   $ hg status --terse marduic --rev 0 --rev 1
   abort: cannot use --terse with --rev
   [255]
+
+Config item to set the default terseness
+  $ cat <<EOF >> $HGRCPATH
+  > [commands]
+  > status.terse = u
+  > EOF
+  $ hg status -mu
+  M x/aa
+  M x/bb
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/
+
+Command line flag overrides the default
+  $ hg status --terse=
+  M x/aa
+  M x/bb
+  ? a
+  ? b
+  ? x/l/aa
+  ? x/l/u/a/bb
+  ? x/l/u/bb
+  ? x/m/aa
+  ? x/n/aa
+  ? y/l
+  ? y/m
+  $ hg status --terse=mardu
+  M x/aa
+  M x/bb
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/
+
+Specifying --rev should still work, with the terseness disabled.
+  $ hg status --rev 0
+  M x/aa
+  M x/bb
+  ? a
+  ? b
+  ? x/l/aa
+  ? x/l/u/a/bb
+  ? x/l/u/bb
+  ? x/m/aa
+  ? x/n/aa
+  ? y/l
+  ? y/m
--- a/tests/test-status.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-status.t	Thu Jul 19 13:55:54 2018 -0400
@@ -109,11 +109,8 @@
 
 tweaking defaults works
   $ hg status --cwd a --config ui.tweakdefaults=yes
-  ? 1/in_a_1
-  ? in_a
-  ? ../b/1/in_b_1
-  ? ../b/2/in_b_2
-  ? ../b/in_b
+  ? .
+  ? ../b/
   ? ../in_root
   $ HGPLAIN=1 hg status --cwd a --config ui.tweakdefaults=yes
   ? a/1/in_a_1 (glob)
@@ -123,11 +120,8 @@
   ? b/in_b (glob)
   ? in_root
   $ HGPLAINEXCEPT=tweakdefaults hg status --cwd a --config ui.tweakdefaults=yes
-  ? 1/in_a_1 (glob)
-  ? in_a
-  ? ../b/1/in_b_1 (glob)
-  ? ../b/2/in_b_2 (glob)
-  ? ../b/in_b (glob)
+  ? .
+  ? ../b/
   ? ../in_root (glob)
 
 relative paths can be requested
@@ -157,11 +151,8 @@
   > status.relative = False
   > EOF
   $ hg status --cwd a --config ui.tweakdefaults=yes
-  ? a/1/in_a_1
-  ? a/in_a
-  ? b/1/in_b_1
-  ? b/2/in_b_2
-  ? b/in_b
+  ? a/
+  ? b/
   ? in_root
 
   $ cd ..
@@ -222,6 +213,16 @@
   C .hgignore
   C modified
 
+  $ hg status -A -T '{status} {path} {node|shortest}\n'
+  A added ffff
+  A copied ffff
+  R removed ffff
+  ! deleted ffff
+  ? unknown ffff
+  I ignored ffff
+  C .hgignore ffff
+  C modified ffff
+
   $ hg status -A -Tjson
   [
    {
@@ -465,12 +466,12 @@
 
   $ hg init repo5
   $ cd repo5
-  >>> open("010a", r"wb").write(b"\1\nfoo")
+  >>> open("010a", r"wb").write(b"\1\nfoo") and None
   $ hg ci -q -A -m 'initial checkin'
   $ hg status -A
   C 010a
 
-  >>> open("010a", r"wb").write(b"\1\nbar")
+  >>> open("010a", r"wb").write(b"\1\nbar") and None
   $ hg status -A
   M 010a
   $ hg ci -q -m 'modify 010a'
--- a/tests/test-strip.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-strip.t	Thu Jul 19 13:55:54 2018 -0400
@@ -719,7 +719,7 @@
                            revisions without this option)
    -f --force              force removal of changesets, discard uncommitted
                            changes (no backup)
-      --no-backup          no backups
+      --no-backup          do not save backup bundle
    -k --keep               do not modify working directory during strip
    -B --bookmark VALUE [+] remove revs only reachable from given bookmark
       --mq                 operate on patch repository
--- a/tests/test-subrepo-deep-nested-change.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-subrepo-deep-nested-change.t	Thu Jul 19 13:55:54 2018 -0400
@@ -379,7 +379,7 @@
   $ touch bar/abc
   $ hg addremove -S ..
   \r (no-eol) (esc)
-  searching for exact renames [                         ] 0/1\r (no-eol) (esc)
+  searching for exact renames [========================>] 1/1\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   adding ../sub1/sub2/folder/test.txt
   removing ../sub1/sub2/test.txt
--- a/tests/test-subrepo-paths.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-subrepo-paths.t	Thu Jul 19 13:55:54 2018 -0400
@@ -55,7 +55,7 @@
   > .* = \1
   > EOF
   $ hg debugsub
-  abort: bad subrepository pattern in $TESTTMP/outer/.hg/hgrc:2: invalid group reference
+  abort: bad subrepository pattern in $TESTTMP/outer/.hg/hgrc:2: invalid group reference* (glob)
   [255]
 
   $ cd ..
--- a/tests/test-subrepo-relative-path.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-subrepo-relative-path.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 Preparing the subrepository 'sub'
 
   $ hg init sub
@@ -58,6 +56,30 @@
   new changesets 863c1745b441
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
+Ensure that subrepos pay attention to default:pushurl
+
+  $ cat > cloned/.hg/hgrc << EOF
+  > [paths]
+  > default:pushurl = http://localhost:$HGPORT/main
+  > EOF
+
+  $ hg -R cloned out -S --config paths.default=bogus://invalid
+  comparing with http://localhost:$HGPORT/main
+  searching for changes
+  no changes found
+  comparing with http://localhost:$HGPORT/sub
+  searching for changes
+  no changes found
+  [1]
+
+  $ hg -R cloned push --config paths.default=bogus://invalid
+  pushing to http://localhost:$HGPORT/main
+  no changes made to subrepo sub since last push to http://localhost:$HGPORT/sub
+  searching for changes
+  no changes found
+  abort: HTTP Error 403: ssl required
+  [255]
+
 Checking cloned repo ids
 
   $ hg id -R cloned
--- a/tests/test-symlink-os-yes-fs-no.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-symlink-os-yes-fs-no.py	Thu Jul 19 13:55:54 2018 -0400
@@ -6,6 +6,7 @@
 from mercurial import (
     commands,
     hg,
+    pycompat,
     ui as uimod,
     util,
 )
@@ -19,13 +20,13 @@
 
 u = uimod.ui.load()
 # hide outer repo
-hg.peer(u, {}, '.', create=True)
+hg.peer(u, {}, b'.', create=True)
 
 # unbundle with symlink support
-hg.peer(u, {}, 'test0', create=True)
+hg.peer(u, {}, b'test0', create=True)
 
-repo = hg.repository(u, 'test0')
-commands.unbundle(u, repo, BUNDLEPATH, update=True)
+repo = hg.repository(u, b'test0')
+commands.unbundle(u, repo, pycompat.fsencode(BUNDLEPATH), update=True)
 
 # wait a bit, or the status call wont update the dirstate
 time.sleep(1)
@@ -42,7 +43,7 @@
 
 # dereference links as if a Samba server has exported this to a
 # Windows client
-for f in 'test0/a.lnk', 'test0/d/b.lnk':
+for f in b'test0/a.lnk', b'test0/d/b.lnk':
     os.unlink(f)
     fp = open(f, 'wb')
     fp.write(util.readfile(f[:-4]))
@@ -50,11 +51,11 @@
 
 # reload repository
 u = uimod.ui.load()
-repo = hg.repository(u, 'test0')
+repo = hg.repository(u, b'test0')
 commands.status(u, repo)
 
 # try unbundling a repo which contains symlinks
 u = uimod.ui.load()
 
-repo = hg.repository(u, 'test1', create=True)
-commands.unbundle(u, repo, BUNDLEPATH, update=True)
+repo = hg.repository(u, b'test1', create=True)
+commands.unbundle(u, repo, pycompat.fsencode(BUNDLEPATH), update=True)
--- a/tests/test-symlink-placeholder.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-symlink-placeholder.t	Thu Jul 19 13:55:54 2018 -0400
@@ -50,13 +50,13 @@
 
 Write binary data to the placeholder:
 
-  >>> open('b', 'w').write('this is a binary\0')
+  >>> open('b', 'w').write('this is a binary\0') and None
   $ hg --config extensions.n=$TESTTMP/nolink.py st --debug
   ignoring suspect symlink placeholder "b"
 
 Write a long string to the placeholder:
 
-  >>> open('b', 'w').write('this' * 1000)
+  >>> open('b', 'w').write('this' * 1000) and None
   $ hg --config extensions.n=$TESTTMP/nolink.py st --debug
   ignoring suspect symlink placeholder "b"
 
@@ -68,7 +68,7 @@
 
 Write a valid string to the placeholder:
 
-  >>> open('b', 'w').write('this')
+  >>> open('b', 'w').write('this') and None
   $ hg --config extensions.n=$TESTTMP/nolink.py st --debug
   M b
   $ hg --config extensions.n=$TESTTMP/nolink.py ci -m1
--- a/tests/test-tags.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-tags.t	Thu Jul 19 13:55:54 2018 -0400
@@ -610,6 +610,27 @@
   localtag                           0:bbd179dfa0a7 local
   globaltag                          0:bbd179dfa0a7
 
+Templated output:
+
+ (immediate values)
+
+  $ hg tags -T '{pad(tag, 9)} {rev}:{node} ({type})\n'
+  tip       1:a0b6fe111088c8c29567d3876cc466aa02927cae ()
+  localtag  0:bbd179dfa0a71671c253b3ae0aa1513b60d199fa (local)
+  globaltag 0:bbd179dfa0a71671c253b3ae0aa1513b60d199fa ()
+
+ (ctx/revcache dependent)
+
+  $ hg tags -T '{pad(tag, 9)} {rev} {file_adds}\n'
+  tip       1 .hgtags
+  localtag  0 foo
+  globaltag 0 foo
+
+  $ hg tags -T '{pad(tag, 9)} {rev}:{node|shortest}\n'
+  tip       1:a0b6
+  localtag  0:bbd1
+  globaltag 0:bbd1
+
 Test for issue3911
 
   $ hg tag -r 0 -l localtag2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-template-basic.t	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,1088 @@
+Test template syntax and basic functionality
+============================================
+
+  $ hg init a
+  $ cd a
+  $ echo a > a
+  $ hg add a
+  $ echo line 1 > b
+  $ echo line 2 >> b
+  $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
+
+  $ hg add b
+  $ echo other 1 > c
+  $ echo other 2 >> c
+  $ echo >> c
+  $ echo other 3 >> c
+  $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
+
+  $ hg add c
+  $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
+  $ echo c >> c
+  $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
+
+  $ echo foo > .hg/branch
+  $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
+
+  $ hg co -q 3
+  $ echo other 4 >> d
+  $ hg add d
+  $ hg commit -m 'new head' -d '1500000 0' -u 'person'
+
+  $ hg merge -q foo
+  $ hg commit -m 'merge' -d '1500001 0' -u 'person'
+
+Test arithmetic operators have the right precedence:
+
+  $ hg log -l 1 -T '{date(date, "%Y") + 5 * 10} {date(date, "%Y") - 2 * 3}\n'
+  2020 1964
+  $ hg log -l 1 -T '{date(date, "%Y") * 5 + 10} {date(date, "%Y") * 3 - 2}\n'
+  9860 5908
+
+Test division:
+
+  $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
+  (template
+    (/
+      (integer '5')
+      (integer '2'))
+    (string ' ')
+    (func
+      (symbol 'mod')
+      (list
+        (integer '5')
+        (integer '2')))
+    (string '\n'))
+  * keywords: 
+  * functions: mod
+  2 1
+  $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
+  (template
+    (/
+      (integer '5')
+      (negate
+        (integer '2')))
+    (string ' ')
+    (func
+      (symbol 'mod')
+      (list
+        (integer '5')
+        (negate
+          (integer '2'))))
+    (string '\n'))
+  * keywords: 
+  * functions: mod
+  -3 -1
+  $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
+  (template
+    (/
+      (negate
+        (integer '5'))
+      (integer '2'))
+    (string ' ')
+    (func
+      (symbol 'mod')
+      (list
+        (negate
+          (integer '5'))
+        (integer '2')))
+    (string '\n'))
+  * keywords: 
+  * functions: mod
+  -3 1
+  $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
+  (template
+    (/
+      (negate
+        (integer '5'))
+      (negate
+        (integer '2')))
+    (string ' ')
+    (func
+      (symbol 'mod')
+      (list
+        (negate
+          (integer '5'))
+        (negate
+          (integer '2'))))
+    (string '\n'))
+  * keywords: 
+  * functions: mod
+  2 -1
+
+Filters bind closer than arithmetic:
+
+  $ hg debugtemplate -r0 -v '{revset(".")|count - 1}\n'
+  (template
+    (-
+      (|
+        (func
+          (symbol 'revset')
+          (string '.'))
+        (symbol 'count'))
+      (integer '1'))
+    (string '\n'))
+  * keywords: 
+  * functions: count, revset
+  0
+
+But negate binds closer still:
+
+  $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
+  (template
+    (-
+      (integer '1')
+      (|
+        (integer '3')
+        (symbol 'stringify')))
+    (string '\n'))
+  * keywords: 
+  * functions: stringify
+  hg: parse error: arithmetic only defined on integers
+  [255]
+  $ hg debugtemplate -r0 -v '{-3|stringify}\n'
+  (template
+    (|
+      (negate
+        (integer '3'))
+      (symbol 'stringify'))
+    (string '\n'))
+  * keywords: 
+  * functions: stringify
+  -3
+
+Filters bind as close as map operator:
+
+  $ hg debugtemplate -r0 -v '{desc|splitlines % "{line}\n"}'
+  (template
+    (%
+      (|
+        (symbol 'desc')
+        (symbol 'splitlines'))
+      (template
+        (symbol 'line')
+        (string '\n'))))
+  * keywords: desc, line
+  * functions: splitlines
+  line 1
+  line 2
+
+Keyword arguments:
+
+  $ hg debugtemplate -r0 -v '{foo=bar|baz}'
+  (template
+    (keyvalue
+      (symbol 'foo')
+      (|
+        (symbol 'bar')
+        (symbol 'baz'))))
+  * keywords: bar, foo
+  * functions: baz
+  hg: parse error: can't use a key-value pair in this context
+  [255]
+
+  $ hg debugtemplate '{pad("foo", width=10, left=true)}\n'
+         foo
+
+Call function which takes named arguments by filter syntax:
+
+  $ hg debugtemplate '{" "|separate}'
+  $ hg debugtemplate '{("not", "an", "argument", "list")|separate}'
+  hg: parse error: unknown method 'list'
+  [255]
+
+Second branch starting at nullrev:
+
+  $ hg update null
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ echo second > second
+  $ hg add second
+  $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
+  created new head
+
+  $ echo third > third
+  $ hg add third
+  $ hg mv second fourth
+  $ hg commit -m third -d "2020-01-01 10:01"
+
+  $ hg log --template '{join(file_copies, ",\n")}\n' -r .
+  fourth (second)
+  $ hg log -T '{file_copies % "{source} -> {name}\n"}' -r .
+  second -> fourth
+  $ hg log -T '{rev} {ifcontains("fourth", file_copies, "t", "f")}\n' -r .:7
+  8 t
+  7 f
+
+Internal resources shouldn't be exposed (issue5699):
+
+  $ hg log -r. -T '{cache}{ctx}{repo}{revcache}{templ}{ui}'
+
+Never crash on internal resource not available:
+
+  $ hg --cwd .. debugtemplate '{"c0bebeef"|shortest}\n'
+  abort: template resource not available: repo
+  [255]
+
+  $ hg config -T '{author}'
+
+Quoting for ui.logtemplate
+
+  $ hg tip --config "ui.logtemplate={rev}\n"
+  8
+  $ hg tip --config "ui.logtemplate='{rev}\n'"
+  8
+  $ hg tip --config 'ui.logtemplate="{rev}\n"'
+  8
+  $ hg tip --config 'ui.logtemplate=n{rev}\n'
+  n8
+
+Check that recursive reference does not fall into RuntimeError (issue4758):
+
+ common mistake:
+
+  $ cat << EOF > issue4758
+  > changeset = '{changeset}\n'
+  > EOF
+  $ hg log --style ./issue4758
+  abort: recursive reference 'changeset' in template
+  [255]
+
+ circular reference:
+
+  $ cat << EOF > issue4758
+  > changeset = '{foo}'
+  > foo = '{changeset}'
+  > EOF
+  $ hg log --style ./issue4758
+  abort: recursive reference 'foo' in template
+  [255]
+
+ buildmap() -> gettemplate(), where no thunk was made:
+
+  $ cat << EOF > issue4758
+  > changeset = '{files % changeset}\n'
+  > EOF
+  $ hg log --style ./issue4758
+  abort: recursive reference 'changeset' in template
+  [255]
+
+ not a recursion if a keyword of the same name exists:
+
+  $ cat << EOF > issue4758
+  > changeset = '{tags % rev}'
+  > rev = '{rev} {tag}\n'
+  > EOF
+  $ hg log --style ./issue4758 -r tip
+  8 tip
+
+Set up phase:
+
+  $ hg phase -r 5 --public
+  $ hg phase -r 7 --secret --force
+
+Add a dummy commit to make up for the instability of the above:
+
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m future
+
+Add a commit that does all possible modifications at once
+
+  $ echo modify >> third
+  $ touch b
+  $ hg add b
+  $ hg mv fourth fifth
+  $ hg rm a
+  $ hg ci -m "Modify, add, remove, rename"
+
+Error on syntax:
+
+  $ cat <<EOF > t
+  > changeset = '{c}'
+  > c = q
+  > x = "f
+  > EOF
+  $ echo '[ui]' > .hg/hgrc
+  $ echo 'style = t' >> .hg/hgrc
+  $ hg log
+  hg: parse error at t:3: unmatched quotes
+  [255]
+
+  $ hg log -T '{date'
+  hg: parse error at 1: unterminated template expansion
+  ({date
+    ^ here)
+  [255]
+  $ hg log -T '{date(}'
+  hg: parse error at 6: not a prefix: end
+  ({date(}
+         ^ here)
+  [255]
+  $ hg log -T '{date)}'
+  hg: parse error at 5: invalid token
+  ({date)}
+        ^ here)
+  [255]
+  $ hg log -T '{date date}'
+  hg: parse error at 6: invalid token
+  ({date date}
+         ^ here)
+  [255]
+
+  $ hg log -T '{}'
+  hg: parse error at 1: not a prefix: end
+  ({}
+    ^ here)
+  [255]
+  $ hg debugtemplate -v '{()}'
+  (template
+    (group
+      None))
+  * keywords: 
+  * functions: 
+  hg: parse error: missing argument
+  [255]
+
+Behind the scenes, this would throw TypeError without intype=bytes
+
+  $ hg log -l 3 --template '{date|obfuscate}\n'
+  &#48;&#46;&#48;&#48;
+  &#48;&#46;&#48;&#48;
+  &#49;&#53;&#55;&#55;&#56;&#55;&#50;&#56;&#54;&#48;&#46;&#48;&#48;
+
+Behind the scenes, this will throw a ValueError
+
+  $ hg log -l 3 --template 'line: {desc|shortdate}\n'
+  hg: parse error: invalid date: 'Modify, add, remove, rename'
+  (template filter 'shortdate' is not compatible with keyword 'desc')
+  [255]
+
+Behind the scenes, this would throw AttributeError without intype=bytes
+
+  $ hg log -l 3 --template 'line: {date|escape}\n'
+  line: 0.00
+  line: 0.00
+  line: 1577872860.00
+
+  $ hg log -l 3 --template 'line: {extras|localdate}\n'
+  hg: parse error: localdate expects a date information
+  [255]
+
+Behind the scenes, this will throw ValueError
+
+  $ hg tip --template '{author|email|date}\n'
+  hg: parse error: date expects a date information
+  [255]
+
+  $ hg tip -T '{author|email|shortdate}\n'
+  hg: parse error: invalid date: 'test'
+  (template filter 'shortdate' is not compatible with keyword 'author')
+  [255]
+
+  $ hg tip -T '{get(extras, "branch")|shortdate}\n'
+  hg: parse error: invalid date: 'default'
+  (incompatible use of template filter 'shortdate')
+  [255]
+
+Error in nested template:
+
+  $ hg log -T '{"date'
+  hg: parse error at 2: unterminated string
+  ({"date
+     ^ here)
+  [255]
+
+  $ hg log -T '{"foo{date|?}"}'
+  hg: parse error at 11: syntax error
+  ({"foo{date|?}"}
+              ^ here)
+  [255]
+
+Thrown an error if a template function doesn't exist
+
+  $ hg tip --template '{foo()}\n'
+  hg: parse error: unknown function 'foo'
+  [255]
+
+  $ cd ..
+
+Set up latesttag repository:
+
+  $ hg init latesttag
+  $ cd latesttag
+
+  $ echo a > file
+  $ hg ci -Am a -d '0 0'
+  adding file
+
+  $ echo b >> file
+  $ hg ci -m b -d '1 0'
+
+  $ echo c >> head1
+  $ hg ci -Am h1c -d '2 0'
+  adding head1
+
+  $ hg update -q 1
+  $ echo d >> head2
+  $ hg ci -Am h2d -d '3 0'
+  adding head2
+  created new head
+
+  $ echo e >> head2
+  $ hg ci -m h2e -d '4 0'
+
+  $ hg merge -q
+  $ hg ci -m merge -d '5 -3600'
+
+  $ hg tag -r 1 -m t1 -d '6 0' t1
+  $ hg tag -r 2 -m t2 -d '7 0' t2
+  $ hg tag -r 3 -m t3 -d '8 0' t3
+  $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
+  $ hg tag -r 5 -m t5 -d '9 0' t5
+  $ hg tag -r 3 -m at3 -d '10 0' at3
+
+  $ cd ..
+
+Test new-style inline templating:
+
+  $ hg log -R latesttag -r tip --template 'modified files: {file_mods % " {file}\n"}\n'
+  modified files:  .hgtags
+  
+
+  $ hg log -R latesttag -r tip -T '{rev % "a"}\n'
+  hg: parse error: 11 is not iterable of mappings
+  (keyword 'rev' does not support map operation)
+  [255]
+  $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "a"}\n'
+  hg: parse error: None is not iterable of mappings
+  [255]
+  $ hg log -R latesttag -r tip -T '{extras % "{key}\n" % "{key}\n"}'
+  hg: parse error: list of strings is not mappable
+  [255]
+
+Test new-style inline templating of non-list/dict type:
+
+  $ hg log -R latesttag -r tip -T '{manifest}\n'
+  11:2bc6e9006ce2
+  $ hg log -R latesttag -r tip -T 'string length: {manifest|count}\n'
+  string length: 15
+  $ hg log -R latesttag -r tip -T '{manifest % "{rev}:{node}"}\n'
+  11:2bc6e9006ce29882383a22d39fd1f4e66dd3e2fc
+
+  $ hg log -R latesttag -r tip -T '{get(extras, "branch") % "{key}: {value}\n"}'
+  branch: default
+  $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "{key}\n"}'
+  hg: parse error: None is not iterable of mappings
+  [255]
+  $ hg log -R latesttag -r tip -T '{min(extras) % "{key}: {value}\n"}'
+  branch: default
+  $ hg log -R latesttag -l1 -T '{min(revset("0:9")) % "{rev}:{node|short}\n"}'
+  0:ce3cec86e6c2
+  $ hg log -R latesttag -l1 -T '{max(revset("0:9")) % "{rev}:{node|short}\n"}'
+  9:fbc7cd862e9c
+
+Test dot operator precedence:
+
+  $ hg debugtemplate -R latesttag -r0 -v '{manifest.node|short}\n'
+  (template
+    (|
+      (.
+        (symbol 'manifest')
+        (symbol 'node'))
+      (symbol 'short'))
+    (string '\n'))
+  * keywords: manifest, node, rev
+  * functions: formatnode, short
+  89f4071fec70
+
+ (the following examples are invalid, but seem natural in parsing POV)
+
+  $ hg debugtemplate -R latesttag -r0 -v '{foo|bar.baz}\n' 2> /dev/null
+  (template
+    (|
+      (symbol 'foo')
+      (.
+        (symbol 'bar')
+        (symbol 'baz')))
+    (string '\n'))
+  [255]
+  $ hg debugtemplate -R latesttag -r0 -v '{foo.bar()}\n' 2> /dev/null
+  (template
+    (.
+      (symbol 'foo')
+      (func
+        (symbol 'bar')
+        None))
+    (string '\n'))
+  * keywords: foo
+  * functions: bar
+  [255]
+
+Test evaluation of dot operator:
+
+  $ hg log -R latesttag -l1 -T '{min(revset("0:9")).node}\n'
+  ce3cec86e6c26bd9bdfc590a6b92abc9680f1796
+  $ hg log -R latesttag -r0 -T '{extras.branch}\n'
+  default
+  $ hg log -R latesttag -r0 -T '{date.unixtime} {localdate(date, "+0200").tzoffset}\n'
+  0 -7200
+
+  $ hg log -R latesttag -l1 -T '{author.invalid}\n'
+  hg: parse error: 'test' is not a dictionary
+  (keyword 'author' does not support member operation)
+  [255]
+  $ hg log -R latesttag -l1 -T '{min("abc").invalid}\n'
+  hg: parse error: 'a' is not a dictionary
+  [255]
+
+Test integer literal:
+
+  $ hg debugtemplate -v '{(0)}\n'
+  (template
+    (group
+      (integer '0'))
+    (string '\n'))
+  * keywords: 
+  * functions: 
+  0
+  $ hg debugtemplate -v '{(123)}\n'
+  (template
+    (group
+      (integer '123'))
+    (string '\n'))
+  * keywords: 
+  * functions: 
+  123
+  $ hg debugtemplate -v '{(-4)}\n'
+  (template
+    (group
+      (negate
+        (integer '4')))
+    (string '\n'))
+  * keywords: 
+  * functions: 
+  -4
+  $ hg debugtemplate '{(-)}\n'
+  hg: parse error at 3: not a prefix: )
+  ({(-)}\n
+      ^ here)
+  [255]
+  $ hg debugtemplate '{(-a)}\n'
+  hg: parse error: negation needs an integer argument
+  [255]
+
+top-level integer literal is interpreted as symbol (i.e. variable name):
+
+  $ hg debugtemplate -D 1=one -v '{1}\n'
+  (template
+    (integer '1')
+    (string '\n'))
+  * keywords: 
+  * functions: 
+  one
+  $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
+  (template
+    (func
+      (symbol 'if')
+      (list
+        (string 't')
+        (template
+          (integer '1'))))
+    (string '\n'))
+  * keywords: 
+  * functions: if
+  one
+  $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
+  (template
+    (|
+      (integer '1')
+      (symbol 'stringify'))
+    (string '\n'))
+  * keywords: 
+  * functions: stringify
+  one
+
+unless explicit symbol is expected:
+
+  $ hg log -Ra -r0 -T '{desc|1}\n'
+  hg: parse error: expected a symbol, got 'integer'
+  [255]
+  $ hg log -Ra -r0 -T '{1()}\n'
+  hg: parse error: expected a symbol, got 'integer'
+  [255]
+
+Test string literal:
+
+  $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
+  (template
+    (string 'string with no template fragment')
+    (string '\n'))
+  * keywords: 
+  * functions: 
+  string with no template fragment
+  $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
+  (template
+    (template
+      (string 'template: ')
+      (symbol 'rev'))
+    (string '\n'))
+  * keywords: rev
+  * functions: 
+  template: 0
+  $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
+  (template
+    (string 'rawstring: {rev}')
+    (string '\n'))
+  * keywords: 
+  * functions: 
+  rawstring: {rev}
+  $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
+  (template
+    (%
+      (symbol 'files')
+      (string 'rawstring: {file}'))
+    (string '\n'))
+  * keywords: files
+  * functions: 
+  rawstring: {file}
+
+Test string escaping:
+
+  $ hg log -R latesttag -r 0 --template '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
+  >
+  <>\n<[>
+  <>\n<]>
+  <>\n<
+
+  $ hg log -R latesttag -r 0 \
+  > --config ui.logtemplate='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
+  >
+  <>\n<[>
+  <>\n<]>
+  <>\n<
+
+  $ hg log -R latesttag -r 0 -T esc \
+  > --config templates.esc='>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
+  >
+  <>\n<[>
+  <>\n<]>
+  <>\n<
+
+  $ cat <<'EOF' > esctmpl
+  > changeset = '>\n<>\\n<{if(rev, "[>\n<>\\n<]")}>\n<>\\n<\n'
+  > EOF
+  $ hg log -R latesttag -r 0 --style ./esctmpl
+  >
+  <>\n<[>
+  <>\n<]>
+  <>\n<
+
+Test string escaping of quotes:
+
+  $ hg log -Ra -r0 -T '{"\""}\n'
+  "
+  $ hg log -Ra -r0 -T '{"\\\""}\n'
+  \"
+  $ hg log -Ra -r0 -T '{r"\""}\n'
+  \"
+  $ hg log -Ra -r0 -T '{r"\\\""}\n'
+  \\\"
+
+
+  $ hg log -Ra -r0 -T '{"\""}\n'
+  "
+  $ hg log -Ra -r0 -T '{"\\\""}\n'
+  \"
+  $ hg log -Ra -r0 -T '{r"\""}\n'
+  \"
+  $ hg log -Ra -r0 -T '{r"\\\""}\n'
+  \\\"
+
+Test exception in quoted template. single backslash before quotation mark is
+stripped before parsing:
+
+  $ cat <<'EOF' > escquotetmpl
+  > changeset = "\" \\" \\\" \\\\" {files % \"{file}\"}\n"
+  > EOF
+  $ cd latesttag
+  $ hg log -r 2 --style ../escquotetmpl
+  " \" \" \\" head1
+
+  $ hg log -r 2 -T esc --config templates.esc='"{\"valid\"}\n"'
+  valid
+  $ hg log -r 2 -T esc --config templates.esc="'"'{\'"'"'valid\'"'"'}\n'"'"
+  valid
+
+Test compatibility with 2.9.2-3.4 of escaped quoted strings in nested
+_evalifliteral() templates (issue4733):
+
+  $ hg log -r 2 -T '{if(rev, "\"{rev}")}\n'
+  "2
+  $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\"{rev}\")}")}\n'
+  "2
+  $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\"{rev}\\\")}\")}")}\n'
+  "2
+
+  $ hg log -r 2 -T '{if(rev, "\\\"")}\n'
+  \"
+  $ hg log -r 2 -T '{if(rev, "{if(rev, \"\\\\\\\"\")}")}\n'
+  \"
+  $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, \\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
+  \"
+
+  $ hg log -r 2 -T '{if(rev, r"\\\"")}\n'
+  \\\"
+  $ hg log -r 2 -T '{if(rev, "{if(rev, r\"\\\\\\\"\")}")}\n'
+  \\\"
+  $ hg log -r 2 -T '{if(rev, "{if(rev, \"{if(rev, r\\\"\\\\\\\\\\\\\\\"\\\")}\")}")}\n'
+  \\\"
+
+escaped single quotes and errors:
+
+  $ hg log -r 2 -T "{if(rev, '{if(rev, \'foo\')}')}"'\n'
+  foo
+  $ hg log -r 2 -T "{if(rev, '{if(rev, r\'foo\')}')}"'\n'
+  foo
+  $ hg log -r 2 -T '{if(rev, "{if(rev, \")}")}\n'
+  hg: parse error at 21: unterminated string
+  ({if(rev, "{if(rev, \")}")}\n
+                        ^ here)
+  [255]
+  $ hg log -r 2 -T '{if(rev, \"\\"")}\n'
+  hg: parse error: trailing \ in string
+  [255]
+  $ hg log -r 2 -T '{if(rev, r\"\\"")}\n'
+  hg: parse error: trailing \ in string
+  [255]
+
+  $ cd ..
+
+Test leading backslashes:
+
+  $ cd latesttag
+  $ hg log -r 2 -T '\{rev} {files % "\{file}"}\n'
+  {rev} {file}
+  $ hg log -r 2 -T '\\{rev} {files % "\\{file}"}\n'
+  \2 \head1
+  $ hg log -r 2 -T '\\\{rev} {files % "\\\{file}"}\n'
+  \{rev} \{file}
+  $ cd ..
+
+Test leading backslashes in "if" expression (issue4714):
+
+  $ cd latesttag
+  $ hg log -r 2 -T '{if("1", "\{rev}")} {if("1", r"\{rev}")}\n'
+  {rev} \{rev}
+  $ hg log -r 2 -T '{if("1", "\\{rev}")} {if("1", r"\\{rev}")}\n'
+  \2 \\{rev}
+  $ hg log -r 2 -T '{if("1", "\\\{rev}")} {if("1", r"\\\{rev}")}\n'
+  \{rev} \\\{rev}
+  $ cd ..
+
+"string-escape"-ed "\x5c\x786e" becomes r"\x6e" (once) or r"n" (twice)
+
+  $ hg log -R a -r 0 --template '{if("1", "\x5c\x786e", "NG")}\n'
+  \x6e
+  $ hg log -R a -r 0 --template '{if("1", r"\x5c\x786e", "NG")}\n'
+  \x5c\x786e
+  $ hg log -R a -r 0 --template '{if("", "NG", "\x5c\x786e")}\n'
+  \x6e
+  $ hg log -R a -r 0 --template '{if("", "NG", r"\x5c\x786e")}\n'
+  \x5c\x786e
+
+  $ hg log -R a -r 2 --template '{ifeq("no perso\x6e", desc, "\x5c\x786e", "NG")}\n'
+  \x6e
+  $ hg log -R a -r 2 --template '{ifeq(r"no perso\x6e", desc, "NG", r"\x5c\x786e")}\n'
+  \x5c\x786e
+  $ hg log -R a -r 2 --template '{ifeq(desc, "no perso\x6e", "\x5c\x786e", "NG")}\n'
+  \x6e
+  $ hg log -R a -r 2 --template '{ifeq(desc, r"no perso\x6e", "NG", r"\x5c\x786e")}\n'
+  \x5c\x786e
+
+  $ hg log -R a -r 8 --template '{join(files, "\n")}\n'
+  fourth
+  second
+  third
+  $ hg log -R a -r 8 --template '{join(files, r"\n")}\n'
+  fourth\nsecond\nthird
+
+  $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", "htm\x6c")}'
+  <p>
+  1st
+  </p>
+  <p>
+  2nd
+  </p>
+  $ hg log -R a -r 2 --template '{rstdoc(r"1st\n\n2nd", "html")}'
+  <p>
+  1st\n\n2nd
+  </p>
+  $ hg log -R a -r 2 --template '{rstdoc("1st\n\n2nd", r"htm\x6c")}'
+  1st
+  
+  2nd
+
+  $ hg log -R a -r 2 --template '{strip(desc, "\x6e")}\n'
+  o perso
+  $ hg log -R a -r 2 --template '{strip(desc, r"\x6e")}\n'
+  no person
+  $ hg log -R a -r 2 --template '{strip("no perso\x6e", "\x6e")}\n'
+  o perso
+  $ hg log -R a -r 2 --template '{strip(r"no perso\x6e", r"\x6e")}\n'
+  no perso
+
+  $ hg log -R a -r 2 --template '{sub("\\x6e", "\x2d", desc)}\n'
+  -o perso-
+  $ hg log -R a -r 2 --template '{sub(r"\\x6e", "-", desc)}\n'
+  no person
+  $ hg log -R a -r 2 --template '{sub("n", r"\\x2d", desc)}\n'
+  \x2do perso\x2d
+  $ hg log -R a -r 2 --template '{sub("n", "\x2d", "no perso\x6e")}\n'
+  -o perso-
+  $ hg log -R a -r 2 --template '{sub("n", r"\\x2d", r"no perso\x6e")}\n'
+  \x2do perso\x6e
+
+  $ hg log -R a -r 8 --template '{files % "{file}\n"}'
+  fourth
+  second
+  third
+
+Test string escaping in nested expression:
+
+  $ hg log -R a -r 8 --template '{ifeq(r"\x6e", if("1", "\x5c\x786e"), join(files, "\x5c\x786e"))}\n'
+  fourth\x6esecond\x6ethird
+  $ hg log -R a -r 8 --template '{ifeq(if("1", r"\x6e"), "\x5c\x786e", join(files, "\x5c\x786e"))}\n'
+  fourth\x6esecond\x6ethird
+
+  $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", "\x5c\x786e"))}\n'
+  fourth\x6esecond\x6ethird
+  $ hg log -R a -r 8 --template '{join(files, ifeq(branch, "default", r"\x5c\x786e"))}\n'
+  fourth\x5c\x786esecond\x5c\x786ethird
+
+  $ hg log -R a -r 3:4 --template '{rev}:{sub(if("1", "\x6e"), ifeq(branch, "foo", r"\x5c\x786e", "\x5c\x786e"), desc)}\n'
+  3:\x6eo user, \x6eo domai\x6e
+  4:\x5c\x786eew bra\x5c\x786ech
+
+Test quotes in nested expression are evaluated just like a $(command)
+substitution in POSIX shells:
+
+  $ hg log -R a -r 8 -T '{"{"{rev}:{node|short}"}"}\n'
+  8:95c24699272e
+  $ hg log -R a -r 8 -T '{"{"\{{rev}} \"{node|short}\""}"}\n'
+  {8} "95c24699272e"
+
+Test recursive evaluation:
+
+  $ hg init r
+  $ cd r
+  $ echo a > a
+  $ hg ci -Am '{rev}'
+  adding a
+  $ hg log -r 0 --template '{if(rev, desc)}\n'
+  {rev}
+  $ hg log -r 0 --template '{if(rev, "{author} {rev}")}\n'
+  test 0
+
+  $ hg branch -q 'text.{rev}'
+  $ echo aa >> aa
+  $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
+
+  $ hg log -l1 --template '{fill(desc, "20", author, branch)}'
+  {node|short}desc to
+  text.{rev}be wrapped
+  text.{rev}desc to be
+  text.{rev}wrapped (no-eol)
+  $ hg log -l1 --template '{fill(desc, "20", "{node|short}:", "text.{rev}:")}'
+  bcc7ff960b8e:desc to
+  text.1:be wrapped
+  text.1:desc to be
+  text.1:wrapped (no-eol)
+  $ hg log -l1 -T '{fill(desc, date, "", "")}\n'
+  hg: parse error: fill expects an integer width
+  [255]
+
+  $ hg log -l 1 --template '{sub(r"[0-9]", "-", author)}'
+  {node|short} (no-eol)
+  $ hg log -l 1 --template '{sub(r"[0-9]", "-", "{node|short}")}'
+  bcc-ff---b-e (no-eol)
+
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > color=
+  > [color]
+  > mode=ansi
+  > text.{rev} = red
+  > text.1 = green
+  > EOF
+  $ hg log --color=always -l 1 --template '{label(branch, "text\n")}'
+  \x1b[0;31mtext\x1b[0m (esc)
+  $ hg log --color=always -l 1 --template '{label("text.{rev}", "text\n")}'
+  \x1b[0;32mtext\x1b[0m (esc)
+
+  $ cd ..
+
+Test bad template with better error message
+
+  $ hg log -Gv -R a --template '{desc|user()}'
+  hg: parse error: expected a symbol, got 'func'
+  [255]
+
+Test broken string escapes:
+
+  $ hg log -T "bogus\\" -R a
+  hg: parse error: trailing \ in string
+  [255]
+  $ hg log -T "\\xy" -R a
+  hg: parse error: invalid \x escape* (glob)
+  [255]
+
+Templater supports aliases of symbol and func() styles:
+
+  $ hg clone -q a aliases
+  $ cd aliases
+  $ cat <<EOF >> .hg/hgrc
+  > [templatealias]
+  > r = rev
+  > rn = "{r}:{node|short}"
+  > status(c, files) = files % "{c} {file}\n"
+  > utcdate(d) = localdate(d, "UTC")
+  > EOF
+
+  $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
+  (template
+    (symbol 'rn')
+    (string ' ')
+    (|
+      (func
+        (symbol 'utcdate')
+        (symbol 'date'))
+      (symbol 'isodate'))
+    (string '\n'))
+  * expanded:
+  (template
+    (template
+      (symbol 'rev')
+      (string ':')
+      (|
+        (symbol 'node')
+        (symbol 'short')))
+    (string ' ')
+    (|
+      (func
+        (symbol 'localdate')
+        (list
+          (symbol 'date')
+          (string 'UTC')))
+      (symbol 'isodate'))
+    (string '\n'))
+  * keywords: date, node, rev
+  * functions: isodate, localdate, short
+  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
+
+  $ hg debugtemplate -vr0 '{status("A", file_adds)}'
+  (template
+    (func
+      (symbol 'status')
+      (list
+        (string 'A')
+        (symbol 'file_adds'))))
+  * expanded:
+  (template
+    (%
+      (symbol 'file_adds')
+      (template
+        (string 'A')
+        (string ' ')
+        (symbol 'file')
+        (string '\n'))))
+  * keywords: file, file_adds
+  * functions: 
+  A a
+
+A unary function alias can be called as a filter:
+
+  $ hg debugtemplate -vr0 '{date|utcdate|isodate}\n'
+  (template
+    (|
+      (|
+        (symbol 'date')
+        (symbol 'utcdate'))
+      (symbol 'isodate'))
+    (string '\n'))
+  * expanded:
+  (template
+    (|
+      (func
+        (symbol 'localdate')
+        (list
+          (symbol 'date')
+          (string 'UTC')))
+      (symbol 'isodate'))
+    (string '\n'))
+  * keywords: date
+  * functions: isodate, localdate
+  1970-01-12 13:46 +0000
+
+Aliases should be applied only to command arguments and templates in hgrc.
+Otherwise, our stock styles and web templates could be corrupted:
+
+  $ hg log -r0 -T '{rn} {utcdate(date)|isodate}\n'
+  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
+
+  $ hg log -r0 --config ui.logtemplate='"{rn} {utcdate(date)|isodate}\n"'
+  0:1e4e1b8f71e0 1970-01-12 13:46 +0000
+
+  $ cat <<EOF > tmpl
+  > changeset = 'nothing expanded:{rn}\n'
+  > EOF
+  $ hg log -r0 --style ./tmpl
+  nothing expanded:
+
+Aliases in formatter:
+
+  $ hg branches -T '{pad(branch, 7)} {rn}\n'
+  default 6:d41e714fe50d
+  foo     4:bbe44766e73d
+
+Aliases should honor HGPLAIN:
+
+  $ HGPLAIN= hg log -r0 -T 'nothing expanded:{rn}\n'
+  nothing expanded:
+  $ HGPLAINEXCEPT=templatealias hg log -r0 -T '{rn}\n'
+  0:1e4e1b8f71e0
+
+Unparsable alias:
+
+  $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
+  (template
+    (symbol 'bad'))
+  abort: bad definition of template alias "bad": at 2: not a prefix: end
+  [255]
+  $ hg log --config templatealias.bad='x(' -T '{bad}'
+  abort: bad definition of template alias "bad": at 2: not a prefix: end
+  [255]
+
+  $ cd ..
+
+Test that template function in extension is registered as expected
+
+  $ cd a
+
+  $ cat <<EOF > $TESTTMP/customfunc.py
+  > from mercurial import registrar
+  > 
+  > templatefunc = registrar.templatefunc()
+  > 
+  > @templatefunc(b'custom()')
+  > def custom(context, mapping, args):
+  >     return b'custom'
+  > EOF
+  $ cat <<EOF > .hg/hgrc
+  > [extensions]
+  > customfunc = $TESTTMP/customfunc.py
+  > EOF
+
+  $ hg log -r . -T "{custom()}\n" --config customfunc.enabled=true
+  custom
+
+  $ cd ..
--- a/tests/test-template-engine.t	Sun Jul 01 23:36:53 2018 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-
-  $ cat > engine.py << EOF
-  > 
-  > from mercurial import (
-  >     pycompat,
-  >     templater,
-  >     templateutil,
-  > )
-  > 
-  > class mytemplater(templater.engine):
-  >     def _load(self, t):
-  >         return self._loader(t)
-  > 
-  >     def process(self, t, map):
-  >         tmpl = self._load(t)
-  >         props = self._defaults.copy()
-  >         props.update(map)
-  >         for k, v in props.items():
-  >             if b'{{%s}}' % k not in tmpl:
-  >                 continue
-  >             if callable(v) and getattr(v, '_requires', None) is None:
-  >                 props = self._resources.copy()
-  >                 props.update(map)
-  >                 v = v(**pycompat.strkwargs(props))
-  >             elif callable(v):
-  >                 v = v(self, props)
-  >             v = templateutil.stringify(self, props, v)
-  >             tmpl = tmpl.replace(b'{{%s}}' % k, v)
-  >         yield tmpl
-  > 
-  > templater.engines[b'my'] = mytemplater
-  > EOF
-  $ hg init test
-  $ echo '[extensions]' > test/.hg/hgrc
-  $ echo "engine = `pwd`/engine.py" >> test/.hg/hgrc
-  $ cd test
-  $ cat > mymap << EOF
-  > changeset = my:changeset.txt
-  > EOF
-  $ cat > changeset.txt << EOF
-  > {{rev}} {{node}} {{author}}
-  > EOF
-  $ hg ci -Ama
-  adding changeset.txt
-  adding mymap
-  $ hg log --style=./mymap
-  0 97e5f848f0936960273bbf75be6388cd0350a32b test
-
-  $ cat > changeset.txt << EOF
-  > {{p1rev}} {{p1node}} {{p2rev}} {{p2node}}
-  > EOF
-  $ hg ci -Ama
-  $ hg log --style=./mymap
-  0 97e5f848f0936960273bbf75be6388cd0350a32b -1 0000000000000000000000000000000000000000
-  -1 0000000000000000000000000000000000000000 -1 0000000000000000000000000000000000000000
-
-invalid engine type:
-
-  $ echo 'changeset = unknown:changeset.txt' > unknownenginemap
-  $ hg log --style=./unknownenginemap
-  abort: invalid template engine: unknown
-  [255]
-
-  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-template-functions.t	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,1430 @@
+Test template filters and functions
+===================================
+
+  $ hg init a
+  $ cd a
+  $ echo a > a
+  $ hg add a
+  $ echo line 1 > b
+  $ echo line 2 >> b
+  $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
+
+  $ hg add b
+  $ echo other 1 > c
+  $ echo other 2 >> c
+  $ echo >> c
+  $ echo other 3 >> c
+  $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
+
+  $ hg add c
+  $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
+  $ echo c >> c
+  $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
+
+  $ echo foo > .hg/branch
+  $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
+
+  $ hg co -q 3
+  $ echo other 4 >> d
+  $ hg add d
+  $ hg commit -m 'new head' -d '1500000 0' -u 'person'
+
+  $ hg merge -q foo
+  $ hg commit -m 'merge' -d '1500001 0' -u 'person'
+
+Second branch starting at nullrev:
+
+  $ hg update null
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ echo second > second
+  $ hg add second
+  $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
+  created new head
+
+  $ echo third > third
+  $ hg add third
+  $ hg mv second fourth
+  $ hg commit -m third -d "2020-01-01 10:01"
+
+  $ hg phase -r 5 --public
+  $ hg phase -r 7 --secret --force
+
+Filters work:
+
+  $ hg log --template '{author|domain}\n'
+  
+  hostname
+  
+  
+  
+  
+  place
+  place
+  hostname
+
+  $ hg log --template '{author|person}\n'
+  test
+  User Name
+  person
+  person
+  person
+  person
+  other
+  A. N. Other
+  User Name
+
+  $ hg log --template '{author|user}\n'
+  test
+  user
+  person
+  person
+  person
+  person
+  other
+  other
+  user
+
+  $ hg log --template '{date|date}\n'
+  Wed Jan 01 10:01:00 2020 +0000
+  Mon Jan 12 13:46:40 1970 +0000
+  Sun Jan 18 08:40:01 1970 +0000
+  Sun Jan 18 08:40:00 1970 +0000
+  Sat Jan 17 04:53:20 1970 +0000
+  Fri Jan 16 01:06:40 1970 +0000
+  Wed Jan 14 21:20:00 1970 +0000
+  Tue Jan 13 17:33:20 1970 +0000
+  Mon Jan 12 13:46:40 1970 +0000
+
+  $ hg log --template '{date|isodate}\n'
+  2020-01-01 10:01 +0000
+  1970-01-12 13:46 +0000
+  1970-01-18 08:40 +0000
+  1970-01-18 08:40 +0000
+  1970-01-17 04:53 +0000
+  1970-01-16 01:06 +0000
+  1970-01-14 21:20 +0000
+  1970-01-13 17:33 +0000
+  1970-01-12 13:46 +0000
+
+  $ hg log --template '{date|isodatesec}\n'
+  2020-01-01 10:01:00 +0000
+  1970-01-12 13:46:40 +0000
+  1970-01-18 08:40:01 +0000
+  1970-01-18 08:40:00 +0000
+  1970-01-17 04:53:20 +0000
+  1970-01-16 01:06:40 +0000
+  1970-01-14 21:20:00 +0000
+  1970-01-13 17:33:20 +0000
+  1970-01-12 13:46:40 +0000
+
+  $ hg log --template '{date|rfc822date}\n'
+  Wed, 01 Jan 2020 10:01:00 +0000
+  Mon, 12 Jan 1970 13:46:40 +0000
+  Sun, 18 Jan 1970 08:40:01 +0000
+  Sun, 18 Jan 1970 08:40:00 +0000
+  Sat, 17 Jan 1970 04:53:20 +0000
+  Fri, 16 Jan 1970 01:06:40 +0000
+  Wed, 14 Jan 1970 21:20:00 +0000
+  Tue, 13 Jan 1970 17:33:20 +0000
+  Mon, 12 Jan 1970 13:46:40 +0000
+
+  $ hg log --template '{desc|firstline}\n'
+  third
+  second
+  merge
+  new head
+  new branch
+  no user, no domain
+  no person
+  other 1
+  line 1
+
+  $ hg log --template '{node|short}\n'
+  95c24699272e
+  29114dbae42b
+  d41e714fe50d
+  13207e5a10d9
+  bbe44766e73d
+  10e46f2dcbf4
+  97054abb4ab8
+  b608e9d1a3f0
+  1e4e1b8f71e0
+
+  $ hg log --template '<changeset author="{author|xmlescape}"/>\n'
+  <changeset author="test"/>
+  <changeset author="User Name &lt;user@hostname&gt;"/>
+  <changeset author="person"/>
+  <changeset author="person"/>
+  <changeset author="person"/>
+  <changeset author="person"/>
+  <changeset author="other@place"/>
+  <changeset author="A. N. Other &lt;other@place&gt;"/>
+  <changeset author="User Name &lt;user@hostname&gt;"/>
+
+  $ hg log --template '{rev}: {children}\n'
+  8: 
+  7: 8:95c24699272e
+  6: 
+  5: 6:d41e714fe50d
+  4: 6:d41e714fe50d
+  3: 4:bbe44766e73d 5:13207e5a10d9
+  2: 3:10e46f2dcbf4
+  1: 2:97054abb4ab8
+  0: 1:b608e9d1a3f0
+
+Formatnode filter works:
+
+  $ hg -q log -r 0 --template '{node|formatnode}\n'
+  1e4e1b8f71e0
+
+  $ hg log -r 0 --template '{node|formatnode}\n'
+  1e4e1b8f71e0
+
+  $ hg -v log -r 0 --template '{node|formatnode}\n'
+  1e4e1b8f71e0
+
+  $ hg --debug log -r 0 --template '{node|formatnode}\n'
+  1e4e1b8f71e05681d422154f5421e385fec3454f
+
+Age filter:
+
+  $ hg init unstable-hash
+  $ cd unstable-hash
+  $ hg log --template '{date|age}\n' > /dev/null || exit 1
+
+  >>> from __future__ import absolute_import
+  >>> import datetime
+  >>> fp = open('a', 'wb')
+  >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
+  >>> fp.write(b'%d-%d-%d 00:00' % (n.year, n.month, n.day)) and None
+  >>> fp.close()
+  $ hg add a
+  $ hg commit -m future -d "`cat a`"
+
+  $ hg log -l1 --template '{date|age}\n'
+  7 years from now
+
+  $ cd ..
+  $ rm -rf unstable-hash
+
+Filename filters:
+
+  $ hg debugtemplate '{"foo/bar"|basename}|{"foo/"|basename}|{"foo"|basename}|\n'
+  bar||foo|
+  $ hg debugtemplate '{"foo/bar"|dirname}|{"foo/"|dirname}|{"foo"|dirname}|\n'
+  foo|foo||
+  $ hg debugtemplate '{"foo/bar"|stripdir}|{"foo/"|stripdir}|{"foo"|stripdir}|\n'
+  foo|foo|foo|
+
+commondir() filter:
+
+  $ hg debugtemplate '{""|splitlines|commondir}\n'
+  
+  $ hg debugtemplate '{"foo/bar\nfoo/baz\nfoo/foobar\n"|splitlines|commondir}\n'
+  foo
+  $ hg debugtemplate '{"foo/bar\nfoo/bar\n"|splitlines|commondir}\n'
+  foo
+  $ hg debugtemplate '{"/foo/bar\n/foo/bar\n"|splitlines|commondir}\n'
+  foo
+  $ hg debugtemplate '{"/foo\n/foo\n"|splitlines|commondir}\n'
+  
+  $ hg debugtemplate '{"foo/bar\nbar/baz"|splitlines|commondir}\n'
+  
+  $ hg debugtemplate '{"foo/bar\nbar/baz\nbar/foo\n"|splitlines|commondir}\n'
+  
+  $ hg debugtemplate '{"foo/../bar\nfoo/bar"|splitlines|commondir}\n'
+  foo
+  $ hg debugtemplate '{"foo\n/foo"|splitlines|commondir}\n'
+  
+
+  $ hg log -r null -T '{rev|commondir}'
+  hg: parse error: argument is not a list of text
+  (template filter 'commondir' is not compatible with keyword 'rev')
+  [255]
+
+Add a dummy commit to make up for the instability of the above:
+
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m future
+
+Count filter:
+
+  $ hg log -l1 --template '{node|count} {node|short|count}\n'
+  40 12
+
+  $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
+  0 1 4
+
+  $ hg log -G --template '{rev}: children: {children|count}, \
+  > tags: {tags|count}, file_adds: {file_adds|count}, \
+  > ancestors: {revset("ancestors(%s)", rev)|count}'
+  @  9: children: 0, tags: 1, file_adds: 1, ancestors: 3
+  |
+  o  8: children: 1, tags: 0, file_adds: 2, ancestors: 2
+  |
+  o  7: children: 1, tags: 0, file_adds: 1, ancestors: 1
+  
+  o    6: children: 0, tags: 0, file_adds: 0, ancestors: 7
+  |\
+  | o  5: children: 1, tags: 0, file_adds: 1, ancestors: 5
+  | |
+  o |  4: children: 1, tags: 0, file_adds: 0, ancestors: 5
+  |/
+  o  3: children: 2, tags: 0, file_adds: 0, ancestors: 4
+  |
+  o  2: children: 1, tags: 0, file_adds: 1, ancestors: 3
+  |
+  o  1: children: 1, tags: 0, file_adds: 1, ancestors: 2
+  |
+  o  0: children: 1, tags: 0, file_adds: 1, ancestors: 1
+  
+
+  $ hg log -l1 -T '{termwidth|count}\n'
+  hg: parse error: not countable
+  (template filter 'count' is not compatible with keyword 'termwidth')
+  [255]
+
+Upper/lower filters:
+
+  $ hg log -r0 --template '{branch|upper}\n'
+  DEFAULT
+  $ hg log -r0 --template '{author|lower}\n'
+  user name <user@hostname>
+  $ hg log -r0 --template '{date|upper}\n'
+  1000000.00
+
+Add a commit that does all possible modifications at once
+
+  $ echo modify >> third
+  $ touch b
+  $ hg add b
+  $ hg mv fourth fifth
+  $ hg rm a
+  $ hg ci -m "Modify, add, remove, rename"
+
+Pass generator object created by template function to filter
+
+  $ hg log -l 1 --template '{if(author, author)|user}\n'
+  test
+
+Test diff function:
+
+  $ hg diff -c 8
+  diff -r 29114dbae42b -r 95c24699272e fourth
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +second
+  diff -r 29114dbae42b -r 95c24699272e second
+  --- a/second	Mon Jan 12 13:46:40 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -second
+  diff -r 29114dbae42b -r 95c24699272e third
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +third
+
+  $ hg log -r 8 -T "{diff()}"
+  diff -r 29114dbae42b -r 95c24699272e fourth
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +second
+  diff -r 29114dbae42b -r 95c24699272e second
+  --- a/second	Mon Jan 12 13:46:40 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -second
+  diff -r 29114dbae42b -r 95c24699272e third
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +third
+
+  $ hg log -r 8 -T "{diff('glob:f*')}"
+  diff -r 29114dbae42b -r 95c24699272e fourth
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +second
+
+  $ hg log -r 8 -T "{diff('', 'glob:f*')}"
+  diff -r 29114dbae42b -r 95c24699272e second
+  --- a/second	Mon Jan 12 13:46:40 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -second
+  diff -r 29114dbae42b -r 95c24699272e third
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +third
+
+  $ hg log -r 8 -T "{diff('FOURTH'|lower)}"
+  diff -r 29114dbae42b -r 95c24699272e fourth
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +second
+
+  $ cd ..
+
+latesttag() function:
+
+  $ hg init latesttag
+  $ cd latesttag
+
+  $ echo a > file
+  $ hg ci -Am a -d '0 0'
+  adding file
+
+  $ echo b >> file
+  $ hg ci -m b -d '1 0'
+
+  $ echo c >> head1
+  $ hg ci -Am h1c -d '2 0'
+  adding head1
+
+  $ hg update -q 1
+  $ echo d >> head2
+  $ hg ci -Am h2d -d '3 0'
+  adding head2
+  created new head
+
+  $ echo e >> head2
+  $ hg ci -m h2e -d '4 0'
+
+  $ hg merge -q
+  $ hg ci -m merge -d '5 -3600'
+
+  $ hg tag -r 1 -m t1 -d '6 0' t1
+  $ hg tag -r 2 -m t2 -d '7 0' t2
+  $ hg tag -r 3 -m t3 -d '8 0' t3
+  $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
+  $ hg tag -r 5 -m t5 -d '9 0' t5
+  $ hg tag -r 3 -m at3 -d '10 0' at3
+
+  $ hg log -G --template "{rev}: {latesttag('re:^t[13]$') % '{tag}, C: {changes}, D: {distance}'}\n"
+  @  11: t3, C: 9, D: 8
+  |
+  o  10: t3, C: 8, D: 7
+  |
+  o  9: t3, C: 7, D: 6
+  |
+  o  8: t3, C: 6, D: 5
+  |
+  o  7: t3, C: 5, D: 4
+  |
+  o  6: t3, C: 4, D: 3
+  |
+  o    5: t3, C: 3, D: 2
+  |\
+  | o  4: t3, C: 1, D: 1
+  | |
+  | o  3: t3, C: 0, D: 0
+  | |
+  o |  2: t1, C: 1, D: 1
+  |/
+  o  1: t1, C: 0, D: 0
+  |
+  o  0: null, C: 1, D: 1
+  
+
+  $ cd ..
+
+Test filter() empty values:
+
+  $ hg log -R a -r 1 -T '{filter(desc|splitlines) % "{line}\n"}'
+  other 1
+  other 2
+  other 3
+  $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1) % "{ifeq(key, "a", "{value}\n")}")}'
+  0
+
+ 0 should not be falsy
+
+  $ hg log -R a -r 0 -T '{filter(revset("0:2"))}\n'
+  0 1 2
+
+Test filter() by expression:
+
+  $ hg log -R a -r 1 -T '{filter(desc|splitlines, ifcontains("1", line, "t"))}\n'
+  other 1
+  $ hg log -R a -r 0 -T '{filter(dict(a=0, b=1), ifeq(key, "b", "t"))}\n'
+  b=1
+
+Test filter() shouldn't crash:
+
+  $ hg log -R a -r 0 -T '{filter(extras)}\n'
+  branch=default
+  $ hg log -R a -r 0 -T '{filter(files)}\n'
+  a
+
+Test filter() unsupported arguments:
+
+  $ hg log -R a -r 0 -T '{filter()}\n'
+  hg: parse error: filter expects one or two arguments
+  [255]
+  $ hg log -R a -r 0 -T '{filter(date)}\n'
+  hg: parse error: date is not iterable
+  [255]
+  $ hg log -R a -r 0 -T '{filter(rev)}\n'
+  hg: parse error: 0 is not iterable
+  [255]
+  $ hg log -R a -r 0 -T '{filter(desc|firstline)}\n'
+  hg: parse error: 'line 1' is not filterable
+  [255]
+  $ hg log -R a -r 0 -T '{filter(manifest)}\n'
+  hg: parse error: '0:a0c8bcbbb45c' is not filterable
+  [255]
+  $ hg log -R a -r 0 -T '{filter(succsandmarkers)}\n'
+  hg: parse error: not filterable without template
+  [255]
+  $ hg log -R a -r 0 -T '{filter(desc|splitlines % "{line}", "")}\n'
+  hg: parse error: not filterable by expression
+  [255]
+
+Test manifest/get() can be join()-ed as string, though it's silly:
+
+  $ hg log -R latesttag -r tip -T '{join(manifest, ".")}\n'
+  1.1.:.2.b.c.6.e.9.0.0.6.c.e.2
+  $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), ".")}\n'
+  d.e.f.a.u.l.t
+
+Test join() over string
+
+  $ hg log -R latesttag -r tip -T '{join(rev|stringify, ".")}\n'
+  1.1
+
+Test join() over uniterable
+
+  $ hg log -R latesttag -r tip -T '{join(rev, "")}\n'
+  hg: parse error: 11 is not iterable
+  [255]
+
+Test min/max of integers
+
+  $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
+  9
+  $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
+  10
+
+Test min/max over map operation:
+
+  $ hg log -R latesttag -r3 -T '{min(tags % "{tag}")}\n'
+  at3
+  $ hg log -R latesttag -r3 -T '{max(tags % "{tag}")}\n'
+  t3
+
+Test min/max of strings:
+
+  $ hg log -R latesttag -l1 -T '{min(desc)}\n'
+  3
+  $ hg log -R latesttag -l1 -T '{max(desc)}\n'
+  t
+
+Test min/max of non-iterable:
+
+  $ hg debugtemplate '{min(1)}'
+  hg: parse error: 1 is not iterable
+  (min first argument should be an iterable)
+  [255]
+  $ hg debugtemplate '{max(2)}'
+  hg: parse error: 2 is not iterable
+  (max first argument should be an iterable)
+  [255]
+
+  $ hg log -R latesttag -l1 -T '{min(date)}'
+  hg: parse error: date is not iterable
+  (min first argument should be an iterable)
+  [255]
+  $ hg log -R latesttag -l1 -T '{max(date)}'
+  hg: parse error: date is not iterable
+  (max first argument should be an iterable)
+  [255]
+
+Test min/max of empty sequence:
+
+  $ hg debugtemplate '{min("")}'
+  hg: parse error: empty string
+  (min first argument should be an iterable)
+  [255]
+  $ hg debugtemplate '{max("")}'
+  hg: parse error: empty string
+  (max first argument should be an iterable)
+  [255]
+  $ hg debugtemplate '{min(dict())}'
+  hg: parse error: empty sequence
+  (min first argument should be an iterable)
+  [255]
+  $ hg debugtemplate '{max(dict())}'
+  hg: parse error: empty sequence
+  (max first argument should be an iterable)
+  [255]
+  $ hg debugtemplate '{min(dict() % "")}'
+  hg: parse error: empty sequence
+  (min first argument should be an iterable)
+  [255]
+  $ hg debugtemplate '{max(dict() % "")}'
+  hg: parse error: empty sequence
+  (max first argument should be an iterable)
+  [255]
+
+Test min/max of if() result
+
+  $ cd latesttag
+  $ hg log -l1 -T '{min(if(true, revset("9:10"), ""))}\n'
+  9
+  $ hg log -l1 -T '{max(if(false, "", revset("9:10")))}\n'
+  10
+  $ hg log -l1 -T '{min(ifcontains("a", "aa", revset("9:10"), ""))}\n'
+  9
+  $ hg log -l1 -T '{max(ifcontains("a", "bb", "", revset("9:10")))}\n'
+  10
+  $ hg log -l1 -T '{min(ifeq(0, 0, revset("9:10"), ""))}\n'
+  9
+  $ hg log -l1 -T '{max(ifeq(0, 1, "", revset("9:10")))}\n'
+  10
+  $ cd ..
+
+Test laziness of if() then/else clause
+
+  $ hg debugtemplate '{count(0)}'
+  hg: parse error: not countable
+  (incompatible use of template filter 'count')
+  [255]
+  $ hg debugtemplate '{if(true, "", count(0))}'
+  $ hg debugtemplate '{if(false, count(0), "")}'
+  $ hg debugtemplate '{ifcontains("a", "aa", "", count(0))}'
+  $ hg debugtemplate '{ifcontains("a", "bb", count(0), "")}'
+  $ hg debugtemplate '{ifeq(0, 0, "", count(0))}'
+  $ hg debugtemplate '{ifeq(0, 1, count(0), "")}'
+
+Test the sub function of templating for expansion:
+
+  $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
+  xx
+
+  $ hg log -R latesttag -r 10 -T '{sub("[", "x", rev)}\n'
+  hg: parse error: sub got an invalid pattern: [
+  [255]
+  $ hg log -R latesttag -r 10 -T '{sub("[0-9]", r"\1", rev)}\n'
+  hg: parse error: sub got an invalid replacement: \1
+  [255]
+
+Test the strip function with chars specified:
+
+  $ hg log -R latesttag --template '{desc}\n'
+  at3
+  t5
+  t4
+  t3
+  t2
+  t1
+  merge
+  h2e
+  h2d
+  h1c
+  b
+  a
+
+  $ hg log -R latesttag --template '{strip(desc, "te")}\n'
+  at3
+  5
+  4
+  3
+  2
+  1
+  merg
+  h2
+  h2d
+  h1c
+  b
+  a
+
+Test date format:
+
+  $ hg log -R latesttag --template 'date: {date(date, "%y %m %d %S %z")}\n'
+  date: 70 01 01 10 +0000
+  date: 70 01 01 09 +0000
+  date: 70 01 01 04 +0000
+  date: 70 01 01 08 +0000
+  date: 70 01 01 07 +0000
+  date: 70 01 01 06 +0000
+  date: 70 01 01 05 +0100
+  date: 70 01 01 04 +0000
+  date: 70 01 01 03 +0000
+  date: 70 01 01 02 +0000
+  date: 70 01 01 01 +0000
+  date: 70 01 01 00 +0000
+
+Test invalid date:
+
+  $ hg log -R latesttag -T '{date(rev)}\n'
+  hg: parse error: date expects a date information
+  [255]
+
+Set up repository containing template fragments in commit metadata:
+
+  $ hg init r
+  $ cd r
+  $ echo a > a
+  $ hg ci -Am '{rev}'
+  adding a
+
+  $ hg branch -q 'text.{rev}'
+  $ echo aa >> aa
+  $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
+
+color effect can be specified without quoting:
+
+  $ hg log --color=always -l 1 --template '{label(red, "text\n")}'
+  \x1b[0;31mtext\x1b[0m (esc)
+
+color effects can be nested (issue5413)
+
+  $ hg debugtemplate --color=always \
+  > '{label(red, "red{label(magenta, "ma{label(cyan, "cyan")}{label(yellow, "yellow")}genta")}")}\n'
+  \x1b[0;31mred\x1b[0;35mma\x1b[0;36mcyan\x1b[0m\x1b[0;31m\x1b[0;35m\x1b[0;33myellow\x1b[0m\x1b[0;31m\x1b[0;35mgenta\x1b[0m (esc)
+
+pad() should interact well with color codes (issue5416)
+
+  $ hg debugtemplate --color=always \
+  > '{pad(label(red, "red"), 5, label(cyan, "-"))}\n'
+  \x1b[0;31mred\x1b[0m\x1b[0;36m-\x1b[0m\x1b[0;36m-\x1b[0m (esc)
+
+label should be no-op if color is disabled:
+
+  $ hg log --color=never -l 1 --template '{label(red, "text\n")}'
+  text
+  $ hg log --config extensions.color=! -l 1 --template '{label(red, "text\n")}'
+  text
+
+Test branches inside if statement:
+
+  $ hg log -r 0 --template '{if(branches, "yes", "no")}\n'
+  no
+
+Test dict constructor:
+
+  $ hg log -r 0 -T '{dict(y=node|short, x=rev)}\n'
+  y=f7769ec2ab97 x=0
+  $ hg log -r 0 -T '{dict(x=rev, y=node|short) % "{key}={value}\n"}'
+  x=0
+  y=f7769ec2ab97
+  $ hg log -r 0 -T '{dict(x=rev, y=node|short)|json}\n'
+  {"x": 0, "y": "f7769ec2ab97"}
+  $ hg log -r 0 -T '{dict()|json}\n'
+  {}
+
+  $ hg log -r 0 -T '{dict(rev, node=node|short)}\n'
+  rev=0 node=f7769ec2ab97
+  $ hg log -r 0 -T '{dict(rev, node|short)}\n'
+  rev=0 node=f7769ec2ab97
+
+  $ hg log -r 0 -T '{dict(rev, rev=rev)}\n'
+  hg: parse error: duplicated dict key 'rev' inferred
+  [255]
+  $ hg log -r 0 -T '{dict(node, node|short)}\n'
+  hg: parse error: duplicated dict key 'node' inferred
+  [255]
+  $ hg log -r 0 -T '{dict(1 + 2)}'
+  hg: parse error: dict key cannot be inferred
+  [255]
+
+  $ hg log -r 0 -T '{dict(x=rev, x=node)}'
+  hg: parse error: dict got multiple values for keyword argument 'x'
+  [255]
+
+Test get function:
+
+  $ hg log -r 0 --template '{get(extras, "branch")}\n'
+  default
+  $ hg log -r 0 --template '{get(extras, "br{"anch"}")}\n'
+  default
+  $ hg log -r 0 --template '{get(files, "should_fail")}\n'
+  hg: parse error: not a dictionary
+  (get() expects a dict as first argument)
+  [255]
+
+Test json filter applied to wrapped object:
+
+  $ hg log -r0 -T '{files|json}\n'
+  ["a"]
+  $ hg log -r0 -T '{extras|json}\n'
+  {"branch": "default"}
+  $ hg log -r0 -T '{date|json}\n'
+  [0, 0]
+
+Test json filter applied to map result:
+
+  $ hg log -r0 -T '{json(extras % "{key}")}\n'
+  ["branch"]
+
+Test localdate(date, tz) function:
+
+  $ TZ=JST-09 hg log -r0 -T '{date|localdate|isodate}\n'
+  1970-01-01 09:00 +0900
+  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "UTC")|isodate}\n'
+  1970-01-01 00:00 +0000
+  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "blahUTC")|isodate}\n'
+  hg: parse error: localdate expects a timezone
+  [255]
+  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "+0200")|isodate}\n'
+  1970-01-01 02:00 +0200
+  $ TZ=JST-09 hg log -r0 -T '{localdate(date, "0")|isodate}\n'
+  1970-01-01 00:00 +0000
+  $ TZ=JST-09 hg log -r0 -T '{localdate(date, 0)|isodate}\n'
+  1970-01-01 00:00 +0000
+  $ hg log -r0 -T '{localdate(date, "invalid")|isodate}\n'
+  hg: parse error: localdate expects a timezone
+  [255]
+  $ hg log -r0 -T '{localdate(date, date)|isodate}\n'
+  hg: parse error: localdate expects a timezone
+  [255]
+
+Test shortest(node) function:
+
+  $ echo b > b
+  $ hg ci -qAm b
+  $ hg log --template '{shortest(node)}\n'
+  e777
+  bcc7
+  f776
+  $ hg log --template '{shortest(node, 10)}\n'
+  e777603221
+  bcc7ff960b
+  f7769ec2ab
+  $ hg log --template '{node|shortest}\n' -l1
+  e777
+
+  $ hg log -r 0 -T '{shortest(node, "1{"0"}")}\n'
+  f7769ec2ab
+  $ hg log -r 0 -T '{shortest(node, "not an int")}\n'
+  hg: parse error: shortest() expects an integer minlength
+  [255]
+
+  $ hg log -r 'wdir()' -T '{node|shortest}\n'
+  ffff
+
+  $ hg log --template '{shortest("f")}\n' -l1
+  f
+
+  $ hg log --template '{shortest("0123456789012345678901234567890123456789")}\n' -l1
+  0123456789012345678901234567890123456789
+
+  $ hg log --template '{shortest("01234567890123456789012345678901234567890123456789")}\n' -l1
+  01234567890123456789012345678901234567890123456789
+
+  $ hg log --template '{shortest("not a hex string")}\n' -l1
+  not a hex string
+
+  $ hg log --template '{shortest("not a hex string, but it'\''s 40 bytes long")}\n' -l1
+  not a hex string, but it's 40 bytes long
+
+  $ hg log --template '{shortest("ffffffffffffffffffffffffffffffffffffffff")}\n' -l1
+  ffff
+
+  $ hg log --template '{shortest("fffffff")}\n' -l1
+  ffff
+
+  $ hg log --template '{shortest("ff")}\n' -l1
+  ffff
+
+  $ cd ..
+
+Test shortest(node) with the repo having short hash collision:
+
+  $ hg init hashcollision
+  $ cd hashcollision
+  $ cat <<EOF >> .hg/hgrc
+  > [experimental]
+  > evolution.createmarkers=True
+  > EOF
+  $ echo 0 > a
+  $ hg ci -qAm 0
+  $ for i in 17 129 248 242 480 580 617 1057 2857 4025; do
+  >   hg up -q 0
+  >   echo $i > a
+  >   hg ci -qm $i
+  > done
+  $ hg up -q null
+  $ hg log -r0: -T '{rev}:{node}\n'
+  0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
+  1:11424df6dc1dd4ea255eae2b58eaca7831973bbc
+  2:11407b3f1b9c3e76a79c1ec5373924df096f0499
+  3:11dd92fe0f39dfdaacdaa5f3997edc533875cfc4
+  4:10776689e627b465361ad5c296a20a487e153ca4
+  5:a00be79088084cb3aff086ab799f8790e01a976b
+  6:a0b0acd79b4498d0052993d35a6a748dd51d13e6
+  7:a0457b3450b8e1b778f1163b31a435802987fe5d
+  8:c56256a09cd28e5764f32e8e2810d0f01e2e357a
+  9:c5623987d205cd6d9d8389bfc40fff9dbb670b48
+  10:c562ddd9c94164376c20b86b0b4991636a3bf84f
+  $ hg debugobsolete a00be79088084cb3aff086ab799f8790e01a976b
+  obsoleted 1 changesets
+  $ hg debugobsolete c5623987d205cd6d9d8389bfc40fff9dbb670b48
+  obsoleted 1 changesets
+  $ hg debugobsolete c562ddd9c94164376c20b86b0b4991636a3bf84f
+  obsoleted 1 changesets
+
+ nodes starting with '11' (we don't have the revision number '11' though)
+
+  $ hg log -r 1:3 -T '{rev}:{shortest(node, 0)}\n'
+  1:1142
+  2:1140
+  3:11d
+
+ '5:a00' is hidden, but still we have two nodes starting with 'a0'
+
+  $ hg log -r 6:7 -T '{rev}:{shortest(node, 0)}\n'
+  6:a0b
+  7:a04
+
+ node '10' conflicts with the revision number '10' even if it is hidden
+ (we could exclude hidden revision numbers, but currently we don't)
+
+  $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n'
+  4:107
+  $ hg log -r 4 -T '{rev}:{shortest(node, 0)}\n' --hidden
+  4:107
+
+ node 'c562' should be unique if the other 'c562' nodes are hidden
+ (but we don't try the slow path to filter out hidden nodes for now)
+
+  $ hg log -r 8 -T '{rev}:{node|shortest}\n'
+  8:c5625
+  $ hg log -r 8:10 -T '{rev}:{node|shortest}\n' --hidden
+  8:c5625
+  9:c5623
+  10:c562d
+
+  $ cd ..
+
+Test pad function
+
+  $ cd r
+
+  $ hg log --template '{pad(rev, 20)} {author|user}\n'
+  2                    test
+  1                    {node|short}
+  0                    test
+
+  $ hg log --template '{pad(rev, 20, " ", True)} {author|user}\n'
+                     2 test
+                     1 {node|short}
+                     0 test
+
+  $ hg log --template '{pad(rev, 20, "-", False)} {author|user}\n'
+  2------------------- test
+  1------------------- {node|short}
+  0------------------- test
+
+Test template string in pad function
+
+  $ hg log -r 0 -T '{pad("\{{rev}}", 10)} {author|user}\n'
+  {0}        test
+
+  $ hg log -r 0 -T '{pad(r"\{rev}", 10)} {author|user}\n'
+  \{rev}     test
+
+Test width argument passed to pad function
+
+  $ hg log -r 0 -T '{pad(rev, "1{"0"}")} {author|user}\n'
+  0          test
+  $ hg log -r 0 -T '{pad(rev, "not an int")}\n'
+  hg: parse error: pad() expects an integer width
+  [255]
+
+Test invalid fillchar passed to pad function
+
+  $ hg log -r 0 -T '{pad(rev, 10, "")}\n'
+  hg: parse error: pad() expects a single fill character
+  [255]
+  $ hg log -r 0 -T '{pad(rev, 10, "--")}\n'
+  hg: parse error: pad() expects a single fill character
+  [255]
+
+Test boolean argument passed to pad function
+
+ no crash
+
+  $ hg log -r 0 -T '{pad(rev, 10, "-", "f{"oo"}")}\n'
+  ---------0
+
+ string/literal
+
+  $ hg log -r 0 -T '{pad(rev, 10, "-", "false")}\n'
+  ---------0
+  $ hg log -r 0 -T '{pad(rev, 10, "-", false)}\n'
+  0---------
+  $ hg log -r 0 -T '{pad(rev, 10, "-", "")}\n'
+  0---------
+
+ unknown keyword is evaluated to ''
+
+  $ hg log -r 0 -T '{pad(rev, 10, "-", unknownkeyword)}\n'
+  0---------
+
+Test separate function
+
+  $ hg log -r 0 -T '{separate("-", "", "a", "b", "", "", "c", "")}\n'
+  a-b-c
+  $ hg log -r 0 -T '{separate(" ", "{rev}:{node|short}", author|user, branch)}\n'
+  0:f7769ec2ab97 test default
+  $ hg log -r 0 --color=always -T '{separate(" ", "a", label(red, "b"), "c", label(red, ""), "d")}\n'
+  a \x1b[0;31mb\x1b[0m c d (esc)
+
+Test boolean expression/literal passed to if function
+
+  $ hg log -r 0 -T '{if(rev, "rev 0 is True")}\n'
+  rev 0 is True
+  $ hg log -r 0 -T '{if(0, "literal 0 is True as well")}\n'
+  literal 0 is True as well
+  $ hg log -r 0 -T '{if(min(revset(r"0")), "0 of hybriditem is also True")}\n'
+  0 of hybriditem is also True
+  $ hg log -r 0 -T '{if("", "", "empty string is False")}\n'
+  empty string is False
+  $ hg log -r 0 -T '{if(revset(r"0 - 0"), "", "empty list is False")}\n'
+  empty list is False
+  $ hg log -r 0 -T '{if(revset(r"0"), "non-empty list is True")}\n'
+  non-empty list is True
+  $ hg log -r 0 -T '{if(revset(r"0") % "", "list of empty strings is True")}\n'
+  list of empty strings is True
+  $ hg log -r 0 -T '{if(true, "true is True")}\n'
+  true is True
+  $ hg log -r 0 -T '{if(false, "", "false is False")}\n'
+  false is False
+  $ hg log -r 0 -T '{if("false", "non-empty string is True")}\n'
+  non-empty string is True
+
+Test ifcontains function
+
+  $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
+  2 is in the string
+  1 is not
+  0 is in the string
+
+  $ hg log -T '{rev} {ifcontains(rev, "2 two{" 0"}", "is in the string", "is not")}\n'
+  2 is in the string
+  1 is not
+  0 is in the string
+
+  $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
+  2 did not add a
+  1 did not add a
+  0 added a
+
+  $ hg log --debug -T '{rev}{ifcontains(1, parents, " is parent of 1")}\n'
+  2 is parent of 1
+  1
+  0
+
+  $ hg log -l1 -T '{ifcontains("branch", extras, "t", "f")}\n'
+  t
+  $ hg log -l1 -T '{ifcontains("branch", extras % "{key}", "t", "f")}\n'
+  t
+  $ hg log -l1 -T '{ifcontains("branc", extras % "{key}", "t", "f")}\n'
+  f
+  $ hg log -l1 -T '{ifcontains("branc", stringify(extras % "{key}"), "t", "f")}\n'
+  t
+
+Test revset function
+
+  $ hg log --template '{rev} {ifcontains(rev, revset("."), "current rev", "not current rev")}\n'
+  2 current rev
+  1 not current rev
+  0 not current rev
+
+  $ hg log --template '{rev} {ifcontains(rev, revset(". + .^"), "match rev", "not match rev")}\n'
+  2 match rev
+  1 match rev
+  0 not match rev
+
+  $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
+  type not match
+
+  $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
+  2 Parents: 1
+  1 Parents: 0
+  0 Parents: 
+
+  $ cat >> .hg/hgrc <<EOF
+  > [revsetalias]
+  > myparents(\$1) = parents(\$1)
+  > EOF
+  $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
+  2 Parents: 1
+  1 Parents: 0
+  0 Parents: 
+
+  $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
+  Rev: 2
+  Ancestor: 0
+  Ancestor: 1
+  Ancestor: 2
+  
+  Rev: 1
+  Ancestor: 0
+  Ancestor: 1
+  
+  Rev: 0
+  Ancestor: 0
+  
+  $ hg log --template '{revset("TIP"|lower)}\n' -l1
+  2
+
+  $ hg log -T '{revset("%s", "t{"ip"}")}\n' -l1
+  2
+
+ a list template is evaluated for each item of revset/parents
+
+  $ hg log -T '{rev} p: {revset("p1(%s)", rev) % "{rev}:{node|short}"}\n'
+  2 p: 1:bcc7ff960b8e
+  1 p: 0:f7769ec2ab97
+  0 p: 
+
+  $ hg log --debug -T '{rev} p:{parents % " {rev}:{node|short}"}\n'
+  2 p: 1:bcc7ff960b8e -1:000000000000
+  1 p: 0:f7769ec2ab97 -1:000000000000
+  0 p: -1:000000000000 -1:000000000000
+
+ therefore, 'revcache' should be recreated for each rev
+
+  $ hg log -T '{rev} {file_adds}\np {revset("p1(%s)", rev) % "{file_adds}"}\n'
+  2 aa b
+  p 
+  1 
+  p a
+  0 a
+  p 
+
+  $ hg log --debug -T '{rev} {file_adds}\np {parents % "{file_adds}"}\n'
+  2 aa b
+  p 
+  1 
+  p a
+  0 a
+  p 
+
+a revset item must be evaluated as an integer revision, not an offset from tip
+
+  $ hg log -l 1 -T '{revset("null") % "{rev}:{node|short}"}\n'
+  -1:000000000000
+  $ hg log -l 1 -T '{revset("%s", "null") % "{rev}:{node|short}"}\n'
+  -1:000000000000
+
+join() should pick '{rev}' from revset items:
+
+  $ hg log -R ../a -T '{join(revset("parents(%d)", rev), ", ")}\n' -r6
+  4, 5
+
+on the other hand, parents are formatted as '{rev}:{node|formatnode}' by
+default. join() should agree with the default formatting:
+
+  $ hg log -R ../a -T '{join(parents, ", ")}\n' -r6
+  5:13207e5a10d9, 4:bbe44766e73d
+
+  $ hg log -R ../a -T '{join(parents, ",\n")}\n' -r6 --debug
+  5:13207e5a10d9fd28ec424934298e176197f2c67f,
+  4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
+
+Invalid arguments passed to revset()
+
+  $ hg log -T '{revset("%whatever", 0)}\n'
+  hg: parse error: unexpected revspec format character w
+  [255]
+  $ hg log -T '{revset("%lwhatever", files)}\n'
+  hg: parse error: unexpected revspec format character w
+  [255]
+  $ hg log -T '{revset("%s %s", 0)}\n'
+  hg: parse error: missing argument for revspec
+  [255]
+  $ hg log -T '{revset("", 0)}\n'
+  hg: parse error: too many revspec arguments specified
+  [255]
+  $ hg log -T '{revset("%s", 0, 1)}\n'
+  hg: parse error: too many revspec arguments specified
+  [255]
+  $ hg log -T '{revset("%", 0)}\n'
+  hg: parse error: incomplete revspec format character
+  [255]
+  $ hg log -T '{revset("%l", 0)}\n'
+  hg: parse error: incomplete revspec format character
+  [255]
+  $ hg log -T '{revset("%d", 'foo')}\n'
+  hg: parse error: invalid argument for revspec
+  [255]
+  $ hg log -T '{revset("%ld", files)}\n'
+  hg: parse error: invalid argument for revspec
+  [255]
+  $ hg log -T '{revset("%ls", 0)}\n'
+  hg: parse error: invalid argument for revspec
+  [255]
+  $ hg log -T '{revset("%b", 'foo')}\n'
+  hg: parse error: invalid argument for revspec
+  [255]
+  $ hg log -T '{revset("%lb", files)}\n'
+  hg: parse error: invalid argument for revspec
+  [255]
+  $ hg log -T '{revset("%r", 0)}\n'
+  hg: parse error: invalid argument for revspec
+  [255]
+
+Test files function
+
+  $ hg log -T "{rev}\n{join(files('*'), '\n')}\n"
+  2
+  a
+  aa
+  b
+  1
+  a
+  0
+  a
+
+  $ hg log -T "{rev}\n{join(files('aa'), '\n')}\n"
+  2
+  aa
+  1
+  
+  0
+  
+  $ hg rm a
+  $ hg log -r "wdir()" -T "{rev}\n{join(files('*'), '\n')}\n"
+  2147483647
+  aa
+  b
+  $ hg revert a
+
+Test relpath function
+
+  $ hg log -r0 -T '{files % "{file|relpath}\n"}'
+  a
+  $ cd ..
+  $ hg log -R r -r0 -T '{files % "{file|relpath}\n"}'
+  r/a
+
+Test stringify on sub expressions
+
+  $ hg log -R a -r 8 --template '{join(files, if("1", if("1", ", ")))}\n'
+  fourth, second, third
+  $ hg log -R a -r 8 --template '{strip(if("1", if("1", "-abc-")), if("1", if("1", "-")))}\n'
+  abc
+
+Test splitlines
+
+  $ hg log -Gv -R a --template "{splitlines(desc) % 'foo {line}\n'}"
+  @  foo Modify, add, remove, rename
+  |
+  o  foo future
+  |
+  o  foo third
+  |
+  o  foo second
+  
+  o    foo merge
+  |\
+  | o  foo new head
+  | |
+  o |  foo new branch
+  |/
+  o  foo no user, no domain
+  |
+  o  foo no person
+  |
+  o  foo other 1
+  |  foo other 2
+  |  foo
+  |  foo other 3
+  o  foo line 1
+     foo line 2
+
+  $ hg log -R a -r0 -T '{desc|splitlines}\n'
+  line 1 line 2
+  $ hg log -R a -r0 -T '{join(desc|splitlines, "|")}\n'
+  line 1|line 2
+
+Test startswith
+  $ hg log -Gv -R a --template "{startswith(desc)}"
+  hg: parse error: startswith expects two arguments
+  [255]
+
+  $ hg log -Gv -R a --template "{startswith('line', desc)}"
+  @
+  |
+  o
+  |
+  o
+  |
+  o
+  
+  o
+  |\
+  | o
+  | |
+  o |
+  |/
+  o
+  |
+  o
+  |
+  o
+  |
+  o  line 1
+     line 2
+
+Test word function (including index out of bounds graceful failure)
+
+  $ hg log -Gv -R a --template "{word('1', desc)}"
+  @  add,
+  |
+  o
+  |
+  o
+  |
+  o
+  
+  o
+  |\
+  | o  head
+  | |
+  o |  branch
+  |/
+  o  user,
+  |
+  o  person
+  |
+  o  1
+  |
+  o  1
+  
+
+Test word third parameter used as splitter
+
+  $ hg log -Gv -R a --template "{word('0', desc, 'o')}"
+  @  M
+  |
+  o  future
+  |
+  o  third
+  |
+  o  sec
+  
+  o    merge
+  |\
+  | o  new head
+  | |
+  o |  new branch
+  |/
+  o  n
+  |
+  o  n
+  |
+  o
+  |
+  o  line 1
+     line 2
+
+Test word error messages for not enough and too many arguments
+
+  $ hg log -Gv -R a --template "{word('0')}"
+  hg: parse error: word expects two or three arguments, got 1
+  [255]
+
+  $ hg log -Gv -R a --template "{word('0', desc, 'o', 'h', 'b', 'o', 'y')}"
+  hg: parse error: word expects two or three arguments, got 7
+  [255]
+
+Test word for integer literal
+
+  $ hg log -R a --template "{word(2, desc)}\n" -r0
+  line
+
+Test word for invalid numbers
+
+  $ hg log -Gv -R a --template "{word('a', desc)}"
+  hg: parse error: word expects an integer index
+  [255]
+
+Test word for out of range
+
+  $ hg log -R a --template "{word(10000, desc)}"
+  $ hg log -R a --template "{word(-10000, desc)}"
+
+Test indent and not adding to empty lines
+
+  $ hg log -T "-----\n{indent(desc, '>> ', ' > ')}\n" -r 0:1 -R a
+  -----
+   > line 1
+  >> line 2
+  -----
+   > other 1
+  >> other 2
+  
+  >> other 3
+
+Test with non-strings like dates
+
+  $ hg log -T "{indent(date, '   ')}\n" -r 2:3 -R a
+     1200000.00
+     1300000.00
+
+json filter should escape HTML tags so that the output can be embedded in hgweb:
+
+  $ hg log -T "{'<foo@example.org>'|json}\n" -R a -l1
+  "\u003cfoo@example.org\u003e"
+
+Set up repository for non-ascii encoding tests:
+
+  $ hg init nonascii
+  $ cd nonascii
+  $ $PYTHON <<EOF
+  > open('latin1', 'wb').write(b'\xe9')
+  > open('utf-8', 'wb').write(b'\xc3\xa9')
+  > EOF
+  $ HGENCODING=utf-8 hg branch -q `cat utf-8`
+  $ HGENCODING=utf-8 hg ci -qAm "non-ascii branch: `cat utf-8`" utf-8
+
+json filter should try round-trip conversion to utf-8:
+
+  $ HGENCODING=ascii hg log -T "{branch|json}\n" -r0
+  "\u00e9"
+  $ HGENCODING=ascii hg log -T "{desc|json}\n" -r0
+  "non-ascii branch: \u00e9"
+
+json filter should take input as utf-8 if it was converted from utf-8:
+
+  $ HGENCODING=latin-1 hg log -T "{branch|json}\n" -r0
+  "\u00e9"
+  $ HGENCODING=latin-1 hg log -T "{desc|json}\n" -r0
+  "non-ascii branch: \u00e9"
+
+json filter takes input as utf-8b:
+
+  $ HGENCODING=ascii hg log -T "{'`cat utf-8`'|json}\n" -l1
+  "\u00e9"
+  $ HGENCODING=ascii hg log -T "{'`cat latin1`'|json}\n" -l1
+  "\udce9"
+
+utf8 filter:
+
+  $ HGENCODING=ascii hg log -T "round-trip: {branch|utf8|hex}\n" -r0
+  round-trip: c3a9
+  $ HGENCODING=latin1 hg log -T "decoded: {'`cat latin1`'|utf8|hex}\n" -l1
+  decoded: c3a9
+  $ HGENCODING=ascii hg log -T "replaced: {'`cat latin1`'|utf8|hex}\n" -l1
+  abort: decoding near * (glob)
+  [255]
+  $ hg log -T "coerced to string: {rev|utf8}\n" -r0
+  coerced to string: 0
+
+pad width:
+
+  $ HGENCODING=utf-8 hg debugtemplate "{pad('`cat utf-8`', 2, '-')}\n"
+  \xc3\xa9- (esc)
+
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-template-keywords.t	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,1195 @@
+Test template keywords
+======================
+
+  $ hg init a
+  $ cd a
+  $ echo a > a
+  $ hg add a
+  $ echo line 1 > b
+  $ echo line 2 >> b
+  $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
+
+  $ hg add b
+  $ echo other 1 > c
+  $ echo other 2 >> c
+  $ echo >> c
+  $ echo other 3 >> c
+  $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
+
+  $ hg add c
+  $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
+  $ echo c >> c
+  $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
+
+  $ echo foo > .hg/branch
+  $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
+
+  $ hg co -q 3
+  $ echo other 4 >> d
+  $ hg add d
+  $ hg commit -m 'new head' -d '1500000 0' -u 'person'
+
+  $ hg merge -q foo
+  $ hg commit -m 'merge' -d '1500001 0' -u 'person'
+
+Second branch starting at nullrev:
+
+  $ hg update null
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ echo second > second
+  $ hg add second
+  $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
+  created new head
+
+  $ echo third > third
+  $ hg add third
+  $ hg mv second fourth
+  $ hg commit -m third -d "2020-01-01 10:01"
+
+Working-directory revision has special identifiers, though they are still
+experimental:
+
+  $ hg log -r 'wdir()' -T '{rev}:{node}\n'
+  2147483647:ffffffffffffffffffffffffffffffffffffffff
+
+Some keywords are invalid for working-directory revision, but they should
+never cause crash:
+
+  $ hg log -r 'wdir()' -T '{manifest}\n'
+  
+
+Check that {phase} works correctly on parents:
+
+  $ cat << EOF > parentphase
+  > changeset_debug = '{rev} ({phase}):{parents}\n'
+  > parent = ' {rev} ({phase})'
+  > EOF
+  $ hg phase -r 5 --public
+  $ hg phase -r 7 --secret --force
+  $ hg log --debug -G --style ./parentphase
+  @  8 (secret): 7 (secret) -1 (public)
+  |
+  o  7 (secret): -1 (public) -1 (public)
+  
+  o    6 (draft): 5 (public) 4 (draft)
+  |\
+  | o  5 (public): 3 (public) -1 (public)
+  | |
+  o |  4 (draft): 3 (public) -1 (public)
+  |/
+  o  3 (public): 2 (public) -1 (public)
+  |
+  o  2 (public): 1 (public) -1 (public)
+  |
+  o  1 (public): 0 (public) -1 (public)
+  |
+  o  0 (public): -1 (public) -1 (public)
+  
+
+Keys work:
+
+  $ for key in author branch branches date desc file_adds file_dels file_mods \
+  >         file_copies file_copies_switch files \
+  >         manifest node parents rev tags diffstat extras \
+  >         p1rev p2rev p1node p2node; do
+  >     for mode in '' --verbose --debug; do
+  >         hg log $mode --template "$key$mode: {$key}\n"
+  >     done
+  > done
+  author: test
+  author: User Name <user@hostname>
+  author: person
+  author: person
+  author: person
+  author: person
+  author: other@place
+  author: A. N. Other <other@place>
+  author: User Name <user@hostname>
+  author--verbose: test
+  author--verbose: User Name <user@hostname>
+  author--verbose: person
+  author--verbose: person
+  author--verbose: person
+  author--verbose: person
+  author--verbose: other@place
+  author--verbose: A. N. Other <other@place>
+  author--verbose: User Name <user@hostname>
+  author--debug: test
+  author--debug: User Name <user@hostname>
+  author--debug: person
+  author--debug: person
+  author--debug: person
+  author--debug: person
+  author--debug: other@place
+  author--debug: A. N. Other <other@place>
+  author--debug: User Name <user@hostname>
+  branch: default
+  branch: default
+  branch: default
+  branch: default
+  branch: foo
+  branch: default
+  branch: default
+  branch: default
+  branch: default
+  branch--verbose: default
+  branch--verbose: default
+  branch--verbose: default
+  branch--verbose: default
+  branch--verbose: foo
+  branch--verbose: default
+  branch--verbose: default
+  branch--verbose: default
+  branch--verbose: default
+  branch--debug: default
+  branch--debug: default
+  branch--debug: default
+  branch--debug: default
+  branch--debug: foo
+  branch--debug: default
+  branch--debug: default
+  branch--debug: default
+  branch--debug: default
+  branches: 
+  branches: 
+  branches: 
+  branches: 
+  branches: foo
+  branches: 
+  branches: 
+  branches: 
+  branches: 
+  branches--verbose: 
+  branches--verbose: 
+  branches--verbose: 
+  branches--verbose: 
+  branches--verbose: foo
+  branches--verbose: 
+  branches--verbose: 
+  branches--verbose: 
+  branches--verbose: 
+  branches--debug: 
+  branches--debug: 
+  branches--debug: 
+  branches--debug: 
+  branches--debug: foo
+  branches--debug: 
+  branches--debug: 
+  branches--debug: 
+  branches--debug: 
+  date: 1577872860.00
+  date: 1000000.00
+  date: 1500001.00
+  date: 1500000.00
+  date: 1400000.00
+  date: 1300000.00
+  date: 1200000.00
+  date: 1100000.00
+  date: 1000000.00
+  date--verbose: 1577872860.00
+  date--verbose: 1000000.00
+  date--verbose: 1500001.00
+  date--verbose: 1500000.00
+  date--verbose: 1400000.00
+  date--verbose: 1300000.00
+  date--verbose: 1200000.00
+  date--verbose: 1100000.00
+  date--verbose: 1000000.00
+  date--debug: 1577872860.00
+  date--debug: 1000000.00
+  date--debug: 1500001.00
+  date--debug: 1500000.00
+  date--debug: 1400000.00
+  date--debug: 1300000.00
+  date--debug: 1200000.00
+  date--debug: 1100000.00
+  date--debug: 1000000.00
+  desc: third
+  desc: second
+  desc: merge
+  desc: new head
+  desc: new branch
+  desc: no user, no domain
+  desc: no person
+  desc: other 1
+  other 2
+  
+  other 3
+  desc: line 1
+  line 2
+  desc--verbose: third
+  desc--verbose: second
+  desc--verbose: merge
+  desc--verbose: new head
+  desc--verbose: new branch
+  desc--verbose: no user, no domain
+  desc--verbose: no person
+  desc--verbose: other 1
+  other 2
+  
+  other 3
+  desc--verbose: line 1
+  line 2
+  desc--debug: third
+  desc--debug: second
+  desc--debug: merge
+  desc--debug: new head
+  desc--debug: new branch
+  desc--debug: no user, no domain
+  desc--debug: no person
+  desc--debug: other 1
+  other 2
+  
+  other 3
+  desc--debug: line 1
+  line 2
+  file_adds: fourth third
+  file_adds: second
+  file_adds: 
+  file_adds: d
+  file_adds: 
+  file_adds: 
+  file_adds: c
+  file_adds: b
+  file_adds: a
+  file_adds--verbose: fourth third
+  file_adds--verbose: second
+  file_adds--verbose: 
+  file_adds--verbose: d
+  file_adds--verbose: 
+  file_adds--verbose: 
+  file_adds--verbose: c
+  file_adds--verbose: b
+  file_adds--verbose: a
+  file_adds--debug: fourth third
+  file_adds--debug: second
+  file_adds--debug: 
+  file_adds--debug: d
+  file_adds--debug: 
+  file_adds--debug: 
+  file_adds--debug: c
+  file_adds--debug: b
+  file_adds--debug: a
+  file_dels: second
+  file_dels: 
+  file_dels: 
+  file_dels: 
+  file_dels: 
+  file_dels: 
+  file_dels: 
+  file_dels: 
+  file_dels: 
+  file_dels--verbose: second
+  file_dels--verbose: 
+  file_dels--verbose: 
+  file_dels--verbose: 
+  file_dels--verbose: 
+  file_dels--verbose: 
+  file_dels--verbose: 
+  file_dels--verbose: 
+  file_dels--verbose: 
+  file_dels--debug: second
+  file_dels--debug: 
+  file_dels--debug: 
+  file_dels--debug: 
+  file_dels--debug: 
+  file_dels--debug: 
+  file_dels--debug: 
+  file_dels--debug: 
+  file_dels--debug: 
+  file_mods: 
+  file_mods: 
+  file_mods: 
+  file_mods: 
+  file_mods: 
+  file_mods: c
+  file_mods: 
+  file_mods: 
+  file_mods: 
+  file_mods--verbose: 
+  file_mods--verbose: 
+  file_mods--verbose: 
+  file_mods--verbose: 
+  file_mods--verbose: 
+  file_mods--verbose: c
+  file_mods--verbose: 
+  file_mods--verbose: 
+  file_mods--verbose: 
+  file_mods--debug: 
+  file_mods--debug: 
+  file_mods--debug: 
+  file_mods--debug: 
+  file_mods--debug: 
+  file_mods--debug: c
+  file_mods--debug: 
+  file_mods--debug: 
+  file_mods--debug: 
+  file_copies: fourth (second)
+  file_copies: 
+  file_copies: 
+  file_copies: 
+  file_copies: 
+  file_copies: 
+  file_copies: 
+  file_copies: 
+  file_copies: 
+  file_copies--verbose: fourth (second)
+  file_copies--verbose: 
+  file_copies--verbose: 
+  file_copies--verbose: 
+  file_copies--verbose: 
+  file_copies--verbose: 
+  file_copies--verbose: 
+  file_copies--verbose: 
+  file_copies--verbose: 
+  file_copies--debug: fourth (second)
+  file_copies--debug: 
+  file_copies--debug: 
+  file_copies--debug: 
+  file_copies--debug: 
+  file_copies--debug: 
+  file_copies--debug: 
+  file_copies--debug: 
+  file_copies--debug: 
+  file_copies_switch: 
+  file_copies_switch: 
+  file_copies_switch: 
+  file_copies_switch: 
+  file_copies_switch: 
+  file_copies_switch: 
+  file_copies_switch: 
+  file_copies_switch: 
+  file_copies_switch: 
+  file_copies_switch--verbose: 
+  file_copies_switch--verbose: 
+  file_copies_switch--verbose: 
+  file_copies_switch--verbose: 
+  file_copies_switch--verbose: 
+  file_copies_switch--verbose: 
+  file_copies_switch--verbose: 
+  file_copies_switch--verbose: 
+  file_copies_switch--verbose: 
+  file_copies_switch--debug: 
+  file_copies_switch--debug: 
+  file_copies_switch--debug: 
+  file_copies_switch--debug: 
+  file_copies_switch--debug: 
+  file_copies_switch--debug: 
+  file_copies_switch--debug: 
+  file_copies_switch--debug: 
+  file_copies_switch--debug: 
+  files: fourth second third
+  files: second
+  files: 
+  files: d
+  files: 
+  files: c
+  files: c
+  files: b
+  files: a
+  files--verbose: fourth second third
+  files--verbose: second
+  files--verbose: 
+  files--verbose: d
+  files--verbose: 
+  files--verbose: c
+  files--verbose: c
+  files--verbose: b
+  files--verbose: a
+  files--debug: fourth second third
+  files--debug: second
+  files--debug: 
+  files--debug: d
+  files--debug: 
+  files--debug: c
+  files--debug: c
+  files--debug: b
+  files--debug: a
+  manifest: 6:94961b75a2da
+  manifest: 5:f2dbc354b94e
+  manifest: 4:4dc3def4f9b4
+  manifest: 4:4dc3def4f9b4
+  manifest: 3:cb5a1327723b
+  manifest: 3:cb5a1327723b
+  manifest: 2:6e0e82995c35
+  manifest: 1:4e8d705b1e53
+  manifest: 0:a0c8bcbbb45c
+  manifest--verbose: 6:94961b75a2da
+  manifest--verbose: 5:f2dbc354b94e
+  manifest--verbose: 4:4dc3def4f9b4
+  manifest--verbose: 4:4dc3def4f9b4
+  manifest--verbose: 3:cb5a1327723b
+  manifest--verbose: 3:cb5a1327723b
+  manifest--verbose: 2:6e0e82995c35
+  manifest--verbose: 1:4e8d705b1e53
+  manifest--verbose: 0:a0c8bcbbb45c
+  manifest--debug: 6:94961b75a2da554b4df6fb599e5bfc7d48de0c64
+  manifest--debug: 5:f2dbc354b94e5ec0b4f10680ee0cee816101d0bf
+  manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
+  manifest--debug: 4:4dc3def4f9b4c6e8de820f6ee74737f91e96a216
+  manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
+  manifest--debug: 3:cb5a1327723bada42f117e4c55a303246eaf9ccc
+  manifest--debug: 2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
+  manifest--debug: 1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
+  manifest--debug: 0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
+  node: 95c24699272ef57d062b8bccc32c878bf841784a
+  node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
+  node: d41e714fe50d9e4a5f11b4d595d543481b5f980b
+  node: 13207e5a10d9fd28ec424934298e176197f2c67f
+  node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
+  node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  node: 97054abb4ab824450e9164180baf491ae0078465
+  node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  node: 1e4e1b8f71e05681d422154f5421e385fec3454f
+  node--verbose: 95c24699272ef57d062b8bccc32c878bf841784a
+  node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
+  node--verbose: d41e714fe50d9e4a5f11b4d595d543481b5f980b
+  node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
+  node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
+  node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  node--verbose: 97054abb4ab824450e9164180baf491ae0078465
+  node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
+  node--debug: 95c24699272ef57d062b8bccc32c878bf841784a
+  node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
+  node--debug: d41e714fe50d9e4a5f11b4d595d543481b5f980b
+  node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
+  node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
+  node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  node--debug: 97054abb4ab824450e9164180baf491ae0078465
+  node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
+  parents: 
+  parents: -1:000000000000 
+  parents: 5:13207e5a10d9 4:bbe44766e73d 
+  parents: 3:10e46f2dcbf4 
+  parents: 
+  parents: 
+  parents: 
+  parents: 
+  parents: 
+  parents--verbose: 
+  parents--verbose: -1:000000000000 
+  parents--verbose: 5:13207e5a10d9 4:bbe44766e73d 
+  parents--verbose: 3:10e46f2dcbf4 
+  parents--verbose: 
+  parents--verbose: 
+  parents--verbose: 
+  parents--verbose: 
+  parents--verbose: 
+  parents--debug: 7:29114dbae42b9f078cf2714dbe3a86bba8ec7453 -1:0000000000000000000000000000000000000000 
+  parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000 
+  parents--debug: 5:13207e5a10d9fd28ec424934298e176197f2c67f 4:bbe44766e73d5f11ed2177f1838de10c53ef3e74 
+  parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000 
+  parents--debug: 3:10e46f2dcbf4823578cf180f33ecf0b957964c47 -1:0000000000000000000000000000000000000000 
+  parents--debug: 2:97054abb4ab824450e9164180baf491ae0078465 -1:0000000000000000000000000000000000000000 
+  parents--debug: 1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965 -1:0000000000000000000000000000000000000000 
+  parents--debug: 0:1e4e1b8f71e05681d422154f5421e385fec3454f -1:0000000000000000000000000000000000000000 
+  parents--debug: -1:0000000000000000000000000000000000000000 -1:0000000000000000000000000000000000000000 
+  rev: 8
+  rev: 7
+  rev: 6
+  rev: 5
+  rev: 4
+  rev: 3
+  rev: 2
+  rev: 1
+  rev: 0
+  rev--verbose: 8
+  rev--verbose: 7
+  rev--verbose: 6
+  rev--verbose: 5
+  rev--verbose: 4
+  rev--verbose: 3
+  rev--verbose: 2
+  rev--verbose: 1
+  rev--verbose: 0
+  rev--debug: 8
+  rev--debug: 7
+  rev--debug: 6
+  rev--debug: 5
+  rev--debug: 4
+  rev--debug: 3
+  rev--debug: 2
+  rev--debug: 1
+  rev--debug: 0
+  tags: tip
+  tags: 
+  tags: 
+  tags: 
+  tags: 
+  tags: 
+  tags: 
+  tags: 
+  tags: 
+  tags--verbose: tip
+  tags--verbose: 
+  tags--verbose: 
+  tags--verbose: 
+  tags--verbose: 
+  tags--verbose: 
+  tags--verbose: 
+  tags--verbose: 
+  tags--verbose: 
+  tags--debug: tip
+  tags--debug: 
+  tags--debug: 
+  tags--debug: 
+  tags--debug: 
+  tags--debug: 
+  tags--debug: 
+  tags--debug: 
+  tags--debug: 
+  diffstat: 3: +2/-1
+  diffstat: 1: +1/-0
+  diffstat: 0: +0/-0
+  diffstat: 1: +1/-0
+  diffstat: 0: +0/-0
+  diffstat: 1: +1/-0
+  diffstat: 1: +4/-0
+  diffstat: 1: +2/-0
+  diffstat: 1: +1/-0
+  diffstat--verbose: 3: +2/-1
+  diffstat--verbose: 1: +1/-0
+  diffstat--verbose: 0: +0/-0
+  diffstat--verbose: 1: +1/-0
+  diffstat--verbose: 0: +0/-0
+  diffstat--verbose: 1: +1/-0
+  diffstat--verbose: 1: +4/-0
+  diffstat--verbose: 1: +2/-0
+  diffstat--verbose: 1: +1/-0
+  diffstat--debug: 3: +2/-1
+  diffstat--debug: 1: +1/-0
+  diffstat--debug: 0: +0/-0
+  diffstat--debug: 1: +1/-0
+  diffstat--debug: 0: +0/-0
+  diffstat--debug: 1: +1/-0
+  diffstat--debug: 1: +4/-0
+  diffstat--debug: 1: +2/-0
+  diffstat--debug: 1: +1/-0
+  extras: branch=default
+  extras: branch=default
+  extras: branch=default
+  extras: branch=default
+  extras: branch=foo
+  extras: branch=default
+  extras: branch=default
+  extras: branch=default
+  extras: branch=default
+  extras--verbose: branch=default
+  extras--verbose: branch=default
+  extras--verbose: branch=default
+  extras--verbose: branch=default
+  extras--verbose: branch=foo
+  extras--verbose: branch=default
+  extras--verbose: branch=default
+  extras--verbose: branch=default
+  extras--verbose: branch=default
+  extras--debug: branch=default
+  extras--debug: branch=default
+  extras--debug: branch=default
+  extras--debug: branch=default
+  extras--debug: branch=foo
+  extras--debug: branch=default
+  extras--debug: branch=default
+  extras--debug: branch=default
+  extras--debug: branch=default
+  p1rev: 7
+  p1rev: -1
+  p1rev: 5
+  p1rev: 3
+  p1rev: 3
+  p1rev: 2
+  p1rev: 1
+  p1rev: 0
+  p1rev: -1
+  p1rev--verbose: 7
+  p1rev--verbose: -1
+  p1rev--verbose: 5
+  p1rev--verbose: 3
+  p1rev--verbose: 3
+  p1rev--verbose: 2
+  p1rev--verbose: 1
+  p1rev--verbose: 0
+  p1rev--verbose: -1
+  p1rev--debug: 7
+  p1rev--debug: -1
+  p1rev--debug: 5
+  p1rev--debug: 3
+  p1rev--debug: 3
+  p1rev--debug: 2
+  p1rev--debug: 1
+  p1rev--debug: 0
+  p1rev--debug: -1
+  p2rev: -1
+  p2rev: -1
+  p2rev: 4
+  p2rev: -1
+  p2rev: -1
+  p2rev: -1
+  p2rev: -1
+  p2rev: -1
+  p2rev: -1
+  p2rev--verbose: -1
+  p2rev--verbose: -1
+  p2rev--verbose: 4
+  p2rev--verbose: -1
+  p2rev--verbose: -1
+  p2rev--verbose: -1
+  p2rev--verbose: -1
+  p2rev--verbose: -1
+  p2rev--verbose: -1
+  p2rev--debug: -1
+  p2rev--debug: -1
+  p2rev--debug: 4
+  p2rev--debug: -1
+  p2rev--debug: -1
+  p2rev--debug: -1
+  p2rev--debug: -1
+  p2rev--debug: -1
+  p2rev--debug: -1
+  p1node: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
+  p1node: 0000000000000000000000000000000000000000
+  p1node: 13207e5a10d9fd28ec424934298e176197f2c67f
+  p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  p1node: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  p1node: 97054abb4ab824450e9164180baf491ae0078465
+  p1node: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  p1node: 1e4e1b8f71e05681d422154f5421e385fec3454f
+  p1node: 0000000000000000000000000000000000000000
+  p1node--verbose: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
+  p1node--verbose: 0000000000000000000000000000000000000000
+  p1node--verbose: 13207e5a10d9fd28ec424934298e176197f2c67f
+  p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  p1node--verbose: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  p1node--verbose: 97054abb4ab824450e9164180baf491ae0078465
+  p1node--verbose: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  p1node--verbose: 1e4e1b8f71e05681d422154f5421e385fec3454f
+  p1node--verbose: 0000000000000000000000000000000000000000
+  p1node--debug: 29114dbae42b9f078cf2714dbe3a86bba8ec7453
+  p1node--debug: 0000000000000000000000000000000000000000
+  p1node--debug: 13207e5a10d9fd28ec424934298e176197f2c67f
+  p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  p1node--debug: 10e46f2dcbf4823578cf180f33ecf0b957964c47
+  p1node--debug: 97054abb4ab824450e9164180baf491ae0078465
+  p1node--debug: b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  p1node--debug: 1e4e1b8f71e05681d422154f5421e385fec3454f
+  p1node--debug: 0000000000000000000000000000000000000000
+  p2node: 0000000000000000000000000000000000000000
+  p2node: 0000000000000000000000000000000000000000
+  p2node: bbe44766e73d5f11ed2177f1838de10c53ef3e74
+  p2node: 0000000000000000000000000000000000000000
+  p2node: 0000000000000000000000000000000000000000
+  p2node: 0000000000000000000000000000000000000000
+  p2node: 0000000000000000000000000000000000000000
+  p2node: 0000000000000000000000000000000000000000
+  p2node: 0000000000000000000000000000000000000000
+  p2node--verbose: 0000000000000000000000000000000000000000
+  p2node--verbose: 0000000000000000000000000000000000000000
+  p2node--verbose: bbe44766e73d5f11ed2177f1838de10c53ef3e74
+  p2node--verbose: 0000000000000000000000000000000000000000
+  p2node--verbose: 0000000000000000000000000000000000000000
+  p2node--verbose: 0000000000000000000000000000000000000000
+  p2node--verbose: 0000000000000000000000000000000000000000
+  p2node--verbose: 0000000000000000000000000000000000000000
+  p2node--verbose: 0000000000000000000000000000000000000000
+  p2node--debug: 0000000000000000000000000000000000000000
+  p2node--debug: 0000000000000000000000000000000000000000
+  p2node--debug: bbe44766e73d5f11ed2177f1838de10c53ef3e74
+  p2node--debug: 0000000000000000000000000000000000000000
+  p2node--debug: 0000000000000000000000000000000000000000
+  p2node--debug: 0000000000000000000000000000000000000000
+  p2node--debug: 0000000000000000000000000000000000000000
+  p2node--debug: 0000000000000000000000000000000000000000
+  p2node--debug: 0000000000000000000000000000000000000000
+
+Add a dummy commit to make up for the instability of the above:
+
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m future
+
+Add a commit that does all possible modifications at once
+
+  $ echo modify >> third
+  $ touch b
+  $ hg add b
+  $ hg mv fourth fifth
+  $ hg rm a
+  $ hg ci -m "Modify, add, remove, rename"
+
+Test index keyword:
+
+  $ hg log -l 2 -T '{index + 10}{files % " {index}:{file}"}\n'
+  10 0:a 1:b 2:fifth 3:fourth 4:third
+  11 0:a
+
+  $ hg branches -T '{index} {branch}\n'
+  0 default
+  1 foo
+
+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 ..
+
+latesttag:
+
+  $ hg init latesttag
+  $ cd latesttag
+
+  $ echo a > file
+  $ hg ci -Am a -d '0 0'
+  adding file
+
+  $ echo b >> file
+  $ hg ci -m b -d '1 0'
+
+  $ echo c >> head1
+  $ hg ci -Am h1c -d '2 0'
+  adding head1
+
+  $ hg update -q 1
+  $ echo d >> head2
+  $ hg ci -Am h2d -d '3 0'
+  adding head2
+  created new head
+
+  $ echo e >> head2
+  $ hg ci -m h2e -d '4 0'
+
+  $ hg merge -q
+  $ hg ci -m merge -d '5 -3600'
+
+No tag set:
+
+  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
+  @    5: null+5
+  |\
+  | o  4: null+4
+  | |
+  | o  3: null+3
+  | |
+  o |  2: null+3
+  |/
+  o  1: null+2
+  |
+  o  0: null+1
+  
+
+One common tag: longest path wins for {latesttagdistance}:
+
+  $ hg tag -r 1 -m t1 -d '6 0' t1
+  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
+  @  6: t1+4
+  |
+  o    5: t1+3
+  |\
+  | o  4: t1+2
+  | |
+  | o  3: t1+1
+  | |
+  o |  2: t1+1
+  |/
+  o  1: t1+0
+  |
+  o  0: null+1
+  
+
+One ancestor tag: closest wins:
+
+  $ hg tag -r 2 -m t2 -d '7 0' t2
+  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
+  @  7: t2+3
+  |
+  o  6: t2+2
+  |
+  o    5: t2+1
+  |\
+  | o  4: t1+2
+  | |
+  | o  3: t1+1
+  | |
+  o |  2: t2+0
+  |/
+  o  1: t1+0
+  |
+  o  0: null+1
+  
+
+Two branch tags: more recent wins if same number of changes:
+
+  $ hg tag -r 3 -m t3 -d '8 0' t3
+  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
+  @  8: t3+5
+  |
+  o  7: t3+4
+  |
+  o  6: t3+3
+  |
+  o    5: t3+2
+  |\
+  | o  4: t3+1
+  | |
+  | o  3: t3+0
+  | |
+  o |  2: t2+0
+  |/
+  o  1: t1+0
+  |
+  o  0: null+1
+  
+
+Two branch tags: fewest changes wins:
+
+  $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
+  $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
+  @  9: t4+5,6
+  |
+  o  8: t4+4,5
+  |
+  o  7: t4+3,4
+  |
+  o  6: t4+2,3
+  |
+  o    5: t4+1,2
+  |\
+  | o  4: t4+0,0
+  | |
+  | o  3: t3+0,0
+  | |
+  o |  2: t2+0,0
+  |/
+  o  1: t1+0,0
+  |
+  o  0: null+1,1
+  
+
+Merged tag overrides:
+
+  $ hg tag -r 5 -m t5 -d '9 0' t5
+  $ hg tag -r 3 -m at3 -d '10 0' at3
+  $ hg log -G --template '{rev}: {latesttag}+{latesttagdistance}\n'
+  @  11: t5+6
+  |
+  o  10: t5+5
+  |
+  o  9: t5+4
+  |
+  o  8: t5+3
+  |
+  o  7: t5+2
+  |
+  o  6: t5+1
+  |
+  o    5: t5+0
+  |\
+  | o  4: t4+0
+  | |
+  | o  3: at3:t3+0
+  | |
+  o |  2: t2+0
+  |/
+  o  1: t1+0
+  |
+  o  0: null+1
+  
+
+  $ hg log -G --template "{rev}: {latesttag % '{tag}+{distance},{changes} '}\n"
+  @  11: t5+6,6
+  |
+  o  10: t5+5,5
+  |
+  o  9: t5+4,4
+  |
+  o  8: t5+3,3
+  |
+  o  7: t5+2,2
+  |
+  o  6: t5+1,1
+  |
+  o    5: t5+0,0
+  |\
+  | o  4: t4+0,0
+  | |
+  | o  3: at3+0,0 t3+0,0
+  | |
+  o |  2: t2+0,0
+  |/
+  o  1: t1+0,0
+  |
+  o  0: null+1,1
+  
+
+  $ cd ..
+
+Set up repository containing template fragments in commit metadata:
+
+  $ hg init r
+  $ cd r
+  $ echo a > a
+  $ hg ci -Am '{rev}'
+  adding a
+
+  $ hg branch -q 'text.{rev}'
+  $ echo aa >> aa
+  $ hg ci -u '{node|short}' -m 'desc to be wrapped desc to be wrapped'
+
+Test termwidth:
+
+  $ COLUMNS=25 hg log -l1 --template '{fill(desc, termwidth, "{node|short}:", "termwidth.{rev}:")}'
+  bcc7ff960b8e:desc to be
+  termwidth.1:wrapped desc
+  termwidth.1:to be wrapped (no-eol)
+
+Just one more commit:
+
+  $ echo b > b
+  $ hg ci -qAm b
+
+Test 'originalnode'
+
+  $ hg log -r 1 -T '{revset("null") % "{node|short} {originalnode|short}"}\n'
+  000000000000 bcc7ff960b8e
+  $ hg log -r 0 -T '{manifest % "{node} {originalnode}"}\n'
+  a0c8bcbbb45c63b90b70ad007bf38961f64f2af0 f7769ec2ab975ad19684098ad1ffd9b81ecc71a1
+
+Test active bookmark templating
+
+  $ hg book foo
+  $ hg book bar
+  $ hg log --template "{rev} {bookmarks % '{bookmark}{ifeq(bookmark, active, \"*\")} '}\n"
+  2 bar* foo 
+  1 
+  0 
+  $ hg log --template "{rev} {activebookmark}\n"
+  2 bar
+  1 
+  0 
+  $ hg bookmarks --inactive bar
+  $ hg log --template "{rev} {activebookmark}\n"
+  2 
+  1 
+  0 
+  $ hg book -r1 baz
+  $ hg log --template "{rev} {join(bookmarks, ' ')}\n"
+  2 bar foo
+  1 baz
+  0 
+  $ hg log --template "{rev} {ifcontains('foo', bookmarks, 't', 'f')}\n"
+  2 t
+  1 f
+  0 f
+
+Test namespaces dict
+
+  $ hg --config extensions.revnamesext=$TESTDIR/revnamesext.py log -T '{rev}\n{namespaces % " {namespace} color={colorname} builtin={builtin}\n  {join(names, ",")}\n"}\n'
+  2
+   bookmarks color=bookmark builtin=True
+    bar,foo
+   tags color=tag builtin=True
+    tip
+   branches color=branch builtin=True
+    text.{rev}
+   revnames color=revname builtin=False
+    r2
+  
+  1
+   bookmarks color=bookmark builtin=True
+    baz
+   tags color=tag builtin=True
+    
+   branches color=branch builtin=True
+    text.{rev}
+   revnames color=revname builtin=False
+    r1
+  
+  0
+   bookmarks color=bookmark builtin=True
+    
+   tags color=tag builtin=True
+    
+   branches color=branch builtin=True
+    default
+   revnames color=revname builtin=False
+    r0
+  
+  $ hg log -r2 -T '{namespaces % "{namespace}: {names}\n"}'
+  bookmarks: bar foo
+  tags: tip
+  branches: text.{rev}
+  $ hg log -r2 -T '{namespaces % "{namespace}:\n{names % " {name}\n"}"}'
+  bookmarks:
+   bar
+   foo
+  tags:
+   tip
+  branches:
+   text.{rev}
+  $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
+  bar
+  foo
+  $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
+  bar
+  foo
+
+  $ cd ..
+
+Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
+printed graphwidths 3, 5, 7, etc. should all line up in their respective
+columns. We don't care about other aspects of the graph rendering here.
+
+  $ hg init graphwidth
+  $ cd graphwidth
+
+  $ wrappabletext="a a a a a a a a a a a a"
+
+  $ printf "first\n" > file
+  $ hg add file
+  $ hg commit -m "$wrappabletext"
+
+  $ printf "first\nsecond\n" > file
+  $ hg commit -m "$wrappabletext"
+
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "third\nfirst\n" > file
+  $ hg commit -m "$wrappabletext"
+  created new head
+
+  $ hg merge
+  merging file
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  | @  5
+  |/
+  o  3
+  
+  $ hg commit -m "$wrappabletext"
+
+  $ hg log --graph -T "{graphwidth}"
+  @    5
+  |\
+  | o  5
+  | |
+  o |  5
+  |/
+  o  3
+  
+
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "third\nfirst\nsecond\n" > file
+  $ hg commit -m "$wrappabletext"
+  created new head
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  | o    7
+  | |\
+  +---o  7
+  | |
+  | o  5
+  |/
+  o  3
+  
+
+  $ hg log --graph -T "{graphwidth}" -r 3
+  o    5
+  |\
+  ~ ~
+
+  $ hg log --graph -T "{graphwidth}" -r 1
+  o  3
+  |
+  ~
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -m "$wrappabletext"
+
+  $ printf "seventh\n" >> file
+  $ hg commit -m "$wrappabletext"
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  o    5
+  |\
+  | o  5
+  | |
+  o |    7
+  |\ \
+  | o |  7
+  | |/
+  o /  5
+  |/
+  o  3
+  
+
+The point of graphwidth is to allow wrapping that accounts for the space taken
+by the graph.
+
+  $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
+  @  a a a a
+  |  a a a a
+  |  a a a a
+  o    a a a
+  |\   a a a
+  | |  a a a
+  | |  a a a
+  | o  a a a
+  | |  a a a
+  | |  a a a
+  | |  a a a
+  o |    a a
+  |\ \   a a
+  | | |  a a
+  | | |  a a
+  | | |  a a
+  | | |  a a
+  | o |  a a
+  | |/   a a
+  | |    a a
+  | |    a a
+  | |    a a
+  | |    a a
+  o |  a a a
+  |/   a a a
+  |    a a a
+  |    a a a
+  o  a a a a
+     a a a a
+     a a a a
+
+Something tricky happens when there are elided nodes; the next drawn row of
+edges can be more than one column wider, but the graph width only increases by
+one column. The remaining columns are added in between the nodes.
+
+  $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
+  o    5
+  |\
+  | \
+  | :\
+  o : :  7
+  :/ /
+  : o  5
+  :/
+  o  3
+  
+
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-template-map.t	Thu Jul 19 13:55:54 2018 -0400
@@ -0,0 +1,1760 @@
+Test template map files and styles
+==================================
+
+  $ hg init a
+  $ cd a
+  $ echo a > a
+  $ hg add a
+  $ echo line 1 > b
+  $ echo line 2 >> b
+  $ hg commit -l b -d '1000000 0' -u 'User Name <user@hostname>'
+
+  $ hg add b
+  $ echo other 1 > c
+  $ echo other 2 >> c
+  $ echo >> c
+  $ echo other 3 >> c
+  $ hg commit -l c -d '1100000 0' -u 'A. N. Other <other@place>'
+
+  $ hg add c
+  $ hg commit -m 'no person' -d '1200000 0' -u 'other@place'
+  $ echo c >> c
+  $ hg commit -m 'no user, no domain' -d '1300000 0' -u 'person'
+
+  $ echo foo > .hg/branch
+  $ hg commit -m 'new branch' -d '1400000 0' -u 'person'
+
+  $ hg co -q 3
+  $ echo other 4 >> d
+  $ hg add d
+  $ hg commit -m 'new head' -d '1500000 0' -u 'person'
+
+  $ hg merge -q foo
+  $ hg commit -m 'merge' -d '1500001 0' -u 'person'
+
+Second branch starting at nullrev:
+
+  $ hg update null
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  $ echo second > second
+  $ hg add second
+  $ hg commit -m second -d '1000000 0' -u 'User Name <user@hostname>'
+  created new head
+
+  $ echo third > third
+  $ hg add third
+  $ hg mv second fourth
+  $ hg commit -m third -d "2020-01-01 10:01"
+
+Make sure user/global hgrc does not affect tests
+
+  $ echo '[ui]' > .hg/hgrc
+  $ echo 'logtemplate =' >> .hg/hgrc
+  $ echo 'style =' >> .hg/hgrc
+
+Add some simple styles to settings
+
+  $ cat <<'EOF' >> .hg/hgrc
+  > [templates]
+  > simple = "{rev}\n"
+  > simple2 = {rev}\n
+  > rev = "should not precede {rev} keyword\n"
+  > EOF
+
+  $ hg log -l1 -Tsimple
+  8
+  $ hg log -l1 -Tsimple2
+  8
+  $ hg log -l1 -Trev
+  should not precede 8 keyword
+  $ hg log -l1 -T '{simple}'
+  8
+
+Map file shouldn't see user templates:
+
+  $ cat <<EOF > tmpl
+  > changeset = 'nothing expanded:{simple}\n'
+  > EOF
+  $ hg log -l1 --style ./tmpl
+  nothing expanded:
+
+Test templates and style maps in files:
+
+  $ echo "{rev}" > tmpl
+  $ hg log -l1 -T./tmpl
+  8
+  $ hg log -l1 -Tblah/blah
+  blah/blah (no-eol)
+
+  $ printf 'changeset = "{rev}\\n"\n' > map-simple
+  $ hg log -l1 -T./map-simple
+  8
+
+ a map file may have [templates] and [templatealias] sections:
+
+  $ cat <<'EOF' > map-simple
+  > [templates]
+  > changeset = "{a}\n"
+  > [templatealias]
+  > a = rev
+  > EOF
+  $ hg log -l1 -T./map-simple
+  8
+
+ so it can be included in hgrc
+
+  $ cat <<EOF > myhgrc
+  > %include $HGRCPATH
+  > %include map-simple
+  > [templates]
+  > foo = "{changeset}"
+  > EOF
+  $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
+  8
+  $ HGRCPATH=./myhgrc hg log -l1 -T'{a}\n'
+  8
+
+Test template map inheritance
+
+  $ echo "__base__ = map-cmdline.default" > map-simple
+  $ printf 'cset = "changeset: ***{rev}***\\n"\n' >> map-simple
+  $ hg log -l1 -T./map-simple
+  changeset: ***8***
+  tag:         tip
+  user:        test
+  date:        Wed Jan 01 10:01:00 2020 +0000
+  summary:     third
+  
+
+Test docheader, docfooter and separator in template map
+
+  $ cat <<'EOF' > map-myjson
+  > docheader = '\{\n'
+  > docfooter = '\n}\n'
+  > separator = ',\n'
+  > changeset = ' {dict(rev, node|short)|json}'
+  > EOF
+  $ hg log -l2 -T./map-myjson
+  {
+   {"node": "95c24699272e", "rev": 8},
+   {"node": "29114dbae42b", "rev": 7}
+  }
+
+Test docheader, docfooter and separator in [templates] section
+
+  $ cat <<'EOF' >> .hg/hgrc
+  > [templates]
+  > myjson = ' {dict(rev, node|short)|json}'
+  > myjson:docheader = '\{\n'
+  > myjson:docfooter = '\n}\n'
+  > myjson:separator = ',\n'
+  > :docheader = 'should not be selected as a docheader for literal templates\n'
+  > EOF
+  $ hg log -l2 -Tmyjson
+  {
+   {"node": "95c24699272e", "rev": 8},
+   {"node": "29114dbae42b", "rev": 7}
+  }
+  $ hg log -l1 -T'{rev}\n'
+  8
+
+Template should precede style option
+
+  $ hg log -l1 --style default -T '{rev}\n'
+  8
+
+Add a commit with empty description, to ensure that the templates
+below will omit the description line.
+
+  $ echo c >> c
+  $ hg add c
+  $ hg commit -qm ' '
+
+Default style is like normal output. Phases style should be the same
+as default style, except for extra phase lines.
+
+  $ hg log > log.out
+  $ hg log --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg log -T phases > phases.out
+  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+
+  $ hg log -v > log.out
+  $ hg log -v --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg log -v -T phases > phases.out
+  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+  +phase:       draft
+
+  $ hg log -q > log.out
+  $ hg log -q --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg log -q -T phases > phases.out
+  $ cmp log.out phases.out || diff -u log.out phases.out
+
+  $ hg log --debug > log.out
+  $ hg log --debug --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg log --debug -T phases > phases.out
+  $ cmp log.out phases.out || diff -u log.out phases.out
+
+Default style of working-directory revision should also be the same (but
+date may change while running tests):
+
+  $ hg log -r 'wdir()' | sed 's|^date:.*|date:|' > log.out
+  $ hg log -r 'wdir()' --style default | sed 's|^date:.*|date:|' > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
+  $ hg log -r 'wdir()' -v | sed 's|^date:.*|date:|' > log.out
+  $ hg log -r 'wdir()' -v --style default | sed 's|^date:.*|date:|' > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
+  $ hg log -r 'wdir()' -q > log.out
+  $ hg log -r 'wdir()' -q --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
+  $ hg log -r 'wdir()' --debug | sed 's|^date:.*|date:|' > log.out
+  $ hg log -r 'wdir()' --debug --style default \
+  > | sed 's|^date:.*|date:|' > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
+Default style should also preserve color information (issue2866):
+
+  $ cp $HGRCPATH $HGRCPATH-bak
+  $ cat <<EOF >> $HGRCPATH
+  > [extensions]
+  > color=
+  > EOF
+
+  $ hg --color=debug log > log.out
+  $ hg --color=debug log --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg --color=debug log -T phases > phases.out
+  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+
+  $ hg --color=debug -v log > log.out
+  $ hg --color=debug -v log --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg --color=debug -v log -T phases > phases.out
+  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+  +[log.phase|phase:       draft]
+
+  $ hg --color=debug -q log > log.out
+  $ hg --color=debug -q log --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg --color=debug -q log -T phases > phases.out
+  $ cmp log.out phases.out || diff -u log.out phases.out
+
+  $ hg --color=debug --debug log > log.out
+  $ hg --color=debug --debug log --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg --color=debug --debug log -T phases > phases.out
+  $ cmp log.out phases.out || diff -u log.out phases.out
+
+  $ mv $HGRCPATH-bak $HGRCPATH
+
+Remove commit with empty commit message, so as to not pollute further
+tests.
+
+  $ hg --config extensions.strip= strip -q .
+
+Revision with no copies (used to print a traceback):
+
+  $ hg tip -v --template '\n'
+  
+
+Compact style works:
+
+  $ hg log -Tcompact
+  8[tip]   95c24699272e   2020-01-01 10:01 +0000   test
+    third
+  
+  7:-1   29114dbae42b   1970-01-12 13:46 +0000   user
+    second
+  
+  6:5,4   d41e714fe50d   1970-01-18 08:40 +0000   person
+    merge
+  
+  5:3   13207e5a10d9   1970-01-18 08:40 +0000   person
+    new head
+  
+  4   bbe44766e73d   1970-01-17 04:53 +0000   person
+    new branch
+  
+  3   10e46f2dcbf4   1970-01-16 01:06 +0000   person
+    no user, no domain
+  
+  2   97054abb4ab8   1970-01-14 21:20 +0000   other
+    no person
+  
+  1   b608e9d1a3f0   1970-01-13 17:33 +0000   other
+    other 1
+  
+  0   1e4e1b8f71e0   1970-01-12 13:46 +0000   user
+    line 1
+  
+
+  $ hg log -v --style compact
+  8[tip]   95c24699272e   2020-01-01 10:01 +0000   test
+    third
+  
+  7:-1   29114dbae42b   1970-01-12 13:46 +0000   User Name <user@hostname>
+    second
+  
+  6:5,4   d41e714fe50d   1970-01-18 08:40 +0000   person
+    merge
+  
+  5:3   13207e5a10d9   1970-01-18 08:40 +0000   person
+    new head
+  
+  4   bbe44766e73d   1970-01-17 04:53 +0000   person
+    new branch
+  
+  3   10e46f2dcbf4   1970-01-16 01:06 +0000   person
+    no user, no domain
+  
+  2   97054abb4ab8   1970-01-14 21:20 +0000   other@place
+    no person
+  
+  1   b608e9d1a3f0   1970-01-13 17:33 +0000   A. N. Other <other@place>
+    other 1
+  other 2
+  
+  other 3
+  
+  0   1e4e1b8f71e0   1970-01-12 13:46 +0000   User Name <user@hostname>
+    line 1
+  line 2
+  
+
+  $ hg log --debug --style compact
+  8[tip]:7,-1   95c24699272e   2020-01-01 10:01 +0000   test
+    third
+  
+  7:-1,-1   29114dbae42b   1970-01-12 13:46 +0000   User Name <user@hostname>
+    second
+  
+  6:5,4   d41e714fe50d   1970-01-18 08:40 +0000   person
+    merge
+  
+  5:3,-1   13207e5a10d9   1970-01-18 08:40 +0000   person
+    new head
+  
+  4:3,-1   bbe44766e73d   1970-01-17 04:53 +0000   person
+    new branch
+  
+  3:2,-1   10e46f2dcbf4   1970-01-16 01:06 +0000   person
+    no user, no domain
+  
+  2:1,-1   97054abb4ab8   1970-01-14 21:20 +0000   other@place
+    no person
+  
+  1:0,-1   b608e9d1a3f0   1970-01-13 17:33 +0000   A. N. Other <other@place>
+    other 1
+  other 2
+  
+  other 3
+  
+  0:-1,-1   1e4e1b8f71e0   1970-01-12 13:46 +0000   User Name <user@hostname>
+    line 1
+  line 2
+  
+
+Test xml styles:
+
+  $ hg log --style xml -r 'not all()'
+  <?xml version="1.0"?>
+  <log>
+  </log>
+
+  $ hg log --style xml
+  <?xml version="1.0"?>
+  <log>
+  <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
+  <tag>tip</tag>
+  <author email="test">test</author>
+  <date>2020-01-01T10:01:00+00:00</date>
+  <msg xml:space="preserve">third</msg>
+  </logentry>
+  <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="user@hostname">User Name</author>
+  <date>1970-01-12T13:46:40+00:00</date>
+  <msg xml:space="preserve">second</msg>
+  </logentry>
+  <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
+  <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
+  <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
+  <author email="person">person</author>
+  <date>1970-01-18T08:40:01+00:00</date>
+  <msg xml:space="preserve">merge</msg>
+  </logentry>
+  <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
+  <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
+  <author email="person">person</author>
+  <date>1970-01-18T08:40:00+00:00</date>
+  <msg xml:space="preserve">new head</msg>
+  </logentry>
+  <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
+  <branch>foo</branch>
+  <author email="person">person</author>
+  <date>1970-01-17T04:53:20+00:00</date>
+  <msg xml:space="preserve">new branch</msg>
+  </logentry>
+  <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
+  <author email="person">person</author>
+  <date>1970-01-16T01:06:40+00:00</date>
+  <msg xml:space="preserve">no user, no domain</msg>
+  </logentry>
+  <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
+  <author email="other@place">other</author>
+  <date>1970-01-14T21:20:00+00:00</date>
+  <msg xml:space="preserve">no person</msg>
+  </logentry>
+  <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
+  <author email="other@place">A. N. Other</author>
+  <date>1970-01-13T17:33:20+00:00</date>
+  <msg xml:space="preserve">other 1
+  other 2
+  
+  other 3</msg>
+  </logentry>
+  <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
+  <author email="user@hostname">User Name</author>
+  <date>1970-01-12T13:46:40+00:00</date>
+  <msg xml:space="preserve">line 1
+  line 2</msg>
+  </logentry>
+  </log>
+
+  $ hg log -v --style xml
+  <?xml version="1.0"?>
+  <log>
+  <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
+  <tag>tip</tag>
+  <author email="test">test</author>
+  <date>2020-01-01T10:01:00+00:00</date>
+  <msg xml:space="preserve">third</msg>
+  <paths>
+  <path action="A">fourth</path>
+  <path action="A">third</path>
+  <path action="R">second</path>
+  </paths>
+  <copies>
+  <copy source="second">fourth</copy>
+  </copies>
+  </logentry>
+  <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="user@hostname">User Name</author>
+  <date>1970-01-12T13:46:40+00:00</date>
+  <msg xml:space="preserve">second</msg>
+  <paths>
+  <path action="A">second</path>
+  </paths>
+  </logentry>
+  <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
+  <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
+  <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
+  <author email="person">person</author>
+  <date>1970-01-18T08:40:01+00:00</date>
+  <msg xml:space="preserve">merge</msg>
+  <paths>
+  </paths>
+  </logentry>
+  <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
+  <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
+  <author email="person">person</author>
+  <date>1970-01-18T08:40:00+00:00</date>
+  <msg xml:space="preserve">new head</msg>
+  <paths>
+  <path action="A">d</path>
+  </paths>
+  </logentry>
+  <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
+  <branch>foo</branch>
+  <author email="person">person</author>
+  <date>1970-01-17T04:53:20+00:00</date>
+  <msg xml:space="preserve">new branch</msg>
+  <paths>
+  </paths>
+  </logentry>
+  <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
+  <author email="person">person</author>
+  <date>1970-01-16T01:06:40+00:00</date>
+  <msg xml:space="preserve">no user, no domain</msg>
+  <paths>
+  <path action="M">c</path>
+  </paths>
+  </logentry>
+  <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
+  <author email="other@place">other</author>
+  <date>1970-01-14T21:20:00+00:00</date>
+  <msg xml:space="preserve">no person</msg>
+  <paths>
+  <path action="A">c</path>
+  </paths>
+  </logentry>
+  <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
+  <author email="other@place">A. N. Other</author>
+  <date>1970-01-13T17:33:20+00:00</date>
+  <msg xml:space="preserve">other 1
+  other 2
+  
+  other 3</msg>
+  <paths>
+  <path action="A">b</path>
+  </paths>
+  </logentry>
+  <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
+  <author email="user@hostname">User Name</author>
+  <date>1970-01-12T13:46:40+00:00</date>
+  <msg xml:space="preserve">line 1
+  line 2</msg>
+  <paths>
+  <path action="A">a</path>
+  </paths>
+  </logentry>
+  </log>
+
+  $ hg log --debug --style xml
+  <?xml version="1.0"?>
+  <log>
+  <logentry revision="8" node="95c24699272ef57d062b8bccc32c878bf841784a">
+  <tag>tip</tag>
+  <parent revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453" />
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="test">test</author>
+  <date>2020-01-01T10:01:00+00:00</date>
+  <msg xml:space="preserve">third</msg>
+  <paths>
+  <path action="A">fourth</path>
+  <path action="A">third</path>
+  <path action="R">second</path>
+  </paths>
+  <copies>
+  <copy source="second">fourth</copy>
+  </copies>
+  <extra key="branch">default</extra>
+  </logentry>
+  <logentry revision="7" node="29114dbae42b9f078cf2714dbe3a86bba8ec7453">
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="user@hostname">User Name</author>
+  <date>1970-01-12T13:46:40+00:00</date>
+  <msg xml:space="preserve">second</msg>
+  <paths>
+  <path action="A">second</path>
+  </paths>
+  <extra key="branch">default</extra>
+  </logentry>
+  <logentry revision="6" node="d41e714fe50d9e4a5f11b4d595d543481b5f980b">
+  <parent revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f" />
+  <parent revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74" />
+  <author email="person">person</author>
+  <date>1970-01-18T08:40:01+00:00</date>
+  <msg xml:space="preserve">merge</msg>
+  <paths>
+  </paths>
+  <extra key="branch">default</extra>
+  </logentry>
+  <logentry revision="5" node="13207e5a10d9fd28ec424934298e176197f2c67f">
+  <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="person">person</author>
+  <date>1970-01-18T08:40:00+00:00</date>
+  <msg xml:space="preserve">new head</msg>
+  <paths>
+  <path action="A">d</path>
+  </paths>
+  <extra key="branch">default</extra>
+  </logentry>
+  <logentry revision="4" node="bbe44766e73d5f11ed2177f1838de10c53ef3e74">
+  <branch>foo</branch>
+  <parent revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47" />
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="person">person</author>
+  <date>1970-01-17T04:53:20+00:00</date>
+  <msg xml:space="preserve">new branch</msg>
+  <paths>
+  </paths>
+  <extra key="branch">foo</extra>
+  </logentry>
+  <logentry revision="3" node="10e46f2dcbf4823578cf180f33ecf0b957964c47">
+  <parent revision="2" node="97054abb4ab824450e9164180baf491ae0078465" />
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="person">person</author>
+  <date>1970-01-16T01:06:40+00:00</date>
+  <msg xml:space="preserve">no user, no domain</msg>
+  <paths>
+  <path action="M">c</path>
+  </paths>
+  <extra key="branch">default</extra>
+  </logentry>
+  <logentry revision="2" node="97054abb4ab824450e9164180baf491ae0078465">
+  <parent revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965" />
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="other@place">other</author>
+  <date>1970-01-14T21:20:00+00:00</date>
+  <msg xml:space="preserve">no person</msg>
+  <paths>
+  <path action="A">c</path>
+  </paths>
+  <extra key="branch">default</extra>
+  </logentry>
+  <logentry revision="1" node="b608e9d1a3f0273ccf70fb85fd6866b3482bf965">
+  <parent revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f" />
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="other@place">A. N. Other</author>
+  <date>1970-01-13T17:33:20+00:00</date>
+  <msg xml:space="preserve">other 1
+  other 2
+  
+  other 3</msg>
+  <paths>
+  <path action="A">b</path>
+  </paths>
+  <extra key="branch">default</extra>
+  </logentry>
+  <logentry revision="0" node="1e4e1b8f71e05681d422154f5421e385fec3454f">
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <parent revision="-1" node="0000000000000000000000000000000000000000" />
+  <author email="user@hostname">User Name</author>
+  <date>1970-01-12T13:46:40+00:00</date>
+  <msg xml:space="preserve">line 1
+  line 2</msg>
+  <paths>
+  <path action="A">a</path>
+  </paths>
+  <extra key="branch">default</extra>
+  </logentry>
+  </log>
+
+
+Test JSON style:
+
+  $ hg log -k nosuch -Tjson
+  [
+  ]
+
+  $ hg log -qr . -Tjson
+  [
+   {
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "rev": 8
+   }
+  ]
+
+  $ hg log -vpr . -Tjson --stat
+  [
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n",
+    "diffstat": " fourth |  1 +\n second |  1 -\n third  |  1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
+    "files": ["fourth", "second", "third"],
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "phase": "draft",
+    "rev": 8,
+    "tags": ["tip"],
+    "user": "test"
+   }
+  ]
+
+honor --git but not format-breaking diffopts
+  $ hg --config diff.noprefix=True log --git -vpr . -Tjson
+  [
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "diff": "diff --git a/second b/fourth\nrename from second\nrename to fourth\ndiff --git a/third b/third\nnew file mode 100644\n--- /dev/null\n+++ b/third\n@@ -0,0 +1,1 @@\n+third\n",
+    "files": ["fourth", "second", "third"],
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "phase": "draft",
+    "rev": 8,
+    "tags": ["tip"],
+    "user": "test"
+   }
+  ]
+
+  $ hg log -T json
+  [
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "phase": "draft",
+    "rev": 8,
+    "tags": ["tip"],
+    "user": "test"
+   },
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1000000, 0],
+    "desc": "second",
+    "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
+    "parents": ["0000000000000000000000000000000000000000"],
+    "phase": "draft",
+    "rev": 7,
+    "tags": [],
+    "user": "User Name <user@hostname>"
+   },
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
+    "phase": "draft",
+    "rev": 6,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1500000, 0],
+    "desc": "new head",
+    "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "phase": "draft",
+    "rev": 5,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "bookmarks": [],
+    "branch": "foo",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "phase": "draft",
+    "rev": 4,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1300000, 0],
+    "desc": "no user, no domain",
+    "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
+    "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
+    "phase": "draft",
+    "rev": 3,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1200000, 0],
+    "desc": "no person",
+    "node": "97054abb4ab824450e9164180baf491ae0078465",
+    "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
+    "phase": "draft",
+    "rev": 2,
+    "tags": [],
+    "user": "other@place"
+   },
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1100000, 0],
+    "desc": "other 1\nother 2\n\nother 3",
+    "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
+    "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
+    "phase": "draft",
+    "rev": 1,
+    "tags": [],
+    "user": "A. N. Other <other@place>"
+   },
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1000000, 0],
+    "desc": "line 1\nline 2",
+    "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
+    "parents": ["0000000000000000000000000000000000000000"],
+    "phase": "draft",
+    "rev": 0,
+    "tags": [],
+    "user": "User Name <user@hostname>"
+   }
+  ]
+
+  $ hg heads -v -Tjson
+  [
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "files": ["fourth", "second", "third"],
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "phase": "draft",
+    "rev": 8,
+    "tags": ["tip"],
+    "user": "test"
+   },
+   {
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "files": [],
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
+    "phase": "draft",
+    "rev": 6,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "bookmarks": [],
+    "branch": "foo",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "files": [],
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "phase": "draft",
+    "rev": 4,
+    "tags": [],
+    "user": "person"
+   }
+  ]
+
+  $ hg log --debug -Tjson
+  [
+   {
+    "added": ["fourth", "third"],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "extra": {"branch": "default"},
+    "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
+    "modified": [],
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "phase": "draft",
+    "removed": ["second"],
+    "rev": 8,
+    "tags": ["tip"],
+    "user": "test"
+   },
+   {
+    "added": ["second"],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1000000, 0],
+    "desc": "second",
+    "extra": {"branch": "default"},
+    "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
+    "modified": [],
+    "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
+    "parents": ["0000000000000000000000000000000000000000"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 7,
+    "tags": [],
+    "user": "User Name <user@hostname>"
+   },
+   {
+    "added": [],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "extra": {"branch": "default"},
+    "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
+    "modified": [],
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 6,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "added": ["d"],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1500000, 0],
+    "desc": "new head",
+    "extra": {"branch": "default"},
+    "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
+    "modified": [],
+    "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 5,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "added": [],
+    "bookmarks": [],
+    "branch": "foo",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "extra": {"branch": "foo"},
+    "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
+    "modified": [],
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 4,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "added": [],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1300000, 0],
+    "desc": "no user, no domain",
+    "extra": {"branch": "default"},
+    "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
+    "modified": ["c"],
+    "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
+    "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 3,
+    "tags": [],
+    "user": "person"
+   },
+   {
+    "added": ["c"],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1200000, 0],
+    "desc": "no person",
+    "extra": {"branch": "default"},
+    "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
+    "modified": [],
+    "node": "97054abb4ab824450e9164180baf491ae0078465",
+    "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 2,
+    "tags": [],
+    "user": "other@place"
+   },
+   {
+    "added": ["b"],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1100000, 0],
+    "desc": "other 1\nother 2\n\nother 3",
+    "extra": {"branch": "default"},
+    "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
+    "modified": [],
+    "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
+    "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 1,
+    "tags": [],
+    "user": "A. N. Other <other@place>"
+   },
+   {
+    "added": ["a"],
+    "bookmarks": [],
+    "branch": "default",
+    "date": [1000000, 0],
+    "desc": "line 1\nline 2",
+    "extra": {"branch": "default"},
+    "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
+    "modified": [],
+    "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
+    "parents": ["0000000000000000000000000000000000000000"],
+    "phase": "draft",
+    "removed": [],
+    "rev": 0,
+    "tags": [],
+    "user": "User Name <user@hostname>"
+   }
+  ]
+
+Error if style not readable:
+
+#if unix-permissions no-root
+  $ touch q
+  $ chmod 0 q
+  $ hg log --style ./q
+  abort: Permission denied: ./q
+  [255]
+#endif
+
+Error if no style:
+
+  $ hg log --style notexist
+  abort: style 'notexist' not found
+  (available styles: bisect, changelog, compact, default, phases, show, status, xml)
+  [255]
+
+  $ hg log -T list
+  available styles: bisect, changelog, compact, default, phases, show, status, xml
+  abort: specify a template
+  [255]
+
+Error if style missing key:
+
+  $ echo 'q = q' > t
+  $ hg log --style ./t
+  abort: "changeset" not in template map
+  [255]
+
+Error if style missing value:
+
+  $ echo 'changeset =' > t
+  $ hg log --style t
+  hg: parse error at t:1: missing value
+  [255]
+
+Error if include fails:
+
+  $ echo 'changeset = q' >> t
+#if unix-permissions no-root
+  $ hg log --style ./t
+  abort: template file ./q: Permission denied
+  [255]
+  $ rm -f q
+#endif
+
+Include works:
+
+  $ echo '{rev}' > q
+  $ hg log --style ./t
+  8
+  7
+  6
+  5
+  4
+  3
+  2
+  1
+  0
+
+  $ hg phase -r 5 --public
+  $ hg phase -r 7 --secret --force
+
+Missing non-standard names give no error (backward compatibility):
+
+  $ echo "changeset = '{c}'" > t
+  $ hg log --style ./t
+
+Defining non-standard name works:
+
+  $ cat <<EOF > t
+  > changeset = '{c}'
+  > c = q
+  > EOF
+  $ hg log --style ./t
+  8
+  7
+  6
+  5
+  4
+  3
+  2
+  1
+  0
+
+ui.style works:
+
+  $ echo '[ui]' > .hg/hgrc
+  $ echo 'style = t' >> .hg/hgrc
+  $ hg log
+  8
+  7
+  6
+  5
+  4
+  3
+  2
+  1
+  0
+
+Issue338:
+
+  $ hg log --style=changelog > changelog
+
+  $ cat changelog
+  2020-01-01  test  <test>
+  
+  	* fourth, second, third:
+  	third
+  	[95c24699272e] [tip]
+  
+  1970-01-12  User Name  <user@hostname>
+  
+  	* second:
+  	second
+  	[29114dbae42b]
+  
+  1970-01-18  person  <person>
+  
+  	* merge
+  	[d41e714fe50d]
+  
+  	* d:
+  	new head
+  	[13207e5a10d9]
+  
+  1970-01-17  person  <person>
+  
+  	* new branch
+  	[bbe44766e73d] <foo>
+  
+  1970-01-16  person  <person>
+  
+  	* c:
+  	no user, no domain
+  	[10e46f2dcbf4]
+  
+  1970-01-14  other  <other@place>
+  
+  	* c:
+  	no person
+  	[97054abb4ab8]
+  
+  1970-01-13  A. N. Other  <other@place>
+  
+  	* b:
+  	other 1 other 2
+  
+  	other 3
+  	[b608e9d1a3f0]
+  
+  1970-01-12  User Name  <user@hostname>
+  
+  	* a:
+  	line 1 line 2
+  	[1e4e1b8f71e0]
+  
+
+Issue2130: xml output for 'hg heads' is malformed
+
+  $ hg heads --style changelog
+  2020-01-01  test  <test>
+  
+  	* fourth, second, third:
+  	third
+  	[95c24699272e] [tip]
+  
+  1970-01-18  person  <person>
+  
+  	* merge
+  	[d41e714fe50d]
+  
+  1970-01-17  person  <person>
+  
+  	* new branch
+  	[bbe44766e73d] <foo>
+  
+
+Add a dummy commit to make up for the instability of the above:
+
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m future
+
+Add a commit that does all possible modifications at once
+
+  $ echo modify >> third
+  $ touch b
+  $ hg add b
+  $ hg mv fourth fifth
+  $ hg rm a
+  $ hg ci -m "Modify, add, remove, rename"
+
+Check the status template
+
+  $ cat <<EOF >> $HGRCPATH
+  > [extensions]
+  > color=
+  > EOF
+
+  $ hg log -T status -r 10
+  changeset:   10:0f9759ec227a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Modify, add, remove, rename
+  files:
+  M third
+  A b
+  A fifth
+  R a
+  R fourth
+  
+  $ hg log -T status -C -r 10
+  changeset:   10:0f9759ec227a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Modify, add, remove, rename
+  files:
+  M third
+  A b
+  A fifth
+    fourth
+  R a
+  R fourth
+  
+  $ hg log -T status -C -r 10 -v
+  changeset:   10:0f9759ec227a
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  description:
+  Modify, add, remove, rename
+  
+  files:
+  M third
+  A b
+  A fifth
+    fourth
+  R a
+  R fourth
+  
+  $ hg log -T status -C -r 10 --debug
+  changeset:   10:0f9759ec227a4859c2014a345cd8a859022b7c6c
+  tag:         tip
+  phase:       secret
+  parent:      9:bf9dfba36635106d6a73ccc01e28b762da60e066
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    8:89dd546f2de0a9d6d664f58d86097eb97baba567
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  extra:       branch=default
+  description:
+  Modify, add, remove, rename
+  
+  files:
+  M third
+  A b
+  A fifth
+    fourth
+  R a
+  R fourth
+  
+  $ hg log -T status -C -r 10 --quiet
+  10:0f9759ec227a
+  $ hg --color=debug log -T status -r 10
+  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
+  [log.tag|tag:         tip]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.summary|summary:     Modify, add, remove, rename]
+  [ui.note log.files|files:]
+  [status.modified|M third]
+  [status.added|A b]
+  [status.added|A fifth]
+  [status.removed|R a]
+  [status.removed|R fourth]
+  
+  $ hg --color=debug log -T status -C -r 10
+  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
+  [log.tag|tag:         tip]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.summary|summary:     Modify, add, remove, rename]
+  [ui.note log.files|files:]
+  [status.modified|M third]
+  [status.added|A b]
+  [status.added|A fifth]
+  [status.copied|  fourth]
+  [status.removed|R a]
+  [status.removed|R fourth]
+  
+  $ hg --color=debug log -T status -C -r 10 -v
+  [log.changeset changeset.secret|changeset:   10:0f9759ec227a]
+  [log.tag|tag:         tip]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [ui.note log.description|description:]
+  [ui.note log.description|Modify, add, remove, rename]
+  
+  [ui.note log.files|files:]
+  [status.modified|M third]
+  [status.added|A b]
+  [status.added|A fifth]
+  [status.copied|  fourth]
+  [status.removed|R a]
+  [status.removed|R fourth]
+  
+  $ hg --color=debug log -T status -C -r 10 --debug
+  [log.changeset changeset.secret|changeset:   10:0f9759ec227a4859c2014a345cd8a859022b7c6c]
+  [log.tag|tag:         tip]
+  [log.phase|phase:       secret]
+  [log.parent changeset.secret|parent:      9:bf9dfba36635106d6a73ccc01e28b762da60e066]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    8:89dd546f2de0a9d6d664f58d86097eb97baba567]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|Modify, add, remove, rename]
+  
+  [ui.note log.files|files:]
+  [status.modified|M third]
+  [status.added|A b]
+  [status.added|A fifth]
+  [status.copied|  fourth]
+  [status.removed|R a]
+  [status.removed|R fourth]
+  
+  $ hg --color=debug log -T status -C -r 10 --quiet
+  [log.node|10:0f9759ec227a]
+
+Check the bisect template
+
+  $ hg bisect -g 1
+  $ hg bisect -b 3 --noupdate
+  Testing changeset 2:97054abb4ab8 (2 changesets remaining, ~1 tests)
+  $ hg log -T bisect -r 0:4
+  changeset:   0:1e4e1b8f71e0
+  bisect:      good (implicit)
+  user:        User Name <user@hostname>
+  date:        Mon Jan 12 13:46:40 1970 +0000
+  summary:     line 1
+  
+  changeset:   1:b608e9d1a3f0
+  bisect:      good
+  user:        A. N. Other <other@place>
+  date:        Tue Jan 13 17:33:20 1970 +0000
+  summary:     other 1
+  
+  changeset:   2:97054abb4ab8
+  bisect:      untested
+  user:        other@place
+  date:        Wed Jan 14 21:20:00 1970 +0000
+  summary:     no person
+  
+  changeset:   3:10e46f2dcbf4
+  bisect:      bad
+  user:        person
+  date:        Fri Jan 16 01:06:40 1970 +0000
+  summary:     no user, no domain
+  
+  changeset:   4:bbe44766e73d
+  bisect:      bad (implicit)
+  branch:      foo
+  user:        person
+  date:        Sat Jan 17 04:53:20 1970 +0000
+  summary:     new branch
+  
+  $ hg log --debug -T bisect -r 0:4
+  changeset:   0:1e4e1b8f71e05681d422154f5421e385fec3454f
+  bisect:      good (implicit)
+  phase:       public
+  parent:      -1:0000000000000000000000000000000000000000
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
+  user:        User Name <user@hostname>
+  date:        Mon Jan 12 13:46:40 1970 +0000
+  files+:      a
+  extra:       branch=default
+  description:
+  line 1
+  line 2
+  
+  
+  changeset:   1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  bisect:      good
+  phase:       public
+  parent:      0:1e4e1b8f71e05681d422154f5421e385fec3454f
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55
+  user:        A. N. Other <other@place>
+  date:        Tue Jan 13 17:33:20 1970 +0000
+  files+:      b
+  extra:       branch=default
+  description:
+  other 1
+  other 2
+  
+  other 3
+  
+  
+  changeset:   2:97054abb4ab824450e9164180baf491ae0078465
+  bisect:      untested
+  phase:       public
+  parent:      1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1
+  user:        other@place
+  date:        Wed Jan 14 21:20:00 1970 +0000
+  files+:      c
+  extra:       branch=default
+  description:
+  no person
+  
+  
+  changeset:   3:10e46f2dcbf4823578cf180f33ecf0b957964c47
+  bisect:      bad
+  phase:       public
+  parent:      2:97054abb4ab824450e9164180baf491ae0078465
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc
+  user:        person
+  date:        Fri Jan 16 01:06:40 1970 +0000
+  files:       c
+  extra:       branch=default
+  description:
+  no user, no domain
+  
+  
+  changeset:   4:bbe44766e73d5f11ed2177f1838de10c53ef3e74
+  bisect:      bad (implicit)
+  branch:      foo
+  phase:       draft
+  parent:      3:10e46f2dcbf4823578cf180f33ecf0b957964c47
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc
+  user:        person
+  date:        Sat Jan 17 04:53:20 1970 +0000
+  extra:       branch=foo
+  description:
+  new branch
+  
+  
+  $ hg log -v -T bisect -r 0:4
+  changeset:   0:1e4e1b8f71e0
+  bisect:      good (implicit)
+  user:        User Name <user@hostname>
+  date:        Mon Jan 12 13:46:40 1970 +0000
+  files:       a
+  description:
+  line 1
+  line 2
+  
+  
+  changeset:   1:b608e9d1a3f0
+  bisect:      good
+  user:        A. N. Other <other@place>
+  date:        Tue Jan 13 17:33:20 1970 +0000
+  files:       b
+  description:
+  other 1
+  other 2
+  
+  other 3
+  
+  
+  changeset:   2:97054abb4ab8
+  bisect:      untested
+  user:        other@place
+  date:        Wed Jan 14 21:20:00 1970 +0000
+  files:       c
+  description:
+  no person
+  
+  
+  changeset:   3:10e46f2dcbf4
+  bisect:      bad
+  user:        person
+  date:        Fri Jan 16 01:06:40 1970 +0000
+  files:       c
+  description:
+  no user, no domain
+  
+  
+  changeset:   4:bbe44766e73d
+  bisect:      bad (implicit)
+  branch:      foo
+  user:        person
+  date:        Sat Jan 17 04:53:20 1970 +0000
+  description:
+  new branch
+  
+  
+  $ hg --color=debug log -T bisect -r 0:4
+  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e0]
+  [log.bisect bisect.good|bisect:      good (implicit)]
+  [log.user|user:        User Name <user@hostname>]
+  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
+  [log.summary|summary:     line 1]
+  
+  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0]
+  [log.bisect bisect.good|bisect:      good]
+  [log.user|user:        A. N. Other <other@place>]
+  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
+  [log.summary|summary:     other 1]
+  
+  [log.changeset changeset.public|changeset:   2:97054abb4ab8]
+  [log.bisect bisect.untested|bisect:      untested]
+  [log.user|user:        other@place]
+  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
+  [log.summary|summary:     no person]
+  
+  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4]
+  [log.bisect bisect.bad|bisect:      bad]
+  [log.user|user:        person]
+  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
+  [log.summary|summary:     no user, no domain]
+  
+  [log.changeset changeset.draft|changeset:   4:bbe44766e73d]
+  [log.bisect bisect.bad|bisect:      bad (implicit)]
+  [log.branch|branch:      foo]
+  [log.user|user:        person]
+  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
+  [log.summary|summary:     new branch]
+  
+  $ hg --color=debug log --debug -T bisect -r 0:4
+  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e05681d422154f5421e385fec3454f]
+  [log.bisect bisect.good|bisect:      good (implicit)]
+  [log.phase|phase:       public]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0]
+  [log.user|user:        User Name <user@hostname>]
+  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
+  [ui.debug log.files|files+:      a]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|line 1
+  line 2]
+  
+  
+  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
+  [log.bisect bisect.good|bisect:      good]
+  [log.phase|phase:       public]
+  [log.parent changeset.public|parent:      0:1e4e1b8f71e05681d422154f5421e385fec3454f]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    1:4e8d705b1e53e3f9375e0e60dc7b525d8211fe55]
+  [log.user|user:        A. N. Other <other@place>]
+  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
+  [ui.debug log.files|files+:      b]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|other 1
+  other 2
+  
+  other 3]
+  
+  
+  [log.changeset changeset.public|changeset:   2:97054abb4ab824450e9164180baf491ae0078465]
+  [log.bisect bisect.untested|bisect:      untested]
+  [log.phase|phase:       public]
+  [log.parent changeset.public|parent:      1:b608e9d1a3f0273ccf70fb85fd6866b3482bf965]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    2:6e0e82995c35d0d57a52aca8da4e56139e06b4b1]
+  [log.user|user:        other@place]
+  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
+  [ui.debug log.files|files+:      c]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|no person]
+  
+  
+  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
+  [log.bisect bisect.bad|bisect:      bad]
+  [log.phase|phase:       public]
+  [log.parent changeset.public|parent:      2:97054abb4ab824450e9164180baf491ae0078465]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
+  [log.user|user:        person]
+  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
+  [ui.debug log.files|files:       c]
+  [ui.debug log.extra|extra:       branch=default]
+  [ui.note log.description|description:]
+  [ui.note log.description|no user, no domain]
+  
+  
+  [log.changeset changeset.draft|changeset:   4:bbe44766e73d5f11ed2177f1838de10c53ef3e74]
+  [log.bisect bisect.bad|bisect:      bad (implicit)]
+  [log.branch|branch:      foo]
+  [log.phase|phase:       draft]
+  [log.parent changeset.public|parent:      3:10e46f2dcbf4823578cf180f33ecf0b957964c47]
+  [log.parent changeset.public|parent:      -1:0000000000000000000000000000000000000000]
+  [ui.debug log.manifest|manifest:    3:cb5a1327723bada42f117e4c55a303246eaf9ccc]
+  [log.user|user:        person]
+  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
+  [ui.debug log.extra|extra:       branch=foo]
+  [ui.note log.description|description:]
+  [ui.note log.description|new branch]
+  
+  
+  $ hg --color=debug log -v -T bisect -r 0:4
+  [log.changeset changeset.public|changeset:   0:1e4e1b8f71e0]
+  [log.bisect bisect.good|bisect:      good (implicit)]
+  [log.user|user:        User Name <user@hostname>]
+  [log.date|date:        Mon Jan 12 13:46:40 1970 +0000]
+  [ui.note log.files|files:       a]
+  [ui.note log.description|description:]
+  [ui.note log.description|line 1
+  line 2]
+  
+  
+  [log.changeset changeset.public|changeset:   1:b608e9d1a3f0]
+  [log.bisect bisect.good|bisect:      good]
+  [log.user|user:        A. N. Other <other@place>]
+  [log.date|date:        Tue Jan 13 17:33:20 1970 +0000]
+  [ui.note log.files|files:       b]
+  [ui.note log.description|description:]
+  [ui.note log.description|other 1
+  other 2
+  
+  other 3]
+  
+  
+  [log.changeset changeset.public|changeset:   2:97054abb4ab8]
+  [log.bisect bisect.untested|bisect:      untested]
+  [log.user|user:        other@place]
+  [log.date|date:        Wed Jan 14 21:20:00 1970 +0000]
+  [ui.note log.files|files:       c]
+  [ui.note log.description|description:]
+  [ui.note log.description|no person]
+  
+  
+  [log.changeset changeset.public|changeset:   3:10e46f2dcbf4]
+  [log.bisect bisect.bad|bisect:      bad]
+  [log.user|user:        person]
+  [log.date|date:        Fri Jan 16 01:06:40 1970 +0000]
+  [ui.note log.files|files:       c]
+  [ui.note log.description|description:]
+  [ui.note log.description|no user, no domain]
+  
+  
+  [log.changeset changeset.draft|changeset:   4:bbe44766e73d]
+  [log.bisect bisect.bad|bisect:      bad (implicit)]
+  [log.branch|branch:      foo]
+  [log.user|user:        person]
+  [log.date|date:        Sat Jan 17 04:53:20 1970 +0000]
+  [ui.note log.description|description:]
+  [ui.note log.description|new branch]
+  
+  
+  $ hg bisect --reset
+
+  $ cd ..
+
+Set up latesttag repository:
+
+  $ hg init latesttag
+  $ cd latesttag
+
+  $ echo a > file
+  $ hg ci -Am a -d '0 0'
+  adding file
+
+  $ echo b >> file
+  $ hg ci -m b -d '1 0'
+
+  $ echo c >> head1
+  $ hg ci -Am h1c -d '2 0'
+  adding head1
+
+  $ hg update -q 1
+  $ echo d >> head2
+  $ hg ci -Am h2d -d '3 0'
+  adding head2
+  created new head
+
+  $ echo e >> head2
+  $ hg ci -m h2e -d '4 0'
+
+  $ hg merge -q
+  $ hg ci -m merge -d '5 -3600'
+
+  $ hg tag -r 1 -m t1 -d '6 0' t1
+  $ hg tag -r 2 -m t2 -d '7 0' t2
+  $ hg tag -r 3 -m t3 -d '8 0' t3
+  $ hg tag -r 4 -m t4 -d '4 0' t4 # older than t2, but should not matter
+  $ hg tag -r 5 -m t5 -d '9 0' t5
+  $ hg tag -r 3 -m at3 -d '10 0' at3
+
+  $ cd ..
+
+Style path expansion: issue1948 - ui.style option doesn't work on OSX
+if it is a relative path
+
+  $ mkdir -p home/styles
+
+  $ cat > home/styles/teststyle <<EOF
+  > changeset = 'test {rev}:{node|short}\n'
+  > EOF
+
+  $ HOME=`pwd`/home; export HOME
+
+  $ cat > latesttag/.hg/hgrc <<EOF
+  > [ui]
+  > style = ~/styles/teststyle
+  > EOF
+
+  $ hg -R latesttag tip
+  test 11:97e5943b523a
+
+Test recursive showlist template (issue1989):
+
+  $ cat > style1989 <<EOF
+  > changeset = '{file_mods}{manifest}{extras}'
+  > file_mod  = 'M|{author|person}\n'
+  > manifest = '{rev},{author}\n'
+  > extra = '{key}: {author}\n'
+  > EOF
+
+  $ hg -R latesttag log -r tip --style=style1989
+  M|test
+  11,test
+  branch: test
--- a/tests/test-transplant.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-transplant.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
   > transplant=
@@ -838,9 +836,9 @@
   $ cd binarysource
   $ echo a > a
   $ hg ci -Am adda a
-  >>> open('b', 'wb').write(b'\0b1')
+  >>> open('b', 'wb').write(b'\0b1') and None
   $ hg ci -Am addb b
-  >>> open('b', 'wb').write(b'\0b2')
+  >>> open('b', 'wb').write(b'\0b2') and None
   $ hg ci -m changeb b
   $ cd ..
 
--- a/tests/test-treediscovery-legacy.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-treediscovery-legacy.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 Tests discovery against servers without getbundle support:
 
   $ cat >> $HGRCPATH <<EOF
@@ -356,6 +354,7 @@
   pulling from http://localhost:$HGPORT/
   searching for changes
   no changes found
+  1 local changesets published
   $ hg push $remote
   pushing to http://localhost:$HGPORT/
   searching for changes
--- a/tests/test-treediscovery.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-treediscovery.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 Tests discovery against servers without getbundle support:
 
   $ CAP="getbundle bundle2"
--- a/tests/test-treemanifest.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-treemanifest.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
   $ cat << EOF >> $HGRCPATH
   > [ui]
   > ssh=$PYTHON "$TESTDIR/dummyssh"
--- a/tests/test-ui-color.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ui-color.py	Thu Jul 19 13:55:54 2018 -0400
@@ -5,6 +5,9 @@
     dispatch,
     ui as uimod,
 )
+from mercurial.utils import (
+    stringutil,
+)
 
 # ensure errors aren't buffered
 testui = uimod.ui()
@@ -12,7 +15,7 @@
 testui.write((b'buffered\n'))
 testui.warn((b'warning\n'))
 testui.write_err(b'error\n')
-print(repr(testui.popbuffer()))
+print(stringutil.pprint(testui.popbuffer(), bprefix=True).decode('ascii'))
 
 # test dispatch.dispatch with the same ui object
 hgrc = open(os.environ["HGRCPATH"], 'wb')
@@ -35,4 +38,3 @@
 print("colored? %s" % (ui_._colormode is not None))
 runcmd()
 print("colored? %s" % (ui_._colormode is not None))
-
--- a/tests/test-ui-color.py.out	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ui-color.py.out	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,5 @@
 warning
 error
-'buffered\n'
+b'buffered\n'
 colored? True
 colored? True
--- a/tests/test-ui-config.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ui-config.py	Thu Jul 19 13:55:54 2018 -0400
@@ -2,8 +2,12 @@
 from mercurial import (
     dispatch,
     error,
+    pycompat,
     ui as uimod,
 )
+from mercurial.utils import (
+    stringutil,
+)
 
 testui = uimod.ui.load()
 
@@ -46,59 +50,62 @@
     b'date.invalid=0'
     ])
 
-print(repr(testui.configitems(b'values')))
-print(repr(testui.configitems(b'lists')))
+def pprint(obj):
+    return stringutil.pprint(obj).decode('ascii')
+
+print(pprint(testui.configitems(b'values')))
+print(pprint(testui.configitems(b'lists')))
 print("---")
-print(repr(testui.config(b'values', b'string')))
-print(repr(testui.config(b'values', b'bool1')))
-print(repr(testui.config(b'values', b'bool2')))
-print(repr(testui.config(b'values', b'unknown')))
+print(pprint(testui.config(b'values', b'string')))
+print(pprint(testui.config(b'values', b'bool1')))
+print(pprint(testui.config(b'values', b'bool2')))
+print(pprint(testui.config(b'values', b'unknown')))
 print("---")
 try:
-    print(repr(testui.configbool(b'values', b'string')))
+    print(pprint(testui.configbool(b'values', b'string')))
 except error.ConfigError as inst:
-    print(inst)
-print(repr(testui.configbool(b'values', b'bool1')))
-print(repr(testui.configbool(b'values', b'bool2')))
-print(repr(testui.configbool(b'values', b'bool2', True)))
-print(repr(testui.configbool(b'values', b'unknown')))
-print(repr(testui.configbool(b'values', b'unknown', True)))
+    print(pprint(pycompat.bytestr(inst)))
+print(pprint(testui.configbool(b'values', b'bool1')))
+print(pprint(testui.configbool(b'values', b'bool2')))
+print(pprint(testui.configbool(b'values', b'bool2', True)))
+print(pprint(testui.configbool(b'values', b'unknown')))
+print(pprint(testui.configbool(b'values', b'unknown', True)))
 print("---")
-print(repr(testui.configint(b'values', b'int1')))
-print(repr(testui.configint(b'values', b'int2')))
+print(pprint(testui.configint(b'values', b'int1')))
+print(pprint(testui.configint(b'values', b'int2')))
 print("---")
-print(repr(testui.configlist(b'lists', b'list1')))
-print(repr(testui.configlist(b'lists', b'list2')))
-print(repr(testui.configlist(b'lists', b'list3')))
-print(repr(testui.configlist(b'lists', b'list4')))
-print(repr(testui.configlist(b'lists', b'list4', [b'foo'])))
-print(repr(testui.configlist(b'lists', b'list5')))
-print(repr(testui.configlist(b'lists', b'list6')))
-print(repr(testui.configlist(b'lists', b'list7')))
-print(repr(testui.configlist(b'lists', b'list8')))
-print(repr(testui.configlist(b'lists', b'list9')))
-print(repr(testui.configlist(b'lists', b'list10')))
-print(repr(testui.configlist(b'lists', b'list11')))
-print(repr(testui.configlist(b'lists', b'list12')))
-print(repr(testui.configlist(b'lists', b'list13')))
-print(repr(testui.configlist(b'lists', b'list14')))
-print(repr(testui.configlist(b'lists', b'list15')))
-print(repr(testui.configlist(b'lists', b'list16')))
-print(repr(testui.configlist(b'lists', b'list17')))
-print(repr(testui.configlist(b'lists', b'list18')))
-print(repr(testui.configlist(b'lists', b'unknown')))
-print(repr(testui.configlist(b'lists', b'unknown', b'')))
-print(repr(testui.configlist(b'lists', b'unknown', b'foo')))
-print(repr(testui.configlist(b'lists', b'unknown', [b'foo'])))
-print(repr(testui.configlist(b'lists', b'unknown', b'foo bar')))
-print(repr(testui.configlist(b'lists', b'unknown', b'foo, bar')))
-print(repr(testui.configlist(b'lists', b'unknown', [b'foo bar'])))
-print(repr(testui.configlist(b'lists', b'unknown', [b'foo', b'bar'])))
+print(pprint(testui.configlist(b'lists', b'list1')))
+print(pprint(testui.configlist(b'lists', b'list2')))
+print(pprint(testui.configlist(b'lists', b'list3')))
+print(pprint(testui.configlist(b'lists', b'list4')))
+print(pprint(testui.configlist(b'lists', b'list4', [b'foo'])))
+print(pprint(testui.configlist(b'lists', b'list5')))
+print(pprint(testui.configlist(b'lists', b'list6')))
+print(pprint(testui.configlist(b'lists', b'list7')))
+print(pprint(testui.configlist(b'lists', b'list8')))
+print(pprint(testui.configlist(b'lists', b'list9')))
+print(pprint(testui.configlist(b'lists', b'list10')))
+print(pprint(testui.configlist(b'lists', b'list11')))
+print(pprint(testui.configlist(b'lists', b'list12')))
+print(pprint(testui.configlist(b'lists', b'list13')))
+print(pprint(testui.configlist(b'lists', b'list14')))
+print(pprint(testui.configlist(b'lists', b'list15')))
+print(pprint(testui.configlist(b'lists', b'list16')))
+print(pprint(testui.configlist(b'lists', b'list17')))
+print(pprint(testui.configlist(b'lists', b'list18')))
+print(pprint(testui.configlist(b'lists', b'unknown')))
+print(pprint(testui.configlist(b'lists', b'unknown', b'')))
+print(pprint(testui.configlist(b'lists', b'unknown', b'foo')))
+print(pprint(testui.configlist(b'lists', b'unknown', [b'foo'])))
+print(pprint(testui.configlist(b'lists', b'unknown', b'foo bar')))
+print(pprint(testui.configlist(b'lists', b'unknown', b'foo, bar')))
+print(pprint(testui.configlist(b'lists', b'unknown', [b'foo bar'])))
+print(pprint(testui.configlist(b'lists', b'unknown', [b'foo', b'bar'])))
 print("---")
-print(repr(testui.configdate(b'date', b'epoch')))
-print(repr(testui.configdate(b'date', b'birth')))
+print(pprint(testui.configdate(b'date', b'epoch')))
+print(pprint(testui.configdate(b'date', b'birth')))
 
-print(repr(testui.config(b'values', b'String')))
+print(pprint(testui.config(b'values', b'String')))
 
 def function():
     pass
--- a/tests/test-ui-config.py.out	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ui-config.py.out	Thu Jul 19 13:55:54 2018 -0400
@@ -6,7 +6,7 @@
 'false'
 None
 ---
-values.string is not a boolean ('string value')
+'values.string is not a boolean (\'string value\')'
 True
 False
 False
--- a/tests/test-ui-verbosity.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-ui-verbosity.py	Thu Jul 19 13:55:54 2018 -0400
@@ -38,9 +38,9 @@
 
     u = uimod.ui.load()
     if cmd_quiet or cmd_debug or cmd_verbose:
-        u.setconfig('ui', 'quiet', str(bool(cmd_quiet)))
-        u.setconfig('ui', 'verbose', str(bool(cmd_verbose)))
-        u.setconfig('ui', 'debug', str(bool(cmd_debug)))
+        u.setconfig(b'ui', b'quiet', pycompat.bytestr(bool(cmd_quiet)))
+        u.setconfig(b'ui', b'verbose', pycompat.bytestr(bool(cmd_verbose)))
+        u.setconfig(b'ui', b'debug', pycompat.bytestr(bool(cmd_debug)))
 
     check = ''
     if u.debugflag:
--- a/tests/test-unbundlehash.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-unbundlehash.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 Test wire protocol unbundle with hashed heads (capability: unbundlehash)
 
   $ cat << EOF >> $HGRCPATH
--- a/tests/test-uncommit.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-uncommit.t	Thu Jul 19 13:55:54 2018 -0400
@@ -278,6 +278,13 @@
   12: draft
   $ hg commit -m 'update ab again'
 
+Phase is preserved
+
+  $ hg uncommit --keep --config phases.new-commit=secret
+  $ hg phase -r .
+  15: draft
+  $ hg commit --amend -m 'update ab again'
+
 Uncommit with public parent
 
   $ hg phase -p "::.^"
@@ -294,7 +301,7 @@
   $ hg status
   A xyz
   $ hg phase -r .
-  16: draft
+  18: draft
   $ hg phase -r ".^"
   12: public
 
--- a/tests/test-unified-test.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-unified-test.t	Thu Jul 19 13:55:54 2018 -0400
@@ -84,7 +84,7 @@
   foo/bar\r (no-eol) (esc)
 #endif
   $ printf 'foo/bar\rfoo/bar\r'
-  foo.bar\r \(no-eol\) (re) (esc)
+  foo.bar\r [(]no-eol[)] (re) (esc)
   foo.bar\r \(no-eol\) (re)
 
 testing hghave
--- a/tests/test-update-branches.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-update-branches.t	Thu Jul 19 13:55:54 2018 -0400
@@ -516,11 +516,33 @@
   $ hg bookmarks
    * bm                        5:ff252e8273df
 
+Test that we abort before we warn about the hidden commit if the working
+directory is dirty
+  $ echo conflict > a
+  $ hg up --hidden 3
+  abort: uncommitted changes
+  (commit or update --clean to discard changes)
+  [255]
+
+Test that we still warn also when there are conflicts
+  $ hg up -m --hidden 3
+  merging a
+  warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges
+  (leaving bookmark bm)
+  updated to hidden changeset 6efa171f091b
+  (hidden revision '6efa171f091b' was rewritten as: d047485b3896)
+  [1]
+  $ hg revert -r . a
+  $ hg resolve -m
+  (no more unresolved files)
+
 Test that 4 is detected as the no-argument destination from 3 and also moves
 the bookmark with it
   $ hg up --quiet 0          # we should be able to update to 3 directly
   $ hg up --quiet --hidden 3 # but not implemented yet.
-  updating to a hidden changeset 6efa171f091b
+  updated to hidden changeset 6efa171f091b
   (hidden revision '6efa171f091b' was rewritten as: d047485b3896)
   $ hg book -f bm
   $ hg up
@@ -532,7 +554,7 @@
 Test that 5 is detected as a valid destination from 1
   $ hg up --quiet 0          # we should be able to update to 3 directly
   $ hg up --quiet --hidden 3 # but not implemented yet.
-  updating to a hidden changeset 6efa171f091b
+  updated to hidden changeset 6efa171f091b
   (hidden revision '6efa171f091b' was rewritten as: d047485b3896)
   $ hg up 5
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-upgrade-repo.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-upgrade-repo.t	Thu Jul 19 13:55:54 2018 -0400
@@ -56,6 +56,7 @@
   fncache:        yes
   dotencode:      yes
   generaldelta:   yes
+  sparserevlog:    no
   plain-cl-delta: yes
   compression:    zlib
   $ hg debugformat --verbose
@@ -63,6 +64,7 @@
   fncache:        yes    yes     yes
   dotencode:      yes    yes     yes
   generaldelta:   yes    yes     yes
+  sparserevlog:    no     no      no
   plain-cl-delta: yes    yes     yes
   compression:    zlib   zlib    zlib
   $ hg debugformat --verbose --config format.usegfncache=no
@@ -70,6 +72,7 @@
   fncache:        yes    yes     yes
   dotencode:      yes    yes     yes
   generaldelta:   yes    yes     yes
+  sparserevlog:    no     no      no
   plain-cl-delta: yes    yes     yes
   compression:    zlib   zlib    zlib
   $ hg debugformat --verbose --config format.usegfncache=no --color=debug
@@ -77,6 +80,7 @@
   [formatvariant.name.uptodate|fncache:       ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
   [formatvariant.name.uptodate|dotencode:     ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
   [formatvariant.name.uptodate|generaldelta:  ][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
+  [formatvariant.name.uptodate|sparserevlog:  ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
   [formatvariant.name.uptodate|plain-cl-delta:][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
   [formatvariant.name.uptodate|compression:   ][formatvariant.repo.uptodate| zlib][formatvariant.config.default|   zlib][formatvariant.default|    zlib]
   $ hg debugformat -Tjson
@@ -100,6 +104,12 @@
     "repo": true
    },
    {
+    "config": false,
+    "default": false,
+    "name": "sparserevlog",
+    "repo": false
+   },
+   {
     "config": true,
     "default": true,
     "name": "plain-cl-delta",
@@ -170,6 +180,7 @@
   fncache:         no
   dotencode:       no
   generaldelta:    no
+  sparserevlog:    no
   plain-cl-delta: yes
   compression:    zlib
   $ hg debugformat --verbose
@@ -177,6 +188,7 @@
   fncache:         no    yes     yes
   dotencode:       no    yes     yes
   generaldelta:    no    yes     yes
+  sparserevlog:    no     no      no
   plain-cl-delta: yes    yes     yes
   compression:    zlib   zlib    zlib
   $ hg debugformat --verbose --config format.usegeneraldelta=no
@@ -184,6 +196,7 @@
   fncache:         no    yes     yes
   dotencode:       no    yes     yes
   generaldelta:    no     no     yes
+  sparserevlog:    no     no      no
   plain-cl-delta: yes    yes     yes
   compression:    zlib   zlib    zlib
   $ hg debugformat --verbose --config format.usegeneraldelta=no --color=debug
@@ -191,6 +204,7 @@
   [formatvariant.name.mismatchconfig|fncache:       ][formatvariant.repo.mismatchconfig|  no][formatvariant.config.default|    yes][formatvariant.default|     yes]
   [formatvariant.name.mismatchconfig|dotencode:     ][formatvariant.repo.mismatchconfig|  no][formatvariant.config.default|    yes][formatvariant.default|     yes]
   [formatvariant.name.mismatchdefault|generaldelta:  ][formatvariant.repo.mismatchdefault|  no][formatvariant.config.special|     no][formatvariant.default|     yes]
+  [formatvariant.name.uptodate|sparserevlog:  ][formatvariant.repo.uptodate|  no][formatvariant.config.default|     no][formatvariant.default|      no]
   [formatvariant.name.uptodate|plain-cl-delta:][formatvariant.repo.uptodate| yes][formatvariant.config.default|    yes][formatvariant.default|     yes]
   [formatvariant.name.uptodate|compression:   ][formatvariant.repo.uptodate| zlib][formatvariant.config.default|   zlib][formatvariant.default|    zlib]
   $ hg debugupgraderepo
@@ -697,3 +711,42 @@
   > [format]
   > maxchainlen = 9001
   > EOF
+
+Check upgrading a sparse-revlog repository
+---------------------------------------
+
+  $ hg init sparserevlogrepo
+  $ cd sparserevlogrepo
+  $ touch foo
+  $ hg add foo
+  $ hg -q commit -m "foo"
+  $ cat .hg/requires
+  dotencode
+  fncache
+  generaldelta
+  revlogv1
+  store
+
+Check that we can add the sparse-revlog format requirement
+  $ hg --config format.sparse-revlog=yes debugupgraderepo --run >/dev/null
+  copy of old repository backed up at $TESTTMP/sparserevlogrepo/.hg/upgradebackup.* (glob)
+  the old repository will not be deleted; remove it to free up disk space once the upgraded repository is verified
+  $ cat .hg/requires
+  dotencode
+  fncache
+  generaldelta
+  revlogv1
+  sparserevlog
+  store
+
+Check that we can remove the sparse-revlog format requirement
+  $ hg --config format.sparse-revlog=no debugupgraderepo --run >/dev/null
+  copy of old repository backed up at $TESTTMP/sparserevlogrepo/.hg/upgradebackup.* (glob)
+  the old repository will not be deleted; remove it to free up disk space once the upgraded repository is verified
+  $ cat .hg/requires
+  dotencode
+  fncache
+  generaldelta
+  revlogv1
+  store
+  $ cd ..
--- a/tests/test-url.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-url.py	Thu Jul 19 13:55:54 2018 -0400
@@ -20,17 +20,17 @@
 check(_verifycert(cert('example.com'), 'example.com'),
       None)
 check(_verifycert(cert('example.com'), 'www.example.com'),
-      'certificate is for example.com')
+      b'certificate is for example.com')
 check(_verifycert(cert('www.example.com'), 'example.com'),
-      'certificate is for www.example.com')
+      b'certificate is for www.example.com')
 
 # Test wildcard certificates
 check(_verifycert(cert('*.example.com'), 'www.example.com'),
       None)
 check(_verifycert(cert('*.example.com'), 'example.com'),
-      'certificate is for *.example.com')
+      b'certificate is for *.example.com')
 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
-      'certificate is for *.example.com')
+      b'certificate is for *.example.com')
 
 # Test subjectAltName
 san_cert = {'subject': ((('commonName', 'example.com'),),),
@@ -42,7 +42,7 @@
       None)
 # no fallback to subject commonName when subjectAltName has DNS
 check(_verifycert(san_cert, 'example.com'),
-      'certificate is for *.example.net, example.net')
+      b'certificate is for *.example.net, example.net')
 # fallback to subject commonName when no DNS in subjectAltName
 san_cert = {'subject': ((('commonName', 'example.com'),),),
             'subjectAltName': (('IP Address', '8.8.8.8'),)}
@@ -50,84 +50,84 @@
 
 # Avoid some pitfalls
 check(_verifycert(cert('*.foo'), 'foo'),
-      'certificate is for *.foo')
+      b'certificate is for *.foo')
 check(_verifycert(cert('*o'), 'foo'), None)
 
 check(_verifycert({'subject': ()},
                   'example.com'),
-      'no commonName or subjectAltName found in certificate')
+      b'no commonName or subjectAltName found in certificate')
 check(_verifycert(None, 'example.com'),
-      'no certificate received')
+      b'no certificate received')
 
 # Unicode (IDN) certname isn't supported
 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
-      'IDN in certificate not supported')
+      b'IDN in certificate not supported')
 
 # The following tests are from CPython's test_ssl.py.
 check(_verifycert(cert('example.com'), 'example.com'), None)
 check(_verifycert(cert('example.com'), 'ExAmple.cOm'), None)
 check(_verifycert(cert('example.com'), 'www.example.com'),
-      'certificate is for example.com')
+      b'certificate is for example.com')
 check(_verifycert(cert('example.com'), '.example.com'),
-      'certificate is for example.com')
+      b'certificate is for example.com')
 check(_verifycert(cert('example.com'), 'example.org'),
-      'certificate is for example.com')
+      b'certificate is for example.com')
 check(_verifycert(cert('example.com'), 'exampleXcom'),
-      'certificate is for example.com')
+      b'certificate is for example.com')
 check(_verifycert(cert('*.a.com'), 'foo.a.com'), None)
 check(_verifycert(cert('*.a.com'), 'bar.foo.a.com'),
-      'certificate is for *.a.com')
+      b'certificate is for *.a.com')
 check(_verifycert(cert('*.a.com'), 'a.com'),
-      'certificate is for *.a.com')
+      b'certificate is for *.a.com')
 check(_verifycert(cert('*.a.com'), 'Xa.com'),
-      'certificate is for *.a.com')
+      b'certificate is for *.a.com')
 check(_verifycert(cert('*.a.com'), '.a.com'),
-      'certificate is for *.a.com')
+      b'certificate is for *.a.com')
 
 # only match one left-most wildcard
 check(_verifycert(cert('f*.com'), 'foo.com'), None)
 check(_verifycert(cert('f*.com'), 'f.com'), None)
 check(_verifycert(cert('f*.com'), 'bar.com'),
-      'certificate is for f*.com')
+      b'certificate is for f*.com')
 check(_verifycert(cert('f*.com'), 'foo.a.com'),
-      'certificate is for f*.com')
+      b'certificate is for f*.com')
 check(_verifycert(cert('f*.com'), 'bar.foo.com'),
-      'certificate is for f*.com')
+      b'certificate is for f*.com')
 
 # NULL bytes are bad, CVE-2013-4073
 check(_verifycert(cert('null.python.org\x00example.org'),
                   'null.python.org\x00example.org'), None)
 check(_verifycert(cert('null.python.org\x00example.org'),
                   'example.org'),
-      'certificate is for null.python.org\x00example.org')
+      b'certificate is for null.python.org\x00example.org')
 check(_verifycert(cert('null.python.org\x00example.org'),
                   'null.python.org'),
-      'certificate is for null.python.org\x00example.org')
+      b'certificate is for null.python.org\x00example.org')
 
 # error cases with wildcards
 check(_verifycert(cert('*.*.a.com'), 'bar.foo.a.com'),
-      'certificate is for *.*.a.com')
+      b'certificate is for *.*.a.com')
 check(_verifycert(cert('*.*.a.com'), 'a.com'),
-      'certificate is for *.*.a.com')
+      b'certificate is for *.*.a.com')
 check(_verifycert(cert('*.*.a.com'), 'Xa.com'),
-      'certificate is for *.*.a.com')
+      b'certificate is for *.*.a.com')
 check(_verifycert(cert('*.*.a.com'), '.a.com'),
-      'certificate is for *.*.a.com')
+      b'certificate is for *.*.a.com')
 
 check(_verifycert(cert('a.*.com'), 'a.foo.com'),
-      'certificate is for a.*.com')
+      b'certificate is for a.*.com')
 check(_verifycert(cert('a.*.com'), 'a..com'),
-      'certificate is for a.*.com')
+      b'certificate is for a.*.com')
 check(_verifycert(cert('a.*.com'), 'a.com'),
-      'certificate is for a.*.com')
+      b'certificate is for a.*.com')
 
 # wildcard doesn't match IDNA prefix 'xn--'
 idna = u'püthon.python.org'.encode('idna').decode('ascii')
 check(_verifycert(cert(idna), idna), None)
 check(_verifycert(cert('x*.python.org'), idna),
-      'certificate is for x*.python.org')
+      b'certificate is for x*.python.org')
 check(_verifycert(cert('xn--p*.python.org'), idna),
-      'certificate is for xn--p*.python.org')
+      b'certificate is for xn--p*.python.org')
 
 # wildcard in first fragment and  IDNA A-labels in sequent fragments
 # are supported.
@@ -140,10 +140,10 @@
       None)
 check(_verifycert(cert(idna),
                   u'ftp.pythön.org'.encode('idna').decode('ascii')),
-      'certificate is for www*.xn--pythn-mua.org')
+      b'certificate is for www*.xn--pythn-mua.org')
 check(_verifycert(cert(idna),
                   u'pythön.org'.encode('idna').decode('ascii')),
-      'certificate is for www*.xn--pythn-mua.org')
+      b'certificate is for www*.xn--pythn-mua.org')
 
 c = {
     'notAfter': 'Jun 26 21:41:46 2011 GMT',
@@ -158,10 +158,10 @@
 check(_verifycert(c, 'linuxfr.com'), None)
 # Not a "DNS" entry
 check(_verifycert(c, '<unsupported>'),
-      'certificate is for linuxfr.org, linuxfr.com')
+      b'certificate is for linuxfr.org, linuxfr.com')
 # When there is a subjectAltName, commonName isn't used
 check(_verifycert(c, 'linuxfrz.org'),
-      'certificate is for linuxfr.org, linuxfr.com')
+      b'certificate is for linuxfr.org, linuxfr.com')
 
 # A pristine real-world example
 c = {
@@ -175,10 +175,10 @@
     ),
 }
 check(_verifycert(c, 'mail.google.com'), None)
-check(_verifycert(c, 'gmail.com'), 'certificate is for mail.google.com')
+check(_verifycert(c, 'gmail.com'), b'certificate is for mail.google.com')
 
 # Only commonName is considered
-check(_verifycert(c, 'California'), 'certificate is for mail.google.com')
+check(_verifycert(c, 'California'), b'certificate is for mail.google.com')
 
 # Neither commonName nor subjectAltName
 c = {
@@ -191,7 +191,7 @@
     ),
 }
 check(_verifycert(c, 'mail.google.com'),
-      'no commonName or subjectAltName found in certificate')
+      b'no commonName or subjectAltName found in certificate')
 
 # No DNS entry in subjectAltName but a commonName
 c = {
@@ -218,25 +218,27 @@
     'subjectAltName': (('othername', 'blabla'),),
 }
 check(_verifycert(c, 'google.com'),
-      'no commonName or subjectAltName found in certificate')
+      b'no commonName or subjectAltName found in certificate')
 
 # Empty cert / no cert
-check(_verifycert(None, 'example.com'), 'no certificate received')
-check(_verifycert({}, 'example.com'), 'no certificate received')
+check(_verifycert(None, 'example.com'), b'no certificate received')
+check(_verifycert({}, 'example.com'), b'no certificate received')
 
 # avoid denials of service by refusing more than one
 # wildcard per fragment.
 check(_verifycert({'subject': (((u'commonName', u'a*b.com'),),)},
                   'axxb.com'), None)
 check(_verifycert({'subject': (((u'commonName', u'a*b.co*'),),)},
-                  'axxb.com'), 'certificate is for a*b.co*')
+                  'axxb.com'), b'certificate is for a*b.co*')
 check(_verifycert({'subject': (((u'commonName', u'a*b*.com'),),)},
                   'axxbxxc.com'),
-      'too many wildcards in certificate DNS name: a*b*.com')
+      b'too many wildcards in certificate DNS name: a*b*.com')
 
 def test_url():
     """
+    >>> from mercurial import error, pycompat
     >>> from mercurial.util import url
+    >>> from mercurial.utils.stringutil import forcebytestr
 
     This tests for edge cases in url.URL's parsing algorithm. Most of
     these aren't useful for documentation purposes, so they aren't
@@ -244,119 +246,120 @@
 
     Query strings and fragments:
 
-    >>> url('http://host/a?b#c')
+    >>> url(b'http://host/a?b#c')
     <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
-    >>> url('http://host/a?')
+    >>> url(b'http://host/a?')
     <url scheme: 'http', host: 'host', path: 'a'>
-    >>> url('http://host/a#b#c')
+    >>> url(b'http://host/a#b#c')
     <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
-    >>> url('http://host/a#b?c')
+    >>> url(b'http://host/a#b?c')
     <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
-    >>> url('http://host/?a#b')
+    >>> url(b'http://host/?a#b')
     <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
-    >>> url('http://host/?a#b', parsequery=False)
+    >>> url(b'http://host/?a#b', parsequery=False)
     <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
-    >>> url('http://host/?a#b', parsefragment=False)
+    >>> url(b'http://host/?a#b', parsefragment=False)
     <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
-    >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
+    >>> url(b'http://host/?a#b', parsequery=False, parsefragment=False)
     <url scheme: 'http', host: 'host', path: '?a#b'>
 
     IPv6 addresses:
 
-    >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
+    >>> url(b'ldap://[2001:db8::7]/c=GB?objectClass?one')
     <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
          query: 'objectClass?one'>
-    >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
+    >>> url(b'ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
     <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
          port: '80', path: 'c=GB', query: 'objectClass?one'>
 
     Missing scheme, host, etc.:
 
-    >>> url('://192.0.2.16:80/')
+    >>> url(b'://192.0.2.16:80/')
     <url path: '://192.0.2.16:80/'>
-    >>> url('https://mercurial-scm.org')
+    >>> url(b'https://mercurial-scm.org')
     <url scheme: 'https', host: 'mercurial-scm.org'>
-    >>> url('/foo')
+    >>> url(b'/foo')
     <url path: '/foo'>
-    >>> url('bundle:/foo')
+    >>> url(b'bundle:/foo')
     <url scheme: 'bundle', path: '/foo'>
-    >>> url('a?b#c')
+    >>> url(b'a?b#c')
     <url path: 'a?b', fragment: 'c'>
-    >>> url('http://x.com?arg=/foo')
+    >>> url(b'http://x.com?arg=/foo')
     <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
-    >>> url('http://joe:xxx@/foo')
+    >>> url(b'http://joe:xxx@/foo')
     <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
 
     Just a scheme and a path:
 
-    >>> url('mailto:John.Doe@example.com')
+    >>> url(b'mailto:John.Doe@example.com')
     <url scheme: 'mailto', path: 'John.Doe@example.com'>
-    >>> url('a:b:c:d')
+    >>> url(b'a:b:c:d')
     <url path: 'a:b:c:d'>
-    >>> url('aa:bb:cc:dd')
+    >>> url(b'aa:bb:cc:dd')
     <url scheme: 'aa', path: 'bb:cc:dd'>
 
     SSH examples:
 
-    >>> url('ssh://joe@host//home/joe')
+    >>> url(b'ssh://joe@host//home/joe')
     <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
-    >>> url('ssh://joe:xxx@host/src')
+    >>> url(b'ssh://joe:xxx@host/src')
     <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
-    >>> url('ssh://joe:xxx@host')
+    >>> url(b'ssh://joe:xxx@host')
     <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
-    >>> url('ssh://joe@host')
+    >>> url(b'ssh://joe@host')
     <url scheme: 'ssh', user: 'joe', host: 'host'>
-    >>> url('ssh://host')
+    >>> url(b'ssh://host')
     <url scheme: 'ssh', host: 'host'>
-    >>> url('ssh://')
+    >>> url(b'ssh://')
     <url scheme: 'ssh'>
-    >>> url('ssh:')
+    >>> url(b'ssh:')
     <url scheme: 'ssh'>
 
     Non-numeric port:
 
-    >>> url('http://example.com:dd')
+    >>> url(b'http://example.com:dd')
     <url scheme: 'http', host: 'example.com', port: 'dd'>
-    >>> url('ssh://joe:xxx@host:ssh/foo')
+    >>> url(b'ssh://joe:xxx@host:ssh/foo')
     <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
          path: 'foo'>
 
     Bad authentication credentials:
 
-    >>> url('http://joe@joeville:123@4:@host/a?b#c')
+    >>> url(b'http://joe@joeville:123@4:@host/a?b#c')
     <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
          host: 'host', path: 'a', query: 'b', fragment: 'c'>
-    >>> url('http://!*#?/@!*#?/:@host/a?b#c')
+    >>> url(b'http://!*#?/@!*#?/:@host/a?b#c')
     <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
-    >>> url('http://!*#?@!*#?:@host/a?b#c')
+    >>> url(b'http://!*#?@!*#?:@host/a?b#c')
     <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
-    >>> url('http://!*@:!*@@host/a?b#c')
+    >>> url(b'http://!*@:!*@@host/a?b#c')
     <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
          path: 'a', query: 'b', fragment: 'c'>
 
     File paths:
 
-    >>> url('a/b/c/d.g.f')
+    >>> url(b'a/b/c/d.g.f')
     <url path: 'a/b/c/d.g.f'>
-    >>> url('/x///z/y/')
+    >>> url(b'/x///z/y/')
     <url path: '/x///z/y/'>
-    >>> url('/foo:bar')
+    >>> url(b'/foo:bar')
     <url path: '/foo:bar'>
-    >>> url('\\\\foo:bar')
+    >>> url(b'\\\\foo:bar')
     <url path: '\\\\foo:bar'>
-    >>> url('./foo:bar')
+    >>> url(b'./foo:bar')
     <url path: './foo:bar'>
 
     Non-localhost file URL:
 
-    >>> u = url('file://mercurial-scm.org/foo')
-    Traceback (most recent call last):
-      File "<stdin>", line 1, in ?
-    Abort: file:// URLs can only refer to localhost
+    >>> try:
+    ...   u = url(b'file://mercurial-scm.org/foo')
+    ... except error.Abort as e:
+    ...   forcebytestr(e)
+    'file:// URLs can only refer to localhost'
 
     Empty URL:
 
-    >>> u = url('')
+    >>> u = url(b'')
     >>> u
     <url path: ''>
     >>> str(u)
@@ -364,54 +367,54 @@
 
     Empty path with query string:
 
-    >>> str(url('http://foo/?bar'))
+    >>> str(url(b'http://foo/?bar'))
     'http://foo/?bar'
 
     Invalid path:
 
-    >>> u = url('http://foo/bar')
-    >>> u.path = 'bar'
+    >>> u = url(b'http://foo/bar')
+    >>> u.path = b'bar'
     >>> str(u)
     'http://foo/bar'
 
-    >>> u = url('file:/foo/bar/baz')
+    >>> u = url(b'file:/foo/bar/baz')
     >>> u
     <url scheme: 'file', path: '/foo/bar/baz'>
     >>> str(u)
     'file:///foo/bar/baz'
-    >>> u.localpath()
+    >>> pycompat.bytestr(u.localpath())
     '/foo/bar/baz'
 
-    >>> u = url('file:///foo/bar/baz')
+    >>> u = url(b'file:///foo/bar/baz')
     >>> u
     <url scheme: 'file', path: '/foo/bar/baz'>
     >>> str(u)
     'file:///foo/bar/baz'
-    >>> u.localpath()
+    >>> pycompat.bytestr(u.localpath())
     '/foo/bar/baz'
 
-    >>> u = url('file:///f:oo/bar/baz')
+    >>> u = url(b'file:///f:oo/bar/baz')
     >>> u
     <url scheme: 'file', path: 'f:oo/bar/baz'>
     >>> str(u)
     'file:///f:oo/bar/baz'
-    >>> u.localpath()
+    >>> pycompat.bytestr(u.localpath())
     'f:oo/bar/baz'
 
-    >>> u = url('file://localhost/f:oo/bar/baz')
+    >>> u = url(b'file://localhost/f:oo/bar/baz')
     >>> u
     <url scheme: 'file', host: 'localhost', path: 'f:oo/bar/baz'>
     >>> str(u)
     'file://localhost/f:oo/bar/baz'
-    >>> u.localpath()
+    >>> pycompat.bytestr(u.localpath())
     'f:oo/bar/baz'
 
-    >>> u = url('file:foo/bar/baz')
+    >>> u = url(b'file:foo/bar/baz')
     >>> u
     <url scheme: 'file', path: 'foo/bar/baz'>
     >>> str(u)
     'file:foo/bar/baz'
-    >>> u.localpath()
+    >>> pycompat.bytestr(u.localpath())
     'foo/bar/baz'
     """
 
--- a/tests/test-walk.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-walk.t	Thu Jul 19 13:55:54 2018 -0400
@@ -28,8 +28,9 @@
   adding mammals/skunk
   $ hg commit -m "commit #0"
 
-  $ hg debugwalk
-  matcher: <alwaysmatcher>
+  $ hg debugwalk -v
+  * matcher:
+  <alwaysmatcher>
   f  beans/black                     beans/black
   f  beans/borlotti                  beans/borlotti
   f  beans/kidney                    beans/kidney
@@ -43,8 +44,9 @@
   f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
   f  mammals/skunk                   mammals/skunk
-  $ hg debugwalk -I.
-  matcher: <includematcher includes='(?:)'>
+  $ hg debugwalk -v -I.
+  * matcher:
+  <includematcher includes='(?:)'>
   f  beans/black                     beans/black
   f  beans/borlotti                  beans/borlotti
   f  beans/kidney                    beans/kidney
@@ -60,8 +62,9 @@
   f  mammals/skunk                   mammals/skunk
 
   $ cd mammals
-  $ hg debugwalk
-  matcher: <alwaysmatcher>
+  $ hg debugwalk -v
+  * matcher:
+  <alwaysmatcher>
   f  beans/black                     ../beans/black
   f  beans/borlotti                  ../beans/borlotti
   f  beans/kidney                    ../beans/kidney
@@ -75,8 +78,11 @@
   f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
   f  mammals/skunk                   skunk
-  $ hg debugwalk -X ../beans
-  matcher: <differencematcher m1=<alwaysmatcher>, m2=<includematcher includes='(?:beans(?:/|$))'>>
+  $ hg debugwalk -v -X ../beans
+  * matcher:
+  <differencematcher
+    m1=<alwaysmatcher>,
+    m2=<includematcher includes='(?:beans(?:/|$))'>>
   f  fennel                          ../fennel
   f  fenugreek                       ../fenugreek
   f  fiddlehead                      ../fiddlehead
@@ -84,40 +90,50 @@
   f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
   f  mammals/skunk                   skunk
-  $ hg debugwalk -I '*k'
-  matcher: <includematcher includes='(?:mammals\\/[^/]*k(?:/|$))'>
+  $ hg debugwalk -v -I '*k'
+  * matcher:
+  <includematcher includes='(?:mammals/[^/]*k(?:/|$))'>
   f  mammals/skunk  skunk
-  $ hg debugwalk -I 'glob:*k'
-  matcher: <includematcher includes='(?:mammals\\/[^/]*k(?:/|$))'>
+  $ hg debugwalk -v -I 'glob:*k'
+  * matcher:
+  <includematcher includes='(?:mammals/[^/]*k(?:/|$))'>
   f  mammals/skunk  skunk
-  $ hg debugwalk -I 'relglob:*k'
-  matcher: <includematcher includes='(?:(?:|.*/)[^/]*k(?:/|$))'>
+  $ hg debugwalk -v -I 'relglob:*k'
+  * matcher:
+  <includematcher includes='(?:(?:|.*/)[^/]*k(?:/|$))'>
   f  beans/black    ../beans/black
   f  fenugreek      ../fenugreek
   f  mammals/skunk  skunk
-  $ hg debugwalk -I 'relglob:*k' .
-  matcher: <intersectionmatcher m1=<patternmatcher patterns='(?:mammals(?:/|$))'>, m2=<includematcher includes='(?:(?:|.*/)[^/]*k(?:/|$))'>>
+  $ hg debugwalk -v -I 'relglob:*k' .
+  * matcher:
+  <intersectionmatcher
+    m1=<patternmatcher patterns='(?:mammals(?:/|$))'>,
+    m2=<includematcher includes='(?:(?:|.*/)[^/]*k(?:/|$))'>>
   f  mammals/skunk  skunk
-  $ hg debugwalk -I 're:.*k$'
-  matcher: <includematcher includes='(?:.*k$)'>
+  $ hg debugwalk -v -I 're:.*k$'
+  * matcher:
+  <includematcher includes='(?:.*k$)'>
   f  beans/black    ../beans/black
   f  fenugreek      ../fenugreek
   f  mammals/skunk  skunk
-  $ hg debugwalk -I 'relre:.*k$'
-  matcher: <includematcher includes='(?:.*.*k$)'>
+  $ hg debugwalk -v -I 'relre:.*k$'
+  * matcher:
+  <includematcher includes='(?:.*.*k$)'>
   f  beans/black    ../beans/black
   f  fenugreek      ../fenugreek
   f  mammals/skunk  skunk
-  $ hg debugwalk -I 'path:beans'
-  matcher: <includematcher includes='(?:beans(?:/|$))'>
+  $ hg debugwalk -v -I 'path:beans'
+  * matcher:
+  <includematcher includes='(?:beans(?:/|$))'>
   f  beans/black     ../beans/black
   f  beans/borlotti  ../beans/borlotti
   f  beans/kidney    ../beans/kidney
   f  beans/navy      ../beans/navy
   f  beans/pinto     ../beans/pinto
   f  beans/turtle    ../beans/turtle
-  $ hg debugwalk -I 'relpath:detour/../../beans'
-  matcher: <includematcher includes='(?:beans(?:/|$))'>
+  $ hg debugwalk -v -I 'relpath:detour/../../beans'
+  * matcher:
+  <includematcher includes='(?:beans(?:/|$))'>
   f  beans/black     ../beans/black
   f  beans/borlotti  ../beans/borlotti
   f  beans/kidney    ../beans/kidney
@@ -125,28 +141,35 @@
   f  beans/pinto     ../beans/pinto
   f  beans/turtle    ../beans/turtle
 
-  $ hg debugwalk 'rootfilesin:'
-  matcher: <patternmatcher patterns='(?:[^/]+$)'>
+  $ hg debugwalk -v 'rootfilesin:'
+  * matcher:
+  <patternmatcher patterns='(?:[^/]+$)'>
   f  fennel      ../fennel
   f  fenugreek   ../fenugreek
   f  fiddlehead  ../fiddlehead
-  $ hg debugwalk -I 'rootfilesin:'
-  matcher: <includematcher includes='(?:[^/]+$)'>
+  $ hg debugwalk -v -I 'rootfilesin:'
+  * matcher:
+  <includematcher includes='(?:[^/]+$)'>
   f  fennel      ../fennel
   f  fenugreek   ../fenugreek
   f  fiddlehead  ../fiddlehead
-  $ hg debugwalk 'rootfilesin:.'
-  matcher: <patternmatcher patterns='(?:[^/]+$)'>
+  $ hg debugwalk -v 'rootfilesin:.'
+  * matcher:
+  <patternmatcher patterns='(?:[^/]+$)'>
   f  fennel      ../fennel
   f  fenugreek   ../fenugreek
   f  fiddlehead  ../fiddlehead
-  $ hg debugwalk -I 'rootfilesin:.'
-  matcher: <includematcher includes='(?:[^/]+$)'>
+  $ hg debugwalk -v -I 'rootfilesin:.'
+  * matcher:
+  <includematcher includes='(?:[^/]+$)'>
   f  fennel      ../fennel
   f  fenugreek   ../fenugreek
   f  fiddlehead  ../fiddlehead
-  $ hg debugwalk -X 'rootfilesin:'
-  matcher: <differencematcher m1=<alwaysmatcher>, m2=<includematcher includes='(?:[^/]+$)'>>
+  $ hg debugwalk -v -X 'rootfilesin:'
+  * matcher:
+  <differencematcher
+    m1=<alwaysmatcher>,
+    m2=<includematcher includes='(?:[^/]+$)'>>
   f  beans/black                     ../beans/black
   f  beans/borlotti                  ../beans/borlotti
   f  beans/kidney                    ../beans/kidney
@@ -157,44 +180,57 @@
   f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
   f  mammals/skunk                   skunk
-  $ hg debugwalk 'rootfilesin:fennel'
-  matcher: <patternmatcher patterns='(?:fennel/[^/]+$)'>
-  $ hg debugwalk -I 'rootfilesin:fennel'
-  matcher: <includematcher includes='(?:fennel/[^/]+$)'>
-  $ hg debugwalk 'rootfilesin:skunk'
-  matcher: <patternmatcher patterns='(?:skunk/[^/]+$)'>
-  $ hg debugwalk -I 'rootfilesin:skunk'
-  matcher: <includematcher includes='(?:skunk/[^/]+$)'>
-  $ hg debugwalk 'rootfilesin:beans'
-  matcher: <patternmatcher patterns='(?:beans/[^/]+$)'>
+  $ hg debugwalk -v 'rootfilesin:fennel'
+  * matcher:
+  <patternmatcher patterns='(?:fennel/[^/]+$)'>
+  $ hg debugwalk -v -I 'rootfilesin:fennel'
+  * matcher:
+  <includematcher includes='(?:fennel/[^/]+$)'>
+  $ hg debugwalk -v 'rootfilesin:skunk'
+  * matcher:
+  <patternmatcher patterns='(?:skunk/[^/]+$)'>
+  $ hg debugwalk -v -I 'rootfilesin:skunk'
+  * matcher:
+  <includematcher includes='(?:skunk/[^/]+$)'>
+  $ hg debugwalk -v 'rootfilesin:beans'
+  * matcher:
+  <patternmatcher patterns='(?:beans/[^/]+$)'>
   f  beans/black     ../beans/black
   f  beans/borlotti  ../beans/borlotti
   f  beans/kidney    ../beans/kidney
   f  beans/navy      ../beans/navy
   f  beans/pinto     ../beans/pinto
   f  beans/turtle    ../beans/turtle
-  $ hg debugwalk -I 'rootfilesin:beans'
-  matcher: <includematcher includes='(?:beans/[^/]+$)'>
+  $ hg debugwalk -v -I 'rootfilesin:beans'
+  * matcher:
+  <includematcher includes='(?:beans/[^/]+$)'>
   f  beans/black     ../beans/black
   f  beans/borlotti  ../beans/borlotti
   f  beans/kidney    ../beans/kidney
   f  beans/navy      ../beans/navy
   f  beans/pinto     ../beans/pinto
   f  beans/turtle    ../beans/turtle
-  $ hg debugwalk 'rootfilesin:mammals'
-  matcher: <patternmatcher patterns='(?:mammals/[^/]+$)'>
+  $ hg debugwalk -v 'rootfilesin:mammals'
+  * matcher:
+  <patternmatcher patterns='(?:mammals/[^/]+$)'>
   f  mammals/skunk  skunk
-  $ hg debugwalk -I 'rootfilesin:mammals'
-  matcher: <includematcher includes='(?:mammals/[^/]+$)'>
+  $ hg debugwalk -v -I 'rootfilesin:mammals'
+  * matcher:
+  <includematcher includes='(?:mammals/[^/]+$)'>
   f  mammals/skunk  skunk
-  $ hg debugwalk 'rootfilesin:mammals/'
-  matcher: <patternmatcher patterns='(?:mammals/[^/]+$)'>
+  $ hg debugwalk -v 'rootfilesin:mammals/'
+  * matcher:
+  <patternmatcher patterns='(?:mammals/[^/]+$)'>
   f  mammals/skunk  skunk
-  $ hg debugwalk -I 'rootfilesin:mammals/'
-  matcher: <includematcher includes='(?:mammals/[^/]+$)'>
+  $ hg debugwalk -v -I 'rootfilesin:mammals/'
+  * matcher:
+  <includematcher includes='(?:mammals/[^/]+$)'>
   f  mammals/skunk  skunk
-  $ hg debugwalk -X 'rootfilesin:mammals'
-  matcher: <differencematcher m1=<alwaysmatcher>, m2=<includematcher includes='(?:mammals/[^/]+$)'>>
+  $ hg debugwalk -v -X 'rootfilesin:mammals'
+  * matcher:
+  <differencematcher
+    m1=<alwaysmatcher>,
+    m2=<includematcher includes='(?:mammals/[^/]+$)'>>
   f  beans/black                     ../beans/black
   f  beans/borlotti                  ../beans/borlotti
   f  beans/kidney                    ../beans/kidney
@@ -208,169 +244,226 @@
   f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
 
-  $ hg debugwalk .
-  matcher: <patternmatcher patterns='(?:mammals(?:/|$))'>
+  $ hg debugwalk -v .
+  * matcher:
+  <patternmatcher patterns='(?:mammals(?:/|$))'>
   f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
   f  mammals/skunk                   skunk
-  $ hg debugwalk -I.
-  matcher: <includematcher includes='(?:mammals(?:/|$))'>
+  $ hg debugwalk -v -I.
+  * matcher:
+  <includematcher includes='(?:mammals(?:/|$))'>
   f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
   f  mammals/skunk                   skunk
-  $ hg debugwalk Procyonidae
-  matcher: <patternmatcher patterns='(?:mammals\\/Procyonidae(?:/|$))'>
+  $ hg debugwalk -v Procyonidae
+  * matcher:
+  <patternmatcher patterns='(?:mammals/Procyonidae(?:/|$))'>
   f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
 
   $ cd Procyonidae
-  $ hg debugwalk .
-  matcher: <patternmatcher patterns='(?:mammals\\/Procyonidae(?:/|$))'>
+  $ hg debugwalk -v .
+  * matcher:
+  <patternmatcher patterns='(?:mammals/Procyonidae(?:/|$))'>
   f  mammals/Procyonidae/cacomistle  cacomistle
   f  mammals/Procyonidae/coatimundi  coatimundi
   f  mammals/Procyonidae/raccoon     raccoon
-  $ hg debugwalk ..
-  matcher: <patternmatcher patterns='(?:mammals(?:/|$))'>
+  $ hg debugwalk -v ..
+  * matcher:
+  <patternmatcher patterns='(?:mammals(?:/|$))'>
   f  mammals/Procyonidae/cacomistle  cacomistle
   f  mammals/Procyonidae/coatimundi  coatimundi
   f  mammals/Procyonidae/raccoon     raccoon
   f  mammals/skunk                   ../skunk
   $ cd ..
 
-  $ hg debugwalk ../beans
-  matcher: <patternmatcher patterns='(?:beans(?:/|$))'>
+  $ hg debugwalk -v ../beans
+  * matcher:
+  <patternmatcher patterns='(?:beans(?:/|$))'>
   f  beans/black     ../beans/black
   f  beans/borlotti  ../beans/borlotti
   f  beans/kidney    ../beans/kidney
   f  beans/navy      ../beans/navy
   f  beans/pinto     ../beans/pinto
   f  beans/turtle    ../beans/turtle
-  $ hg debugwalk .
-  matcher: <patternmatcher patterns='(?:mammals(?:/|$))'>
+  $ hg debugwalk -v .
+  * matcher:
+  <patternmatcher patterns='(?:mammals(?:/|$))'>
   f  mammals/Procyonidae/cacomistle  Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     Procyonidae/raccoon
   f  mammals/skunk                   skunk
-  $ hg debugwalk .hg
+  $ hg debugwalk -v .hg
   abort: path 'mammals/.hg' is inside nested repo 'mammals'
   [255]
-  $ hg debugwalk ../.hg
+  $ hg debugwalk -v ../.hg
   abort: path contains illegal component: .hg
   [255]
   $ cd ..
 
-  $ hg debugwalk -Ibeans
-  matcher: <includematcher includes='(?:beans(?:/|$))'>
+  $ hg debugwalk -v -Ibeans
+  * matcher:
+  <includematcher includes='(?:beans(?:/|$))'>
   f  beans/black     beans/black
   f  beans/borlotti  beans/borlotti
   f  beans/kidney    beans/kidney
   f  beans/navy      beans/navy
   f  beans/pinto     beans/pinto
   f  beans/turtle    beans/turtle
-  $ hg debugwalk -I '{*,{b,m}*/*}k'
-  matcher: <includematcher includes='(?:(?:[^/]*|(?:b|m)[^/]*\\/[^/]*)k(?:/|$))'>
+  $ hg debugwalk -v -I '{*,{b,m}*/*}k'
+  * matcher:
+  <includematcher includes='(?:(?:[^/]*|(?:b|m)[^/]*/[^/]*)k(?:/|$))'>
   f  beans/black    beans/black
   f  fenugreek      fenugreek
   f  mammals/skunk  mammals/skunk
-  $ hg debugwalk -Ibeans mammals
-  matcher: <intersectionmatcher m1=<patternmatcher patterns='(?:mammals(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
-  $ hg debugwalk -Inon-existent
-  matcher: <includematcher includes='(?:non\\-existent(?:/|$))'>
-  $ hg debugwalk -Inon-existent -Ibeans/black
-  matcher: <includematcher includes='(?:non\\-existent(?:/|$)|beans\\/black(?:/|$))'>
+  $ hg debugwalk -v -Ibeans mammals
+  * matcher:
+  <intersectionmatcher
+    m1=<patternmatcher patterns='(?:mammals(?:/|$))'>,
+    m2=<includematcher includes='(?:beans(?:/|$))'>>
+  $ hg debugwalk -v -Inon-existent
+  * matcher:
+  <includematcher includes='(?:non\\-existent(?:/|$))'>
+  $ hg debugwalk -v -Inon-existent -Ibeans/black
+  * matcher:
+  <includematcher includes='(?:non\\-existent(?:/|$)|beans/black(?:/|$))'>
   f  beans/black  beans/black
-  $ hg debugwalk -Ibeans beans/black
-  matcher: <intersectionmatcher m1=<patternmatcher patterns='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
+  $ hg debugwalk -v -Ibeans beans/black
+  * matcher:
+  <intersectionmatcher
+    m1=<patternmatcher patterns='(?:beans/black(?:/|$))'>,
+    m2=<includematcher includes='(?:beans(?:/|$))'>>
   f  beans/black  beans/black  exact
-  $ hg debugwalk -Ibeans/black beans
-  matcher: <intersectionmatcher m1=<patternmatcher patterns='(?:beans(?:/|$))'>, m2=<includematcher includes='(?:beans\\/black(?:/|$))'>>
+  $ hg debugwalk -v -Ibeans/black beans
+  * matcher:
+  <intersectionmatcher
+    m1=<patternmatcher patterns='(?:beans(?:/|$))'>,
+    m2=<includematcher includes='(?:beans/black(?:/|$))'>>
   f  beans/black  beans/black
-  $ hg debugwalk -Xbeans/black beans
-  matcher: <differencematcher m1=<patternmatcher patterns='(?:beans(?:/|$))'>, m2=<includematcher includes='(?:beans\\/black(?:/|$))'>>
+  $ hg debugwalk -v -Xbeans/black beans
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:beans(?:/|$))'>,
+    m2=<includematcher includes='(?:beans/black(?:/|$))'>>
   f  beans/borlotti  beans/borlotti
   f  beans/kidney    beans/kidney
   f  beans/navy      beans/navy
   f  beans/pinto     beans/pinto
   f  beans/turtle    beans/turtle
-  $ hg debugwalk -Xbeans/black -Ibeans
-  matcher: <differencematcher m1=<includematcher includes='(?:beans(?:/|$))'>, m2=<includematcher includes='(?:beans\\/black(?:/|$))'>>
+  $ hg debugwalk -v -Xbeans/black -Ibeans
+  * matcher:
+  <differencematcher
+    m1=<includematcher includes='(?:beans(?:/|$))'>,
+    m2=<includematcher includes='(?:beans/black(?:/|$))'>>
   f  beans/borlotti  beans/borlotti
   f  beans/kidney    beans/kidney
   f  beans/navy      beans/navy
   f  beans/pinto     beans/pinto
   f  beans/turtle    beans/turtle
-  $ hg debugwalk -Xbeans/black beans/black
-  matcher: <differencematcher m1=<patternmatcher patterns='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans\\/black(?:/|$))'>>
-  $ hg debugwalk -Xbeans/black -Ibeans/black
-  matcher: <differencematcher m1=<includematcher includes='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans\\/black(?:/|$))'>>
-  $ hg debugwalk -Xbeans beans/black
-  matcher: <differencematcher m1=<patternmatcher patterns='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
-  $ hg debugwalk -Xbeans -Ibeans/black
-  matcher: <differencematcher m1=<includematcher includes='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
-  $ hg debugwalk 'glob:mammals/../beans/b*'
-  matcher: <patternmatcher patterns='(?:beans\\/b[^/]*$)'>
+  $ hg debugwalk -v -Xbeans/black beans/black
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:beans/black(?:/|$))'>,
+    m2=<includematcher includes='(?:beans/black(?:/|$))'>>
+  $ hg debugwalk -v -Xbeans/black -Ibeans/black
+  * matcher:
+  <differencematcher
+    m1=<includematcher includes='(?:beans/black(?:/|$))'>,
+    m2=<includematcher includes='(?:beans/black(?:/|$))'>>
+  $ hg debugwalk -v -Xbeans beans/black
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:beans/black(?:/|$))'>,
+    m2=<includematcher includes='(?:beans(?:/|$))'>>
+  $ hg debugwalk -v -Xbeans -Ibeans/black
+  * matcher:
+  <differencematcher
+    m1=<includematcher includes='(?:beans/black(?:/|$))'>,
+    m2=<includematcher includes='(?:beans(?:/|$))'>>
+  $ hg debugwalk -v 'glob:mammals/../beans/b*'
+  * matcher:
+  <patternmatcher patterns='(?:beans/b[^/]*$)'>
   f  beans/black     beans/black
   f  beans/borlotti  beans/borlotti
-  $ hg debugwalk '-X*/Procyonidae' mammals
-  matcher: <differencematcher m1=<patternmatcher patterns='(?:mammals(?:/|$))'>, m2=<includematcher includes='(?:[^/]*\\/Procyonidae(?:/|$))'>>
+  $ hg debugwalk -v '-X*/Procyonidae' mammals
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:mammals(?:/|$))'>,
+    m2=<includematcher includes='(?:[^/]*/Procyonidae(?:/|$))'>>
   f  mammals/skunk  mammals/skunk
-  $ hg debugwalk path:mammals
-  matcher: <patternmatcher patterns='(?:mammals(?:/|$))'>
+  $ hg debugwalk -v path:mammals
+  * matcher:
+  <patternmatcher patterns='(?:mammals(?:/|$))'>
   f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
   f  mammals/skunk                   mammals/skunk
-  $ hg debugwalk ..
+  $ hg debugwalk -v ..
   abort: .. not under root '$TESTTMP/t'
   [255]
-  $ hg debugwalk beans/../..
+  $ hg debugwalk -v beans/../..
   abort: beans/../.. not under root '$TESTTMP/t'
   [255]
-  $ hg debugwalk .hg
+  $ hg debugwalk -v .hg
   abort: path contains illegal component: .hg
   [255]
-  $ hg debugwalk beans/../.hg
+  $ hg debugwalk -v beans/../.hg
   abort: path contains illegal component: .hg
   [255]
-  $ hg debugwalk beans/../.hg/data
+  $ hg debugwalk -v beans/../.hg/data
   abort: path contains illegal component: .hg/data
   [255]
-  $ hg debugwalk beans/.hg
+  $ hg debugwalk -v beans/.hg
   abort: path 'beans/.hg' is inside nested repo 'beans'
   [255]
 
 Test explicit paths and excludes:
 
-  $ hg debugwalk fennel -X fennel
-  matcher: <differencematcher m1=<patternmatcher patterns='(?:fennel(?:/|$))'>, m2=<includematcher includes='(?:fennel(?:/|$))'>>
-  $ hg debugwalk fennel -X 'f*'
-  matcher: <differencematcher m1=<patternmatcher patterns='(?:fennel(?:/|$))'>, m2=<includematcher includes='(?:f[^/]*(?:/|$))'>>
-  $ hg debugwalk beans/black -X 'path:beans'
-  matcher: <differencematcher m1=<patternmatcher patterns='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
-  $ hg debugwalk -I 'path:beans/black' -X 'path:beans'
-  matcher: <differencematcher m1=<includematcher includes='(?:beans\\/black(?:/|$))'>, m2=<includematcher includes='(?:beans(?:/|$))'>>
+  $ hg debugwalk -v fennel -X fennel
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:fennel(?:/|$))'>,
+    m2=<includematcher includes='(?:fennel(?:/|$))'>>
+  $ hg debugwalk -v fennel -X 'f*'
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:fennel(?:/|$))'>,
+    m2=<includematcher includes='(?:f[^/]*(?:/|$))'>>
+  $ hg debugwalk -v beans/black -X 'path:beans'
+  * matcher:
+  <differencematcher
+    m1=<patternmatcher patterns='(?:beans/black(?:/|$))'>,
+    m2=<includematcher includes='(?:beans(?:/|$))'>>
+  $ hg debugwalk -v -I 'path:beans/black' -X 'path:beans'
+  * matcher:
+  <differencematcher
+    m1=<includematcher includes='(?:beans/black(?:/|$))'>,
+    m2=<includematcher includes='(?:beans(?:/|$))'>>
 
 Test absolute paths:
 
-  $ hg debugwalk `pwd`/beans
-  matcher: <patternmatcher patterns='(?:beans(?:/|$))'>
+  $ hg debugwalk -v `pwd`/beans
+  * matcher:
+  <patternmatcher patterns='(?:beans(?:/|$))'>
   f  beans/black     beans/black
   f  beans/borlotti  beans/borlotti
   f  beans/kidney    beans/kidney
   f  beans/navy      beans/navy
   f  beans/pinto     beans/pinto
   f  beans/turtle    beans/turtle
-  $ hg debugwalk `pwd`/..
+  $ hg debugwalk -v `pwd`/..
   abort: $TESTTMP/t/.. not under root '$TESTTMP/t'
   [255]
 
 Test patterns:
 
-  $ hg debugwalk glob:\*
-  matcher: <patternmatcher patterns='(?:[^/]*$)'>
+  $ hg debugwalk -v glob:\*
+  * matcher:
+  <patternmatcher patterns='(?:[^/]*$)'>
   f  fennel      fennel
   f  fenugreek   fenugreek
   f  fiddlehead  fiddlehead
@@ -379,138 +472,165 @@
   $ hg addremove
   adding glob:glob
   warning: filename contains ':', which is reserved on Windows: 'glob:glob'
-  $ hg debugwalk glob:\*
-  matcher: <patternmatcher patterns='(?:[^/]*$)'>
+  $ hg debugwalk -v glob:\*
+  * matcher:
+  <patternmatcher patterns='(?:[^/]*$)'>
   f  fennel      fennel
   f  fenugreek   fenugreek
   f  fiddlehead  fiddlehead
   f  glob:glob   glob:glob
-  $ hg debugwalk glob:glob
-  matcher: <patternmatcher patterns='(?:glob$)'>
+  $ hg debugwalk -v glob:glob
+  * matcher:
+  <patternmatcher patterns='(?:glob$)'>
   glob: $ENOENT$
-  $ hg debugwalk glob:glob:glob
-  matcher: <patternmatcher patterns='(?:glob\\:glob$)'>
+  $ hg debugwalk -v glob:glob:glob
+  * matcher:
+  <patternmatcher patterns='(?:glob:glob$)'>
   f  glob:glob  glob:glob  exact
-  $ hg debugwalk path:glob:glob
-  matcher: <patternmatcher patterns='(?:glob\\:glob(?:/|$))'>
+  $ hg debugwalk -v path:glob:glob
+  * matcher:
+  <patternmatcher patterns='(?:glob:glob(?:/|$))'>
   f  glob:glob  glob:glob  exact
   $ rm glob:glob
   $ hg addremove
   removing glob:glob
 #endif
 
-  $ hg debugwalk 'glob:**e'
-  matcher: <patternmatcher patterns='(?:.*e$)'>
+  $ hg debugwalk -v 'glob:**e'
+  * matcher:
+  <patternmatcher patterns='(?:.*e$)'>
   f  beans/turtle                    beans/turtle
   f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
 
-  $ hg debugwalk 're:.*[kb]$'
-  matcher: <patternmatcher patterns='(?:.*[kb]$)'>
+  $ hg debugwalk -v 're:.*[kb]$'
+  * matcher:
+  <patternmatcher patterns='(?:.*[kb]$)'>
   f  beans/black    beans/black
   f  fenugreek      fenugreek
   f  mammals/skunk  mammals/skunk
 
-  $ hg debugwalk path:beans/black
-  matcher: <patternmatcher patterns='(?:beans\\/black(?:/|$))'>
+  $ hg debugwalk -v path:beans/black
+  * matcher:
+  <patternmatcher patterns='(?:beans/black(?:/|$))'>
   f  beans/black  beans/black  exact
-  $ hg debugwalk path:beans//black
-  matcher: <patternmatcher patterns='(?:beans\\/black(?:/|$))'>
+  $ hg debugwalk -v path:beans//black
+  * matcher:
+  <patternmatcher patterns='(?:beans/black(?:/|$))'>
   f  beans/black  beans/black  exact
 
-  $ hg debugwalk relglob:Procyonidae
-  matcher: <patternmatcher patterns='(?:(?:|.*/)Procyonidae$)'>
-  $ hg debugwalk 'relglob:Procyonidae/**'
-  matcher: <patternmatcher patterns='(?:(?:|.*/)Procyonidae\\/.*$)'>
+  $ hg debugwalk -v relglob:Procyonidae
+  * matcher:
+  <patternmatcher patterns='(?:(?:|.*/)Procyonidae$)'>
+  $ hg debugwalk -v 'relglob:Procyonidae/**'
+  * matcher:
+  <patternmatcher patterns='(?:(?:|.*/)Procyonidae/.*$)'>
   f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
-  $ hg debugwalk 'relglob:Procyonidae/**' fennel
-  matcher: <patternmatcher patterns='(?:(?:|.*/)Procyonidae\\/.*$|fennel(?:/|$))'>
+  $ hg debugwalk -v 'relglob:Procyonidae/**' fennel
+  * matcher:
+  <patternmatcher patterns='(?:(?:|.*/)Procyonidae/.*$|fennel(?:/|$))'>
   f  fennel                          fennel                          exact
   f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
-  $ hg debugwalk beans 'glob:beans/*'
-  matcher: <patternmatcher patterns='(?:beans(?:/|$)|beans\\/[^/]*$)'>
+  $ hg debugwalk -v beans 'glob:beans/*'
+  * matcher:
+  <patternmatcher patterns='(?:beans(?:/|$)|beans/[^/]*$)'>
   f  beans/black     beans/black
   f  beans/borlotti  beans/borlotti
   f  beans/kidney    beans/kidney
   f  beans/navy      beans/navy
   f  beans/pinto     beans/pinto
   f  beans/turtle    beans/turtle
-  $ hg debugwalk 'glob:mamm**'
-  matcher: <patternmatcher patterns='(?:mamm.*$)'>
+  $ hg debugwalk -v 'glob:mamm**'
+  * matcher:
+  <patternmatcher patterns='(?:mamm.*$)'>
   f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
   f  mammals/skunk                   mammals/skunk
-  $ hg debugwalk 'glob:mamm**' fennel
-  matcher: <patternmatcher patterns='(?:mamm.*$|fennel(?:/|$))'>
+  $ hg debugwalk -v 'glob:mamm**' fennel
+  * matcher:
+  <patternmatcher patterns='(?:mamm.*$|fennel(?:/|$))'>
   f  fennel                          fennel                          exact
   f  mammals/Procyonidae/cacomistle  mammals/Procyonidae/cacomistle
   f  mammals/Procyonidae/coatimundi  mammals/Procyonidae/coatimundi
   f  mammals/Procyonidae/raccoon     mammals/Procyonidae/raccoon
   f  mammals/skunk                   mammals/skunk
-  $ hg debugwalk 'glob:j*'
-  matcher: <patternmatcher patterns='(?:j[^/]*$)'>
-  $ hg debugwalk NOEXIST
-  matcher: <patternmatcher patterns='(?:NOEXIST(?:/|$))'>
+  $ hg debugwalk -v 'glob:j*'
+  * matcher:
+  <patternmatcher patterns='(?:j[^/]*$)'>
+  $ hg debugwalk -v NOEXIST
+  * matcher:
+  <patternmatcher patterns='(?:NOEXIST(?:/|$))'>
   NOEXIST: * (glob)
 
 #if fifo
   $ mkfifo fifo
-  $ hg debugwalk fifo
-  matcher: <patternmatcher patterns='(?:fifo(?:/|$))'>
+  $ hg debugwalk -v fifo
+  * matcher:
+  <patternmatcher patterns='(?:fifo(?:/|$))'>
   fifo: unsupported file type (type is fifo)
 #endif
 
   $ rm fenugreek
-  $ hg debugwalk fenugreek
-  matcher: <patternmatcher patterns='(?:fenugreek(?:/|$))'>
+  $ hg debugwalk -v fenugreek
+  * matcher:
+  <patternmatcher patterns='(?:fenugreek(?:/|$))'>
   f  fenugreek  fenugreek  exact
   $ hg rm fenugreek
-  $ hg debugwalk fenugreek
-  matcher: <patternmatcher patterns='(?:fenugreek(?:/|$))'>
+  $ hg debugwalk -v fenugreek
+  * matcher:
+  <patternmatcher patterns='(?:fenugreek(?:/|$))'>
   f  fenugreek  fenugreek  exact
   $ touch new
-  $ hg debugwalk new
-  matcher: <patternmatcher patterns='(?:new(?:/|$))'>
+  $ hg debugwalk -v new
+  * matcher:
+  <patternmatcher patterns='(?:new(?:/|$))'>
   f  new  new  exact
 
   $ mkdir ignored
   $ touch ignored/file
   $ echo '^ignored$' > .hgignore
-  $ hg debugwalk ignored
-  matcher: <patternmatcher patterns='(?:ignored(?:/|$))'>
-  $ hg debugwalk ignored/file
-  matcher: <patternmatcher patterns='(?:ignored\\/file(?:/|$))'>
+  $ hg debugwalk -v ignored
+  * matcher:
+  <patternmatcher patterns='(?:ignored(?:/|$))'>
+  $ hg debugwalk -v ignored/file
+  * matcher:
+  <patternmatcher patterns='(?:ignored/file(?:/|$))'>
   f  ignored/file  ignored/file  exact
 
 Test listfile and listfile0
 
   $ $PYTHON -c "open('listfile0', 'wb').write(b'fenugreek\0new\0')"
-  $ hg debugwalk -I 'listfile0:listfile0'
-  matcher: <includematcher includes='(?:fenugreek(?:/|$)|new(?:/|$))'>
+  $ hg debugwalk -v -I 'listfile0:listfile0'
+  * matcher:
+  <includematcher includes='(?:fenugreek(?:/|$)|new(?:/|$))'>
   f  fenugreek  fenugreek
   f  new        new
   $ $PYTHON -c "open('listfile', 'wb').write(b'fenugreek\nnew\r\nmammals/skunk\n')"
-  $ hg debugwalk -I 'listfile:listfile'
-  matcher: <includematcher includes='(?:fenugreek(?:/|$)|new(?:/|$)|mammals\\/skunk(?:/|$))'>
+  $ hg debugwalk -v -I 'listfile:listfile'
+  * matcher:
+  <includematcher includes='(?:fenugreek(?:/|$)|new(?:/|$)|mammals/skunk(?:/|$))'>
   f  fenugreek      fenugreek
   f  mammals/skunk  mammals/skunk
   f  new            new
 
   $ cd ..
-  $ hg debugwalk -R t t/mammals/skunk
-  matcher: <patternmatcher patterns='(?:mammals\\/skunk(?:/|$))'>
+  $ hg debugwalk -v -R t t/mammals/skunk
+  * matcher:
+  <patternmatcher patterns='(?:mammals/skunk(?:/|$))'>
   f  mammals/skunk  t/mammals/skunk  exact
   $ mkdir t2
   $ cd t2
-  $ hg debugwalk -R ../t ../t/mammals/skunk
-  matcher: <patternmatcher patterns='(?:mammals\\/skunk(?:/|$))'>
+  $ hg debugwalk -v -R ../t ../t/mammals/skunk
+  * matcher:
+  <patternmatcher patterns='(?:mammals/skunk(?:/|$))'>
   f  mammals/skunk  ../t/mammals/skunk  exact
-  $ hg debugwalk --cwd ../t mammals/skunk
-  matcher: <patternmatcher patterns='(?:mammals\\/skunk(?:/|$))'>
+  $ hg debugwalk -v --cwd ../t mammals/skunk
+  * matcher:
+  <patternmatcher patterns='(?:mammals/skunk(?:/|$))'>
   f  mammals/skunk  mammals/skunk  exact
 
   $ cd ..
@@ -526,7 +646,7 @@
   > EOF
   $ $PYTHON printnum.py >> overflow.list
   $ echo fenugreek >> overflow.list
-  $ hg debugwalk 'listfile:overflow.list' 2>&1 | egrep -v '(^matcher: |^xxx)'
+  $ hg debugwalk 'listfile:overflow.list' 2>&1 | egrep -v '^xxx'
   f  fennel     fennel     exact
   f  fenugreek  fenugreek  exact
   $ cd ..
--- a/tests/test-walkrepo.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-walkrepo.py	Thu Jul 19 13:55:54 2018 -0400
@@ -17,22 +17,22 @@
 checklink = util.checklink
 
 u = uimod.ui.load()
-sym = checklink('.')
+sym = checklink(b'.')
 
-hg.repository(u, 'top1', create=1)
-mkdir('subdir')
-chdir('subdir')
-hg.repository(u, 'sub1', create=1)
-mkdir('subsubdir')
-chdir('subsubdir')
-hg.repository(u, 'subsub1', create=1)
+hg.repository(u, b'top1', create=1)
+mkdir(b'subdir')
+chdir(b'subdir')
+hg.repository(u, b'sub1', create=1)
+mkdir(b'subsubdir')
+chdir(b'subsubdir')
+hg.repository(u, b'subsub1', create=1)
 chdir(os.path.pardir)
 if sym:
-    os.symlink(os.path.pardir, 'circle')
-    os.symlink(pjoin('subsubdir', 'subsub1'), 'subsub1')
+    os.symlink(os.path.pardir, b'circle')
+    os.symlink(pjoin(b'subsubdir', b'subsub1'), b'subsub1')
 
 def runtest():
-    reposet = frozenset(walkrepos('.', followsym=True))
+    reposet = frozenset(walkrepos(b'.', followsym=True))
     if sym and (len(reposet) != 3):
         print("reposet = %r" % (reposet,))
         print(("Found %d repositories when I should have found 3"
@@ -41,19 +41,19 @@
         print("reposet = %r" % (reposet,))
         print(("Found %d repositories when I should have found 2"
                % (len(reposet),)))
-    sub1set = frozenset((pjoin('.', 'sub1'),
-                         pjoin('.', 'circle', 'subdir', 'sub1')))
+    sub1set = frozenset((pjoin(b'.', b'sub1'),
+                         pjoin(b'.', b'circle', b'subdir', b'sub1')))
     if len(sub1set & reposet) != 1:
         print("sub1set = %r" % (sub1set,))
         print("reposet = %r" % (reposet,))
         print("sub1set and reposet should have exactly one path in common.")
-    sub2set = frozenset((pjoin('.', 'subsub1'),
-                         pjoin('.', 'subsubdir', 'subsub1')))
+    sub2set = frozenset((pjoin(b'.', b'subsub1'),
+                         pjoin(b'.', b'subsubdir', b'subsub1')))
     if len(sub2set & reposet) != 1:
         print("sub2set = %r" % (sub2set,))
         print("reposet = %r" % (reposet,))
         print("sub2set and reposet should have exactly one path in common.")
-    sub3 = pjoin('.', 'circle', 'top1')
+    sub3 = pjoin(b'.', b'circle', b'top1')
     if sym and sub3 not in reposet:
         print("reposet = %r" % (reposet,))
         print("Symbolic links are supported and %s is not in reposet" % (sub3,))
--- a/tests/test-wireproto-command-capabilities.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-wireproto-command-capabilities.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,3 +1,5 @@
+#require no-chg
+
   $ . $TESTDIR/wireprotohelpers.sh
 
   $ hg init server
--- a/tests/test-wireproto.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-wireproto.py	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,7 @@
 from __future__ import absolute_import, print_function
 
+import sys
+
 from mercurial import (
     error,
     pycompat,
@@ -9,6 +11,9 @@
     wireprotov1peer,
     wireprotov1server,
 )
+from mercurial.utils import (
+    stringutil,
+)
 stringio = util.stringio
 
 class proto(object):
@@ -92,10 +97,16 @@
 srv = serverrepo()
 clt = clientpeer(srv, uimod.ui())
 
-print(clt.greet(b"Foobar"))
+def printb(data, end=b'\n'):
+    out = getattr(sys.stdout, 'buffer', sys.stdout)
+    out.write(data + end)
+    out.flush()
+
+printb(clt.greet(b"Foobar"))
 
 with clt.commandexecutor() as e:
     fgreet1 = e.callcommand(b'greet', {b'name': b'Fo, =;:<o'})
     fgreet2 = e.callcommand(b'greet', {b'name': b'Bar'})
 
-print([f.result() for f in (fgreet1, fgreet2)])
+printb(stringutil.pprint([f.result() for f in (fgreet1, fgreet2)],
+                         bprefix=True))
--- a/tests/test-wireproto.py.out	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-wireproto.py.out	Thu Jul 19 13:55:54 2018 -0400
@@ -1,2 +1,2 @@
 Hello, Foobar
-['Hello, Fo, =;:<o', 'Hello, Bar']
+[b'Hello, Fo, =;:<o', b'Hello, Bar']
--- a/tests/test-wireproto.t	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-wireproto.t	Thu Jul 19 13:55:54 2018 -0400
@@ -1,5 +1,3 @@
-#require killdaemons
-
 Test wire protocol argument passing
 
 Setup repo:
--- a/tests/test-wsgirequest.py	Sun Jul 01 23:36:53 2018 +0900
+++ b/tests/test-wsgirequest.py	Thu Jul 19 13:55:54 2018 -0400
@@ -206,18 +206,18 @@
         """repository path components get stripped from URL."""
 
         with self.assertRaisesRegex(error.ProgrammingError,
-                                     b'reponame requires PATH_INFO'):
+                                    'reponame requires PATH_INFO'):
             parse(DEFAULT_ENV, reponame=b'repo')
 
         with self.assertRaisesRegex(error.ProgrammingError,
-                                     b'PATH_INFO does not begin with repo '
-                                     b'name'):
+                                    'PATH_INFO does not begin with repo '
+                                    'name'):
             parse(DEFAULT_ENV, reponame=b'repo', extra={
                 r'PATH_INFO': r'/pathinfo',
             })
 
         with self.assertRaisesRegex(error.ProgrammingError,
-                                     b'reponame prefix of PATH_INFO'):
+                                    'reponame prefix of PATH_INFO'):
             parse(DEFAULT_ENV, reponame=b'repo', extra={
                 r'PATH_INFO': r'/repoextra/path',
             })
@@ -251,7 +251,7 @@
 
     def testaltbaseurl(self):
         # Simple hostname remap.
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver')
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver')
 
         self.assertEqual(r.url, b'http://testserver')
         self.assertEqual(r.baseurl, b'http://testserver')
@@ -264,7 +264,7 @@
         self.assertIsNone(r.reponame)
 
         # With a custom port.
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver:8000')
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver:8000')
         self.assertEqual(r.url, b'http://testserver')
         self.assertEqual(r.baseurl, b'http://testserver')
         self.assertEqual(r.advertisedurl, b'http://altserver:8000')
@@ -276,7 +276,7 @@
         self.assertIsNone(r.reponame)
 
         # With a changed protocol.
-        r = parse(DEFAULT_ENV, altbaseurl='https://altserver')
+        r = parse(DEFAULT_ENV, altbaseurl=b'https://altserver')
         self.assertEqual(r.url, b'http://testserver')
         self.assertEqual(r.baseurl, b'http://testserver')
         self.assertEqual(r.advertisedurl, b'https://altserver')
@@ -289,7 +289,7 @@
         self.assertIsNone(r.reponame)
 
         # Need to specify explicit port number for proper https:// alt URLs.
-        r = parse(DEFAULT_ENV, altbaseurl='https://altserver:443')
+        r = parse(DEFAULT_ENV, altbaseurl=b'https://altserver:443')
         self.assertEqual(r.url, b'http://testserver')
         self.assertEqual(r.baseurl, b'http://testserver')
         self.assertEqual(r.advertisedurl, b'https://altserver')
@@ -301,7 +301,7 @@
         self.assertIsNone(r.reponame)
 
         # With only PATH_INFO defined.
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver', extra={
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver', extra={
             r'PATH_INFO': r'/path1/path2',
         })
         self.assertEqual(r.url, b'http://testserver/path1/path2')
@@ -315,7 +315,7 @@
         self.assertIsNone(r.reponame)
 
         # Path on alt URL.
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver/altpath')
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver/altpath')
         self.assertEqual(r.url, b'http://testserver')
         self.assertEqual(r.baseurl, b'http://testserver')
         self.assertEqual(r.advertisedurl, b'http://altserver/altpath')
@@ -327,7 +327,7 @@
         self.assertIsNone(r.reponame)
 
         # With a trailing slash.
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver/altpath/')
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver/altpath/')
         self.assertEqual(r.url, b'http://testserver')
         self.assertEqual(r.baseurl, b'http://testserver')
         self.assertEqual(r.advertisedurl, b'http://altserver/altpath/')
@@ -339,7 +339,7 @@
         self.assertIsNone(r.reponame)
 
         # PATH_INFO + path on alt URL.
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver/altpath', extra={
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver/altpath', extra={
             r'PATH_INFO': r'/path1/path2',
         })
         self.assertEqual(r.url, b'http://testserver/path1/path2')
@@ -354,7 +354,7 @@
         self.assertIsNone(r.reponame)
 
         # PATH_INFO + path on alt URL with trailing slash.
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver/altpath/', extra={
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver/altpath/', extra={
             r'PATH_INFO': r'/path1/path2',
         })
         self.assertEqual(r.url, b'http://testserver/path1/path2')
@@ -369,7 +369,7 @@
         self.assertIsNone(r.reponame)
 
         # Local SCRIPT_NAME is ignored.
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver', extra={
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver', extra={
             r'SCRIPT_NAME': r'/script',
             r'PATH_INFO': r'/path1/path2',
         })
@@ -384,7 +384,7 @@
         self.assertIsNone(r.reponame)
 
         # Use remote's path for script name, app path
-        r = parse(DEFAULT_ENV, altbaseurl='http://altserver/altroot', extra={
+        r = parse(DEFAULT_ENV, altbaseurl=b'http://altserver/altroot', extra={
             r'SCRIPT_NAME': r'/script',
             r'PATH_INFO': r'/path1/path2',
         })
@@ -401,7 +401,7 @@
 
         # reponame is factored in properly.
         r = parse(DEFAULT_ENV, reponame=b'repo',
-                  altbaseurl='http://altserver/altroot',
+                  altbaseurl=b'http://altserver/altroot',
                   extra={
                 r'SCRIPT_NAME': r'/script',
                 r'PATH_INFO': r'/repo/path1/path2',